• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007, 2009 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 COMPUTER, 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 COMPUTER, 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 #if ENABLE(DRAG_SUPPORT)
30 #include "CSSStyleDeclaration.h"
31 #include "Clipboard.h"
32 #include "ClipboardAccessPolicy.h"
33 #include "DocLoader.h"
34 #include "Document.h"
35 #include "DocumentFragment.h"
36 #include "DragActions.h"
37 #include "DragClient.h"
38 #include "DragData.h"
39 #include "Editor.h"
40 #include "EditorClient.h"
41 #include "Element.h"
42 #include "EventHandler.h"
43 #include "FloatRect.h"
44 #include "Frame.h"
45 #include "FrameLoader.h"
46 #include "FrameView.h"
47 #include "HTMLAnchorElement.h"
48 #include "HTMLInputElement.h"
49 #include "HTMLNames.h"
50 #include "HitTestRequest.h"
51 #include "HitTestResult.h"
52 #include "Image.h"
53 #include "MoveSelectionCommand.h"
54 #include "Node.h"
55 #include "Page.h"
56 #include "RenderFileUploadControl.h"
57 #include "RenderImage.h"
58 #include "RenderView.h"
59 #include "ReplaceSelectionCommand.h"
60 #include "ResourceRequest.h"
61 #include "SelectionController.h"
62 #include "Settings.h"
63 #include "Text.h"
64 #include "htmlediting.h"
65 #include "markup.h"
66 #include <wtf/CurrentTime.h>
67 #include <wtf/RefPtr.h>
68 
69 namespace WebCore {
70 
createMouseEvent(DragData * dragData)71 static PlatformMouseEvent createMouseEvent(DragData* dragData)
72 {
73     // FIXME: We should fake modifier keys here.
74     return PlatformMouseEvent(dragData->clientPosition(), dragData->globalPosition(),
75                               LeftButton, MouseEventMoved, 0, false, false, false, false, currentTime());
76 
77 }
78 
DragController(Page * page,DragClient * client)79 DragController::DragController(Page* page, DragClient* client)
80     : m_page(page)
81     , m_client(client)
82     , m_documentUnderMouse(0)
83     , m_dragInitiator(0)
84     , m_dragDestinationAction(DragDestinationActionNone)
85     , m_dragSourceAction(DragSourceActionNone)
86     , m_didInitiateDrag(false)
87     , m_isHandlingDrag(false)
88     , m_sourceDragOperation(DragOperationNone)
89 {
90 }
91 
~DragController()92 DragController::~DragController()
93 {
94     m_client->dragControllerDestroyed();
95 }
96 
documentFragmentFromDragData(DragData * dragData,RefPtr<Range> context,bool allowPlainText,bool & chosePlainText)97 static PassRefPtr<DocumentFragment> documentFragmentFromDragData(DragData* dragData, RefPtr<Range> context,
98                                           bool allowPlainText, bool& chosePlainText)
99 {
100     ASSERT(dragData);
101     chosePlainText = false;
102 
103     Document* document = context->ownerDocument();
104     ASSERT(document);
105     if (document && dragData->containsCompatibleContent()) {
106         if (PassRefPtr<DocumentFragment> fragment = dragData->asFragment(document))
107             return fragment;
108 
109         if (dragData->containsURL()) {
110             String title;
111             String url = dragData->asURL(&title);
112             if (!url.isEmpty()) {
113                 RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::create(document);
114                 anchor->setHref(url);
115                 ExceptionCode ec;
116                 RefPtr<Node> anchorText = document->createTextNode(title);
117                 anchor->appendChild(anchorText, ec);
118                 RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
119                 fragment->appendChild(anchor, ec);
120                 return fragment.get();
121             }
122         }
123     }
124     if (allowPlainText && dragData->containsPlainText()) {
125         chosePlainText = true;
126         return createFragmentFromText(context.get(), dragData->asPlainText()).get();
127     }
128 
129     return 0;
130 }
131 
dragIsMove(SelectionController * selection)132 bool DragController::dragIsMove(SelectionController* selection)
133 {
134     return m_documentUnderMouse == m_dragInitiator && selection->isContentEditable() && !isCopyKeyDown();
135 }
136 
137 // FIXME: This method is poorly named.  We're just clearing the selection from the document this drag is exiting.
cancelDrag()138 void DragController::cancelDrag()
139 {
140     m_page->dragCaretController()->clear();
141 }
142 
dragEnded()143 void DragController::dragEnded()
144 {
145     m_dragInitiator = 0;
146     m_didInitiateDrag = false;
147     m_page->dragCaretController()->clear();
148 }
149 
dragEntered(DragData * dragData)150 DragOperation DragController::dragEntered(DragData* dragData)
151 {
152     return dragEnteredOrUpdated(dragData);
153 }
154 
dragExited(DragData * dragData)155 void DragController::dragExited(DragData* dragData)
156 {
157     ASSERT(dragData);
158     Frame* mainFrame = m_page->mainFrame();
159 
160     if (RefPtr<FrameView> v = mainFrame->view()) {
161         ClipboardAccessPolicy policy = (!m_documentUnderMouse || m_documentUnderMouse->securityOrigin()->isLocal()) ? ClipboardReadable : ClipboardTypesReadable;
162         RefPtr<Clipboard> clipboard = dragData->createClipboard(policy);
163         clipboard->setSourceOperation(dragData->draggingSourceOperationMask());
164         mainFrame->eventHandler()->cancelDragAndDrop(createMouseEvent(dragData), clipboard.get());
165         clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
166     }
167     mouseMovedIntoDocument(0);
168 }
169 
dragUpdated(DragData * dragData)170 DragOperation DragController::dragUpdated(DragData* dragData)
171 {
172     return dragEnteredOrUpdated(dragData);
173 }
174 
performDrag(DragData * dragData)175 bool DragController::performDrag(DragData* dragData)
176 {
177     ASSERT(dragData);
178     m_documentUnderMouse = m_page->mainFrame()->documentAtPoint(dragData->clientPosition());
179     if (m_isHandlingDrag) {
180         ASSERT(m_dragDestinationAction & DragDestinationActionDHTML);
181         m_client->willPerformDragDestinationAction(DragDestinationActionDHTML, dragData);
182         RefPtr<Frame> mainFrame = m_page->mainFrame();
183         if (mainFrame->view()) {
184             // Sending an event can result in the destruction of the view and part.
185             RefPtr<Clipboard> clipboard = dragData->createClipboard(ClipboardReadable);
186             clipboard->setSourceOperation(dragData->draggingSourceOperationMask());
187             mainFrame->eventHandler()->performDragAndDrop(createMouseEvent(dragData), clipboard.get());
188             clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
189         }
190         m_documentUnderMouse = 0;
191         return true;
192     }
193 
194     if ((m_dragDestinationAction & DragDestinationActionEdit) && concludeEditDrag(dragData)) {
195         m_documentUnderMouse = 0;
196         return true;
197     }
198 
199     m_documentUnderMouse = 0;
200 
201     if (operationForLoad(dragData) == DragOperationNone)
202         return false;
203 
204     m_client->willPerformDragDestinationAction(DragDestinationActionLoad, dragData);
205     m_page->mainFrame()->loader()->load(ResourceRequest(dragData->asURL()), false);
206     return true;
207 }
208 
mouseMovedIntoDocument(Document * newDocument)209 void DragController::mouseMovedIntoDocument(Document* newDocument)
210 {
211     if (m_documentUnderMouse == newDocument)
212         return;
213 
214     // If we were over another document clear the selection
215     if (m_documentUnderMouse)
216         cancelDrag();
217     m_documentUnderMouse = newDocument;
218 }
219 
dragEnteredOrUpdated(DragData * dragData)220 DragOperation DragController::dragEnteredOrUpdated(DragData* dragData)
221 {
222     ASSERT(dragData);
223     ASSERT(m_page->mainFrame()); // It is not possible in Mac WebKit to have a Page without a mainFrame()
224     mouseMovedIntoDocument(m_page->mainFrame()->documentAtPoint(dragData->clientPosition()));
225 
226     m_dragDestinationAction = m_client->actionMaskForDrag(dragData);
227     if (m_dragDestinationAction == DragDestinationActionNone) {
228         cancelDrag(); // FIXME: Why not call mouseMovedIntoDocument(0)?
229         return DragOperationNone;
230     }
231 
232     DragOperation operation = DragOperationNone;
233     bool handledByDocument = tryDocumentDrag(dragData, m_dragDestinationAction, operation);
234     if (!handledByDocument && (m_dragDestinationAction & DragDestinationActionLoad))
235         return operationForLoad(dragData);
236     return operation;
237 }
238 
asFileInput(Node * node)239 static HTMLInputElement* asFileInput(Node* node)
240 {
241     ASSERT(node);
242 
243     // The button for a FILE input is a sub element with no set input type
244     // In order to get around this problem we assume any non-FILE input element
245     // is this internal button, and try querying the shadow parent node.
246     if (node->hasTagName(HTMLNames::inputTag) && node->isShadowNode() && static_cast<HTMLInputElement*>(node)->inputType() != HTMLInputElement::FILE)
247       node = node->shadowParentNode();
248 
249     if (!node || !node->hasTagName(HTMLNames::inputTag))
250         return 0;
251 
252     HTMLInputElement* inputElem = static_cast<HTMLInputElement*>(node);
253     if (inputElem->inputType() == HTMLInputElement::FILE)
254         return inputElem;
255 
256     return 0;
257 }
258 
elementUnderMouse(Document * documentUnderMouse,const IntPoint & p)259 static Element* elementUnderMouse(Document* documentUnderMouse, const IntPoint& p)
260 {
261     float zoomFactor = documentUnderMouse->frame()->pageZoomFactor();
262     IntPoint point = roundedIntPoint(FloatPoint(p.x() * zoomFactor, p.y() * zoomFactor));
263 
264     HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active);
265     HitTestResult result(point);
266     documentUnderMouse->renderView()->layer()->hitTest(request, result);
267 
268     Node* n = result.innerNode();
269     while (n && !n->isElementNode())
270         n = n->parentNode();
271     if (n)
272         n = n->shadowAncestorNode();
273 
274     ASSERT(n);
275     return static_cast<Element*>(n);
276 }
277 
tryDocumentDrag(DragData * dragData,DragDestinationAction actionMask,DragOperation & operation)278 bool DragController::tryDocumentDrag(DragData* dragData, DragDestinationAction actionMask, DragOperation& operation)
279 {
280     ASSERT(dragData);
281 
282     if (!m_documentUnderMouse)
283         return false;
284 
285     m_isHandlingDrag = false;
286     if (actionMask & DragDestinationActionDHTML) {
287         m_isHandlingDrag = tryDHTMLDrag(dragData, operation);
288         // Do not continue if m_documentUnderMouse has been reset by tryDHTMLDrag.
289         // tryDHTMLDrag fires dragenter event. The event listener that listens
290         // to this event may create a nested message loop (open a modal dialog),
291         // which could process dragleave event and reset m_documentUnderMouse in
292         // dragExited.
293         if (!m_documentUnderMouse)
294             return false;
295     }
296 
297     // It's unclear why this check is after tryDHTMLDrag.
298     // We send drag events in tryDHTMLDrag and that may be the reason.
299     RefPtr<FrameView> frameView = m_documentUnderMouse->view();
300     if (!frameView)
301         return false;
302 
303     if (m_isHandlingDrag) {
304         m_page->dragCaretController()->clear();
305         return true;
306     } else if ((actionMask & DragDestinationActionEdit) && canProcessDrag(dragData)) {
307         if (dragData->containsColor()) {
308             operation = DragOperationGeneric;
309             return true;
310         }
311 
312         IntPoint point = frameView->windowToContents(dragData->clientPosition());
313         Element* element = elementUnderMouse(m_documentUnderMouse, point);
314         if (!asFileInput(element)) {
315             VisibleSelection dragCaret = m_documentUnderMouse->frame()->visiblePositionForPoint(point);
316             m_page->dragCaretController()->setSelection(dragCaret);
317         }
318 
319         Frame* innerFrame = element->document()->frame();
320         operation = dragIsMove(innerFrame->selection()) ? DragOperationMove : DragOperationCopy;
321         return true;
322     }
323     // If we're not over an editable region, make sure we're clearing any prior drag cursor.
324     m_page->dragCaretController()->clear();
325     return false;
326 }
327 
delegateDragSourceAction(const IntPoint & windowPoint)328 DragSourceAction DragController::delegateDragSourceAction(const IntPoint& windowPoint)
329 {
330     m_dragSourceAction = m_client->dragSourceActionMaskForPoint(windowPoint);
331     return m_dragSourceAction;
332 }
333 
operationForLoad(DragData * dragData)334 DragOperation DragController::operationForLoad(DragData* dragData)
335 {
336     ASSERT(dragData);
337     Document* doc = m_page->mainFrame()->documentAtPoint(dragData->clientPosition());
338     if (doc && (m_didInitiateDrag || doc->isPluginDocument() || (doc->frame() && doc->frame()->editor()->clientIsEditable())))
339         return DragOperationNone;
340     return dragOperation(dragData);
341 }
342 
setSelectionToDragCaret(Frame * frame,VisibleSelection & dragCaret,RefPtr<Range> & range,const IntPoint & point)343 static bool setSelectionToDragCaret(Frame* frame, VisibleSelection& dragCaret, RefPtr<Range>& range, const IntPoint& point)
344 {
345     frame->selection()->setSelection(dragCaret);
346     if (frame->selection()->isNone()) {
347         dragCaret = frame->visiblePositionForPoint(point);
348         frame->selection()->setSelection(dragCaret);
349         range = dragCaret.toNormalizedRange();
350     }
351     return !frame->selection()->isNone() && frame->selection()->isContentEditable();
352 }
353 
concludeEditDrag(DragData * dragData)354 bool DragController::concludeEditDrag(DragData* dragData)
355 {
356     ASSERT(dragData);
357     ASSERT(!m_isHandlingDrag);
358 
359     if (!m_documentUnderMouse)
360         return false;
361 
362     IntPoint point = m_documentUnderMouse->view()->windowToContents(dragData->clientPosition());
363     Element* element = elementUnderMouse(m_documentUnderMouse, point);
364     Frame* innerFrame = element->ownerDocument()->frame();
365     ASSERT(innerFrame);
366 
367     if (dragData->containsColor()) {
368         Color color = dragData->asColor();
369         if (!color.isValid())
370             return false;
371         if (!innerFrame)
372             return false;
373         RefPtr<Range> innerRange = innerFrame->selection()->toNormalizedRange();
374         RefPtr<CSSStyleDeclaration> style = m_documentUnderMouse->createCSSStyleDeclaration();
375         ExceptionCode ec;
376         style->setProperty("color", color.name(), ec);
377         if (!innerFrame->editor()->shouldApplyStyle(style.get(), innerRange.get()))
378             return false;
379         m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
380         innerFrame->editor()->applyStyle(style.get(), EditActionSetColor);
381         return true;
382     }
383 
384     if (!m_page->dragController()->canProcessDrag(dragData)) {
385         m_page->dragCaretController()->clear();
386         return false;
387     }
388 
389     if (HTMLInputElement* fileInput = asFileInput(element)) {
390         if (!fileInput->isEnabledFormControl())
391             return false;
392 
393         if (!dragData->containsFiles())
394             return false;
395 
396         Vector<String> filenames;
397         dragData->asFilenames(filenames);
398         if (filenames.isEmpty())
399             return false;
400 
401         // Ugly. For security none of the APIs available to us can set the input value
402         // on file inputs. Even forcing a change in HTMLInputElement doesn't work as
403         // RenderFileUploadControl clears the file when doing updateFromElement().
404         RenderFileUploadControl* renderer = toRenderFileUploadControl(fileInput->renderer());
405         if (!renderer)
406             return false;
407 
408         renderer->receiveDroppedFiles(filenames);
409         return true;
410     }
411 
412     VisibleSelection dragCaret(m_page->dragCaretController()->selection());
413     m_page->dragCaretController()->clear();
414     RefPtr<Range> range = dragCaret.toNormalizedRange();
415 
416     // For range to be null a WebKit client must have done something bad while
417     // manually controlling drag behaviour
418     if (!range)
419         return false;
420     DocLoader* loader = range->ownerDocument()->docLoader();
421     loader->setAllowStaleResources(true);
422     if (dragIsMove(innerFrame->selection()) || dragCaret.isContentRichlyEditable()) {
423         bool chosePlainText = false;
424         RefPtr<DocumentFragment> fragment = documentFragmentFromDragData(dragData, range, true, chosePlainText);
425         if (!fragment || !innerFrame->editor()->shouldInsertFragment(fragment, range, EditorInsertActionDropped)) {
426             loader->setAllowStaleResources(false);
427             return false;
428         }
429 
430         m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
431         if (dragIsMove(innerFrame->selection())) {
432             bool smartMove = innerFrame->selectionGranularity() == WordGranularity
433                           && innerFrame->editor()->smartInsertDeleteEnabled()
434                           && dragData->canSmartReplace();
435             applyCommand(MoveSelectionCommand::create(fragment, dragCaret.base(), smartMove));
436         } else {
437             if (setSelectionToDragCaret(innerFrame, dragCaret, range, point))
438                 applyCommand(ReplaceSelectionCommand::create(m_documentUnderMouse, fragment, true, dragData->canSmartReplace(), chosePlainText));
439         }
440     } else {
441         String text = dragData->asPlainText();
442         if (text.isEmpty() || !innerFrame->editor()->shouldInsertText(text, range.get(), EditorInsertActionDropped)) {
443             loader->setAllowStaleResources(false);
444             return false;
445         }
446 
447         m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData);
448         if (setSelectionToDragCaret(innerFrame, dragCaret, range, point))
449             applyCommand(ReplaceSelectionCommand::create(m_documentUnderMouse, createFragmentFromText(range.get(), text), true, false, true));
450     }
451     loader->setAllowStaleResources(false);
452 
453     return true;
454 }
455 
canProcessDrag(DragData * dragData)456 bool DragController::canProcessDrag(DragData* dragData)
457 {
458     ASSERT(dragData);
459 
460     if (!dragData->containsCompatibleContent())
461         return false;
462 
463     IntPoint point = m_page->mainFrame()->view()->windowToContents(dragData->clientPosition());
464     HitTestResult result = HitTestResult(point);
465     if (!m_page->mainFrame()->contentRenderer())
466         return false;
467 
468     result = m_page->mainFrame()->eventHandler()->hitTestResultAtPoint(point, true);
469 
470     if (!result.innerNonSharedNode())
471         return false;
472 
473     if (dragData->containsFiles() && asFileInput(result.innerNonSharedNode()))
474         return true;
475 
476     if (!result.innerNonSharedNode()->isContentEditable())
477         return false;
478 
479     if (m_didInitiateDrag && m_documentUnderMouse == m_dragInitiator && result.isSelected())
480         return false;
481 
482     return true;
483 }
484 
tryDHTMLDrag(DragData * dragData,DragOperation & operation)485 bool DragController::tryDHTMLDrag(DragData* dragData, DragOperation& operation)
486 {
487     ASSERT(dragData);
488     ASSERT(m_documentUnderMouse);
489     RefPtr<Frame> mainFrame = m_page->mainFrame();
490     RefPtr<FrameView> viewProtector = mainFrame->view();
491     if (!viewProtector)
492         return false;
493 
494     ClipboardAccessPolicy policy = m_documentUnderMouse->securityOrigin()->isLocal() ? ClipboardReadable : ClipboardTypesReadable;
495     RefPtr<Clipboard> clipboard = dragData->createClipboard(policy);
496     DragOperation srcOpMask = dragData->draggingSourceOperationMask();
497     clipboard->setSourceOperation(srcOpMask);
498 
499     PlatformMouseEvent event = createMouseEvent(dragData);
500     if (!mainFrame->eventHandler()->updateDragAndDrop(event, clipboard.get())) {
501         clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
502         return false;
503     }
504 
505     operation = clipboard->destinationOperation();
506     if (!(srcOpMask & operation)) {
507         // The element picked an operation which is not supported by the source
508         operation = DragOperationNone;
509     }
510 
511     clipboard->setAccessPolicy(ClipboardNumb);    // invalidate clipboard here for security
512     return true;
513 }
514 
mayStartDragAtEventLocation(const Frame * frame,const IntPoint & framePos)515 bool DragController::mayStartDragAtEventLocation(const Frame* frame, const IntPoint& framePos)
516 {
517     ASSERT(frame);
518     ASSERT(frame->settings());
519 
520     if (!frame->view() || !frame->contentRenderer())
521         return false;
522 
523     HitTestResult mouseDownTarget = HitTestResult(framePos);
524 
525     mouseDownTarget = frame->eventHandler()->hitTestResultAtPoint(framePos, true);
526 
527     if (mouseDownTarget.image()
528         && !mouseDownTarget.absoluteImageURL().isEmpty()
529         && frame->settings()->loadsImagesAutomatically()
530         && m_dragSourceAction & DragSourceActionImage)
531         return true;
532 
533     if (!mouseDownTarget.absoluteLinkURL().isEmpty()
534         && m_dragSourceAction & DragSourceActionLink
535         && mouseDownTarget.isLiveLink())
536         return true;
537 
538     if (mouseDownTarget.isSelected()
539         && m_dragSourceAction & DragSourceActionSelection)
540         return true;
541 
542     return false;
543 
544 }
545 
getCachedImage(Element * element)546 static CachedImage* getCachedImage(Element* element)
547 {
548     ASSERT(element);
549     RenderObject* renderer = element->renderer();
550     if (!renderer || !renderer->isImage())
551         return 0;
552     RenderImage* image = toRenderImage(renderer);
553     return image->cachedImage();
554 }
555 
getImage(Element * element)556 static Image* getImage(Element* element)
557 {
558     ASSERT(element);
559     RenderObject* renderer = element->renderer();
560     if (!renderer || !renderer->isImage())
561         return 0;
562 
563     RenderImage* image = toRenderImage(renderer);
564     if (image->cachedImage() && !image->cachedImage()->errorOccurred())
565         return image->cachedImage()->image();
566     return 0;
567 }
568 
prepareClipboardForImageDrag(Frame * src,Clipboard * clipboard,Element * node,const KURL & linkURL,const KURL & imageURL,const String & label)569 static void prepareClipboardForImageDrag(Frame* src, Clipboard* clipboard, Element* node, const KURL& linkURL, const KURL& imageURL, const String& label)
570 {
571     RefPtr<Range> range = src->document()->createRange();
572     ExceptionCode ec = 0;
573     range->selectNode(node, ec);
574     ASSERT(!ec);
575     src->selection()->setSelection(VisibleSelection(range.get(), DOWNSTREAM));
576     clipboard->declareAndWriteDragImage(node, !linkURL.isEmpty() ? linkURL : imageURL, label, src);
577 }
578 
dragLocForDHTMLDrag(const IntPoint & mouseDraggedPoint,const IntPoint & dragOrigin,const IntPoint & dragImageOffset,bool isLinkImage)579 static IntPoint dragLocForDHTMLDrag(const IntPoint& mouseDraggedPoint, const IntPoint& dragOrigin, const IntPoint& dragImageOffset, bool isLinkImage)
580 {
581     // dragImageOffset is the cursor position relative to the lower-left corner of the image.
582 #if PLATFORM(MAC)
583     // We add in the Y dimension because we are a flipped view, so adding moves the image down.
584     const int yOffset = dragImageOffset.y();
585 #else
586     const int yOffset = -dragImageOffset.y();
587 #endif
588 
589     if (isLinkImage)
590         return IntPoint(mouseDraggedPoint.x() - dragImageOffset.x(), mouseDraggedPoint.y() + yOffset);
591 
592     return IntPoint(dragOrigin.x() - dragImageOffset.x(), dragOrigin.y() + yOffset);
593 }
594 
dragLocForSelectionDrag(Frame * src)595 static IntPoint dragLocForSelectionDrag(Frame* src)
596 {
597     IntRect draggingRect = enclosingIntRect(src->selectionBounds());
598     int xpos = draggingRect.right();
599     xpos = draggingRect.x() < xpos ? draggingRect.x() : xpos;
600     int ypos = draggingRect.bottom();
601 #if PLATFORM(MAC)
602     // Deal with flipped coordinates on Mac
603     ypos = draggingRect.y() > ypos ? draggingRect.y() : ypos;
604 #else
605     ypos = draggingRect.y() < ypos ? draggingRect.y() : ypos;
606 #endif
607     return IntPoint(xpos, ypos);
608 }
609 
startDrag(Frame * src,Clipboard * clipboard,DragOperation srcOp,const PlatformMouseEvent & dragEvent,const IntPoint & dragOrigin,bool isDHTMLDrag)610 bool DragController::startDrag(Frame* src, Clipboard* clipboard, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin, bool isDHTMLDrag)
611 {
612     ASSERT(src);
613     ASSERT(clipboard);
614 
615     if (!src->view() || !src->contentRenderer())
616         return false;
617 
618     HitTestResult dragSource = HitTestResult(dragOrigin);
619     dragSource = src->eventHandler()->hitTestResultAtPoint(dragOrigin, true);
620     KURL linkURL = dragSource.absoluteLinkURL();
621     KURL imageURL = dragSource.absoluteImageURL();
622     bool isSelected = dragSource.isSelected();
623 
624     IntPoint mouseDraggedPoint = src->view()->windowToContents(dragEvent.pos());
625 
626     m_draggingImageURL = KURL();
627     m_sourceDragOperation = srcOp;
628 
629     DragImageRef dragImage = 0;
630     IntPoint dragLoc(0, 0);
631     IntPoint dragImageOffset(0, 0);
632 
633     if (isDHTMLDrag)
634         dragImage = clipboard->createDragImage(dragImageOffset);
635     else {
636         // This drag operation is not a DHTML drag and may go outside the WebView.
637         // We provide a default set of allowed drag operations that follows from:
638         // http://trac.webkit.org/browser/trunk/WebKit/mac/WebView/WebHTMLView.mm?rev=48526#L3430
639         m_sourceDragOperation = (DragOperation)(DragOperationGeneric | DragOperationCopy);
640     }
641 
642     // We allow DHTML/JS to set the drag image, even if its a link, image or text we're dragging.
643     // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp.
644     if (dragImage) {
645         dragLoc = dragLocForDHTMLDrag(mouseDraggedPoint, dragOrigin, dragImageOffset, !linkURL.isEmpty());
646         m_dragOffset = dragImageOffset;
647     }
648 
649     bool startedDrag = true; // optimism - we almost always manage to start the drag
650 
651     Node* node = dragSource.innerNonSharedNode();
652 
653     Image* image = getImage(static_cast<Element*>(node));
654     if (!imageURL.isEmpty() && node && node->isElementNode() && image
655             && (m_dragSourceAction & DragSourceActionImage)) {
656         // We shouldn't be starting a drag for an image that can't provide an extension.
657         // This is an early detection for problems encountered later upon drop.
658         ASSERT(!image->filenameExtension().isEmpty());
659         Element* element = static_cast<Element*>(node);
660         if (!clipboard->hasData()) {
661             m_draggingImageURL = imageURL;
662             prepareClipboardForImageDrag(src, clipboard, element, linkURL, imageURL, dragSource.altDisplayString());
663         }
664 
665         m_client->willPerformDragSourceAction(DragSourceActionImage, dragOrigin, clipboard);
666 
667         if (!dragImage) {
668             IntRect imageRect = dragSource.imageRect();
669             imageRect.setLocation(m_page->mainFrame()->view()->windowToContents(src->view()->contentsToWindow(imageRect.location())));
670             doImageDrag(element, dragOrigin, dragSource.imageRect(), clipboard, src, m_dragOffset);
671         } else
672             // DHTML defined drag image
673             doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false);
674 
675     } else if (!linkURL.isEmpty() && (m_dragSourceAction & DragSourceActionLink)) {
676         if (!clipboard->hasData())
677             // Simplify whitespace so the title put on the clipboard resembles what the user sees
678             // on the web page. This includes replacing newlines with spaces.
679             clipboard->writeURL(linkURL, dragSource.textContent().simplifyWhiteSpace(), src);
680 
681         if (src->selection()->isCaret() && src->selection()->isContentEditable()) {
682             // a user can initiate a drag on a link without having any text
683             // selected.  In this case, we should expand the selection to
684             // the enclosing anchor element
685             Position pos = src->selection()->base();
686             Node* node = enclosingAnchorElement(pos);
687             if (node)
688                 src->selection()->setSelection(VisibleSelection::selectionFromContentsOfNode(node));
689         }
690 
691         m_client->willPerformDragSourceAction(DragSourceActionLink, dragOrigin, clipboard);
692         if (!dragImage) {
693             dragImage = m_client->createDragImageForLink(linkURL, dragSource.textContent(), src);
694             IntSize size = dragImageSize(dragImage);
695             m_dragOffset = IntPoint(-size.width() / 2, -LinkDragBorderInset);
696             dragLoc = IntPoint(mouseDraggedPoint.x() + m_dragOffset.x(), mouseDraggedPoint.y() + m_dragOffset.y());
697         }
698         doSystemDrag(dragImage, dragLoc, mouseDraggedPoint, clipboard, src, true);
699     } else if (isSelected && (m_dragSourceAction & DragSourceActionSelection)) {
700         if (!clipboard->hasData()) {
701             if (isNodeInTextFormControl(src->selection()->start().node()))
702                 clipboard->writePlainText(src->selectedText());
703             else {
704                 RefPtr<Range> selectionRange = src->selection()->toNormalizedRange();
705                 ASSERT(selectionRange);
706 
707                 clipboard->writeRange(selectionRange.get(), src);
708             }
709         }
710         m_client->willPerformDragSourceAction(DragSourceActionSelection, dragOrigin, clipboard);
711         if (!dragImage) {
712             dragImage = createDragImageForSelection(src);
713             dragLoc = dragLocForSelectionDrag(src);
714             m_dragOffset = IntPoint((int)(dragOrigin.x() - dragLoc.x()), (int)(dragOrigin.y() - dragLoc.y()));
715         }
716         doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false);
717     } else if (isDHTMLDrag) {
718         ASSERT(m_dragSourceAction & DragSourceActionDHTML);
719         m_client->willPerformDragSourceAction(DragSourceActionDHTML, dragOrigin, clipboard);
720         doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false);
721     } else {
722         // Only way I know to get here is if to get here is if the original element clicked on in the mousedown is no longer
723         // under the mousedown point, so linkURL, imageURL and isSelected are all false/empty.
724         startedDrag = false;
725     }
726 
727     if (dragImage)
728         deleteDragImage(dragImage);
729     return startedDrag;
730 }
731 
doImageDrag(Element * element,const IntPoint & dragOrigin,const IntRect & rect,Clipboard * clipboard,Frame * frame,IntPoint & dragImageOffset)732 void DragController::doImageDrag(Element* element, const IntPoint& dragOrigin, const IntRect& rect, Clipboard* clipboard, Frame* frame, IntPoint& dragImageOffset)
733 {
734     IntPoint mouseDownPoint = dragOrigin;
735     DragImageRef dragImage;
736     IntPoint origin;
737 
738     Image* image = getImage(element);
739     if (image && image->size().height() * image->size().width() <= MaxOriginalImageArea
740         && (dragImage = createDragImageFromImage(image))) {
741         IntSize originalSize = rect.size();
742         origin = rect.location();
743 
744         dragImage = fitDragImageToMaxSize(dragImage, rect.size(), maxDragImageSize());
745         dragImage = dissolveDragImageToFraction(dragImage, DragImageAlpha);
746         IntSize newSize = dragImageSize(dragImage);
747 
748         // Properly orient the drag image and orient it differently if it's smaller than the original
749         float scale = newSize.width() / (float)originalSize.width();
750         float dx = origin.x() - mouseDownPoint.x();
751         dx *= scale;
752         origin.setX((int)(dx + 0.5));
753 #if PLATFORM(MAC)
754         //Compensate for accursed flipped coordinates in cocoa
755         origin.setY(origin.y() + originalSize.height());
756 #endif
757         float dy = origin.y() - mouseDownPoint.y();
758         dy *= scale;
759         origin.setY((int)(dy + 0.5));
760     } else {
761         dragImage = createDragImageIconForCachedImage(getCachedImage(element));
762         if (dragImage)
763             origin = IntPoint(DragIconRightInset - dragImageSize(dragImage).width(), DragIconBottomInset);
764     }
765 
766     dragImageOffset.setX(mouseDownPoint.x() + origin.x());
767     dragImageOffset.setY(mouseDownPoint.y() + origin.y());
768     doSystemDrag(dragImage, dragImageOffset, dragOrigin, clipboard, frame, false);
769 
770     deleteDragImage(dragImage);
771 }
772 
doSystemDrag(DragImageRef image,const IntPoint & dragLoc,const IntPoint & eventPos,Clipboard * clipboard,Frame * frame,bool forLink)773 void DragController::doSystemDrag(DragImageRef image, const IntPoint& dragLoc, const IntPoint& eventPos, Clipboard* clipboard, Frame* frame, bool forLink)
774 {
775     m_didInitiateDrag = true;
776     m_dragInitiator = frame->document();
777     // Protect this frame and view, as a load may occur mid drag and attempt to unload this frame
778     RefPtr<Frame> frameProtector = m_page->mainFrame();
779     RefPtr<FrameView> viewProtector = frameProtector->view();
780     m_client->startDrag(image, viewProtector->windowToContents(frame->view()->contentsToWindow(dragLoc)),
781         viewProtector->windowToContents(frame->view()->contentsToWindow(eventPos)), clipboard, frameProtector.get(), forLink);
782 
783     cleanupAfterSystemDrag();
784 }
785 
786 // Manual drag caret manipulation
placeDragCaret(const IntPoint & windowPoint)787 void DragController::placeDragCaret(const IntPoint& windowPoint)
788 {
789     mouseMovedIntoDocument(m_page->mainFrame()->documentAtPoint(windowPoint));
790     if (!m_documentUnderMouse)
791         return;
792     Frame* frame = m_documentUnderMouse->frame();
793     FrameView* frameView = frame->view();
794     if (!frameView)
795         return;
796     IntPoint framePoint = frameView->windowToContents(windowPoint);
797     VisibleSelection dragCaret(frame->visiblePositionForPoint(framePoint));
798     m_page->dragCaretController()->setSelection(dragCaret);
799 }
800 
801 } // namespace WebCore
802 
803 #endif // ENABLE(DRAG_SUPPORT)
804