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