DragController.cpp 39.1 KB
Newer Older
1
/*
2
 * Copyright (C) 2007, 2009, 2010, 2013 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)
30

31
#include "CachedImage.h"
32 33
#include "Clipboard.h"
#include "ClipboardAccessPolicy.h"
34
#include "CachedResourceLoader.h"
35 36 37
#include "Document.h"
#include "DocumentFragment.h"
#include "DragActions.h"
darin's avatar
darin committed
38 39
#include "DragClient.h"
#include "DragData.h"
40
#include "DragImage.h"
41
#include "DragSession.h"
42
#include "DragState.h"
43 44 45 46
#include "Editor.h"
#include "EditorClient.h"
#include "Element.h"
#include "EventHandler.h"
47
#include "ExceptionCodePlaceholder.h"
48
#include "FloatRect.h"
49
#include "FrameLoadRequest.h"
50
#include "FrameLoader.h"
51
#include "FrameSelection.h"
52 53
#include "FrameView.h"
#include "HTMLAnchorElement.h"
54
#include "HTMLImageElement.h"
55 56
#include "HTMLInputElement.h"
#include "HTMLNames.h"
57
#include "HTMLPlugInElement.h"
58
#include "HitTestRequest.h"
darin's avatar
darin committed
59
#include "HitTestResult.h"
60
#include "Image.h"
61
#include "ImageOrientation.h"
darin@apple.com's avatar
darin@apple.com committed
62
#include "MainFrame.h"
63 64
#include "MoveSelectionCommand.h"
#include "Page.h"
65
#include "Pasteboard.h"
66
#include "PlatformKeyboardEvent.h"
67 68
#include "PluginDocument.h"
#include "PluginViewBase.h"
69
#include "RenderFileUploadControl.h"
70
#include "RenderImage.h"
71
#include "RenderView.h"
72 73
#include "ReplaceSelectionCommand.h"
#include "ResourceRequest.h"
74
#include "SecurityOrigin.h"
oliver's avatar
 
oliver committed
75
#include "Settings.h"
76
#include "ShadowRoot.h"
77
#include "StyleProperties.h"
78
#include "Text.h"
79
#include "TextEvent.h"
80
#include "htmlediting.h"
darin's avatar
darin committed
81
#include "markup.h"
ap@webkit.org's avatar
ap@webkit.org committed
82
#include <wtf/CurrentTime.h>
83
#include <wtf/RefPtr.h>
84 85 86

namespace WebCore {

87
static PlatformMouseEvent createMouseEvent(DragData& dragData)
88
{
89 90
    bool shiftKey, ctrlKey, altKey, metaKey;
    shiftKey = ctrlKey = altKey = metaKey = false;
91
    int keyState = dragData.modifierKeyState();
92 93 94 95 96
    shiftKey = static_cast<bool>(keyState & PlatformEvent::ShiftKey);
    ctrlKey = static_cast<bool>(keyState & PlatformEvent::CtrlKey);
    altKey = static_cast<bool>(keyState & PlatformEvent::AltKey);
    metaKey = static_cast<bool>(keyState & PlatformEvent::MetaKey);

97
    return PlatformMouseEvent(dragData.clientPosition(), dragData.globalPosition(),
98
                              LeftButton, PlatformEvent::MouseMoved, 0, shiftKey, ctrlKey, altKey,
99
                              metaKey, currentTime());
100
}
101

102
DragController::DragController(Page& page, DragClient& client)
103 104
    : m_page(page)
    , m_client(client)
105
    , m_documentUnderMouse(0)
106
    , m_dragInitiator(0)
107
    , m_fileInputElementUnderMouse(0)
108
    , m_documentIsHandlingDrag(false)
109 110 111
    , m_dragDestinationAction(DragDestinationActionNone)
    , m_dragSourceAction(DragSourceActionNone)
    , m_didInitiateDrag(false)
112
    , m_sourceDragOperation(DragOperationNone)
113 114
{
}
115

116
DragController::~DragController()
117
{
118
    m_client.dragControllerDestroyed();
119 120
}

121
static PassRefPtr<DocumentFragment> documentFragmentFromDragData(DragData& dragData, Frame* frame, Range& context, bool allowPlainText, bool& chosePlainText)
122 123 124
{
    chosePlainText = false;

125 126 127
    Document& document = context.ownerDocument();
    if (dragData.containsCompatibleContent()) {
        if (PassRefPtr<DocumentFragment> fragment = dragData.asFragment(frame, context, allowPlainText, chosePlainText))
128 129
            return fragment;

130
        if (dragData.containsURL(frame, DragData::DoNotConvertFilenames)) {
131
            String title;
132
            String url = dragData.asURL(frame, DragData::DoNotConvertFilenames, &title);
ggaren's avatar
ggaren committed
133
            if (!url.isEmpty()) {
134
                RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::create(document);
135
                anchor->setHref(url);
136 137
                if (title.isEmpty()) {
                    // Try the plain text first because the url might be normalized or escaped.
138 139
                    if (dragData.containsPlainText())
                        title = dragData.asPlainText(frame);
140 141 142
                    if (title.isEmpty())
                        title = url;
                }
143
                RefPtr<Node> anchorText = document.createTextNode(title);
144
                anchor->appendChild(anchorText, IGNORE_EXCEPTION);
145
                RefPtr<DocumentFragment> fragment = document.createDocumentFragment();
146
                fragment->appendChild(anchor, IGNORE_EXCEPTION);
147 148 149 150
                return fragment.get();
            }
        }
    }
151
    if (allowPlainText && dragData.containsPlainText()) {
152
        chosePlainText = true;
153
        return createFragmentFromText(context, dragData.asPlainText(frame)).get();
154
    }
155

156 157 158
    return 0;
}

159
bool DragController::dragIsMove(FrameSelection& selection, DragData& dragData)
160
{
161
    return m_documentUnderMouse == m_dragInitiator && selection.isContentEditable() && selection.isRange() && !isCopyKeyDown(dragData);
162 163
}

164
// FIXME: This method is poorly named.  We're just clearing the selection from the document this drag is exiting.
165 166
void DragController::cancelDrag()
{
167
    m_page.dragCaretController().clear();
168 169
}

oliver's avatar
oliver committed
170 171 172
void DragController::dragEnded()
{
    m_dragInitiator = 0;
173
    m_didInitiateDrag = false;
174
    m_page.dragCaretController().clear();
175
    
176
    m_client.dragEnded();
177
}
178

179
DragSession DragController::dragEntered(DragData& dragData)
180 181 182
{
    return dragEnteredOrUpdated(dragData);
}
183

184
void DragController::dragExited(DragData& dragData)
185
{
186
    if (RefPtr<FrameView> v = m_page.mainFrame().view()) {
187
        ClipboardAccessPolicy policy = (!m_documentUnderMouse || m_documentUnderMouse->securityOrigin()->isLocal()) ? ClipboardReadable : ClipboardTypesReadable;
188 189
        RefPtr<Clipboard> clipboard = Clipboard::createForDragAndDrop(policy, dragData);
        clipboard->setSourceOperation(dragData.draggingSourceOperationMask());
190
        m_page.mainFrame().eventHandler().cancelDragAndDrop(createMouseEvent(dragData), clipboard.get());
191 192
        clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
    }
193
    mouseMovedIntoDocument(0);
194 195 196
    if (m_fileInputElementUnderMouse)
        m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
    m_fileInputElementUnderMouse = 0;
197
}
ggaren@apple.com's avatar
ggaren@apple.com committed
198

199
DragSession DragController::dragUpdated(DragData& dragData)
200 201 202
{
    return dragEnteredOrUpdated(dragData);
}
203

204
bool DragController::performDrag(DragData& dragData)
205
{
206
    m_documentUnderMouse = m_page.mainFrame().documentAtPoint(dragData.clientPosition());
207
    if ((m_dragDestinationAction & DragDestinationActionDHTML) && m_documentIsHandlingDrag) {
208
        m_client.willPerformDragDestinationAction(DragDestinationActionDHTML, dragData);
akling@apple.com's avatar
akling@apple.com committed
209
        Ref<MainFrame> mainFrame(m_page.mainFrame());
210
        bool preventedDefault = false;
211 212
        if (mainFrame->view()) {
            // Sending an event can result in the destruction of the view and part.
213 214
            RefPtr<Clipboard> clipboard = Clipboard::createForDragAndDrop(ClipboardReadable, dragData);
            clipboard->setSourceOperation(dragData.draggingSourceOperationMask());
215
            preventedDefault = mainFrame->eventHandler().performDragAndDrop(createMouseEvent(dragData), clipboard.get());
216 217
            clipboard->setAccessPolicy(ClipboardNumb); // Invalidate clipboard here for security
        }
218
        if (preventedDefault) {
219 220
            m_documentUnderMouse = 0;
            return true;
221
        }
222 223
    }

224
    if ((m_dragDestinationAction & DragDestinationActionEdit) && concludeEditDrag(dragData)) {
225
        m_documentUnderMouse = 0;
226 227
        return true;
    }
228

229
    m_documentUnderMouse = 0;
230

231
    if (operationForLoad(dragData) == DragOperationNone)
232
        return false;
233

234
    m_client.willPerformDragDestinationAction(DragDestinationActionLoad, dragData);
235
    m_page.mainFrame().loader().load(FrameLoadRequest(&m_page.mainFrame(), ResourceRequest(dragData.asURL(&m_page.mainFrame()))));
236 237
    return true;
}
238 239 240 241 242 243 244 245 246 247 248 249

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;
}

250
DragSession DragController::dragEnteredOrUpdated(DragData& dragData)
251
{
252
    mouseMovedIntoDocument(m_page.mainFrame().documentAtPoint(dragData.clientPosition()));
253

254
    m_dragDestinationAction = m_client.actionMaskForDrag(dragData);
255
    if (m_dragDestinationAction == DragDestinationActionNone) {
256
        cancelDrag(); // FIXME: Why not call mouseMovedIntoDocument(0)?
257
        return DragSession();
258
    }
259

260
    DragSession dragSession;
261 262
    m_documentIsHandlingDrag = tryDocumentDrag(dragData, m_dragDestinationAction, dragSession);
    if (!m_documentIsHandlingDrag && (m_dragDestinationAction & DragDestinationActionLoad))
263 264
        dragSession.operation = operationForLoad(dragData);
    return dragSession;
265
}
266 267 268 269

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

271
    HTMLInputElement* inputElement = node->toInputElement();
272

273
    // If this is a button inside of the a file input, move up to the file input.
274 275
    if (inputElement && inputElement->isTextButton() && inputElement->treeScope().rootNode()->isShadowRoot())
        inputElement = toShadowRoot(inputElement->treeScope().rootNode())->hostElement()->toInputElement();
276

277
    return inputElement && inputElement->isFileUpload() ? inputElement : 0;
278
}
279

280
// This can return null if an empty document is loaded.
281 282
static Element* elementUnderMouse(Document* documentUnderMouse, const IntPoint& p)
{
283 284
    Frame* frame = documentUnderMouse->frame();
    float zoomFactor = frame ? frame->pageZoomFactor() : 1;
285
    LayoutPoint point = roundedLayoutPoint(FloatPoint(p.x() * zoomFactor, p.y() * zoomFactor));
286

287
    HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowShadowContent);
288
    HitTestResult result(point);
289
    documentUnderMouse->renderView()->hitTest(request, result);
290 291 292 293 294

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

297
    return toElement(n);
298 299
}

300
bool DragController::tryDocumentDrag(DragData& dragData, DragDestinationAction actionMask, DragSession& dragSession)
301
{
302
    if (!m_documentUnderMouse)
303
        return false;
304

305 306 307
    if (m_dragInitiator && !m_documentUnderMouse->securityOrigin()->canReceiveDragData(m_dragInitiator->securityOrigin()))
        return false;

308
    bool isHandlingDrag = false;
309
    if (actionMask & DragDestinationActionDHTML) {
310
        isHandlingDrag = tryDHTMLDrag(dragData, dragSession.operation);
311 312 313 314 315 316
        // 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)
317
            return false;
318
    }
319

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

326
    if (isHandlingDrag) {
327
        m_page.dragCaretController().clear();
328
        return true;
329 330 331
    }

    if ((actionMask & DragDestinationActionEdit) && canProcessDrag(dragData)) {
332
        if (dragData.containsColor()) {
333
            dragSession.operation = DragOperationGeneric;
334 335
            return true;
        }
336

337
        IntPoint point = frameView->windowToContents(dragData.clientPosition());
338
        Element* element = elementUnderMouse(m_documentUnderMouse.get(), point);
339 340
        if (!element)
            return false;
341 342
        
        HTMLInputElement* elementAsFileInput = asFileInput(element);
343 344 345 346 347 348 349
        if (m_fileInputElementUnderMouse != elementAsFileInput) {
            if (m_fileInputElementUnderMouse)
                m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
            m_fileInputElementUnderMouse = elementAsFileInput;
        }
        
        if (!m_fileInputElementUnderMouse)
350
            m_page.dragCaretController().setCaretPosition(m_documentUnderMouse->frame()->visiblePositionForPoint(point));
351

352
        Frame* innerFrame = element->document().frame();
353
        dragSession.operation = dragIsMove(innerFrame->selection(), dragData) ? DragOperationMove : DragOperationCopy;
354
        dragSession.mouseIsOverFileInput = m_fileInputElementUnderMouse;
355 356
        dragSession.numberOfItemsToBeAccepted = 0;

357
        unsigned numberOfFiles = dragData.numberOfFiles();
358
        if (m_fileInputElementUnderMouse) {
359
            if (m_fileInputElementUnderMouse->isDisabledFormControl())
360
                dragSession.numberOfItemsToBeAccepted = 0;
361
            else if (m_fileInputElementUnderMouse->multiple())
362
                dragSession.numberOfItemsToBeAccepted = numberOfFiles;
363 364
            else if (numberOfFiles > 1)
                dragSession.numberOfItemsToBeAccepted = 0;
365 366 367 368 369
            else
                dragSession.numberOfItemsToBeAccepted = 1;
            
            if (!dragSession.numberOfItemsToBeAccepted)
                dragSession.operation = DragOperationNone;
370
            m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(dragSession.numberOfItemsToBeAccepted);
371 372 373 374 375 376
        } else {
            // We are not over a file input element. The dragged item(s) will only
            // be loaded into the view the number of dragged items is 1.
            dragSession.numberOfItemsToBeAccepted = numberOfFiles != 1 ? 0 : 1;
        }
        
377 378
        return true;
    }
379 380
    
    // We are not over an editable region. Make sure we're clearing any prior drag cursor.
381
    m_page.dragCaretController().clear();
382 383 384
    if (m_fileInputElementUnderMouse)
        m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
    m_fileInputElementUnderMouse = 0;
385
    return false;
386
}
387

388
DragSourceAction DragController::delegateDragSourceAction(const IntPoint& rootViewPoint)
389
{
390
    m_dragSourceAction = m_client.dragSourceActionMaskForPoint(rootViewPoint);
391 392
    return m_dragSourceAction;
}
393

394
DragOperation DragController::operationForLoad(DragData& dragData)
395
{
396
    Document* doc = m_page.mainFrame().documentAtPoint(dragData.clientPosition());
397 398 399 400

    bool pluginDocumentAcceptsDrags = false;

    if (doc && doc->isPluginDocument()) {
401
        const Widget* widget = toPluginDocument(doc)->pluginWidget();
402
        const PluginViewBase* pluginView = (widget && widget->isPluginViewBase()) ? toPluginViewBase(widget) : nullptr;
403 404 405 406 407 408

        if (pluginView)
            pluginDocumentAcceptsDrags = pluginView->shouldAllowNavigationFromDrags();
    }

    if (doc && (m_didInitiateDrag || (doc->isPluginDocument() && !pluginDocumentAcceptsDrags) || doc->rendererIsEditable()))
409 410 411 412
        return DragOperationNone;
    return dragOperation(dragData);
}

413
static bool setSelectionToDragCaret(Frame* frame, VisibleSelection& dragCaret, RefPtr<Range>& range, const IntPoint& point)
414
{
415 416
    frame->selection().setSelection(dragCaret);
    if (frame->selection().isNone()) {
oliver's avatar
oliver committed
417
        dragCaret = frame->visiblePositionForPoint(point);
418
        frame->selection().setSelection(dragCaret);
eric@webkit.org's avatar
eric@webkit.org committed
419
        range = dragCaret.toNormalizedRange();
420
    }
421
    return !frame->selection().isNone() && frame->selection().isContentEditable();
422 423
}

424
bool DragController::dispatchTextInputEventFor(Frame* innerFrame, DragData& dragData)
425
{
426
    ASSERT(m_page.dragCaretController().hasCaret());
427
    String text = m_page.dragCaretController().isContentRichlyEditable() ? emptyString() : dragData.asPlainText(innerFrame);
428
    Node* target = innerFrame->editor().findEventTargetFrom(m_page.dragCaretController().caretPosition());
429
    return target->dispatchEvent(TextEvent::createForDrop(innerFrame->document()->domWindow(), text), IGNORE_EXCEPTION);
430 431
}

432
bool DragController::concludeEditDrag(DragData& dragData)
433
{
434
    RefPtr<HTMLInputElement> fileInput = m_fileInputElementUnderMouse;
435 436 437 438 439
    if (m_fileInputElementUnderMouse) {
        m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
        m_fileInputElementUnderMouse = 0;
    }

440
    if (!m_documentUnderMouse)
441
        return false;
442

443
    IntPoint point = m_documentUnderMouse->view()->windowToContents(dragData.clientPosition());
444
    Element* element = elementUnderMouse(m_documentUnderMouse.get(), point);
445 446
    if (!element)
        return false;
447
    RefPtr<Frame> innerFrame = element->document().frame();
448
    ASSERT(innerFrame);
449

450
    if (m_page.dragCaretController().hasCaret() && !dispatchTextInputEventFor(innerFrame.get(), dragData))
451 452
        return true;

453 454
    if (dragData.containsColor()) {
        Color color = dragData.asColor();
455 456
        if (!color.isValid())
            return false;
457
        RefPtr<Range> innerRange = innerFrame->selection().toNormalizedRange();
458
        RefPtr<MutableStyleProperties> style = MutableStyleProperties::create();
459
        style->setProperty(CSSPropertyColor, color.serialized(), false);
460
        if (!innerFrame->editor().shouldApplyStyle(style.get(), innerRange.get()))
461
            return false;
462
        m_client.willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
463
        innerFrame->editor().applyStyle(style.get(), EditActionSetColor);
464 465
        return true;
    }
466

467
    if (dragData.containsFiles() && fileInput) {
468 469 470
        // fileInput should be the element we hit tested for, unless it was made
        // display:none in a drop event handler.
        ASSERT(fileInput == element || !fileInput->renderer());
471
        if (fileInput->isDisabledFormControl())
adele@apple.com's avatar
adele@apple.com committed
472
            return false;
473

474
        return fileInput->receiveDroppedFiles(dragData);
475
    }
476

477 478
    if (!m_page.dragController().canProcessDrag(dragData)) {
        m_page.dragCaretController().clear();
479 480 481
        return false;
    }

482 483
    VisibleSelection dragCaret = m_page.dragCaretController().caretPosition();
    m_page.dragCaretController().clear();
eric@webkit.org's avatar
eric@webkit.org committed
484
    RefPtr<Range> range = dragCaret.toNormalizedRange();
485
    RefPtr<Element> rootEditableElement = innerFrame->selection().rootEditableElement();
486

oliver's avatar
oliver committed
487 488
    // For range to be null a WebKit client must have done something bad while
    // manually controlling drag behaviour
489
    if (!range)
oliver's avatar
oliver committed
490
        return false;
491

492
    CachedResourceLoader* cachedResourceLoader = range->ownerDocument().cachedResourceLoader();
493
    ResourceCacheValidationSuppressor validationSuppressor(cachedResourceLoader);
494
    if (dragIsMove(innerFrame->selection(), dragData) || dragCaret.isContentRichlyEditable()) {
495
        bool chosePlainText = false;
496
        RefPtr<DocumentFragment> fragment = documentFragmentFromDragData(dragData, innerFrame.get(), *range, true, chosePlainText);
497
        if (!fragment || !innerFrame->editor().shouldInsertFragment(fragment, range, EditorInsertActionDropped)) {
498
            return false;
499
        }
500

501
        m_client.willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
502
        if (dragIsMove(innerFrame->selection(), dragData)) {
503 504
            // NSTextView behavior is to always smart delete on moving a selection,
            // but only to smart insert if the selection granularity is word granularity.
505
            bool smartDelete = innerFrame->editor().smartInsertDeleteEnabled();
506
            bool smartInsert = smartDelete && innerFrame->selection().granularity() == WordGranularity && dragData.canSmartReplace();
507
            applyCommand(MoveSelectionCommand::create(fragment, dragCaret.base(), smartInsert, smartDelete));
508
        } else {
509
            if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point)) {
510
                ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::PreventNesting;
511
                if (dragData.canSmartReplace())
512 513 514
                    options |= ReplaceSelectionCommand::SmartReplace;
                if (chosePlainText)
                    options |= ReplaceSelectionCommand::MatchStyle;
515
                applyCommand(ReplaceSelectionCommand::create(*m_documentUnderMouse, fragment, options));
516
            }
517
        }
518
    } else {
519
        String text = dragData.asPlainText(innerFrame.get());
520
        if (text.isEmpty() || !innerFrame->editor().shouldInsertText(text, range.get(), EditorInsertActionDropped)) {
521
            return false;
522
        }
523

524
        m_client.willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
525
        if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point))
526
            applyCommand(ReplaceSelectionCommand::create(*m_documentUnderMouse, createFragmentFromText(*range, text),  ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::MatchStyle | ReplaceSelectionCommand::PreventNesting));
527 528
    }

529
    if (rootEditableElement) {
530
        if (Frame* frame = rootEditableElement->document().frame())
531
            frame->eventHandler().updateDragStateAfterEditDragIfNeeded(rootEditableElement.get());
532 533
    }

534 535
    return true;
}
536

537
bool DragController::canProcessDrag(DragData& dragData)
538
{
539
    if (!dragData.containsCompatibleContent())
540
        return false;
541

542
    IntPoint point = m_page.mainFrame().view()->windowToContents(dragData.clientPosition());
543
    HitTestResult result = HitTestResult(point);
544
    if (!m_page.mainFrame().contentRenderer())
545 546
        return false;

547
    result = m_page.mainFrame().eventHandler().hitTestResultAtPoint(point, HitTestRequest::ReadOnly | HitTestRequest::Active);
548 549

    if (!result.innerNonSharedNode())
550
        return false;
551

552
    if (dragData.containsFiles() && asFileInput(result.innerNonSharedNode()))
553
        return true;
554

555
    if (result.innerNonSharedNode()->isPluginElement()) {
556
        if (!toHTMLPlugInElement(result.innerNonSharedNode())->canProcessDrag() && !result.innerNonSharedNode()->rendererIsEditable())
557 558
            return false;
    } else if (!result.innerNonSharedNode()->rendererIsEditable())
559
        return false;
560

561
    if (m_didInitiateDrag && m_documentUnderMouse == m_dragInitiator && result.isSelected())
562
        return false;
563

564 565 566
    return true;
}

567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
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;
}

586
bool DragController::tryDHTMLDrag(DragData& dragData, DragOperation& operation)
587
{
588
    ASSERT(m_documentUnderMouse);
akling@apple.com's avatar
akling@apple.com committed
589
    Ref<MainFrame> mainFrame(m_page.mainFrame());
ggaren@apple.com's avatar
ggaren@apple.com committed
590
    RefPtr<FrameView> viewProtector = mainFrame->view();
591
    if (!viewProtector)
592 593
        return false;

ggaren@apple.com's avatar
ggaren@apple.com committed
594
    ClipboardAccessPolicy policy = m_documentUnderMouse->securityOrigin()->isLocal() ? ClipboardReadable : ClipboardTypesReadable;
595 596
    RefPtr<Clipboard> clipboard = Clipboard::createForDragAndDrop(policy, dragData);
    DragOperation srcOpMask = dragData.draggingSourceOperationMask();
597
    clipboard->setSourceOperation(srcOpMask);
598

599
    PlatformMouseEvent event = createMouseEvent(dragData);
600
    if (!mainFrame->eventHandler().updateDragAndDrop(event, clipboard.get())) {
601
        clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
602 603 604
        return false;
    }

605
    operation = clipboard->destinationOperation();
606 607 608
    if (clipboard->dropEffectIsUninitialized())
        operation = defaultOperationForDrag(srcOpMask);
    else if (!(srcOpMask & operation)) {
609 610
        // The element picked an operation which is not supported by the source
        operation = DragOperationNone;
611
    }
612 613 614

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

617
Element* DragController::draggableElement(const Frame* sourceFrame, Element* startElement, const IntPoint& dragOrigin, DragState& state) const
618
{
619
    state.type = (sourceFrame->selection().contains(dragOrigin)) ? DragSourceActionSelection : DragSourceActionNone;
620 621
    if (!startElement)
        return 0;
622

623 624 625 626 627
    for (auto renderer = startElement->renderer(); renderer; renderer = renderer->parent()) {
        Element* element = renderer->nonPseudoElement();
        if (!element) {
            // Anonymous render blocks don't correspond to actual DOM elements, so we skip over them
            // for the purposes of finding a draggable element.
628
            continue;
629
        }
630
        EUserDrag dragMode = renderer->style().userDrag();
631 632 633 634 635 636 637 638 639 640
        if ((m_dragSourceAction & DragSourceActionDHTML) && dragMode == DRAG_ELEMENT) {
            state.type = static_cast<DragSourceAction>(state.type | DragSourceActionDHTML);
            return element;
        }
        if (dragMode == DRAG_AUTO) {
            if ((m_dragSourceAction & DragSourceActionImage)
                && isHTMLImageElement(element)
                && sourceFrame->settings().loadsImagesAutomatically()) {
                state.type = static_cast<DragSourceAction>(state.type | DragSourceActionImage);
                return element;
641
            }
642 643 644 645 646
            if ((m_dragSourceAction & DragSourceActionLink)
                && isHTMLAnchorElement(element)
                && toHTMLAnchorElement(element)->isLiveLink()) {
                state.type = static_cast<DragSourceAction>(state.type | DragSourceActionLink);
                return element;
647 648 649
            }
        }
    }
oliver's avatar
 
oliver committed
650

651
    // We either have nothing to drag or we have a selection and we're not over a draggable element.
652
    return (state.type & DragSourceActionSelection) ? startElement : 0;
oliver's avatar
 
oliver committed
653
}
654

655
static CachedImage* getCachedImage(Element& element)
656
{
657
    RenderObject* renderer = element.renderer();
658
    if (!renderer || !renderer->isImage())
659
        return 0;
660
    RenderImage* image = toRenderImage(renderer);
661 662
    return image->cachedImage();
}
663

664
static Image* getImage(Element& element)
665
{
666
    CachedImage* cachedImage = getCachedImage(element);
667 668 669
    // Don't use cachedImage->imageForRenderer() here as that may return BitmapImages for cached SVG Images.
    // Users of getImage() want access to the SVGImage, in order to figure out the filename extensions,
    // which would be empty when asking the cached BitmapImages.
670
    return (cachedImage && !cachedImage->errorOccurred()) ?
671
        cachedImage->image() : 0;
672
}
673

674
static void selectElement(Element& element)
675
{
676 677 678
    RefPtr<Range> range = element.document().createRange();
    range->selectNode(&element);
    element.document().frame()->selection().setSelection(VisibleSelection(range.get(), DOWNSTREAM));
679
}
680

681 682 683
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.
684 685
#if PLATFORM(MAC)
    // We add in the Y dimension because we are a flipped view, so adding moves the image down.
686 687 688 689
    const int yOffset = dragImageOffset.y();
#else
    const int yOffset = -dragImageOffset.y();
#endif
690

691 692
    if (isLinkImage)
        return IntPoint(mouseDraggedPoint.x() - dragImageOffset.x(), mouseDraggedPoint.y() + yOffset);
693

694 695
    return IntPoint(dragOrigin.x() - dragImageOffset.x(), dragOrigin.y() + yOffset);
}
696

697
static IntPoint dragLocForSelectionDrag(Frame& src)
698
{
699
    IntRect draggingRect = enclosingIntRect(src.selection().bounds());
700
    int xpos = draggingRect.maxX();
701
    xpos = draggingRect.x() < xpos ? draggingRect.x() : xpos;
702
    int ypos = draggingRect.maxY();
703 704 705 706 707 708 709 710
#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);
}
711

712
bool DragController::startDrag(Frame& src, const DragState& state, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin)
713
{
714
    if (!src.view() || !src.contentRenderer())
715
        return false;
716

717
    HitTestResult hitTestResult = src.eventHandler().hitTestResultAtPoint(dragOrigin, HitTestRequest::ReadOnly | HitTestRequest::Active);
718
    if (!state.source->contains(hitTestResult.innerNode()))
719 720 721 722
        // The original node being dragged isn't under the drag origin anymore... maybe it was
        // hidden or moved out from under the cursor. Regardless, we don't want to start a drag on
        // something that's not actually under the drag origin.
        return false;
darin@apple.com's avatar
darin@apple.com committed
723 724
    URL linkURL = hitTestResult.absoluteLinkURL();
    URL imageURL = hitTestResult.absoluteImageURL();
725

726
    IntPoint mouseDraggedPoint = src.view()->windowToContents(dragEvent.position());
727

darin@apple.com's avatar
darin@apple.com committed
728
    m_draggingImageURL = URL();
729
    m_sourceDragOperation = srcOp;
730

731 732 733
    DragImageRef dragImage = 0;
    IntPoint dragLoc(0, 0);
    IntPoint dragImageOffset(0, 0);
734