DragController.cpp 35.6 KB
Newer Older
1
/*
2
 * Copyright (C) 2007, 2009, 2010 Apple Inc. All rights reserved.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include "config.h"
#include "DragController.h"

bolsinga@apple.com's avatar
bolsinga@apple.com committed
29
#if ENABLE(DRAG_SUPPORT)
darin's avatar
darin committed
30
#include "CSSStyleDeclaration.h"
31 32
#include "Clipboard.h"
#include "ClipboardAccessPolicy.h"
33
#include "CachedResourceLoader.h"
34 35 36
#include "Document.h"
#include "DocumentFragment.h"
#include "DragActions.h"
darin's avatar
darin committed
37 38
#include "DragClient.h"
#include "DragData.h"
39 40 41 42
#include "Editor.h"
#include "EditorClient.h"
#include "Element.h"
#include "EventHandler.h"
43
#include "FloatRect.h"
44 45
#include "Frame.h"
#include "FrameLoader.h"
46
#include "FrameSelection.h"
47 48
#include "FrameView.h"
#include "HTMLAnchorElement.h"
49 50
#include "HTMLInputElement.h"
#include "HTMLNames.h"
51
#include "HitTestRequest.h"
darin's avatar
darin committed
52
#include "HitTestResult.h"
53
#include "Image.h"
54 55 56
#include "MoveSelectionCommand.h"
#include "Node.h"
#include "Page.h"
57
#include "PlatformKeyboardEvent.h"
58
#include "RenderFileUploadControl.h"
59
#include "RenderImage.h"
60
#include "RenderLayer.h"
61
#include "RenderView.h"
62 63
#include "ReplaceSelectionCommand.h"
#include "ResourceRequest.h"
64
#include "SecurityOrigin.h"
oliver's avatar
 
oliver committed
65
#include "Settings.h"
66
#include "Text.h"
67
#include "TextEvent.h"
68
#include "htmlediting.h"
darin's avatar
darin committed
69
#include "markup.h"
ap@webkit.org's avatar
ap@webkit.org committed
70
#include <wtf/CurrentTime.h>
71
#include <wtf/RefPtr.h>
72 73 74 75 76

namespace WebCore {

static PlatformMouseEvent createMouseEvent(DragData* dragData)
{
77 78 79
    bool shiftKey, ctrlKey, altKey, metaKey;
    shiftKey = ctrlKey = altKey = metaKey = false;
    PlatformKeyboardEvent::getCurrentModifierState(shiftKey, ctrlKey, altKey, metaKey);
80
    return PlatformMouseEvent(dragData->clientPosition(), dragData->globalPosition(),
81 82
                              LeftButton, MouseEventMoved, 0, shiftKey, ctrlKey, altKey,
                              metaKey, currentTime());
83
}
84

85 86 87
DragController::DragController(Page* page, DragClient* client)
    : m_page(page)
    , m_client(client)
88
    , m_documentUnderMouse(0)
89 90 91 92
    , m_dragInitiator(0)
    , m_dragDestinationAction(DragDestinationActionNone)
    , m_dragSourceAction(DragSourceActionNone)
    , m_didInitiateDrag(false)
93
    , m_isHandlingDrag(false)
94
    , m_sourceDragOperation(DragOperationNone)
95 96
{
}
97

98
DragController::~DragController()
99
{
100 101
    m_client->dragControllerDestroyed();
}
102

103
static PassRefPtr<DocumentFragment> documentFragmentFromDragData(DragData* dragData, Frame* frame, RefPtr<Range> context,
oliver's avatar
oliver committed
104
                                          bool allowPlainText, bool& chosePlainText)
105 106 107 108
{
    ASSERT(dragData);
    chosePlainText = false;

109
    Document* document = context->ownerDocument();
110 111
    ASSERT(document);
    if (document && dragData->containsCompatibleContent()) {
112
        if (PassRefPtr<DocumentFragment> fragment = dragData->asFragment(frame, context, allowPlainText, chosePlainText))
113 114
            return fragment;

115
        if (dragData->containsURL(frame, DragData::DoNotConvertFilenames)) {
116
            String title;
117
            String url = dragData->asURL(frame, DragData::DoNotConvertFilenames, &title);
ggaren's avatar
ggaren committed
118
            if (!url.isEmpty()) {
119
                RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::create(document);
120
                anchor->setHref(url);
121 122 123
                if (title.isEmpty()) {
                    // Try the plain text first because the url might be normalized or escaped.
                    if (dragData->containsPlainText())
124
                        title = dragData->asPlainText(frame);
125 126 127
                    if (title.isEmpty())
                        title = url;
                }
128
                RefPtr<Node> anchorText = document->createTextNode(title);
129
                ExceptionCode ec;
130 131 132 133 134 135 136 137 138
                anchor->appendChild(anchorText, ec);
                RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
                fragment->appendChild(anchor, ec);
                return fragment.get();
            }
        }
    }
    if (allowPlainText && dragData->containsPlainText()) {
        chosePlainText = true;
139
        return createFragmentFromText(context.get(), dragData->asPlainText(frame)).get();
140
    }
141

142 143 144
    return 0;
}

145
bool DragController::dragIsMove(FrameSelection* selection, DragData* dragData)
146
{
147
    return m_documentUnderMouse == m_dragInitiator && selection->isContentEditable() && !isCopyKeyDown(dragData);
148 149
}

150
// FIXME: This method is poorly named.  We're just clearing the selection from the document this drag is exiting.
151 152 153 154 155
void DragController::cancelDrag()
{
    m_page->dragCaretController()->clear();
}

oliver's avatar
oliver committed
156 157 158
void DragController::dragEnded()
{
    m_dragInitiator = 0;
159 160 161
    m_didInitiateDrag = false;
    m_page->dragCaretController()->clear();
}
162

163
DragOperation DragController::dragEntered(DragData* dragData)
164 165 166
{
    return dragEnteredOrUpdated(dragData);
}
167 168 169

void DragController::dragExited(DragData* dragData)
{
170 171
    ASSERT(dragData);
    Frame* mainFrame = m_page->mainFrame();
172

173
    if (RefPtr<FrameView> v = mainFrame->view()) {
174
        ClipboardAccessPolicy policy = (!m_documentUnderMouse || m_documentUnderMouse->securityOrigin()->isLocal()) ? ClipboardReadable : ClipboardTypesReadable;
175
        RefPtr<Clipboard> clipboard = Clipboard::create(policy, dragData, mainFrame);
176 177 178 179
        clipboard->setSourceOperation(dragData->draggingSourceOperationMask());
        mainFrame->eventHandler()->cancelDragAndDrop(createMouseEvent(dragData), clipboard.get());
        clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
    }
180
    mouseMovedIntoDocument(0);
181
}
ggaren@apple.com's avatar
ggaren@apple.com committed
182

183
DragOperation DragController::dragUpdated(DragData* dragData)
184 185 186
{
    return dragEnteredOrUpdated(dragData);
}
187

188
bool DragController::performDrag(DragData* dragData)
189
{
190
    ASSERT(dragData);
191
    m_documentUnderMouse = m_page->mainFrame()->documentAtPoint(dragData->clientPosition());
192 193 194
    if (m_isHandlingDrag) {
        ASSERT(m_dragDestinationAction & DragDestinationActionDHTML);
        m_client->willPerformDragDestinationAction(DragDestinationActionDHTML, dragData);
195
        RefPtr<Frame> mainFrame = m_page->mainFrame();
196 197
        if (mainFrame->view()) {
            // Sending an event can result in the destruction of the view and part.
198
            RefPtr<Clipboard> clipboard = Clipboard::create(ClipboardReadable, dragData, mainFrame.get());
199 200 201 202
            clipboard->setSourceOperation(dragData->draggingSourceOperationMask());
            mainFrame->eventHandler()->performDragAndDrop(createMouseEvent(dragData), clipboard.get());
            clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
        }
203
        m_documentUnderMouse = 0;
204
        return true;
205 206
    }

207
    if ((m_dragDestinationAction & DragDestinationActionEdit) && concludeEditDrag(dragData)) {
208
        m_documentUnderMouse = 0;
209 210
        return true;
    }
211

212
    m_documentUnderMouse = 0;
213

214
    if (operationForLoad(dragData) == DragOperationNone)
215
        return false;
216 217

    m_client->willPerformDragDestinationAction(DragDestinationActionLoad, dragData);
218
    m_page->mainFrame()->loader()->load(ResourceRequest(dragData->asURL(m_page->mainFrame())), false);
219 220
    return true;
}
221 222 223 224 225 226 227 228 229 230 231 232

void DragController::mouseMovedIntoDocument(Document* newDocument)
{
    if (m_documentUnderMouse == newDocument)
        return;

    // If we were over another document clear the selection
    if (m_documentUnderMouse)
        cancelDrag();
    m_documentUnderMouse = newDocument;
}

233 234 235
DragOperation DragController::dragEnteredOrUpdated(DragData* dragData)
{
    ASSERT(dragData);
236 237 238
    ASSERT(m_page->mainFrame()); // It is not possible in Mac WebKit to have a Page without a mainFrame()
    mouseMovedIntoDocument(m_page->mainFrame()->documentAtPoint(dragData->clientPosition()));

239
    m_dragDestinationAction = m_client->actionMaskForDrag(dragData);
240
    if (m_dragDestinationAction == DragDestinationActionNone) {
241
        cancelDrag(); // FIXME: Why not call mouseMovedIntoDocument(0)?
242
        return DragOperationNone;
243
    }
244 245 246 247

    DragOperation operation = DragOperationNone;
    bool handledByDocument = tryDocumentDrag(dragData, m_dragDestinationAction, operation);
    if (!handledByDocument && (m_dragDestinationAction & DragDestinationActionLoad))
248
        return operationForLoad(dragData);
249 250
    return operation;
}
251 252 253 254

static HTMLInputElement* asFileInput(Node* node)
{
    ASSERT(node);
255

256 257 258
    // The button for a FILE input is a sub element with no set input type
    // In order to get around this problem we assume any non-FILE input element
    // is this internal button, and try querying the shadow parent node.
259
    if (node->hasTagName(HTMLNames::inputTag) && node->isShadowRoot() && !static_cast<HTMLInputElement*>(node)->isFileUpload())
260
        node = node->shadowHost();
261

262 263
    if (!node || !node->hasTagName(HTMLNames::inputTag))
        return 0;
264

265 266 267
    HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node);
    if (!inputElement->isFileUpload())
        return 0;
268

269
    return inputElement;
270
}
271

272
// This can return null if an empty document is loaded.
273 274
static Element* elementUnderMouse(Document* documentUnderMouse, const IntPoint& p)
{
275 276
    Frame* frame = documentUnderMouse->frame();
    float zoomFactor = frame ? frame->pageZoomFactor() : 1;
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
    IntPoint point = roundedIntPoint(FloatPoint(p.x() * zoomFactor, p.y() * zoomFactor));

    HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active);
    HitTestResult result(point);
    documentUnderMouse->renderView()->layer()->hitTest(request, result);

    Node* n = result.innerNode();
    while (n && !n->isElementNode())
        n = n->parentNode();
    if (n)
        n = n->shadowAncestorNode();

    return static_cast<Element*>(n);
}

292
bool DragController::tryDocumentDrag(DragData* dragData, DragDestinationAction actionMask, DragOperation& operation)
293 294
{
    ASSERT(dragData);
295

296
    if (!m_documentUnderMouse)
297
        return false;
298

299 300 301
    if (m_dragInitiator && !m_documentUnderMouse->securityOrigin()->canReceiveDragData(m_dragInitiator->securityOrigin()))
        return false;

302
    m_isHandlingDrag = false;
303
    if (actionMask & DragDestinationActionDHTML) {
304
        m_isHandlingDrag = tryDHTMLDrag(dragData, operation);
305 306 307 308 309 310
        // Do not continue if m_documentUnderMouse has been reset by tryDHTMLDrag.
        // tryDHTMLDrag fires dragenter event. The event listener that listens
        // to this event may create a nested message loop (open a modal dialog),
        // which could process dragleave event and reset m_documentUnderMouse in
        // dragExited.
        if (!m_documentUnderMouse)
311
            return false;
312
    }
313

314 315
    // It's unclear why this check is after tryDHTMLDrag.
    // We send drag events in tryDHTMLDrag and that may be the reason.
316
    RefPtr<FrameView> frameView = m_documentUnderMouse->view();
317
    if (!frameView)
318 319 320 321 322
        return false;

    if (m_isHandlingDrag) {
        m_page->dragCaretController()->clear();
        return true;
323
    } else if ((actionMask & DragDestinationActionEdit) && canProcessDrag(dragData)) {
324 325 326 327
        if (dragData->containsColor()) {
            operation = DragOperationGeneric;
            return true;
        }
328

329
        IntPoint point = frameView->windowToContents(dragData->clientPosition());
330
        Element* element = elementUnderMouse(m_documentUnderMouse.get(), point);
331 332
        if (!element)
            return false;
333 334
        if (!asFileInput(element))
            m_page->dragCaretController()->setCaretPosition(m_documentUnderMouse->frame()->visiblePositionForPoint(point));
335 336

        Frame* innerFrame = element->document()->frame();
337
        operation = dragIsMove(innerFrame->selection(), dragData) ? DragOperationMove : DragOperationCopy;
338 339
        return true;
    }
340 341
    // If we're not over an editable region, make sure we're clearing any prior drag cursor.
    m_page->dragCaretController()->clear();
342
    return false;
343
}
344 345

DragSourceAction DragController::delegateDragSourceAction(const IntPoint& windowPoint)
346
{
347 348 349
    m_dragSourceAction = m_client->dragSourceActionMaskForPoint(windowPoint);
    return m_dragSourceAction;
}
350

351 352 353
DragOperation DragController::operationForLoad(DragData* dragData)
{
    ASSERT(dragData);
354
    Document* doc = m_page->mainFrame()->documentAtPoint(dragData->clientPosition());
355
    if (doc && (m_didInitiateDrag || doc->isPluginDocument() || doc->rendererIsEditable()))
356 357 358 359
        return DragOperationNone;
    return dragOperation(dragData);
}

360
static bool setSelectionToDragCaret(Frame* frame, VisibleSelection& dragCaret, RefPtr<Range>& range, const IntPoint& point)
361
{
darin@apple.com's avatar
darin@apple.com committed
362 363
    frame->selection()->setSelection(dragCaret);
    if (frame->selection()->isNone()) {
oliver's avatar
oliver committed
364
        dragCaret = frame->visiblePositionForPoint(point);
darin@apple.com's avatar
darin@apple.com committed
365
        frame->selection()->setSelection(dragCaret);
eric@webkit.org's avatar
eric@webkit.org committed
366
        range = dragCaret.toNormalizedRange();
367
    }
darin@apple.com's avatar
darin@apple.com committed
368
    return !frame->selection()->isNone() && frame->selection()->isContentEditable();
369 370
}

371 372
bool DragController::dispatchTextInputEventFor(Frame* innerFrame, DragData* dragData)
{
373 374 375
    ASSERT(m_page->dragCaretController()->hasCaret());
    String text = m_page->dragCaretController()->isContentRichlyEditable() ? "" : dragData->asPlainText(innerFrame);
    Node* target = innerFrame->editor()->findEventTargetFrom(m_page->dragCaretController()->caretPosition());
376 377 378 379
    ExceptionCode ec = 0;
    return target->dispatchEvent(TextEvent::createForDrop(innerFrame->domWindow(), text), ec);
}

380
bool DragController::concludeEditDrag(DragData* dragData)
381 382 383
{
    ASSERT(dragData);
    ASSERT(!m_isHandlingDrag);
384

385
    if (!m_documentUnderMouse)
386
        return false;
387

388
    IntPoint point = m_documentUnderMouse->view()->windowToContents(dragData->clientPosition());
389
    Element* element = elementUnderMouse(m_documentUnderMouse.get(), point);
390 391
    if (!element)
        return false;
392
    Frame* innerFrame = element->ownerDocument()->frame();
393
    ASSERT(innerFrame);
394

395
    if (m_page->dragCaretController()->hasCaret() && !dispatchTextInputEventFor(innerFrame, dragData))
396 397
        return true;

398 399 400 401
    if (dragData->containsColor()) {
        Color color = dragData->asColor();
        if (!color.isValid())
            return false;
eric@webkit.org's avatar
eric@webkit.org committed
402
        RefPtr<Range> innerRange = innerFrame->selection()->toNormalizedRange();
403
        RefPtr<CSSStyleDeclaration> style = m_documentUnderMouse->createCSSStyleDeclaration();
404
        ExceptionCode ec;
405
        style->setProperty("color", color.serialized(), ec);
406 407 408 409 410 411
        if (!innerFrame->editor()->shouldApplyStyle(style.get(), innerRange.get()))
            return false;
        m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
        innerFrame->editor()->applyStyle(style.get(), EditActionSetColor);
        return true;
    }
412

413 414 415 416
    if (!m_page->dragController()->canProcessDrag(dragData)) {
        m_page->dragCaretController()->clear();
        return false;
    }
417

418
    if (HTMLInputElement* fileInput = asFileInput(element)) {
419
        if (fileInput->disabled())
adele@apple.com's avatar
adele@apple.com committed
420
            return false;
421

422 423
        if (!dragData->containsFiles())
            return false;
424

425 426 427 428
        Vector<String> filenames;
        dragData->asFilenames(filenames);
        if (filenames.isEmpty())
            return false;
429

430 431 432 433
        // Ugly. For security none of the APIs available to us can set the input value
        // on file inputs. Even forcing a change in HTMLInputElement doesn't work as
        // RenderFileUploadControl clears the file when doing updateFromElement().
        RenderFileUploadControl* renderer = toRenderFileUploadControl(fileInput->renderer());
434 435
        if (!renderer)
            return false;
436

adele@apple.com's avatar
adele@apple.com committed
437
        renderer->receiveDroppedFiles(filenames);
438 439
        return true;
    }
440

441
    VisibleSelection dragCaret = m_page->dragCaretController()->caretPosition();
justing's avatar
justing committed
442
    m_page->dragCaretController()->clear();
eric@webkit.org's avatar
eric@webkit.org committed
443
    RefPtr<Range> range = dragCaret.toNormalizedRange();
444

oliver's avatar
oliver committed
445 446
    // For range to be null a WebKit client must have done something bad while
    // manually controlling drag behaviour
447
    if (!range)
oliver's avatar
oliver committed
448
        return false;
449 450
    CachedResourceLoader* cachedResourceLoader = range->ownerDocument()->cachedResourceLoader();
    cachedResourceLoader->setAllowStaleResources(true);
451
    if (dragIsMove(innerFrame->selection(), dragData) || dragCaret.isContentRichlyEditable()) {
452
        bool chosePlainText = false;
453
        RefPtr<DocumentFragment> fragment = documentFragmentFromDragData(dragData, innerFrame, range, true, chosePlainText);
454
        if (!fragment || !innerFrame->editor()->shouldInsertFragment(fragment, range, EditorInsertActionDropped)) {
455
            cachedResourceLoader->setAllowStaleResources(false);
456
            return false;
457
        }
458

459
        m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
460
        if (dragIsMove(innerFrame->selection(), dragData)) {
461 462 463
            // NSTextView behavior is to always smart delete on moving a selection,
            // but only to smart insert if the selection granularity is word granularity.
            bool smartDelete = innerFrame->editor()->smartInsertDeleteEnabled();
464
            bool smartInsert = smartDelete && innerFrame->selection()->granularity() == WordGranularity && dragData->canSmartReplace();
465
            applyCommand(MoveSelectionCommand::create(fragment, dragCaret.base(), smartInsert, smartDelete));
466
        } else {
467 468 469 470 471 472 473 474
            if (setSelectionToDragCaret(innerFrame, dragCaret, range, point)) {
                ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::PreventNesting;
                if (dragData->canSmartReplace())
                    options |= ReplaceSelectionCommand::SmartReplace;
                if (chosePlainText)
                    options |= ReplaceSelectionCommand::MatchStyle;
                applyCommand(ReplaceSelectionCommand::create(m_documentUnderMouse.get(), fragment, options));
            }
475
        }
476
    } else {
477
        String text = dragData->asPlainText(innerFrame);
478
        if (text.isEmpty() || !innerFrame->editor()->shouldInsertText(text, range.get(), EditorInsertActionDropped)) {
479
            cachedResourceLoader->setAllowStaleResources(false);
480
            return false;
481
        }
482

483
        m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
484
        if (setSelectionToDragCaret(innerFrame, dragCaret, range, point))
485
            applyCommand(ReplaceSelectionCommand::create(m_documentUnderMouse.get(), createFragmentFromText(range.get(), text),  ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::MatchStyle | ReplaceSelectionCommand::PreventNesting));
486
    }
487
    cachedResourceLoader->setAllowStaleResources(false);
488 489 490

    return true;
}
491 492

bool DragController::canProcessDrag(DragData* dragData)
493 494 495 496 497
{
    ASSERT(dragData);

    if (!dragData->containsCompatibleContent())
        return false;
498

499
    IntPoint point = m_page->mainFrame()->view()->windowToContents(dragData->clientPosition());
500
    HitTestResult result = HitTestResult(point);
eric@webkit.org's avatar
eric@webkit.org committed
501
    if (!m_page->mainFrame()->contentRenderer())
502 503 504
        return false;

    result = m_page->mainFrame()->eventHandler()->hitTestResultAtPoint(point, true);
505 506

    if (!result.innerNonSharedNode())
507
        return false;
508

509 510
    if (dragData->containsFiles() && asFileInput(result.innerNonSharedNode()))
        return true;
511

512
    if (!result.innerNonSharedNode()->rendererIsEditable())
513
        return false;
514

515
    if (m_didInitiateDrag && m_documentUnderMouse == m_dragInitiator && result.isSelected())
516
        return false;
517

518 519 520
    return true;
}

521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
static DragOperation defaultOperationForDrag(DragOperation srcOpMask)
{
    // This is designed to match IE's operation fallback for the case where
    // the page calls preventDefault() in a drag event but doesn't set dropEffect.
    if (srcOpMask == DragOperationEvery)
        return DragOperationCopy;
    if (srcOpMask == DragOperationNone)
        return DragOperationNone;
    if (srcOpMask & DragOperationMove || srcOpMask & DragOperationGeneric)
        return DragOperationMove;
    if (srcOpMask & DragOperationCopy)
        return DragOperationCopy;
    if (srcOpMask & DragOperationLink)
        return DragOperationLink;
    
    // FIXME: Does IE really return "generic" even if no operations were allowed by the source?
    return DragOperationGeneric;
}

540
bool DragController::tryDHTMLDrag(DragData* dragData, DragOperation& operation)
541
{
542
    ASSERT(dragData);
543
    ASSERT(m_documentUnderMouse);
ggaren@apple.com's avatar
ggaren@apple.com committed
544 545
    RefPtr<Frame> mainFrame = m_page->mainFrame();
    RefPtr<FrameView> viewProtector = mainFrame->view();
546
    if (!viewProtector)
547 548
        return false;

ggaren@apple.com's avatar
ggaren@apple.com committed
549
    ClipboardAccessPolicy policy = m_documentUnderMouse->securityOrigin()->isLocal() ? ClipboardReadable : ClipboardTypesReadable;
550
    RefPtr<Clipboard> clipboard = Clipboard::create(policy, dragData, mainFrame.get());
551 552
    DragOperation srcOpMask = dragData->draggingSourceOperationMask();
    clipboard->setSourceOperation(srcOpMask);
553

554 555
    PlatformMouseEvent event = createMouseEvent(dragData);
    if (!mainFrame->eventHandler()->updateDragAndDrop(event, clipboard.get())) {
556
        clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
557 558 559
        return false;
    }

560
    operation = clipboard->destinationOperation();
561 562 563
    if (clipboard->dropEffectIsUninitialized())
        operation = defaultOperationForDrag(srcOpMask);
    else if (!(srcOpMask & operation)) {
564 565
        // The element picked an operation which is not supported by the source
        operation = DragOperationNone;
566
    }
567 568 569

    clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
    return true;
570 571
}

572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606
Node* DragController::draggableNode(const Frame* src, Node* startNode, bool dhtmlOK, bool uaOK, int x, int y, bool& dhtmlWillDrag) const
{
    if (!dhtmlOK && !uaOK)
        return 0;

    for (const RenderObject* renderer = startNode->renderer(); renderer; renderer = renderer->parent()) {
        Node* node = renderer->node();
        if (node && node->nodeType() == Node::TEXT_NODE) {
            // Since there's no way for the author to address the -webkit-user-drag style for a text node,
            // we use our own judgement.
            if (uaOK && mayStartDragAtEventLocation(src, IntPoint(x, y), node)) {
                dhtmlWillDrag = false;
                return node;
            }
            if (node->canStartSelection())
                // In this case we have a click in the unselected portion of text. If this text is
                // selectable, we want to start the selection process instead of looking for a parent
                // to try to drag.
                return 0;
        } else {
            EUserDrag dragMode = renderer->style()->userDrag();
            if (dhtmlOK && dragMode == DRAG_ELEMENT) {
                dhtmlWillDrag = true;
                return node;
            }
            if (uaOK && dragMode == DRAG_AUTO && mayStartDragAtEventLocation(src, IntPoint(x, y), node)) {
                dhtmlWillDrag = false;
                return node;
            }
        }
    }
    return 0;
}

bool DragController::mayStartDragAtEventLocation(const Frame* frame, const IntPoint& framePos, Node* node) const
oliver's avatar
 
oliver committed
607 608
{
    ASSERT(frame);
ggaren's avatar
ggaren committed
609
    ASSERT(frame->settings());
oliver's avatar
 
oliver committed
610

eric@webkit.org's avatar
eric@webkit.org committed
611
    if (!frame->view() || !frame->contentRenderer())
oliver's avatar
 
oliver committed
612 613 614 615 616
        return false;

    HitTestResult mouseDownTarget = HitTestResult(framePos);

    mouseDownTarget = frame->eventHandler()->hitTestResultAtPoint(framePos, true);
617 618
    if (node)
        mouseDownTarget.setInnerNonSharedNode(node);
oliver's avatar
 
oliver committed
619

620
    if (mouseDownTarget.image()
oliver's avatar
 
oliver committed
621 622 623 624 625 626 627
        && !mouseDownTarget.absoluteImageURL().isEmpty()
        && frame->settings()->loadsImagesAutomatically()
        && m_dragSourceAction & DragSourceActionImage)
        return true;

    if (!mouseDownTarget.absoluteLinkURL().isEmpty()
        && m_dragSourceAction & DragSourceActionLink
628 629
        && mouseDownTarget.isLiveLink()
        && mouseDownTarget.URLElement()->renderer() && mouseDownTarget.URLElement()->renderer()->style()->userDrag() != DRAG_NONE)
oliver's avatar
 
oliver committed
630 631 632 633 634 635 636 637
        return true;

    if (mouseDownTarget.isSelected()
        && m_dragSourceAction & DragSourceActionSelection)
        return true;

    return false;
}
638

639 640 641 642
static CachedImage* getCachedImage(Element* element)
{
    ASSERT(element);
    RenderObject* renderer = element->renderer();
643
    if (!renderer || !renderer->isImage())
644
        return 0;
645
    RenderImage* image = toRenderImage(renderer);
646 647
    return image->cachedImage();
}
648

649 650 651
static Image* getImage(Element* element)
{
    ASSERT(element);
652 653 654
    CachedImage* cachedImage = getCachedImage(element);
    return (cachedImage && !cachedImage->errorOccurred()) ?
        cachedImage->image() : 0;
655
}
656

657 658 659 660 661
static void prepareClipboardForImageDrag(Frame* src, Clipboard* clipboard, Element* node, const KURL& linkURL, const KURL& imageURL, const String& label)
{
    RefPtr<Range> range = src->document()->createRange();
    ExceptionCode ec = 0;
    range->selectNode(node, ec);
662 663
    ASSERT(!ec);
    src->selection()->setSelection(VisibleSelection(range.get(), DOWNSTREAM));
664 665
    clipboard->declareAndWriteDragImage(node, !linkURL.isEmpty() ? linkURL : imageURL, label, src);
}
666

667 668 669
static IntPoint dragLocForDHTMLDrag(const IntPoint& mouseDraggedPoint, const IntPoint& dragOrigin, const IntPoint& dragImageOffset, bool isLinkImage)
{
    // dragImageOffset is the cursor position relative to the lower-left corner of the image.
670 671
#if PLATFORM(MAC)
    // We add in the Y dimension because we are a flipped view, so adding moves the image down.
672 673 674 675
    const int yOffset = dragImageOffset.y();
#else
    const int yOffset = -dragImageOffset.y();
#endif
676

677 678
    if (isLinkImage)
        return IntPoint(mouseDraggedPoint.x() - dragImageOffset.x(), mouseDraggedPoint.y() + yOffset);
679

680 681
    return IntPoint(dragOrigin.x() - dragImageOffset.x(), dragOrigin.y() + yOffset);
}
682

683 684
static IntPoint dragLocForSelectionDrag(Frame* src)
{
685
    IntRect draggingRect = enclosingIntRect(src->selection()->bounds());
686
    int xpos = draggingRect.maxX();
687
    xpos = draggingRect.x() < xpos ? draggingRect.x() : xpos;
688
    int ypos = draggingRect.maxY();
689 690 691 692 693 694 695 696
#if PLATFORM(MAC)
    // Deal with flipped coordinates on Mac
    ypos = draggingRect.y() > ypos ? draggingRect.y() : ypos;
#else
    ypos = draggingRect.y() < ypos ? draggingRect.y() : ypos;
#endif
    return IntPoint(xpos, ypos);
}
697

698
bool DragController::startDrag(Frame* src, Clipboard* clipboard, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin, bool isDHTMLDrag)
699
{
700 701
    ASSERT(src);
    ASSERT(clipboard);
702

eric@webkit.org's avatar
eric@webkit.org committed
703
    if (!src->view() || !src->contentRenderer())
704
        return false;
705

706 707 708 709 710
    HitTestResult dragSource = HitTestResult(dragOrigin);
    dragSource = src->eventHandler()->hitTestResultAtPoint(dragOrigin, true);
    KURL linkURL = dragSource.absoluteLinkURL();
    KURL imageURL = dragSource.absoluteImageURL();
    bool isSelected = dragSource.isSelected();
711

712
    IntPoint mouseDraggedPoint = src->view()->windowToContents(dragEvent.pos());
713

714
    m_draggingImageURL = KURL();
715
    m_sourceDragOperation = srcOp;
716

717 718 719
    DragImageRef dragImage = 0;
    IntPoint dragLoc(0, 0);
    IntPoint dragImageOffset(0, 0);
720 721

    if (isDHTMLDrag)
722
        dragImage = clipboard->createDragImage(dragImageOffset);
723 724 725 726 727 728
    else {
        // This drag operation is not a DHTML drag and may go outside the WebView.
        // We provide a default set of allowed drag operations that follows from:
        // http://trac.webkit.org/browser/trunk/WebKit/mac/WebView/WebHTMLView.mm?rev=48526#L3430
        m_sourceDragOperation = (DragOperation)(DragOperationGeneric | DragOperationCopy);
    }
729

730 731 732 733 734 735
    // We allow DHTML/JS to set the drag image, even if its a link, image or text we're dragging.
    // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp.
    if (dragImage) {
        dragLoc = dragLocForDHTMLDrag(mouseDraggedPoint, dragOrigin, dragImageOffset, !linkURL.isEmpty());
        m_dragOffset = dragImageOffset;
    }
736

737
    bool startedDrag = true; // optimism - we almost always manage to start the drag
738

739
    Node* node = dragSource.innerNonSharedNode();
740

alice.liu@apple.com's avatar
alice.liu@apple.com committed
741 742
    Image* image = getImage(static_cast<Element*>(node));
    if (!imageURL.isEmpty() && node && node->isElementNode() && image
743
            && (m_dragSourceAction & DragSourceActionImage)) {
744
        // We shouldn't be starting a drag for an image that can't provide an extension.
alice.liu@apple.com's avatar
alice.liu@apple.com committed
745 746
        // This is an early detection for problems encountered later upon drop.
        ASSERT(!image->filenameExtension().isEmpty());
747 748
        Element* element = static_cast<Element*>(node);
        if (!clipboard->hasData()) {
749
            m_draggingImageURL = imageURL;
750 751
            prepareClipboardForImageDrag(src, clipboard, element, linkURL, imageURL, dragSource.altDisplayString());
        }
752

753
        m_client->willPerformDragSourceAction(DragSourceActionImage, dragOrigin, clipboard);
754

755 756 757 758
        if (!dragImage) {
            IntRect imageRect = dragSource.imageRect();
            imageRect.setLocation(m_page->mainFrame()->view()->windowToContents(src->view()->contentsToWindow(imageRect.location())));
            doImageDrag(element, dragOrigin, dragSource.imageRect(), clipboard, src, m_dragOffset);
759
        } else
760 761 762 763
            // DHTML defined drag image
            doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false);

    } else if (!linkURL.isEmpty() && (m_dragSourceAction & DragSourceActionLink)) {
sullivan's avatar
sullivan committed
764 765 766 767
        if (!clipboard->hasData())
            // Simplify whitespace so the title put on the clipboard resembles what the user sees
            // on the web page. This includes replacing newlines with spaces.
            clipboard->writeURL(linkURL, dragSource.textContent().simplifyWhiteSpace(), src);
768 769 770 771 772 773 774 775

        if (src->selection()->isCaret() && src->selection()->isContentEditable()) {
            // a user can initiate a drag on a link without having any text
            // selected.  In this case, we should expand the selection to
            // the enclosing anchor element
            Position pos = src->selection()->base();
            Node* node = enclosingAnchorElement(pos);
            if (node)
776
                src->selection()->setSelection(VisibleSelection::selectionFromContentsOfNode(node));
777 778
        }

779 780
        m_client->willPerformDragSourceAction(DragSourceActionLink, dragOrigin, clipboard);
        if (!dragImage) {
781
            dragImage = createDragImageForLink(linkURL, dragSource.textContent(), src);
782 783 784
            IntSize size = dragImageSize(dragImage);
            m_dragOffset = IntPoint(-size.width() / 2, -LinkDragBorderInset);
            dragLoc = IntPoint(mouseDraggedPoint.x() + m_dragOffset.x(), mouseDraggedPoint.y() + m_dragOffset.y());
785
        }
786
        doSystemDrag(dragImage, dragLoc, mouseDraggedPoint, clipboard, src, true);
787
    } else if (isSelected && (m_dragSourceAction & DragSourceActionSelection)) {
788
        if (!clipboard->hasData()) {
789
            if (isNodeInTextFormControl(src->selection()->start().deprecatedNode()))
790
                clipboard->writePlainText(src->editor()->selectedText());
791 792 793 794 795 796 797
            else {
                RefPtr<Range> selectionRange = src->selection()->toNormalizedRange();
                ASSERT(selectionRange);

                clipboard->writeRange(selectionRange.get(), src);
            }
        }
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
        m_client->willPerformDragSourceAction(DragSourceActionSelection, dragOrigin, clipboard);
        if (!dragImage) {
            dragImage = createDragImageForSelection(src);
            dragLoc = dragLocForSelectionDrag(src);
            m_dragOffset = IntPoint((int)(dragOrigin.x() - dragLoc.x()), (int)(dragOrigin.y() - dragLoc.y()));
        }
        doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false);
    } else if (isDHTMLDrag) {
        ASSERT(m_dragSourceAction & DragSourceActionDHTML);
        m_client->willPerformDragSourceAction(DragSourceActionDHTML, dragOrigin, clipboard);
        doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false);
    } else {
        // Only way I know to get here is if to get here is if the original element clicked on in the mousedown is no longer
        // under the mousedown point, so linkURL, imageURL and isSelected are all false/empty.
        startedDrag = false;
    }
814

815 816 817 818
    if (dragImage)
        deleteDragImage(dragImage);
    return startedDrag;
}
819

820 821 822 823 824
void DragController::doImageDrag(Element* element, const IntPoint& dragOrigin, const IntRect& rect, Clipboard* clipboard, Frame* frame, IntPoint& dragImageOffset)
{
    IntPoint mouseDownPoint = dragOrigin;
    DragImageRef dragImage;
    IntPoint origin;
825

826
    Image* image = getImage(element);
827 828
    if (image && image->size().height() * image->size().width() <= MaxOriginalImageArea
        && (dragImage = createDragImageFromImage(image))) {
829 830
        IntSize originalSize = rect.size();
        origin = rect.location();
831

oliver's avatar
oliver committed
832
        dragImage = fitDragImageToMaxSize(dragImage, rect.size(), maxDragImageSize());
833 834
        dragImage = dissolveDragImageToFraction(dragImage, DragImageAlpha);
        IntSize newSize = dragImageSize(dragImage);
835

836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
        // Properly orient the drag image and orient it differently if it's smaller than the original
        float scale = newSize.width() / (float)originalSize.width();
        float dx = origin.x() - mouseDownPoint.x();
        dx *= scale;
        origin.setX((int)(dx + 0.5));
#if PLATFORM(MAC)
        //Compensate for accursed flipped coordinates in cocoa
        origin.setY(origin.y() + originalSize.height());
#endif
        float dy = origin.y() - mouseDownPoint.y();
        dy *= scale;
        origin.setY((int)(dy + 0.5));
    } else {
        dragImage = createDragImageIconForCachedImage(getCachedImage(element));
        if (dragImage)
            origin = IntPoint(DragIconRightInset - dragImageSize(dragImage).width(), DragIconBottomInset);
    }
853

854 855 856
    dragImageOffset.setX(mouseDownPoint.x() + origin.x());
    dragImageOffset.setY(mouseDownPoint.y() + origin.y());
    doSystemDrag(dragImage, dragImageOffset, dragOrigin, clipboard, frame, false);
857

858 859
    deleteDragImage(dragImage);
}
860

861 862 863 864 865 866 867
void DragController::doSystemDrag(DragImageRef image, const IntPoint& dragLoc, const IntPoint& eventPos, Clipboard* clipboard, Frame* frame, bool forLink)
{
    m_didInitiateDrag = true;
    m_dragInitiator = frame->document();
    // Protect this frame and view, as a load may occur mid drag and attempt to unload this frame
    RefPtr<Frame> frameProtector = m_page->mainFrame();
    RefPtr<FrameView> viewProtector = frameProtector->view();
868 869
    m_client->startDrag(image, viewProtector->windowToContents(frame->view()->contentsToWindow(dragLoc)),
        viewProtector->windowToContents(frame->view()->contentsToWindow(eventPos)), clipboard, frameProtector.get(), forLink);
870

871
    cleanupAfterSystemDrag();
872
}
873

oliver's avatar
oliver committed
874 875 876
// Manual drag caret manipulation
void DragController::placeDragCaret(const IntPoint& windowPoint)
{
877 878
    mouseMovedIntoDocument(m_page->mainFrame()->documentAtPoint(windowPoint));
    if (!m_documentUnderMouse)
oliver's avatar
oliver committed
879
        return;
880
    Frame* frame = m_documentUnderMouse->frame();
oliver's avatar
oliver committed
881 882 883 884
    FrameView* frameView = frame->view();
    if (!frameView)
        return;
    IntPoint framePoint = frameView->windowToContents(windowPoint);
885 886

    m_page->dragCaretController()->setCaretPosition(frame->visiblePositionForPoint(framePoint));
oliver's avatar
oliver committed
887
}
888

889
} // namespace WebCore
bolsinga@apple.com's avatar
bolsinga@apple.com committed
890 891

#endif // ENABLE(DRAG_SUPPORT)