Home | History | Annotate | Download | only in page
      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 
     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 
     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 
     92 DragController::~DragController()
     93 {
     94     m_client->dragControllerDestroyed();
     95 }
     96 
     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 
    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.
    138 void DragController::cancelDrag()
    139 {
    140     m_page->dragCaretController()->clear();
    141 }
    142 
    143 void DragController::dragEnded()
    144 {
    145     m_dragInitiator = 0;
    146     m_didInitiateDrag = false;
    147     m_page->dragCaretController()->clear();
    148 }
    149 
    150 DragOperation DragController::dragEntered(DragData* dragData)
    151 {
    152     return dragEnteredOrUpdated(dragData);
    153 }
    154 
    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 
    170 DragOperation DragController::dragUpdated(DragData* dragData)
    171 {
    172     return dragEnteredOrUpdated(dragData);
    173 }
    174 
    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 
    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 
    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 
    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 
    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 
    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 
    328 DragSourceAction DragController::delegateDragSourceAction(const IntPoint& windowPoint)
    329 {
    330     m_dragSourceAction = m_client->dragSourceActionMaskForPoint(windowPoint);
    331     return m_dragSourceAction;
    332 }
    333 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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