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