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