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