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