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