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