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