Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / page / DragController.cpp
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
86 namespace blink {
87
88 const int DragController::DragIconRightInset = 7;
89 const int DragController::DragIconBottomInset = 3;
90
91 static const int MaxOriginalImageArea = 1500 * 1500;
92 static const int LinkDragBorderInset = 2;
93 static const float DragImageAlpha = 0.75f;
94
95 #if ENABLE(ASSERT)
96 static 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
112 static 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
125 static PassRefPtrWillBeRawPtr<DataTransfer> createDraggingDataTransfer(DataTransferAccessPolicy policy, DragData* dragData)
126 {
127     return DataTransfer::create(DataTransfer::DragAndDrop, policy, dragData->platformData());
128 }
129
130 DragController::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
143 DragController::~DragController()
144 {
145 }
146
147 PassOwnPtrWillBeRawPtr<DragController> DragController::create(Page* page, DragClient* client)
148 {
149     return adoptPtrWillBeNoop(new DragController(page, client));
150 }
151
152 static 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
191 bool 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.
197 void DragController::cancelDrag()
198 {
199     m_page->dragCaretController().clear();
200 }
201
202 void DragController::dragEnded()
203 {
204     m_dragInitiator = nullptr;
205     m_didInitiateDrag = false;
206     m_page->dragCaretController().clear();
207 }
208
209 DragSession DragController::dragEntered(DragData* dragData)
210 {
211     return dragEnteredOrUpdated(dragData);
212 }
213
214 void DragController::dragExited(DragData* dragData)
215 {
216     ASSERT(dragData);
217     LocalFrame* mainFrame = m_page->deprecatedLocalMainFrame();
218
219     RefPtrWillBeRawPtr<FrameView> frameView(mainFrame->view());
220     if (frameView) {
221         DataTransferAccessPolicy policy = (!m_documentUnderMouse || m_documentUnderMouse->securityOrigin()->isLocal()) ? DataTransferReadable : DataTransferTypesReadable;
222         RefPtrWillBeRawPtr<DataTransfer> dataTransfer = createDraggingDataTransfer(policy, dragData);
223         dataTransfer->setSourceOperation(dragData->draggingSourceOperationMask());
224         mainFrame->eventHandler().cancelDragAndDrop(createMouseEvent(dragData), dataTransfer.get());
225         dataTransfer->setAccessPolicy(DataTransferNumb); // invalidate clipboard here for security
226     }
227     mouseMovedIntoDocument(nullptr);
228     if (m_fileInputElementUnderMouse)
229         m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
230     m_fileInputElementUnderMouse = nullptr;
231 }
232
233 DragSession DragController::dragUpdated(DragData* dragData)
234 {
235     return dragEnteredOrUpdated(dragData);
236 }
237
238 bool DragController::performDrag(DragData* dragData)
239 {
240     ASSERT(dragData);
241     m_documentUnderMouse = m_page->deprecatedLocalMainFrame()->documentAtPoint(dragData->clientPosition());
242     if ((m_dragDestinationAction & DragDestinationActionDHTML) && m_documentIsHandlingDrag) {
243         RefPtrWillBeRawPtr<LocalFrame> mainFrame = m_page->deprecatedLocalMainFrame();
244         bool preventedDefault = false;
245         if (mainFrame->view()) {
246             // Sending an event can result in the destruction of the view and part.
247             RefPtrWillBeRawPtr<DataTransfer> dataTransfer = createDraggingDataTransfer(DataTransferReadable, dragData);
248             dataTransfer->setSourceOperation(dragData->draggingSourceOperationMask());
249             preventedDefault = mainFrame->eventHandler().performDragAndDrop(createMouseEvent(dragData), dataTransfer.get());
250             dataTransfer->setAccessPolicy(DataTransferNumb); // Invalidate clipboard here for security
251         }
252         if (preventedDefault) {
253             m_documentUnderMouse = nullptr;
254             cancelDrag();
255             return true;
256         }
257     }
258
259     if ((m_dragDestinationAction & DragDestinationActionEdit) && concludeEditDrag(dragData)) {
260         m_documentUnderMouse = nullptr;
261         return true;
262     }
263
264     m_documentUnderMouse = nullptr;
265
266     if (operationForLoad(dragData) == DragOperationNone)
267         return false;
268
269     if (m_page->settings().navigateOnDragDrop())
270         m_page->deprecatedLocalMainFrame()->loader().load(FrameLoadRequest(nullptr, ResourceRequest(dragData->asURL())));
271     return true;
272 }
273
274 void DragController::mouseMovedIntoDocument(Document* newDocument)
275 {
276     if (m_documentUnderMouse == newDocument)
277         return;
278
279     // If we were over another document clear the selection
280     if (m_documentUnderMouse)
281         cancelDrag();
282     m_documentUnderMouse = newDocument;
283 }
284
285 DragSession DragController::dragEnteredOrUpdated(DragData* dragData)
286 {
287     ASSERT(dragData);
288     ASSERT(m_page->mainFrame());
289     mouseMovedIntoDocument(m_page->deprecatedLocalMainFrame()->documentAtPoint(dragData->clientPosition()));
290
291     m_dragDestinationAction = m_client->actionMaskForDrag(dragData);
292     if (m_dragDestinationAction == DragDestinationActionNone) {
293         cancelDrag(); // FIXME: Why not call mouseMovedIntoDocument(0)?
294         return DragSession();
295     }
296
297     DragSession dragSession;
298     m_documentIsHandlingDrag = tryDocumentDrag(dragData, m_dragDestinationAction, dragSession);
299     if (!m_documentIsHandlingDrag && (m_dragDestinationAction & DragDestinationActionLoad))
300         dragSession.operation = operationForLoad(dragData);
301     return dragSession;
302 }
303
304 static HTMLInputElement* asFileInput(Node* node)
305 {
306     ASSERT(node);
307     for (; node; node = node->shadowHost()) {
308         if (isHTMLInputElement(*node) && toHTMLInputElement(node)->type() == InputTypeNames::file)
309             return toHTMLInputElement(node);
310     }
311     return nullptr;
312 }
313
314 // This can return null if an empty document is loaded.
315 static Element* elementUnderMouse(Document* documentUnderMouse, const IntPoint& p)
316 {
317     LocalFrame* frame = documentUnderMouse->frame();
318     float zoomFactor = frame ? frame->pageZoomFactor() : 1;
319     LayoutPoint point = roundedLayoutPoint(FloatPoint(p.x() * zoomFactor, p.y() * zoomFactor));
320
321     HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active);
322     HitTestResult result(point);
323     documentUnderMouse->renderView()->hitTest(request, result);
324
325     Node* n = result.innerNode();
326     while (n && !n->isElementNode())
327         n = n->parentOrShadowHostNode();
328     if (n && n->isInShadowTree())
329         n = n->shadowHost();
330
331     return toElement(n);
332 }
333
334 bool DragController::tryDocumentDrag(DragData* dragData, DragDestinationAction actionMask, DragSession& dragSession)
335 {
336     ASSERT(dragData);
337
338     if (!m_documentUnderMouse)
339         return false;
340
341     if (m_dragInitiator && !m_documentUnderMouse->securityOrigin()->canReceiveDragData(m_dragInitiator->securityOrigin()))
342         return false;
343
344     bool isHandlingDrag = false;
345     if (actionMask & DragDestinationActionDHTML) {
346         isHandlingDrag = tryDHTMLDrag(dragData, dragSession.operation);
347         // Do not continue if m_documentUnderMouse has been reset by tryDHTMLDrag.
348         // tryDHTMLDrag fires dragenter event. The event listener that listens
349         // to this event may create a nested message loop (open a modal dialog),
350         // which could process dragleave event and reset m_documentUnderMouse in
351         // dragExited.
352         if (!m_documentUnderMouse)
353             return false;
354     }
355
356     // It's unclear why this check is after tryDHTMLDrag.
357     // We send drag events in tryDHTMLDrag and that may be the reason.
358     RefPtrWillBeRawPtr<FrameView> frameView = m_documentUnderMouse->view();
359     if (!frameView)
360         return false;
361
362     if (isHandlingDrag) {
363         m_page->dragCaretController().clear();
364         return true;
365     }
366
367     if ((actionMask & DragDestinationActionEdit) && canProcessDrag(dragData)) {
368         IntPoint point = frameView->windowToContents(dragData->clientPosition());
369         Element* element = elementUnderMouse(m_documentUnderMouse.get(), point);
370         if (!element)
371             return false;
372
373         HTMLInputElement* elementAsFileInput = asFileInput(element);
374         if (m_fileInputElementUnderMouse != elementAsFileInput) {
375             if (m_fileInputElementUnderMouse)
376                 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
377             m_fileInputElementUnderMouse = elementAsFileInput;
378         }
379
380         if (!m_fileInputElementUnderMouse)
381             m_page->dragCaretController().setCaretPosition(m_documentUnderMouse->frame()->visiblePositionForPoint(point));
382
383         LocalFrame* innerFrame = element->document().frame();
384         dragSession.operation = dragIsMove(innerFrame->selection(), dragData) ? DragOperationMove : DragOperationCopy;
385         dragSession.mouseIsOverFileInput = m_fileInputElementUnderMouse;
386         dragSession.numberOfItemsToBeAccepted = 0;
387
388         Vector<String> paths;
389         dragData->asFilePaths(paths);
390         const unsigned numberOfFiles = paths.size();
391         if (m_fileInputElementUnderMouse) {
392             if (m_fileInputElementUnderMouse->isDisabledFormControl())
393                 dragSession.numberOfItemsToBeAccepted = 0;
394             else if (m_fileInputElementUnderMouse->multiple())
395                 dragSession.numberOfItemsToBeAccepted = numberOfFiles;
396             else if (numberOfFiles == 1)
397                 dragSession.numberOfItemsToBeAccepted = 1;
398             else
399                 dragSession.numberOfItemsToBeAccepted = 0;
400
401             if (!dragSession.numberOfItemsToBeAccepted)
402                 dragSession.operation = DragOperationNone;
403             m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(dragSession.numberOfItemsToBeAccepted);
404         } else {
405             // We are not over a file input element. The dragged item(s) will only
406             // be loaded into the view the number of dragged items is 1.
407             dragSession.numberOfItemsToBeAccepted = numberOfFiles != 1 ? 0 : 1;
408         }
409
410         return true;
411     }
412
413     // We are not over an editable region. Make sure we're clearing any prior drag cursor.
414     m_page->dragCaretController().clear();
415     if (m_fileInputElementUnderMouse)
416         m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
417     m_fileInputElementUnderMouse = nullptr;
418     return false;
419 }
420
421 DragOperation DragController::operationForLoad(DragData* dragData)
422 {
423     ASSERT(dragData);
424     Document* doc = m_page->deprecatedLocalMainFrame()->documentAtPoint(dragData->clientPosition());
425
426     if (doc && (m_didInitiateDrag || doc->isPluginDocument() || doc->hasEditableStyle()))
427         return DragOperationNone;
428     return dragOperation(dragData);
429 }
430
431 static bool setSelectionToDragCaret(LocalFrame* frame, VisibleSelection& dragCaret, RefPtrWillBeRawPtr<Range>& range, const IntPoint& point)
432 {
433     frame->selection().setSelection(dragCaret);
434     if (frame->selection().isNone()) {
435         dragCaret = VisibleSelection(frame->visiblePositionForPoint(point));
436         frame->selection().setSelection(dragCaret);
437         range = dragCaret.toNormalizedRange();
438     }
439     return !frame->selection().isNone() && frame->selection().isContentEditable();
440 }
441
442 bool DragController::dispatchTextInputEventFor(LocalFrame* innerFrame, DragData* dragData)
443 {
444     ASSERT(m_page->dragCaretController().hasCaret());
445     String text = m_page->dragCaretController().isContentRichlyEditable() ? "" : dragData->asPlainText();
446     Element* target = innerFrame->editor().findEventTargetFrom(VisibleSelection(m_page->dragCaretController().caretPosition()));
447     return target->dispatchEvent(TextEvent::createForDrop(innerFrame->domWindow(), text), IGNORE_EXCEPTION);
448 }
449
450 bool DragController::concludeEditDrag(DragData* dragData)
451 {
452     ASSERT(dragData);
453
454     RefPtrWillBeRawPtr<HTMLInputElement> fileInput = m_fileInputElementUnderMouse;
455     if (m_fileInputElementUnderMouse) {
456         m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false);
457         m_fileInputElementUnderMouse = nullptr;
458     }
459
460     if (!m_documentUnderMouse)
461         return false;
462
463     IntPoint point = m_documentUnderMouse->view()->windowToContents(dragData->clientPosition());
464     Element* element = elementUnderMouse(m_documentUnderMouse.get(), point);
465     if (!element)
466         return false;
467     RefPtrWillBeRawPtr<LocalFrame> innerFrame = element->ownerDocument()->frame();
468     ASSERT(innerFrame);
469
470     if (m_page->dragCaretController().hasCaret() && !dispatchTextInputEventFor(innerFrame.get(), dragData))
471         return true;
472
473     if (dragData->containsFiles() && fileInput) {
474         // fileInput should be the element we hit tested for, unless it was made
475         // display:none in a drop event handler.
476         ASSERT(fileInput == element || !fileInput->renderer());
477         if (fileInput->isDisabledFormControl())
478             return false;
479
480         return fileInput->receiveDroppedFiles(dragData);
481     }
482
483     if (!m_page->dragController().canProcessDrag(dragData)) {
484         m_page->dragCaretController().clear();
485         return false;
486     }
487
488     VisibleSelection dragCaret(m_page->dragCaretController().caretPosition());
489     m_page->dragCaretController().clear();
490     RefPtrWillBeRawPtr<Range> range = dragCaret.toNormalizedRange();
491     RefPtrWillBeRawPtr<Element> rootEditableElement = innerFrame->selection().rootEditableElement();
492
493     // For range to be null a WebKit client must have done something bad while
494     // manually controlling drag behaviour
495     if (!range)
496         return false;
497     ResourceFetcher* fetcher = range->ownerDocument().fetcher();
498     ResourceCacheValidationSuppressor validationSuppressor(fetcher);
499     if (dragIsMove(innerFrame->selection(), dragData) || dragCaret.isContentRichlyEditable()) {
500         bool chosePlainText = false;
501         RefPtrWillBeRawPtr<DocumentFragment> fragment = documentFragmentFromDragData(dragData, innerFrame.get(), range, true, chosePlainText);
502         if (!fragment)
503             return false;
504
505         if (dragIsMove(innerFrame->selection(), dragData)) {
506             // NSTextView behavior is to always smart delete on moving a selection,
507             // but only to smart insert if the selection granularity is word granularity.
508             bool smartDelete = innerFrame->editor().smartInsertDeleteEnabled();
509             bool smartInsert = smartDelete && innerFrame->selection().granularity() == WordGranularity && dragData->canSmartReplace();
510             MoveSelectionCommand::create(fragment, dragCaret.base(), smartInsert, smartDelete)->apply();
511         } else {
512             if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point)) {
513                 ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::PreventNesting;
514                 if (dragData->canSmartReplace())
515                     options |= ReplaceSelectionCommand::SmartReplace;
516                 if (chosePlainText)
517                     options |= ReplaceSelectionCommand::MatchStyle;
518                 ASSERT(m_documentUnderMouse);
519                 ReplaceSelectionCommand::create(*m_documentUnderMouse.get(), fragment, options)->apply();
520             }
521         }
522     } else {
523         String text = dragData->asPlainText();
524         if (text.isEmpty())
525             return false;
526
527         if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point)) {
528             ASSERT(m_documentUnderMouse);
529             ReplaceSelectionCommand::create(*m_documentUnderMouse.get(), createFragmentFromText(range.get(), text),  ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::MatchStyle | ReplaceSelectionCommand::PreventNesting)->apply();
530         }
531     }
532
533     if (rootEditableElement) {
534         if (LocalFrame* frame = rootEditableElement->document().frame())
535             frame->eventHandler().updateDragStateAfterEditDragIfNeeded(rootEditableElement.get());
536     }
537
538     return true;
539 }
540
541 bool DragController::canProcessDrag(DragData* dragData)
542 {
543     ASSERT(dragData);
544
545     if (!dragData->containsCompatibleContent())
546         return false;
547
548     IntPoint point = m_page->deprecatedLocalMainFrame()->view()->windowToContents(dragData->clientPosition());
549     HitTestResult result = HitTestResult(point);
550     if (!m_page->deprecatedLocalMainFrame()->contentRenderer())
551         return false;
552
553     result = m_page->deprecatedLocalMainFrame()->eventHandler().hitTestResultAtPoint(point);
554
555     if (!result.innerNonSharedNode())
556         return false;
557
558     if (dragData->containsFiles() && asFileInput(result.innerNonSharedNode()))
559         return true;
560
561     if (isHTMLPlugInElement(*result.innerNonSharedNode())) {
562         HTMLPlugInElement* plugin = toHTMLPlugInElement(result.innerNonSharedNode());
563         if (!plugin->canProcessDrag() && !result.innerNonSharedNode()->hasEditableStyle())
564             return false;
565     } else if (!result.innerNonSharedNode()->hasEditableStyle())
566         return false;
567
568     if (m_didInitiateDrag && m_documentUnderMouse == m_dragInitiator && result.isSelected())
569         return false;
570
571     return true;
572 }
573
574 static DragOperation defaultOperationForDrag(DragOperation srcOpMask)
575 {
576     // This is designed to match IE's operation fallback for the case where
577     // the page calls preventDefault() in a drag event but doesn't set dropEffect.
578     if (srcOpMask == DragOperationEvery)
579         return DragOperationCopy;
580     if (srcOpMask == DragOperationNone)
581         return DragOperationNone;
582     if (srcOpMask & DragOperationMove || srcOpMask & DragOperationGeneric)
583         return DragOperationMove;
584     if (srcOpMask & DragOperationCopy)
585         return DragOperationCopy;
586     if (srcOpMask & DragOperationLink)
587         return DragOperationLink;
588
589     // FIXME: Does IE really return "generic" even if no operations were allowed by the source?
590     return DragOperationGeneric;
591 }
592
593 bool DragController::tryDHTMLDrag(DragData* dragData, DragOperation& operation)
594 {
595     ASSERT(dragData);
596     ASSERT(m_documentUnderMouse);
597     RefPtrWillBeRawPtr<LocalFrame> mainFrame = m_page->deprecatedLocalMainFrame();
598     if (!mainFrame->view())
599         return false;
600
601     RefPtrWillBeRawPtr<FrameView> viewProtector(mainFrame->view());
602     DataTransferAccessPolicy policy = m_documentUnderMouse->securityOrigin()->isLocal() ? DataTransferReadable : DataTransferTypesReadable;
603     RefPtrWillBeRawPtr<DataTransfer> dataTransfer = createDraggingDataTransfer(policy, dragData);
604     DragOperation srcOpMask = dragData->draggingSourceOperationMask();
605     dataTransfer->setSourceOperation(srcOpMask);
606
607     PlatformMouseEvent event = createMouseEvent(dragData);
608     if (!mainFrame->eventHandler().updateDragAndDrop(event, dataTransfer.get())) {
609         dataTransfer->setAccessPolicy(DataTransferNumb); // invalidate clipboard here for security
610         return false;
611     }
612
613     operation = dataTransfer->destinationOperation();
614     if (dataTransfer->dropEffectIsUninitialized())
615         operation = defaultOperationForDrag(srcOpMask);
616     else if (!(srcOpMask & operation)) {
617         // The element picked an operation which is not supported by the source
618         operation = DragOperationNone;
619     }
620
621     dataTransfer->setAccessPolicy(DataTransferNumb); // invalidate clipboard here for security
622     return true;
623 }
624
625 Node* DragController::draggableNode(const LocalFrame* src, Node* startNode, const IntPoint& dragOrigin, SelectionDragPolicy selectionDragPolicy, DragSourceAction& dragType) const
626 {
627     if (src->selection().contains(dragOrigin)) {
628         dragType = DragSourceActionSelection;
629         if (selectionDragPolicy == ImmediateSelectionDragResolution)
630             return startNode;
631     } else {
632         dragType = DragSourceActionNone;
633     }
634
635     Node* node = nullptr;
636     DragSourceAction candidateDragType = DragSourceActionNone;
637     for (const RenderObject* renderer = startNode->renderer(); renderer; renderer = renderer->parent()) {
638         node = renderer->nonPseudoNode();
639         if (!node) {
640             // Anonymous render blocks don't correspond to actual DOM nodes, so we skip over them
641             // for the purposes of finding a draggable node.
642             continue;
643         }
644         if (dragType != DragSourceActionSelection && node->isTextNode() && node->canStartSelection()) {
645             // In this case we have a click in the unselected portion of text. If this text is
646             // selectable, we want to start the selection process instead of looking for a parent
647             // to try to drag.
648             return nullptr;
649         }
650         if (node->isElementNode()) {
651             EUserDrag dragMode = renderer->style()->userDrag();
652             if (dragMode == DRAG_NONE)
653                 continue;
654             // Even if the image is part of a selection, we always only drag the image in this case.
655             if (renderer->isImage()
656                 && src->settings()
657                 && src->settings()->loadsImagesAutomatically()) {
658                 dragType = DragSourceActionImage;
659                 return node;
660             }
661             // Other draggable elements are considered unselectable.
662             if (isHTMLAnchorElement(*node) && toHTMLAnchorElement(node)->isLiveLink()) {
663                 candidateDragType = DragSourceActionLink;
664                 break;
665             }
666             if (dragMode == DRAG_ELEMENT) {
667                 candidateDragType = DragSourceActionDHTML;
668                 break;
669             }
670         }
671     }
672
673     if (candidateDragType == DragSourceActionNone) {
674         // Either:
675         // 1) Nothing under the cursor is considered draggable, so we bail out.
676         // 2) There was a selection under the cursor but selectionDragPolicy is set to
677         //    DelayedSelectionDragResolution and no other draggable element could be found, so bail
678         //    out and allow text selection to start at the cursor instead.
679         return nullptr;
680     }
681
682     ASSERT(node);
683     if (dragType == DragSourceActionSelection) {
684         // Dragging unselectable elements in a selection has special behavior if selectionDragPolicy
685         // is DelayedSelectionDragResolution and this drag was flagged as a potential selection
686         // drag. In that case, don't allow selection and just drag the entire selection instead.
687         ASSERT(selectionDragPolicy == DelayedSelectionDragResolution);
688         node = startNode;
689     } else {
690         // If the cursor isn't over a selection, then just drag the node we found earlier.
691         ASSERT(dragType == DragSourceActionNone);
692         dragType = candidateDragType;
693     }
694     return node;
695 }
696
697 static ImageResource* getImageResource(Element* element)
698 {
699     ASSERT(element);
700     RenderObject* renderer = element->renderer();
701     if (!renderer || !renderer->isImage())
702         return nullptr;
703     RenderImage* image = toRenderImage(renderer);
704     return image->cachedImage();
705 }
706
707 static Image* getImage(Element* element)
708 {
709     ASSERT(element);
710     ImageResource* cachedImage = getImageResource(element);
711     // Don't use cachedImage->imageForRenderer() here as that may return BitmapImages for cached SVG Images.
712     // Users of getImage() want access to the SVGImage, in order to figure out the filename extensions,
713     // which would be empty when asking the cached BitmapImages.
714     return (cachedImage && !cachedImage->errorOccurred()) ?
715         cachedImage->image() : nullptr;
716 }
717
718 static void prepareDataTransferForImageDrag(LocalFrame* source, DataTransfer* dataTransfer, Element* node, const KURL& linkURL, const KURL& imageURL, const String& label)
719 {
720     if (node->isContentRichlyEditable()) {
721         RefPtrWillBeRawPtr<Range> range = source->document()->createRange();
722         range->selectNode(node, ASSERT_NO_EXCEPTION);
723         source->selection().setSelection(VisibleSelection(range.get(), DOWNSTREAM));
724     }
725     dataTransfer->declareAndWriteDragImage(node, !linkURL.isEmpty() ? linkURL : imageURL, label);
726 }
727
728 static ShadowRoot::ShadowRootType treeScopeType(const TreeScope& scope)
729 {
730     // Treat document like an author shadow root.
731     if (scope.rootNode().isDocumentNode())
732         return ShadowRoot::AuthorShadowRoot;
733     return toShadowRoot(scope.rootNode()).type();
734 }
735
736 static bool containsExcludingUserAgentShadowTrees(const Node& dragSrc, Node* dragOrigin)
737 {
738     if (!dragOrigin)
739         return false;
740     if (treeScopeType(dragSrc.treeScope()) != treeScopeType(dragOrigin->treeScope()))
741         return false;
742     return dragSrc.containsIncludingShadowDOM(dragOrigin);
743 }
744
745 bool DragController::populateDragDataTransfer(LocalFrame* src, const DragState& state, const IntPoint& dragOrigin)
746 {
747     ASSERT(dragTypeIsValid(state.m_dragType));
748     ASSERT(src);
749     if (!src->view() || !src->contentRenderer())
750         return false;
751
752     HitTestResult hitTestResult = src->eventHandler().hitTestResultAtPoint(dragOrigin);
753     // FIXME: Can this even happen? I guess it's possible, but should verify
754     // with a layout test.
755     if (!containsExcludingUserAgentShadowTrees(*state.m_dragSrc, hitTestResult.innerNode())) {
756         // The original node being dragged isn't under the drag origin anymore... maybe it was
757         // hidden or moved out from under the cursor. Regardless, we don't want to start a drag on
758         // something that's not actually under the drag origin.
759         return false;
760     }
761     const KURL& linkURL = hitTestResult.absoluteLinkURL();
762     const KURL& imageURL = hitTestResult.absoluteImageURL();
763
764     DataTransfer* dataTransfer = state.m_dragDataTransfer.get();
765     Node* node = state.m_dragSrc.get();
766
767     if (state.m_dragType == DragSourceActionSelection) {
768         if (enclosingTextFormControl(src->selection().start())) {
769             dataTransfer->writePlainText(src->selectedTextForClipboard());
770         } else {
771             RefPtrWillBeRawPtr<Range> selectionRange = src->selection().toNormalizedRange();
772             ASSERT(selectionRange);
773
774             dataTransfer->writeRange(selectionRange.get(), src);
775         }
776     } else if (state.m_dragType == DragSourceActionImage) {
777         if (imageURL.isEmpty() || !node || !node->isElementNode())
778             return false;
779         Element* element = toElement(node);
780         prepareDataTransferForImageDrag(src, dataTransfer, element, linkURL, imageURL, hitTestResult.altDisplayString());
781     } else if (state.m_dragType == DragSourceActionLink) {
782         if (linkURL.isEmpty())
783             return false;
784         // Simplify whitespace so the title put on the clipboard resembles what the user sees
785         // on the web page. This includes replacing newlines with spaces.
786         dataTransfer->writeURL(linkURL, hitTestResult.textContent().simplifyWhiteSpace());
787     }
788     // FIXME: For DHTML/draggable element drags, write element markup to clipboard.
789     return true;
790 }
791
792 static IntPoint dragLocationForDHTMLDrag(const IntPoint& mouseDraggedPoint, const IntPoint& dragOrigin, const IntPoint& dragImageOffset, bool isLinkImage)
793 {
794     // dragImageOffset is the cursor position relative to the lower-left corner of the image.
795     const int yOffset = -dragImageOffset.y();
796
797     if (isLinkImage)
798         return IntPoint(mouseDraggedPoint.x() - dragImageOffset.x(), mouseDraggedPoint.y() + yOffset);
799
800     return IntPoint(dragOrigin.x() - dragImageOffset.x(), dragOrigin.y() + yOffset);
801 }
802
803 static IntPoint dragLocationForSelectionDrag(LocalFrame* sourceFrame)
804 {
805     IntRect draggingRect = enclosingIntRect(sourceFrame->selection().bounds());
806     int xpos = draggingRect.maxX();
807     xpos = draggingRect.x() < xpos ? draggingRect.x() : xpos;
808     int ypos = draggingRect.maxY();
809     ypos = draggingRect.y() < ypos ? draggingRect.y() : ypos;
810     return IntPoint(xpos, ypos);
811 }
812
813 static const IntSize& maxDragImageSize()
814 {
815 #if OS(MACOSX)
816     // Match Safari's drag image size.
817     static const IntSize maxDragImageSize(400, 400);
818 #else
819     static const IntSize maxDragImageSize(200, 200);
820 #endif
821     return maxDragImageSize;
822 }
823
824 static PassOwnPtr<DragImage> dragImageForImage(Element* element, Image* image, const IntPoint& dragOrigin, const IntRect& imageRect, IntPoint& dragLocation)
825 {
826     OwnPtr<DragImage> dragImage;
827     IntPoint origin;
828
829     if (image->size().height() * image->size().width() <= MaxOriginalImageArea
830         && (dragImage = DragImage::create(image, element->renderer() ? element->renderer()->shouldRespectImageOrientation() : DoNotRespectImageOrientation))) {
831         IntSize originalSize = imageRect.size();
832         origin = imageRect.location();
833
834         dragImage->fitToMaxSize(imageRect.size(), maxDragImageSize());
835         dragImage->dissolveToFraction(DragImageAlpha);
836         IntSize newSize = dragImage->size();
837
838         // Properly orient the drag image and orient it differently if it's smaller than the original
839         float scale = newSize.width() / (float)originalSize.width();
840         float dx = origin.x() - dragOrigin.x();
841         dx *= scale;
842         origin.setX((int)(dx + 0.5));
843         float dy = origin.y() - dragOrigin.y();
844         dy *= scale;
845         origin.setY((int)(dy + 0.5));
846     }
847
848     dragLocation = dragOrigin + origin;
849     return dragImage.release();
850 }
851
852 static PassOwnPtr<DragImage> dragImageForLink(const KURL& linkURL, const String& linkText, float deviceScaleFactor, const IntPoint& mouseDraggedPoint, IntPoint& dragLoc)
853 {
854     FontDescription fontDescription;
855     RenderTheme::theme().systemFont(blink::CSSValueNone, fontDescription);
856     OwnPtr<DragImage> dragImage = DragImage::create(linkURL, linkText, fontDescription, deviceScaleFactor);
857
858     IntSize size = dragImage ? dragImage->size() : IntSize();
859     IntPoint dragImageOffset(-size.width() / 2, -LinkDragBorderInset);
860     dragLoc = IntPoint(mouseDraggedPoint.x() + dragImageOffset.x(), mouseDraggedPoint.y() + dragImageOffset.y());
861
862     return dragImage.release();
863 }
864
865 bool DragController::startDrag(LocalFrame* src, const DragState& state, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin)
866 {
867     ASSERT(dragTypeIsValid(state.m_dragType));
868     ASSERT(src);
869     if (!src->view() || !src->contentRenderer())
870         return false;
871
872     HitTestResult hitTestResult = src->eventHandler().hitTestResultAtPoint(dragOrigin);
873     if (!containsExcludingUserAgentShadowTrees(*state.m_dragSrc, hitTestResult.innerNode())) {
874         // The original node being dragged isn't under the drag origin anymore... maybe it was
875         // hidden or moved out from under the cursor. Regardless, we don't want to start a drag on
876         // something that's not actually under the drag origin.
877         return false;
878     }
879     const KURL& linkURL = hitTestResult.absoluteLinkURL();
880     const KURL& imageURL = hitTestResult.absoluteImageURL();
881
882     IntPoint mouseDraggedPoint = src->view()->windowToContents(dragEvent.position());
883
884     IntPoint dragLocation;
885     IntPoint dragOffset;
886
887     DataTransfer* dataTransfer = state.m_dragDataTransfer.get();
888     // We allow DHTML/JS to set the drag image, even if its a link, image or text we're dragging.
889     // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp.
890     OwnPtr<DragImage> dragImage = dataTransfer->createDragImage(dragOffset, src);
891     if (dragImage) {
892         dragLocation = dragLocationForDHTMLDrag(mouseDraggedPoint, dragOrigin, dragOffset, !linkURL.isEmpty());
893     }
894
895     Node* node = state.m_dragSrc.get();
896     if (state.m_dragType == DragSourceActionSelection) {
897         if (!dragImage) {
898             dragImage = src->dragImageForSelection();
899             if (dragImage)
900                 dragImage->dissolveToFraction(DragImageAlpha);
901             dragLocation = dragLocationForSelectionDrag(src);
902         }
903         doSystemDrag(dragImage.get(), dragLocation, dragOrigin, dataTransfer, src, false);
904     } else if (state.m_dragType == DragSourceActionImage) {
905         if (imageURL.isEmpty() || !node || !node->isElementNode())
906             return false;
907         Element* element = toElement(node);
908         Image* image = getImage(element);
909         if (!image || image->isNull())
910             return false;
911         // We shouldn't be starting a drag for an image that can't provide an extension.
912         // This is an early detection for problems encountered later upon drop.
913         ASSERT(!image->filenameExtension().isEmpty());
914         if (!dragImage) {
915             dragImage = dragImageForImage(element, image, dragOrigin, hitTestResult.imageRect(), dragLocation);
916         }
917         doSystemDrag(dragImage.get(), dragLocation, dragOrigin, dataTransfer, src, false);
918     } else if (state.m_dragType == DragSourceActionLink) {
919         if (linkURL.isEmpty())
920             return false;
921         if (src->selection().isCaret() && src->selection().isContentEditable()) {
922             // a user can initiate a drag on a link without having any text
923             // selected.  In this case, we should expand the selection to
924             // the enclosing anchor element
925             if (Node* node = enclosingAnchorElement(src->selection().base()))
926                 src->selection().setSelection(VisibleSelection::selectionFromContentsOfNode(node));
927         }
928
929         if (!dragImage) {
930             ASSERT(src->page());
931             float deviceScaleFactor = src->page()->deviceScaleFactor();
932             dragImage = dragImageForLink(linkURL, hitTestResult.textContent(), deviceScaleFactor, mouseDraggedPoint, dragLocation);
933         }
934         doSystemDrag(dragImage.get(), dragLocation, mouseDraggedPoint, dataTransfer, src, true);
935     } else if (state.m_dragType == DragSourceActionDHTML) {
936         if (!dragImage)
937             return false;
938         doSystemDrag(dragImage.get(), dragLocation, dragOrigin, dataTransfer, src, false);
939     } else {
940         ASSERT_NOT_REACHED();
941         return false;
942     }
943
944     return true;
945 }
946
947 void DragController::doSystemDrag(DragImage* image, const IntPoint& dragLocation, const IntPoint& eventPos, DataTransfer* dataTransfer, LocalFrame* frame, bool forLink)
948 {
949     m_didInitiateDrag = true;
950     m_dragInitiator = frame->document();
951     // Protect this frame and view, as a load may occur mid drag and attempt to unload this frame
952     RefPtrWillBeRawPtr<LocalFrame> mainFrame = m_page->deprecatedLocalMainFrame();
953     RefPtrWillBeRawPtr<FrameView> mainFrameView = mainFrame->view();
954
955     m_client->startDrag(image, mainFrameView->rootViewToContents(frame->view()->contentsToRootView(dragLocation)),
956         mainFrameView->rootViewToContents(frame->view()->contentsToRootView(eventPos)), dataTransfer, frame, forLink);
957     // DragClient::startDrag can cause our Page to dispear, deallocating |this|.
958     if (!frame->page())
959         return;
960
961     cleanupAfterSystemDrag();
962 }
963
964 DragOperation DragController::dragOperation(DragData* dragData)
965 {
966     // FIXME: To match the MacOS behaviour we should return DragOperationNone
967     // if we are a modal window, we are the drag source, or the window is an
968     // attached sheet If this can be determined from within WebCore
969     // operationForDrag can be pulled into WebCore itself
970     ASSERT(dragData);
971     return dragData->containsURL() && !m_didInitiateDrag ? DragOperationCopy : DragOperationNone;
972 }
973
974 bool DragController::isCopyKeyDown(DragData* dragData)
975 {
976     int keyState = dragData->modifierKeyState();
977
978 #if OS(MACOSX)
979     return keyState & PlatformEvent::AltKey;
980 #else
981     return keyState & PlatformEvent::CtrlKey;
982 #endif
983 }
984
985 void DragController::cleanupAfterSystemDrag()
986 {
987 }
988
989 void DragController::trace(Visitor* visitor)
990 {
991     visitor->trace(m_page);
992     visitor->trace(m_documentUnderMouse);
993     visitor->trace(m_dragInitiator);
994     visitor->trace(m_fileInputElementUnderMouse);
995 }
996
997 } // namespace blink