1/*
2 * Copyright (C) 2007, 2009, 2010, 2013 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "DragController.h"
28
29#if ENABLE(DRAG_SUPPORT)
30
31#include "CachedImage.h"
32#include "Clipboard.h"
33#include "ClipboardAccessPolicy.h"
34#include "CachedResourceLoader.h"
35#include "Document.h"
36#include "DocumentFragment.h"
37#include "DragActions.h"
38#include "DragClient.h"
39#include "DragData.h"
40#include "DragSession.h"
41#include "DragState.h"
42#include "Editor.h"
43#include "EditorClient.h"
44#include "Element.h"
45#include "EventHandler.h"
46#include "ExceptionCodePlaceholder.h"
47#include "FloatRect.h"
48#include "Frame.h"
49#include "FrameLoadRequest.h"
50#include "FrameLoader.h"
51#include "FrameSelection.h"
52#include "FrameView.h"
53#include "HTMLAnchorElement.h"
54#include "HTMLInputElement.h"
55#include "HTMLNames.h"
56#include "HTMLPlugInElement.h"
57#include "HitTestRequest.h"
58#include "HitTestResult.h"
59#include "Image.h"
60#include "ImageOrientation.h"
61#include "MoveSelectionCommand.h"
62#include "Page.h"
63#include "PlatformKeyboardEvent.h"
64#include "PluginDocument.h"
65#include "PluginViewBase.h"
66#include "RenderFileUploadControl.h"
67#include "RenderImage.h"
68#include "RenderView.h"
69#include "ReplaceSelectionCommand.h"
70#include "ResourceRequest.h"
71#include "SecurityOrigin.h"
72#include "Settings.h"
73#include "ShadowRoot.h"
74#include "StylePropertySet.h"
75#include "Text.h"
76#include "TextEvent.h"
77#include "htmlediting.h"
78#include "markup.h"
79#include <wtf/CurrentTime.h>
80#include <wtf/RefPtr.h>
81
82namespace WebCore {
83
84static PlatformMouseEvent createMouseEvent(DragData* dragData)
85{
86    bool shiftKey, ctrlKey, altKey, metaKey;
87    shiftKey = ctrlKey = altKey = metaKey = false;
88    int keyState = dragData->modifierKeyState();
89    shiftKey = static_cast<bool>(keyState & PlatformEvent::ShiftKey);
90    ctrlKey = static_cast<bool>(keyState & PlatformEvent::CtrlKey);
91    altKey = static_cast<bool>(keyState & PlatformEvent::AltKey);
92    metaKey = static_cast<bool>(keyState & PlatformEvent::MetaKey);
93
94    return PlatformMouseEvent(dragData->clientPosition(), dragData->globalPosition(),
95                              LeftButton, PlatformEvent::MouseMoved, 0, shiftKey, ctrlKey, altKey,
96                              metaKey, currentTime());
97}
98
99DragController::DragController(Page* page, DragClient* client)
100    : m_page(page)
101    , m_client(client)
102    , m_documentUnderMouse(0)
103    , m_dragInitiator(0)
104    , m_fileInputElementUnderMouse(0)
105    , m_documentIsHandlingDrag(false)
106    , m_dragDestinationAction(DragDestinationActionNone)
107    , m_dragSourceAction(DragSourceActionNone)
108    , m_didInitiateDrag(false)
109    , m_sourceDragOperation(DragOperationNone)
110{
111    ASSERT(m_client);
112}
113
114DragController::~DragController()
115{
116    m_client->dragControllerDestroyed();
117}
118
119PassOwnPtr<DragController> DragController::create(Page* page, DragClient* client)
120{
121    return adoptPtr(new DragController(page, client));
122}
123
124static PassRefPtr<DocumentFragment> documentFragmentFromDragData(DragData* dragData, Frame* frame, RefPtr<Range> context,
125                                          bool allowPlainText, bool& chosePlainText)
126{
127    ASSERT(dragData);
128    chosePlainText = false;
129
130    Document* document = context->ownerDocument();
131    ASSERT(document);
132    if (document && dragData->containsCompatibleContent()) {
133        if (PassRefPtr<DocumentFragment> fragment = dragData->asFragment(frame, context, allowPlainText, chosePlainText))
134            return fragment;
135
136        if (dragData->containsURL(frame, DragData::DoNotConvertFilenames)) {
137            String title;
138            String url = dragData->asURL(frame, DragData::DoNotConvertFilenames, &title);
139            if (!url.isEmpty()) {
140                RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::create(document);
141                anchor->setHref(url);
142                if (title.isEmpty()) {
143                    // Try the plain text first because the url might be normalized or escaped.
144                    if (dragData->containsPlainText())
145                        title = dragData->asPlainText(frame);
146                    if (title.isEmpty())
147                        title = url;
148                }
149                RefPtr<Node> anchorText = document->createTextNode(title);
150                anchor->appendChild(anchorText, IGNORE_EXCEPTION);
151                RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
152                fragment->appendChild(anchor, IGNORE_EXCEPTION);
153                return fragment.get();
154            }
155        }
156    }
157    if (allowPlainText && dragData->containsPlainText()) {
158        chosePlainText = true;
159        return createFragmentFromText(context.get(), dragData->asPlainText(frame)).get();
160    }
161
162    return 0;
163}
164
165bool DragController::dragIsMove(FrameSelection* selection, DragData* dragData)
166{
167    return m_documentUnderMouse == m_dragInitiator && selection->isContentEditable() && selection->isRange() && !isCopyKeyDown(dragData);
168}
169
170// FIXME: This method is poorly named.  We're just clearing the selection from the document this drag is exiting.
171void DragController::cancelDrag()
172{
173    m_page->dragCaretController()->clear();
174}
175
176void DragController::dragEnded()
177{
178    m_dragInitiator = 0;
179    m_didInitiateDrag = false;
180    m_page->dragCaretController()->clear();
181
182    m_client->dragEnded();
183}
184
185DragSession DragController::dragEntered(DragData* dragData)
186{
187    return dragEnteredOrUpdated(dragData);
188}
189
190void DragController::dragExited(DragData* dragData)
191{
192    ASSERT(dragData);
193    Frame* mainFrame = m_page->mainFrame();
194
195    if (RefPtr<FrameView> v = mainFrame->view()) {
196        ClipboardAccessPolicy policy = (!m_documentUnderMouse || m_documentUnderMouse->securityOrigin()->isLocal()) ? ClipboardReadable : ClipboardTypesReadable;
197        RefPtr<Clipboard> clipboard = Clipboard::create(policy, dragData, mainFrame);
198        clipboard->setSourceOperation(dragData->draggingSourceOperationMask());
199        mainFrame->eventHandler()->cancelDragAndDrop(createMouseEvent(dragData), clipboard.get());
200        clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
201    }
202    mouseMovedIntoDocument(0);
203    if (m_fileInputElementUnderMouse)
204        m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
205    m_fileInputElementUnderMouse = 0;
206}
207
208DragSession DragController::dragUpdated(DragData* dragData)
209{
210    return dragEnteredOrUpdated(dragData);
211}
212
213bool DragController::performDrag(DragData* dragData)
214{
215    ASSERT(dragData);
216    m_documentUnderMouse = m_page->mainFrame()->documentAtPoint(dragData->clientPosition());
217    if ((m_dragDestinationAction & DragDestinationActionDHTML) && m_documentIsHandlingDrag) {
218        m_client->willPerformDragDestinationAction(DragDestinationActionDHTML, dragData);
219        RefPtr<Frame> mainFrame = m_page->mainFrame();
220        bool preventedDefault = false;
221        if (mainFrame->view()) {
222            // Sending an event can result in the destruction of the view and part.
223            RefPtr<Clipboard> clipboard = Clipboard::create(ClipboardReadable, dragData, mainFrame.get());
224            clipboard->setSourceOperation(dragData->draggingSourceOperationMask());
225            preventedDefault = mainFrame->eventHandler()->performDragAndDrop(createMouseEvent(dragData), clipboard.get());
226            clipboard->setAccessPolicy(ClipboardNumb); // Invalidate clipboard here for security
227        }
228        if (preventedDefault) {
229            m_documentUnderMouse = 0;
230            return true;
231        }
232    }
233
234    if ((m_dragDestinationAction & DragDestinationActionEdit) && concludeEditDrag(dragData)) {
235        m_documentUnderMouse = 0;
236        return true;
237    }
238
239    m_documentUnderMouse = 0;
240
241    if (operationForLoad(dragData) == DragOperationNone)
242        return false;
243
244    m_client->willPerformDragDestinationAction(DragDestinationActionLoad, dragData);
245    m_page->mainFrame()->loader()->load(FrameLoadRequest(m_page->mainFrame(), ResourceRequest(dragData->asURL(m_page->mainFrame()))));
246    return true;
247}
248
249void DragController::mouseMovedIntoDocument(Document* newDocument)
250{
251    if (m_documentUnderMouse == newDocument)
252        return;
253
254    // If we were over another document clear the selection
255    if (m_documentUnderMouse)
256        cancelDrag();
257    m_documentUnderMouse = newDocument;
258}
259
260DragSession DragController::dragEnteredOrUpdated(DragData* dragData)
261{
262    ASSERT(dragData);
263    ASSERT(m_page->mainFrame());
264    mouseMovedIntoDocument(m_page->mainFrame()->documentAtPoint(dragData->clientPosition()));
265
266    m_dragDestinationAction = m_client->actionMaskForDrag(dragData);
267    if (m_dragDestinationAction == DragDestinationActionNone) {
268        cancelDrag(); // FIXME: Why not call mouseMovedIntoDocument(0)?
269        return DragSession();
270    }
271
272    DragSession dragSession;
273    m_documentIsHandlingDrag = tryDocumentDrag(dragData, m_dragDestinationAction, dragSession);
274    if (!m_documentIsHandlingDrag && (m_dragDestinationAction & DragDestinationActionLoad))
275        dragSession.operation = operationForLoad(dragData);
276    return dragSession;
277}
278
279static HTMLInputElement* asFileInput(Node* node)
280{
281    ASSERT(node);
282
283    HTMLInputElement* inputElement = node->toInputElement();
284
285    // If this is a button inside of the a file input, move up to the file input.
286    if (inputElement && inputElement->isTextButton() && inputElement->treeScope()->rootNode()->isShadowRoot())
287        inputElement = toShadowRoot(inputElement->treeScope()->rootNode())->host()->toInputElement();
288
289    return inputElement && inputElement->isFileUpload() ? inputElement : 0;
290}
291
292// This can return null if an empty document is loaded.
293static Element* elementUnderMouse(Document* documentUnderMouse, const IntPoint& p)
294{
295    Frame* frame = documentUnderMouse->frame();
296    float zoomFactor = frame ? frame->pageZoomFactor() : 1;
297    LayoutPoint point = roundedLayoutPoint(FloatPoint(p.x() * zoomFactor, p.y() * zoomFactor));
298
299    HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowShadowContent);
300    HitTestResult result(point);
301    documentUnderMouse->renderView()->hitTest(request, result);
302
303    Node* n = result.innerNode();
304    while (n && !n->isElementNode())
305        n = n->parentNode();
306    if (n)
307        n = n->deprecatedShadowAncestorNode();
308
309    return toElement(n);
310}
311
312bool DragController::tryDocumentDrag(DragData* dragData, DragDestinationAction actionMask, DragSession& dragSession)
313{
314    ASSERT(dragData);
315
316    if (!m_documentUnderMouse)
317        return false;
318
319    if (m_dragInitiator && !m_documentUnderMouse->securityOrigin()->canReceiveDragData(m_dragInitiator->securityOrigin()))
320        return false;
321
322    bool isHandlingDrag = false;
323    if (actionMask & DragDestinationActionDHTML) {
324        isHandlingDrag = tryDHTMLDrag(dragData, dragSession.operation);
325        // Do not continue if m_documentUnderMouse has been reset by tryDHTMLDrag.
326        // tryDHTMLDrag fires dragenter event. The event listener that listens
327        // to this event may create a nested message loop (open a modal dialog),
328        // which could process dragleave event and reset m_documentUnderMouse in
329        // dragExited.
330        if (!m_documentUnderMouse)
331            return false;
332    }
333
334    // It's unclear why this check is after tryDHTMLDrag.
335    // We send drag events in tryDHTMLDrag and that may be the reason.
336    RefPtr<FrameView> frameView = m_documentUnderMouse->view();
337    if (!frameView)
338        return false;
339
340    if (isHandlingDrag) {
341        m_page->dragCaretController()->clear();
342        return true;
343    }
344
345    if ((actionMask & DragDestinationActionEdit) && canProcessDrag(dragData)) {
346        if (dragData->containsColor()) {
347            dragSession.operation = DragOperationGeneric;
348            return true;
349        }
350
351        IntPoint point = frameView->windowToContents(dragData->clientPosition());
352        Element* element = elementUnderMouse(m_documentUnderMouse.get(), point);
353        if (!element)
354            return false;
355
356        HTMLInputElement* elementAsFileInput = asFileInput(element);
357        if (m_fileInputElementUnderMouse != elementAsFileInput) {
358            if (m_fileInputElementUnderMouse)
359                m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
360            m_fileInputElementUnderMouse = elementAsFileInput;
361        }
362
363        if (!m_fileInputElementUnderMouse)
364            m_page->dragCaretController()->setCaretPosition(m_documentUnderMouse->frame()->visiblePositionForPoint(point));
365
366        Frame* innerFrame = element->document()->frame();
367        dragSession.operation = dragIsMove(innerFrame->selection(), dragData) ? DragOperationMove : DragOperationCopy;
368        dragSession.mouseIsOverFileInput = m_fileInputElementUnderMouse;
369        dragSession.numberOfItemsToBeAccepted = 0;
370
371        unsigned numberOfFiles = dragData->numberOfFiles();
372        if (m_fileInputElementUnderMouse) {
373            if (m_fileInputElementUnderMouse->isDisabledFormControl())
374                dragSession.numberOfItemsToBeAccepted = 0;
375            else if (m_fileInputElementUnderMouse->multiple())
376                dragSession.numberOfItemsToBeAccepted = numberOfFiles;
377            else if (numberOfFiles > 1)
378                dragSession.numberOfItemsToBeAccepted = 0;
379            else
380                dragSession.numberOfItemsToBeAccepted = 1;
381
382            if (!dragSession.numberOfItemsToBeAccepted)
383                dragSession.operation = DragOperationNone;
384            m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(dragSession.numberOfItemsToBeAccepted);
385        } else {
386            // We are not over a file input element. The dragged item(s) will only
387            // be loaded into the view the number of dragged items is 1.
388            dragSession.numberOfItemsToBeAccepted = numberOfFiles != 1 ? 0 : 1;
389        }
390
391        return true;
392    }
393
394    // We are not over an editable region. Make sure we're clearing any prior drag cursor.
395    m_page->dragCaretController()->clear();
396    if (m_fileInputElementUnderMouse)
397        m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
398    m_fileInputElementUnderMouse = 0;
399    return false;
400}
401
402DragSourceAction DragController::delegateDragSourceAction(const IntPoint& rootViewPoint)
403{
404    m_dragSourceAction = m_client->dragSourceActionMaskForPoint(rootViewPoint);
405    return m_dragSourceAction;
406}
407
408DragOperation DragController::operationForLoad(DragData* dragData)
409{
410    ASSERT(dragData);
411    Document* doc = m_page->mainFrame()->documentAtPoint(dragData->clientPosition());
412
413    bool pluginDocumentAcceptsDrags = false;
414
415    if (doc && doc->isPluginDocument()) {
416        const Widget* widget = toPluginDocument(doc)->pluginWidget();
417        const PluginViewBase* pluginView = (widget && widget->isPluginViewBase()) ? static_cast<const PluginViewBase*>(widget) : 0;
418
419        if (pluginView)
420            pluginDocumentAcceptsDrags = pluginView->shouldAllowNavigationFromDrags();
421    }
422
423    if (doc && (m_didInitiateDrag || (doc->isPluginDocument() && !pluginDocumentAcceptsDrags) || doc->rendererIsEditable()))
424        return DragOperationNone;
425    return dragOperation(dragData);
426}
427
428static bool setSelectionToDragCaret(Frame* frame, VisibleSelection& dragCaret, RefPtr<Range>& range, const IntPoint& point)
429{
430    frame->selection()->setSelection(dragCaret);
431    if (frame->selection()->isNone()) {
432        dragCaret = frame->visiblePositionForPoint(point);
433        frame->selection()->setSelection(dragCaret);
434        range = dragCaret.toNormalizedRange();
435    }
436    return !frame->selection()->isNone() && frame->selection()->isContentEditable();
437}
438
439bool DragController::dispatchTextInputEventFor(Frame* innerFrame, DragData* dragData)
440{
441    ASSERT(m_page->dragCaretController()->hasCaret());
442    String text = m_page->dragCaretController()->isContentRichlyEditable() ? "" : dragData->asPlainText(innerFrame);
443    Node* target = innerFrame->editor().findEventTargetFrom(m_page->dragCaretController()->caretPosition());
444    return target->dispatchEvent(TextEvent::createForDrop(innerFrame->document()->domWindow(), text), IGNORE_EXCEPTION);
445}
446
447bool DragController::concludeEditDrag(DragData* dragData)
448{
449    ASSERT(dragData);
450
451    RefPtr<HTMLInputElement> fileInput = m_fileInputElementUnderMouse;
452    if (m_fileInputElementUnderMouse) {
453        m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
454        m_fileInputElementUnderMouse = 0;
455    }
456
457    if (!m_documentUnderMouse)
458        return false;
459
460    IntPoint point = m_documentUnderMouse->view()->windowToContents(dragData->clientPosition());
461    Element* element = elementUnderMouse(m_documentUnderMouse.get(), point);
462    if (!element)
463        return false;
464    RefPtr<Frame> innerFrame = element->ownerDocument()->frame();
465    ASSERT(innerFrame);
466
467    if (m_page->dragCaretController()->hasCaret() && !dispatchTextInputEventFor(innerFrame.get(), dragData))
468        return true;
469
470    if (dragData->containsColor()) {
471        Color color = dragData->asColor();
472        if (!color.isValid())
473            return false;
474        RefPtr<Range> innerRange = innerFrame->selection()->toNormalizedRange();
475        RefPtr<MutableStylePropertySet> style = MutableStylePropertySet::create();
476        style->setProperty(CSSPropertyColor, color.serialized(), false);
477        if (!innerFrame->editor().shouldApplyStyle(style.get(), innerRange.get()))
478            return false;
479        m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
480        innerFrame->editor().applyStyle(style.get(), EditActionSetColor);
481        return true;
482    }
483
484    if (dragData->containsFiles() && fileInput) {
485        // fileInput should be the element we hit tested for, unless it was made
486        // display:none in a drop event handler.
487        ASSERT(fileInput == element || !fileInput->renderer());
488        if (fileInput->isDisabledFormControl())
489            return false;
490
491        return fileInput->receiveDroppedFiles(dragData);
492    }
493
494    if (!m_page->dragController()->canProcessDrag(dragData)) {
495        m_page->dragCaretController()->clear();
496        return false;
497    }
498
499    VisibleSelection dragCaret = m_page->dragCaretController()->caretPosition();
500    m_page->dragCaretController()->clear();
501    RefPtr<Range> range = dragCaret.toNormalizedRange();
502    RefPtr<Element> rootEditableElement = innerFrame->selection()->rootEditableElement();
503
504    // For range to be null a WebKit client must have done something bad while
505    // manually controlling drag behaviour
506    if (!range)
507        return false;
508    CachedResourceLoader* cachedResourceLoader = range->ownerDocument()->cachedResourceLoader();
509    ResourceCacheValidationSuppressor validationSuppressor(cachedResourceLoader);
510    if (dragIsMove(innerFrame->selection(), dragData) || dragCaret.isContentRichlyEditable()) {
511        bool chosePlainText = false;
512        RefPtr<DocumentFragment> fragment = documentFragmentFromDragData(dragData, innerFrame.get(), range, true, chosePlainText);
513        if (!fragment || !innerFrame->editor().shouldInsertFragment(fragment, range, EditorInsertActionDropped)) {
514            return false;
515        }
516
517        m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
518        if (dragIsMove(innerFrame->selection(), dragData)) {
519            // NSTextView behavior is to always smart delete on moving a selection,
520            // but only to smart insert if the selection granularity is word granularity.
521            bool smartDelete = innerFrame->editor().smartInsertDeleteEnabled();
522            bool smartInsert = smartDelete && innerFrame->selection()->granularity() == WordGranularity && dragData->canSmartReplace();
523            applyCommand(MoveSelectionCommand::create(fragment, dragCaret.base(), smartInsert, smartDelete));
524        } else {
525            if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point)) {
526                ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::PreventNesting;
527                if (dragData->canSmartReplace())
528                    options |= ReplaceSelectionCommand::SmartReplace;
529                if (chosePlainText)
530                    options |= ReplaceSelectionCommand::MatchStyle;
531                applyCommand(ReplaceSelectionCommand::create(m_documentUnderMouse.get(), fragment, options));
532            }
533        }
534    } else {
535        String text = dragData->asPlainText(innerFrame.get());
536        if (text.isEmpty() || !innerFrame->editor().shouldInsertText(text, range.get(), EditorInsertActionDropped)) {
537            return false;
538        }
539
540        m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
541        if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point))
542            applyCommand(ReplaceSelectionCommand::create(m_documentUnderMouse.get(), createFragmentFromText(range.get(), text),  ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::MatchStyle | ReplaceSelectionCommand::PreventNesting));
543    }
544
545    if (rootEditableElement) {
546        if (Frame* frame = rootEditableElement->document()->frame())
547            frame->eventHandler()->updateDragStateAfterEditDragIfNeeded(rootEditableElement.get());
548    }
549
550    return true;
551}
552
553bool DragController::canProcessDrag(DragData* dragData)
554{
555    ASSERT(dragData);
556
557    if (!dragData->containsCompatibleContent())
558        return false;
559
560    IntPoint point = m_page->mainFrame()->view()->windowToContents(dragData->clientPosition());
561    HitTestResult result = HitTestResult(point);
562    if (!m_page->mainFrame()->contentRenderer())
563        return false;
564
565    result = m_page->mainFrame()->eventHandler()->hitTestResultAtPoint(point, HitTestRequest::ReadOnly | HitTestRequest::Active);
566
567    if (!result.innerNonSharedNode())
568        return false;
569
570    if (dragData->containsFiles() && asFileInput(result.innerNonSharedNode()))
571        return true;
572
573    if (result.innerNonSharedNode()->isPluginElement()) {
574        HTMLPlugInElement* plugin = static_cast<HTMLPlugInElement*>(result.innerNonSharedNode());
575        if (!plugin->canProcessDrag() && !result.innerNonSharedNode()->rendererIsEditable())
576            return false;
577    } else if (!result.innerNonSharedNode()->rendererIsEditable())
578        return false;
579
580    if (m_didInitiateDrag && m_documentUnderMouse == m_dragInitiator && result.isSelected())
581        return false;
582
583    return true;
584}
585
586static DragOperation defaultOperationForDrag(DragOperation srcOpMask)
587{
588    // This is designed to match IE's operation fallback for the case where
589    // the page calls preventDefault() in a drag event but doesn't set dropEffect.
590    if (srcOpMask == DragOperationEvery)
591        return DragOperationCopy;
592    if (srcOpMask == DragOperationNone)
593        return DragOperationNone;
594    if (srcOpMask & DragOperationMove || srcOpMask & DragOperationGeneric)
595        return DragOperationMove;
596    if (srcOpMask & DragOperationCopy)
597        return DragOperationCopy;
598    if (srcOpMask & DragOperationLink)
599        return DragOperationLink;
600
601    // FIXME: Does IE really return "generic" even if no operations were allowed by the source?
602    return DragOperationGeneric;
603}
604
605bool DragController::tryDHTMLDrag(DragData* dragData, DragOperation& operation)
606{
607    ASSERT(dragData);
608    ASSERT(m_documentUnderMouse);
609    RefPtr<Frame> mainFrame = m_page->mainFrame();
610    RefPtr<FrameView> viewProtector = mainFrame->view();
611    if (!viewProtector)
612        return false;
613
614    ClipboardAccessPolicy policy = m_documentUnderMouse->securityOrigin()->isLocal() ? ClipboardReadable : ClipboardTypesReadable;
615    RefPtr<Clipboard> clipboard = Clipboard::create(policy, dragData, mainFrame.get());
616    DragOperation srcOpMask = dragData->draggingSourceOperationMask();
617    clipboard->setSourceOperation(srcOpMask);
618
619    PlatformMouseEvent event = createMouseEvent(dragData);
620    if (!mainFrame->eventHandler()->updateDragAndDrop(event, clipboard.get())) {
621        clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
622        return false;
623    }
624
625    operation = clipboard->destinationOperation();
626    if (clipboard->dropEffectIsUninitialized())
627        operation = defaultOperationForDrag(srcOpMask);
628    else if (!(srcOpMask & operation)) {
629        // The element picked an operation which is not supported by the source
630        operation = DragOperationNone;
631    }
632
633    clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
634    return true;
635}
636
637Element* DragController::draggableElement(const Frame* sourceFrame, Element* startElement, const IntPoint& dragOrigin, DragState& state) const
638{
639    state.type = (sourceFrame->selection()->contains(dragOrigin)) ? DragSourceActionSelection : DragSourceActionNone;
640    if (!startElement)
641        return 0;
642
643    for (const RenderObject* renderer = startElement->renderer(); renderer; renderer = renderer->parent()) {
644        Node* node = renderer->nonPseudoNode();
645        if (!node)
646            // Anonymous render blocks don't correspond to actual DOM nodes, so we skip over them
647            // for the purposes of finding a draggable node.
648            continue;
649        if (!(state.type & DragSourceActionSelection) && node->isTextNode() && node->canStartSelection())
650            // In this case we have a click in the unselected portion of text. If this text is
651            // selectable, we want to start the selection process instead of looking for a parent
652            // to try to drag.
653            return 0;
654        if (node->isElementNode()) {
655            EUserDrag dragMode = renderer->style()->userDrag();
656            if ((m_dragSourceAction & DragSourceActionDHTML) && dragMode == DRAG_ELEMENT) {
657                state.type = static_cast<DragSourceAction>(state.type | DragSourceActionDHTML);
658                return toElement(node);
659            }
660            if (dragMode == DRAG_AUTO) {
661                if ((m_dragSourceAction & DragSourceActionImage)
662                    && node->hasTagName(HTMLNames::imgTag)
663                    && sourceFrame->settings()
664                    && sourceFrame->settings()->loadsImagesAutomatically()) {
665                    state.type = static_cast<DragSourceAction>(state.type | DragSourceActionImage);
666                    return toElement(node);
667                }
668                if ((m_dragSourceAction & DragSourceActionLink)
669                    && node->hasTagName(HTMLNames::aTag)
670                    && static_cast<HTMLAnchorElement*>(node)->isLiveLink()) {
671                    state.type = static_cast<DragSourceAction>(state.type | DragSourceActionLink);
672                    return toElement(node);
673                }
674            }
675        }
676    }
677
678    // We either have nothing to drag or we have a selection and we're not over a draggable element.
679    return (state.type & DragSourceActionSelection) ? startElement : 0;
680}
681
682static CachedImage* getCachedImage(Element* element)
683{
684    ASSERT(element);
685    RenderObject* renderer = element->renderer();
686    if (!renderer || !renderer->isRenderImage())
687        return 0;
688    RenderImage* image = toRenderImage(renderer);
689    return image->cachedImage();
690}
691
692static Image* getImage(Element* element)
693{
694    ASSERT(element);
695    CachedImage* cachedImage = getCachedImage(element);
696    // Don't use cachedImage->imageForRenderer() here as that may return BitmapImages for cached SVG Images.
697    // Users of getImage() want access to the SVGImage, in order to figure out the filename extensions,
698    // which would be empty when asking the cached BitmapImages.
699    return (cachedImage && !cachedImage->errorOccurred()) ?
700        cachedImage->image() : 0;
701}
702
703static void prepareClipboardForImageDrag(Frame* source, Clipboard* clipboard, Element* node, const KURL& linkURL, const KURL& imageURL, const String& label)
704{
705    if (node->isContentRichlyEditable()) {
706        RefPtr<Range> range = source->document()->createRange();
707        range->selectNode(node, ASSERT_NO_EXCEPTION);
708        source->selection()->setSelection(VisibleSelection(range.get(), DOWNSTREAM));
709    }
710    clipboard->declareAndWriteDragImage(node, !linkURL.isEmpty() ? linkURL : imageURL, label, source);
711}
712
713static IntPoint dragLocForDHTMLDrag(const IntPoint& mouseDraggedPoint, const IntPoint& dragOrigin, const IntPoint& dragImageOffset, bool isLinkImage)
714{
715    // dragImageOffset is the cursor position relative to the lower-left corner of the image.
716#if PLATFORM(MAC)
717    // We add in the Y dimension because we are a flipped view, so adding moves the image down.
718    const int yOffset = dragImageOffset.y();
719#else
720    const int yOffset = -dragImageOffset.y();
721#endif
722
723    if (isLinkImage)
724        return IntPoint(mouseDraggedPoint.x() - dragImageOffset.x(), mouseDraggedPoint.y() + yOffset);
725
726    return IntPoint(dragOrigin.x() - dragImageOffset.x(), dragOrigin.y() + yOffset);
727}
728
729static IntPoint dragLocForSelectionDrag(Frame* src)
730{
731    IntRect draggingRect = enclosingIntRect(src->selection()->bounds());
732    int xpos = draggingRect.maxX();
733    xpos = draggingRect.x() < xpos ? draggingRect.x() : xpos;
734    int ypos = draggingRect.maxY();
735#if PLATFORM(MAC)
736    // Deal with flipped coordinates on Mac
737    ypos = draggingRect.y() > ypos ? draggingRect.y() : ypos;
738#else
739    ypos = draggingRect.y() < ypos ? draggingRect.y() : ypos;
740#endif
741    return IntPoint(xpos, ypos);
742}
743
744bool DragController::startDrag(Frame* src, const DragState& state, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin)
745{
746    ASSERT(src);
747
748    if (!src->view() || !src->contentRenderer())
749        return false;
750
751    HitTestResult hitTestResult = src->eventHandler()->hitTestResultAtPoint(dragOrigin, HitTestRequest::ReadOnly | HitTestRequest::Active);
752    if (!state.source->contains(hitTestResult.innerNode()))
753        // The original node being dragged isn't under the drag origin anymore... maybe it was
754        // hidden or moved out from under the cursor. Regardless, we don't want to start a drag on
755        // something that's not actually under the drag origin.
756        return false;
757    KURL linkURL = hitTestResult.absoluteLinkURL();
758    KURL imageURL = hitTestResult.absoluteImageURL();
759
760    IntPoint mouseDraggedPoint = src->view()->windowToContents(dragEvent.position());
761
762    m_draggingImageURL = KURL();
763    m_sourceDragOperation = srcOp;
764
765    DragImageRef dragImage = 0;
766    IntPoint dragLoc(0, 0);
767    IntPoint dragImageOffset(0, 0);
768
769    Clipboard* clipboard = state.clipboard.get();
770    if (state.type == DragSourceActionDHTML)
771        dragImage = clipboard->createDragImage(dragImageOffset);
772    if (state.type == DragSourceActionSelection || !imageURL.isEmpty() || !linkURL.isEmpty())
773        // Selection, image, and link drags receive a default set of allowed drag operations that
774        // follows from:
775        // http://trac.webkit.org/browser/trunk/WebKit/mac/WebView/WebHTMLView.mm?rev=48526#L3430
776        m_sourceDragOperation = static_cast<DragOperation>(m_sourceDragOperation | DragOperationGeneric | DragOperationCopy);
777
778    // We allow DHTML/JS to set the drag image, even if its a link, image or text we're dragging.
779    // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp.
780    if (dragImage) {
781        dragLoc = dragLocForDHTMLDrag(mouseDraggedPoint, dragOrigin, dragImageOffset, !linkURL.isEmpty());
782        m_dragOffset = dragImageOffset;
783    }
784
785    bool startedDrag = true; // optimism - we almost always manage to start the drag
786
787    Element* element = state.source.get();
788
789    Image* image = getImage(element);
790    if (state.type == DragSourceActionSelection) {
791        if (!clipboard->hasData()) {
792            RefPtr<Range> selectionRange = src->selection()->toNormalizedRange();
793            ASSERT(selectionRange);
794
795            src->editor().willWriteSelectionToPasteboard(selectionRange.get());
796
797            if (enclosingTextFormControl(src->selection()->start()))
798                clipboard->writePlainText(src->editor().selectedTextForClipboard());
799            else
800                clipboard->writeRange(selectionRange.get(), src);
801
802            src->editor().didWriteSelectionToPasteboard();
803        }
804        m_client->willPerformDragSourceAction(DragSourceActionSelection, dragOrigin, clipboard);
805        if (!dragImage) {
806            dragImage = dissolveDragImageToFraction(src->dragImageForSelection(), DragImageAlpha);
807            dragLoc = dragLocForSelectionDrag(src);
808            m_dragOffset = IntPoint(dragOrigin.x() - dragLoc.x(), dragOrigin.y() - dragLoc.y());
809        }
810        doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false);
811    } else if (!src->document()->securityOrigin()->canDisplay(linkURL)) {
812         src->document()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, "Not allowed to drag local resource: " + linkURL.stringCenterEllipsizedToLength());
813         startedDrag = false;
814    } else if (!imageURL.isEmpty() && element && image && !image->isNull()
815               && (m_dragSourceAction & DragSourceActionImage)) {
816        // We shouldn't be starting a drag for an image that can't provide an extension.
817        // This is an early detection for problems encountered later upon drop.
818        ASSERT(!image->filenameExtension().isEmpty());
819        if (!clipboard->hasData()) {
820            m_draggingImageURL = imageURL;
821            prepareClipboardForImageDrag(src, clipboard, element, linkURL, imageURL, hitTestResult.altDisplayString());
822        }
823
824        m_client->willPerformDragSourceAction(DragSourceActionImage, dragOrigin, clipboard);
825
826        if (!dragImage) {
827            IntRect imageRect = hitTestResult.imageRect();
828            imageRect.setLocation(m_page->mainFrame()->view()->rootViewToContents(src->view()->contentsToRootView(imageRect.location())));
829            doImageDrag(element, dragOrigin, hitTestResult.imageRect(), clipboard, src, m_dragOffset);
830        } else
831            // DHTML defined drag image
832            doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false);
833
834    } else if (!linkURL.isEmpty() && (m_dragSourceAction & DragSourceActionLink)) {
835        if (!clipboard->hasData())
836            // Simplify whitespace so the title put on the clipboard resembles what the user sees
837            // on the web page. This includes replacing newlines with spaces.
838            clipboard->writeURL(linkURL, hitTestResult.textContent().simplifyWhiteSpace(), src);
839
840        if (src->selection()->isCaret() && src->selection()->isContentEditable()) {
841            // a user can initiate a drag on a link without having any text
842            // selected.  In this case, we should expand the selection to
843            // the enclosing anchor element
844            Position pos = src->selection()->base();
845            Node* node = enclosingAnchorElement(pos);
846            if (node)
847                src->selection()->setSelection(VisibleSelection::selectionFromContentsOfNode(node));
848        }
849
850        m_client->willPerformDragSourceAction(DragSourceActionLink, dragOrigin, clipboard);
851        if (!dragImage) {
852            dragImage = createDragImageForLink(linkURL, hitTestResult.textContent(), src->settings() ? src->settings()->fontRenderingMode() : NormalRenderingMode);
853            IntSize size = dragImageSize(dragImage);
854            m_dragOffset = IntPoint(-size.width() / 2, -LinkDragBorderInset);
855            dragLoc = IntPoint(mouseDraggedPoint.x() + m_dragOffset.x(), mouseDraggedPoint.y() + m_dragOffset.y());
856        }
857        doSystemDrag(dragImage, dragLoc, mouseDraggedPoint, clipboard, src, true);
858    } else if (state.type == DragSourceActionDHTML) {
859        if (dragImage) {
860            ASSERT(m_dragSourceAction & DragSourceActionDHTML);
861            m_client->willPerformDragSourceAction(DragSourceActionDHTML, dragOrigin, clipboard);
862            doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false);
863        } else
864            startedDrag = false;
865    } else {
866        // draggableElement() determined an image or link node was draggable, but it turns out the
867        // image or link had no URL, so there is nothing to drag.
868        startedDrag = false;
869    }
870
871    if (dragImage)
872        deleteDragImage(dragImage);
873    return startedDrag;
874}
875
876void DragController::doImageDrag(Element* element, const IntPoint& dragOrigin, const IntRect& rect, Clipboard* clipboard, Frame* frame, IntPoint& dragImageOffset)
877{
878    IntPoint mouseDownPoint = dragOrigin;
879    DragImageRef dragImage = 0;
880    IntPoint origin;
881
882    Image* image = getImage(element);
883    if (image && image->size().height() * image->size().width() <= MaxOriginalImageArea
884        && (dragImage = createDragImageFromImage(image, element->renderer() ? element->renderer()->shouldRespectImageOrientation() : DoNotRespectImageOrientation))) {
885        IntSize originalSize = rect.size();
886        origin = rect.location();
887
888        dragImage = fitDragImageToMaxSize(dragImage, rect.size(), maxDragImageSize());
889        dragImage = dissolveDragImageToFraction(dragImage, DragImageAlpha);
890        IntSize newSize = dragImageSize(dragImage);
891
892        // Properly orient the drag image and orient it differently if it's smaller than the original
893        float scale = newSize.width() / (float)originalSize.width();
894        float dx = origin.x() - mouseDownPoint.x();
895        dx *= scale;
896        origin.setX((int)(dx + 0.5));
897#if PLATFORM(MAC)
898        //Compensate for accursed flipped coordinates in cocoa
899        origin.setY(origin.y() + originalSize.height());
900#endif
901        float dy = origin.y() - mouseDownPoint.y();
902        dy *= scale;
903        origin.setY((int)(dy + 0.5));
904    } else {
905        if (CachedImage* cachedImage = getCachedImage(element)) {
906            dragImage = createDragImageIconForCachedImageFilename(cachedImage->response().suggestedFilename());
907            if (dragImage)
908                origin = IntPoint(DragIconRightInset - dragImageSize(dragImage).width(), DragIconBottomInset);
909        }
910    }
911
912    dragImageOffset = mouseDownPoint + origin;
913    doSystemDrag(dragImage, dragImageOffset, dragOrigin, clipboard, frame, false);
914
915    deleteDragImage(dragImage);
916}
917
918void DragController::doSystemDrag(DragImageRef image, const IntPoint& dragLoc, const IntPoint& eventPos, Clipboard* clipboard, Frame* frame, bool forLink)
919{
920    m_didInitiateDrag = true;
921    m_dragInitiator = frame->document();
922    // Protect this frame and view, as a load may occur mid drag and attempt to unload this frame
923    RefPtr<Frame> frameProtector = m_page->mainFrame();
924    RefPtr<FrameView> viewProtector = frameProtector->view();
925    m_client->startDrag(image, viewProtector->rootViewToContents(frame->view()->contentsToRootView(dragLoc)),
926        viewProtector->rootViewToContents(frame->view()->contentsToRootView(eventPos)), clipboard, frameProtector.get(), forLink);
927    // DragClient::startDrag can cause our Page to dispear, deallocating |this|.
928    if (!frameProtector->page())
929        return;
930
931    cleanupAfterSystemDrag();
932}
933
934// Manual drag caret manipulation
935void DragController::placeDragCaret(const IntPoint& windowPoint)
936{
937    mouseMovedIntoDocument(m_page->mainFrame()->documentAtPoint(windowPoint));
938    if (!m_documentUnderMouse)
939        return;
940    Frame* frame = m_documentUnderMouse->frame();
941    FrameView* frameView = frame->view();
942    if (!frameView)
943        return;
944    IntPoint framePoint = frameView->windowToContents(windowPoint);
945
946    m_page->dragCaretController()->setCaretPosition(frame->visiblePositionForPoint(framePoint));
947}
948
949} // namespace WebCore
950
951#endif // ENABLE(DRAG_SUPPORT)
952