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