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