diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog index d71c84777ea824bedf57af7986fd8cc53d8fcd20..beb4fce232e5bdb5e543fc8860eca9672ec47c16 100644 --- a/LayoutTests/ChangeLog +++ b/LayoutTests/ChangeLog @@ -1,3 +1,17 @@ +2011-05-13 Daniel Cheng + + Reviewed by Tony Chang. + + Improve drag start logic + https://bugs.webkit.org/show_bug.cgi?id=59409 + + Add a new test to test drag start edge cases on Mac (because of a non-zero text drag delay) + as well as rebase an existing test. + + * fast/css/user-drag-none.html: Text nodes are no longer draggable. + * platform/mac/editing/pasteboard/drag-selections-to-contenteditable-expected.txt: Added. + * platform/mac/editing/pasteboard/drag-selections-to-contenteditable.html: Added. + 2011-05-13 Anders Carlsson Reviewed by Sam Weinig. diff --git a/LayoutTests/fast/css/user-drag-none.html b/LayoutTests/fast/css/user-drag-none.html index c88caaf6ee56c386b8e3b6f445d4c1a773e1a08c..f9335135c5d9c1a5f917e7e0c5be84a58e9a814a 100644 --- a/LayoutTests/fast/css/user-drag-none.html +++ b/LayoutTests/fast/css/user-drag-none.html @@ -19,11 +19,11 @@ -
+
x
y
-
+
x
y
@@ -31,11 +31,11 @@ x
y
-
+
x
y
-
+
x
y
@@ -43,11 +43,11 @@ x
y
-
+
x
y
-
+
x
y
diff --git a/LayoutTests/platform/mac/editing/pasteboard/drag-selections-to-contenteditable-expected.txt b/LayoutTests/platform/mac/editing/pasteboard/drag-selections-to-contenteditable-expected.txt new file mode 100644 index 0000000000000000000000000000000000000000..dbffae02a2c7c261284e1a1ce649d7e3e5475467 --- /dev/null +++ b/LayoutTests/platform/mac/editing/pasteboard/drag-selections-to-contenteditable-expected.txt @@ -0,0 +1,16 @@ +  Link Random text. + Link Random text. +This test checks selection drag edge cases on Mac. To run the test manually: + +Select everything above, start the drag over the image, and with no delay, drag it to the content editable area. Only the image should be dragged. +Select everything above, start the drag over the link, and with no delay, drag it to the content editable area. The entire selection should be dragged. +Select everything above, start the drag over the text, and with no delay, drag it to the content editable area. Nothing should be dragged, but a bunch of text should be selected instead. +Dumping info about contenteditable div: +Number of children: 4 +Contents: +IMG +IMG +A +SPAN +Number of selected ranges: 1 + diff --git a/LayoutTests/platform/mac/editing/pasteboard/drag-selections-to-contenteditable.html b/LayoutTests/platform/mac/editing/pasteboard/drag-selections-to-contenteditable.html new file mode 100644 index 0000000000000000000000000000000000000000..51f5c9f074776797c0e871510fc557604d5cf9e5 --- /dev/null +++ b/LayoutTests/platform/mac/editing/pasteboard/drag-selections-to-contenteditable.html @@ -0,0 +1,81 @@ + + + + + + + +
+
+ +Link +Random text.
+ +

This test checks selection drag edge cases on Mac. To run the test manually: +

    +
  1. Select everything above, start the drag over the image, and with no delay, drag it to the content editable area. Only the image should be dragged. +
  2. Select everything above, start the drag over the link, and with no delay, drag it to the content editable area. The entire selection should be dragged. +
  3. Select everything above, start the drag over the text, and with no delay, drag it to the content editable area. Nothing should be dragged, but a bunch of text should be selected instead. +
+

+
+ diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog index c88214029d797565561167d0c4b793b0b2bf4248..bb04aa8ddd57a475db38c6e0b3e9dcc5f461a0b5 100644 --- a/Source/WebCore/ChangeLog +++ b/Source/WebCore/ChangeLog @@ -1,3 +1,38 @@ +2011-05-13 Daniel Cheng + + Reviewed by Tony Chang. + + Improve drag start logic + https://bugs.webkit.org/show_bug.cgi?id=59409 + + Rewrite and simplify the dragging logic to better match IE, Firefox, and the behavior + defined in the spec. Among other things: + - draggableNode() no longer returns text nodes when dragging anchors. + - When starting a drag over an image in a selection, prefer to drag the selection. + - Several redundant hit tests have been removed. + - Minor refactoring to make the logic easier to follow. + + Test: platform/mac/editing/pasteboard/drag-selections-to-contenteditable.html + + * WebCore.xcodeproj/project.pbxproj: + * page/DragController.cpp: + (WebCore::DragController::draggableNode): + (WebCore::DragController::startDrag): + * page/DragController.h: + * page/DragState.h: + (WebCore::DragState::shouldDispatchEvents): + * page/EventHandler.cpp: + (WebCore::EventHandler::EventHandler): + (WebCore::EventHandler::eventMayStartDrag): + (WebCore::EventHandler::updateDragSourceActionsAllowed): + (WebCore::EventHandler::updateDragAndDrop): + (WebCore::EventHandler::cancelDragAndDrop): + (WebCore::EventHandler::dragHysteresisExceeded): + (WebCore::EventHandler::dragSourceEndedAt): + (WebCore::ExactlyOneBitSet): + (WebCore::EventHandler::handleDrag): + * page/EventHandler.h: + 2011-05-13 Oliver Hunt Reviewed by Geoffrey Garen. diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj index b795567b9a5203ffba4fd77c8476b3a22e3648f4..04aa8e37565043881700aa953b8c330b66ca9306 100644 --- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj +++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj @@ -1675,7 +1675,7 @@ 81BE209911F4AB8D00915DFA /* IDBCursorBackendInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 81BE209411F4AB8D00915DFA /* IDBCursorBackendInterface.h */; }; 81BE20D211F4BC3200915DFA /* JSIDBCursor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81BE20A711F4B66F00915DFA /* JSIDBCursor.cpp */; }; 81BE20D311F4BC3200915DFA /* JSIDBCursor.h in Headers */ = {isa = PBXBuildFile; fileRef = 81BE20A811F4B66F00915DFA /* JSIDBCursor.h */; }; - 81F65FF613788FAA00FF6F2D /* DragState.h in Headers */ = {isa = PBXBuildFile; fileRef = 81F65FF513788FAA00FF6F2D /* DragState.h */; }; + 81F65FF613788FAA00FF6F2D /* DragState.h in Headers */ = {isa = PBXBuildFile; fileRef = 81F65FF513788FAA00FF6F2D /* DragState.h */; settings = {ATTRIBUTES = (Private, ); }; }; 82AB1743124B99EC00C5069D /* InspectorCSSAgent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82AB1741124B99EC00C5069D /* InspectorCSSAgent.cpp */; }; 82AB1744124B99EC00C5069D /* InspectorCSSAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 82AB1742124B99EC00C5069D /* InspectorCSSAgent.h */; }; 82AB1773125C826700C5069D /* InspectorStyleSheet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82AB176F125C826700C5069D /* InspectorStyleSheet.cpp */; }; diff --git a/Source/WebCore/page/DragController.cpp b/Source/WebCore/page/DragController.cpp index 81ce92d4f50d6990cbb3fdfa2e0c5ed7124e8d6a..e555b9033a3e037086a66a245abe16e22fb39fcc 100644 --- a/Source/WebCore/page/DragController.cpp +++ b/Source/WebCore/page/DragController.cpp @@ -569,71 +569,46 @@ bool DragController::tryDHTMLDrag(DragData* dragData, DragOperation& operation) return true; } -Node* DragController::draggableNode(const Frame* src, Node* startNode, bool dhtmlOK, bool uaOK, int x, int y, bool& dhtmlWillDrag) const +Node* DragController::draggableNode(const Frame* src, Node* startNode, const IntPoint& dragOrigin, DragState& state) const { - if (!dhtmlOK && !uaOK) - return 0; + state.m_dragType = (src->selection()->contains(dragOrigin)) ? DragSourceActionSelection : DragSourceActionNone; for (const RenderObject* renderer = startNode->renderer(); renderer; renderer = renderer->parent()) { Node* node = renderer->node(); - if (node && node->nodeType() == Node::TEXT_NODE) { - // Since there's no way for the author to address the -webkit-user-drag style for a text node, - // we use our own judgement. - if (uaOK && mayStartDragAtEventLocation(src, IntPoint(x, y), node)) { - dhtmlWillDrag = false; - return node; - } - if (node->canStartSelection()) - // In this case we have a click in the unselected portion of text. If this text is - // selectable, we want to start the selection process instead of looking for a parent - // to try to drag. - return 0; - } else { + if (!node) + // Anonymous render blocks don't correspond to actual DOM nodes, so we skip over them + // for the purposes of finding a draggable node. + continue; + if (!(state.m_dragType & DragSourceActionSelection) && node->isTextNode() && node->canStartSelection()) + // In this case we have a click in the unselected portion of text. If this text is + // selectable, we want to start the selection process instead of looking for a parent + // to try to drag. + return 0; + if (node->isElementNode()) { EUserDrag dragMode = renderer->style()->userDrag(); - if (dhtmlOK && dragMode == DRAG_ELEMENT) { - dhtmlWillDrag = true; + if ((m_dragSourceAction & DragSourceActionDHTML) && dragMode == DRAG_ELEMENT) { + state.m_dragType = static_cast(state.m_dragType | DragSourceActionDHTML); return node; } - if (uaOK && dragMode == DRAG_AUTO && mayStartDragAtEventLocation(src, IntPoint(x, y), node)) { - dhtmlWillDrag = false; - return node; + if (dragMode == DRAG_AUTO) { + if ((m_dragSourceAction & DragSourceActionImage) + && node->hasTagName(HTMLNames::imgTag) + && src->settings()->loadsImagesAutomatically()) { + state.m_dragType = static_cast(state.m_dragType | DragSourceActionImage); + return node; + } + if ((m_dragSourceAction & DragSourceActionLink) + && node->hasTagName(HTMLNames::aTag) + && static_cast(node)->isLiveLink()) { + state.m_dragType = static_cast(state.m_dragType | DragSourceActionLink); + return node; + } } } } - return 0; -} - -bool DragController::mayStartDragAtEventLocation(const Frame* frame, const IntPoint& framePos, Node* node) const -{ - ASSERT(frame); - ASSERT(frame->settings()); - - if (!frame->view() || !frame->contentRenderer()) - return false; - - HitTestResult mouseDownTarget = HitTestResult(framePos); - mouseDownTarget = frame->eventHandler()->hitTestResultAtPoint(framePos, true); - if (node) - mouseDownTarget.setInnerNonSharedNode(node); - - if (mouseDownTarget.image() - && !mouseDownTarget.absoluteImageURL().isEmpty() - && frame->settings()->loadsImagesAutomatically() - && m_dragSourceAction & DragSourceActionImage) - return true; - - if (!mouseDownTarget.absoluteLinkURL().isEmpty() - && m_dragSourceAction & DragSourceActionLink - && mouseDownTarget.isLiveLink() - && mouseDownTarget.URLElement()->renderer() && mouseDownTarget.URLElement()->renderer()->style()->userDrag() != DRAG_NONE) - return true; - - if (mouseDownTarget.isSelected() - && m_dragSourceAction & DragSourceActionSelection) - return true; - - return false; + // We either have nothing to drag or we have a selection and we're not over a draggable element. + return (state.m_dragType & DragSourceActionSelection) ? startNode : 0; } static CachedImage* getCachedImage(Element* element) @@ -695,19 +670,21 @@ static IntPoint dragLocForSelectionDrag(Frame* src) return IntPoint(xpos, ypos); } -bool DragController::startDrag(Frame* src, Clipboard* clipboard, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin, bool isDHTMLDrag) +bool DragController::startDrag(Frame* src, const DragState& state, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin) { ASSERT(src); - ASSERT(clipboard); if (!src->view() || !src->contentRenderer()) return false; - HitTestResult dragSource = HitTestResult(dragOrigin); - dragSource = src->eventHandler()->hitTestResultAtPoint(dragOrigin, true); - KURL linkURL = dragSource.absoluteLinkURL(); - KURL imageURL = dragSource.absoluteImageURL(); - bool isSelected = dragSource.isSelected(); + HitTestResult hitTestResult = src->eventHandler()->hitTestResultAtPoint(dragOrigin, true); + if (!state.m_dragSrc->contains(hitTestResult.innerNode())) + // The original node being dragged isn't under the drag origin anymore... maybe it was + // hidden or moved out from under the cursor. Regardless, we don't want to start a drag on + // something that's not actually under the drag origin. + return false; + KURL linkURL = hitTestResult.absoluteLinkURL(); + KURL imageURL = hitTestResult.absoluteImageURL(); IntPoint mouseDraggedPoint = src->view()->windowToContents(dragEvent.pos()); @@ -718,14 +695,14 @@ bool DragController::startDrag(Frame* src, Clipboard* clipboard, DragOperation s IntPoint dragLoc(0, 0); IntPoint dragImageOffset(0, 0); - if (isDHTMLDrag) + Clipboard* clipboard = state.m_dragClipboard.get(); + if (state.m_dragType == DragSourceActionDHTML) dragImage = clipboard->createDragImage(dragImageOffset); - else { - // This drag operation is not a DHTML drag and may go outside the WebView. - // We provide a default set of allowed drag operations that follows from: + if (state.m_dragType == DragSourceActionSelection || !imageURL.isEmpty() || !linkURL.isEmpty()) + // Selection, image, and link drags receive a default set of allowed drag operations that + // follows from: // http://trac.webkit.org/browser/trunk/WebKit/mac/WebView/WebHTMLView.mm?rev=48526#L3430 - m_sourceDragOperation = (DragOperation)(DragOperationGeneric | DragOperationCopy); - } + m_sourceDragOperation = static_cast(m_sourceDragOperation | DragOperationGeneric | DragOperationCopy); // We allow DHTML/JS to set the drag image, even if its a link, image or text we're dragging. // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp. @@ -736,26 +713,44 @@ bool DragController::startDrag(Frame* src, Clipboard* clipboard, DragOperation s bool startedDrag = true; // optimism - we almost always manage to start the drag - Node* node = dragSource.innerNonSharedNode(); + Node* node = state.m_dragSrc.get(); Image* image = getImage(static_cast(node)); - if (!imageURL.isEmpty() && node && node->isElementNode() && image - && (m_dragSourceAction & DragSourceActionImage)) { + if (state.m_dragType == DragSourceActionSelection) { + if (!clipboard->hasData()) { + if (isNodeInTextFormControl(src->selection()->start().deprecatedNode())) + clipboard->writePlainText(src->editor()->selectedText()); + else { + RefPtr selectionRange = src->selection()->toNormalizedRange(); + ASSERT(selectionRange); + + clipboard->writeRange(selectionRange.get(), src); + } + } + m_client->willPerformDragSourceAction(DragSourceActionSelection, dragOrigin, clipboard); + if (!dragImage) { + dragImage = createDragImageForSelection(src); + dragLoc = dragLocForSelectionDrag(src); + m_dragOffset = IntPoint(dragOrigin.x() - dragLoc.x(), dragOrigin.y() - dragLoc.y()); + } + doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); + } else if (!imageURL.isEmpty() && node && node->isElementNode() && image + && (m_dragSourceAction & DragSourceActionImage)) { // We shouldn't be starting a drag for an image that can't provide an extension. // This is an early detection for problems encountered later upon drop. ASSERT(!image->filenameExtension().isEmpty()); Element* element = static_cast(node); if (!clipboard->hasData()) { m_draggingImageURL = imageURL; - prepareClipboardForImageDrag(src, clipboard, element, linkURL, imageURL, dragSource.altDisplayString()); + prepareClipboardForImageDrag(src, clipboard, element, linkURL, imageURL, hitTestResult.altDisplayString()); } m_client->willPerformDragSourceAction(DragSourceActionImage, dragOrigin, clipboard); if (!dragImage) { - IntRect imageRect = dragSource.imageRect(); + IntRect imageRect = hitTestResult.imageRect(); imageRect.setLocation(m_page->mainFrame()->view()->windowToContents(src->view()->contentsToWindow(imageRect.location()))); - doImageDrag(element, dragOrigin, dragSource.imageRect(), clipboard, src, m_dragOffset); + doImageDrag(element, dragOrigin, hitTestResult.imageRect(), clipboard, src, m_dragOffset); } else // DHTML defined drag image doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); @@ -764,7 +759,7 @@ bool DragController::startDrag(Frame* src, Clipboard* clipboard, DragOperation s if (!clipboard->hasData()) // Simplify whitespace so the title put on the clipboard resembles what the user sees // on the web page. This includes replacing newlines with spaces. - clipboard->writeURL(linkURL, dragSource.textContent().simplifyWhiteSpace(), src); + clipboard->writeURL(linkURL, hitTestResult.textContent().simplifyWhiteSpace(), src); if (src->selection()->isCaret() && src->selection()->isContentEditable()) { // a user can initiate a drag on a link without having any text @@ -778,37 +773,19 @@ bool DragController::startDrag(Frame* src, Clipboard* clipboard, DragOperation s m_client->willPerformDragSourceAction(DragSourceActionLink, dragOrigin, clipboard); if (!dragImage) { - dragImage = createDragImageForLink(linkURL, dragSource.textContent(), src); + dragImage = createDragImageForLink(linkURL, hitTestResult.textContent(), src); IntSize size = dragImageSize(dragImage); m_dragOffset = IntPoint(-size.width() / 2, -LinkDragBorderInset); dragLoc = IntPoint(mouseDraggedPoint.x() + m_dragOffset.x(), mouseDraggedPoint.y() + m_dragOffset.y()); } doSystemDrag(dragImage, dragLoc, mouseDraggedPoint, clipboard, src, true); - } else if (isSelected && (m_dragSourceAction & DragSourceActionSelection)) { - if (!clipboard->hasData()) { - if (isNodeInTextFormControl(src->selection()->start().deprecatedNode())) - clipboard->writePlainText(src->editor()->selectedText()); - else { - RefPtr selectionRange = src->selection()->toNormalizedRange(); - ASSERT(selectionRange); - - clipboard->writeRange(selectionRange.get(), src); - } - } - m_client->willPerformDragSourceAction(DragSourceActionSelection, dragOrigin, clipboard); - if (!dragImage) { - dragImage = createDragImageForSelection(src); - dragLoc = dragLocForSelectionDrag(src); - m_dragOffset = IntPoint((int)(dragOrigin.x() - dragLoc.x()), (int)(dragOrigin.y() - dragLoc.y())); - } - doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); - } else if (isDHTMLDrag) { + } else if (state.m_dragType == DragSourceActionDHTML) { ASSERT(m_dragSourceAction & DragSourceActionDHTML); m_client->willPerformDragSourceAction(DragSourceActionDHTML, dragOrigin, clipboard); doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); } else { - // Only way I know to get here is if to get here is if the original element clicked on in the mousedown is no longer - // under the mousedown point, so linkURL, imageURL and isSelected are all false/empty. + // draggableNode() determined an image or link node was draggable, but it turns out the + // image or link had no URL, so there is nothing to drag. startedDrag = false; } diff --git a/Source/WebCore/page/DragController.h b/Source/WebCore/page/DragController.h index be8c834aee61569bde21c8b90fae9c04fa35a0ba..261a8d53f73658b071f21056ae58b24b1bb45969 100644 --- a/Source/WebCore/page/DragController.h +++ b/Source/WebCore/page/DragController.h @@ -37,6 +37,7 @@ namespace WebCore { class Document; class DragClient; class DragData; + struct DragState; class Element; class Frame; class FrameSelection; @@ -75,13 +76,12 @@ namespace WebCore { DragDestinationAction dragDestinationAction() const { return m_dragDestinationAction; } DragSourceAction delegateDragSourceAction(const IntPoint& pagePoint); - Node* draggableNode(const Frame*, Node*, bool dhtmlOK, bool uaOK, int x, int y, bool& dhtmlWillDrag) const; - bool mayStartDragAtEventLocation(const Frame*, const IntPoint& framePos, Node*) const; + Node* draggableNode(const Frame*, Node*, const IntPoint&, DragState&) const; void dragEnded(); void placeDragCaret(const IntPoint&); - bool startDrag(Frame* src, Clipboard*, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin, bool isDHTMLDrag); + bool startDrag(Frame* src, const DragState&, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin); static const IntSize& maxDragImageSize(); static const int LinkDragBorderInset; diff --git a/Source/WebCore/page/DragState.h b/Source/WebCore/page/DragState.h index 6b1f6756e3d21e77fb19b0b987b24031d70ed0fa..8f5fb5761078ce925ca7667fda657c2d3a8ee03b 100644 --- a/Source/WebCore/page/DragState.h +++ b/Source/WebCore/page/DragState.h @@ -26,23 +26,29 @@ #ifndef DragState_h #define DragState_h +#include "DragActions.h" #include +#include #include namespace WebCore { +class Clipboard; +class Node; + struct DragState { WTF_MAKE_NONCOPYABLE(DragState); WTF_MAKE_FAST_ALLOCATED; public: + enum EventDispatchPolicy { + DoNotDispatchEvents, + DispatchEvents, + }; DragState() { } + bool shouldDispatchEvents() const { return m_eventDispatchPolicy == DispatchEvents; } RefPtr m_dragSrc; // element that may be a drag source, for the current mouse gesture - bool m_dragSrcIsLink; - bool m_dragSrcIsImage; - bool m_dragSrcInSelection; - bool m_dragSrcMayBeDHTML; - bool m_dragSrcMayBeUA; // Are DHTML and/or the UserAgent allowed to drag out? - bool m_dragSrcIsDHTML; + EventDispatchPolicy m_eventDispatchPolicy; + DragSourceAction m_dragType; RefPtr m_dragClipboard; // used on only the source side of dragging }; diff --git a/Source/WebCore/page/EventHandler.cpp b/Source/WebCore/page/EventHandler.cpp index d56686038a88848c666f76f8567d7baf1b8248b8..d415ee06df3255c906e7b5e3365347d86fc3dd01 100644 --- a/Source/WebCore/page/EventHandler.cpp +++ b/Source/WebCore/page/EventHandler.cpp @@ -79,6 +79,7 @@ #include "UserTypingGestureIndicator.h" #include "WheelEvent.h" #include "WindowsKeyboardCodes.h" +#include #include #include @@ -178,6 +179,7 @@ EventHandler::EventHandler(Frame* frame) , m_mouseDownMayStartSelect(false) #if ENABLE(DRAG_SUPPORT) , m_mouseDownMayStartDrag(false) + , m_dragMayStartSelectionInstead(false) #endif , m_mouseDownWasSingleClickInSelection(false) , m_beganSelectingText(false) @@ -594,12 +596,6 @@ bool EventHandler::eventMayStartDrag(const PlatformMouseEvent& event) const if (event.button() != LeftButton || event.clickCount() != 1) return false; - bool DHTMLFlag; - bool UAFlag; - allowDHTMLDrag(DHTMLFlag, UAFlag); - if (!DHTMLFlag && !UAFlag) - return false; - FrameView* view = m_frame->view(); if (!view) return false; @@ -608,11 +604,12 @@ bool EventHandler::eventMayStartDrag(const PlatformMouseEvent& event) const if (!page) return false; + updateDragSourceActionsAllowed(); HitTestRequest request(HitTestRequest::ReadOnly); HitTestResult result(view->windowToContents(event.pos())); m_frame->contentRenderer()->layer()->hitTest(request, result); - bool srcIsDHTML; - return result.innerNode() && page->dragController()->draggableNode(m_frame, result.innerNode(), DHTMLFlag, UAFlag, result.point().x(), result.point().y(), srcIsDHTML); + DragState state; + return result.innerNode() && page->dragController()->draggableNode(m_frame, result.innerNode(), result.point(), state); } void EventHandler::updateSelectionForMouseDrag() @@ -899,25 +896,20 @@ void EventHandler::setAutoscrollRenderer(RenderObject* renderer) } #if ENABLE(DRAG_SUPPORT) -void EventHandler::allowDHTMLDrag(bool& flagDHTML, bool& flagUA) const +DragSourceAction EventHandler::updateDragSourceActionsAllowed() const { - flagDHTML = false; - flagUA = false; - if (!m_frame) - return; + return DragSourceActionNone; Page* page = m_frame->page(); if (!page) - return; + return DragSourceActionNone; FrameView* view = m_frame->view(); if (!view) - return; + return DragSourceActionNone; - unsigned mask = page->dragController()->delegateDragSourceAction(view->contentsToWindow(m_mouseDownPos)); - flagDHTML = (mask & DragSourceActionDHTML) != DragSourceActionNone; - flagUA = ((mask & DragSourceActionImage) || (mask & DragSourceActionLink) || (mask & DragSourceActionSelection)); + return page->dragController()->delegateDragSourceAction(view->contentsToWindow(m_mouseDownPos)); } #endif // ENABLE(DRAG_SUPPORT) @@ -1809,7 +1801,7 @@ bool EventHandler::updateDragAndDrop(const PlatformMouseEvent& event, Clipboard* // Moreover, this ordering conforms to section 7.9.4 of the HTML 5 spec. . if (newTarget && canHandleDragAndDropForTarget(UpdateDragAndDrop, newTarget, event, clipboard, &accept)) { // As per section 7.9.4 of the HTML 5 spec., we must always fire a drag event before firing a dragenter, dragleave, or dragover event. - if (dragState().m_dragSrc && dragState().m_dragSrcMayBeDHTML) { + if (dragState().m_dragSrc && dragState().shouldDispatchEvents()) { // for now we don't care if event handler cancels default behavior, since there is none dispatchDragSrcEvent(eventNames().dragEvent, event); } @@ -1827,7 +1819,7 @@ bool EventHandler::updateDragAndDrop(const PlatformMouseEvent& event, Clipboard* } else { if (newTarget && canHandleDragAndDropForTarget(UpdateDragAndDrop, newTarget, event, clipboard, &accept)) { // Note, when dealing with sub-frames, we may need to fire only a dragover event as a drag event may have been fired earlier. - if (!m_shouldOnlyFireDragOverEvent && dragState().m_dragSrc && dragState().m_dragSrcMayBeDHTML) { + if (!m_shouldOnlyFireDragOverEvent && dragState().m_dragSrc && dragState().shouldDispatchEvents()) { // for now we don't care if event handler cancels default behavior, since there is none dispatchDragSrcEvent(eventNames().dragEvent, event); } @@ -1843,7 +1835,7 @@ bool EventHandler::updateDragAndDrop(const PlatformMouseEvent& event, Clipboard* void EventHandler::cancelDragAndDrop(const PlatformMouseEvent& event, Clipboard* clipboard) { if (m_dragTarget && canHandleDragAndDropForTarget(CancelDragAndDrop, m_dragTarget.get(), event, clipboard)) { - if (dragState().m_dragSrc && dragState().m_dragSrcMayBeDHTML) + if (dragState().m_dragSrc && dragState().shouldDispatchEvents()) dispatchDragSrcEvent(eventNames().dragEvent, event); dispatchDragEvent(eventNames().dragleaveEvent, m_dragTarget.get(), event, clipboard); } @@ -2636,12 +2628,22 @@ bool EventHandler::dragHysteresisExceeded(const IntPoint& dragViewportLocation) IntSize delta = dragLocation - m_mouseDownPos; int threshold = GeneralDragHysteresis; - if (dragState().m_dragSrcIsImage) + switch (dragState().m_dragType) { + case DragSourceActionSelection: + threshold = TextDragHysteresis; + break; + case DragSourceActionImage: threshold = ImageDragHysteresis; - else if (dragState().m_dragSrcIsLink) + break; + case DragSourceActionLink: threshold = LinkDragHysteresis; - else if (dragState().m_dragSrcInSelection) - threshold = TextDragHysteresis; + break; + case DragSourceActionDHTML: + break; + case DragSourceActionNone: + case DragSourceActionAny: + ASSERT_NOT_REACHED(); + } return abs(delta.width()) >= threshold || abs(delta.height()) >= threshold; } @@ -2654,7 +2656,7 @@ void EventHandler::freeClipboard() void EventHandler::dragSourceEndedAt(const PlatformMouseEvent& event, DragOperation operation) { - if (dragState().m_dragSrc && dragState().m_dragSrcMayBeDHTML) { + if (dragState().m_dragSrc && dragState().shouldDispatchEvents()) { dragState().m_dragClipboard->setDestinationOperation(operation); // for now we don't care if event handler cancels default behavior, since there is none dispatchDragSrcEvent(eventNames().dragendEvent, event); @@ -2672,6 +2674,11 @@ bool EventHandler::dispatchDragSrcEvent(const AtomicString& eventType, const Pla return !dispatchDragEvent(eventType, dragState().m_dragSrc.get(), event, dragState().m_dragClipboard.get()); } +static bool ExactlyOneBitSet(DragSourceAction n) +{ + return n && !(n & (n - 1)); +} + bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event) { if (event.event().button() != LeftButton || event.event().eventType() != MouseEventMoved) { @@ -2689,50 +2696,62 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event) // Careful that the drag starting logic stays in sync with eventMayStartDrag() if (m_mouseDownMayStartDrag && !dragState().m_dragSrc) { - allowDHTMLDrag(dragState().m_dragSrcMayBeDHTML, dragState().m_dragSrcMayBeUA); - if (!dragState().m_dragSrcMayBeDHTML && !dragState().m_dragSrcMayBeUA) - m_mouseDownMayStartDrag = false; // no element is draggable - } + dragState().m_eventDispatchPolicy = (updateDragSourceActionsAllowed() & DragSourceActionDHTML) ? DragState::DispatchEvents: DragState::DoNotDispatchEvents; - if (m_mouseDownMayStartDrag && !dragState().m_dragSrc) { // try to find an element that wants to be dragged HitTestRequest request(HitTestRequest::ReadOnly); HitTestResult result(m_mouseDownPos); m_frame->contentRenderer()->layer()->hitTest(request, result); Node* node = result.innerNode(); if (node && m_frame->page()) - dragState().m_dragSrc = m_frame->page()->dragController()->draggableNode(m_frame, node, dragState().m_dragSrcMayBeDHTML, dragState().m_dragSrcMayBeUA, - m_mouseDownPos.x(), m_mouseDownPos.y(), dragState().m_dragSrcIsDHTML); + dragState().m_dragSrc = m_frame->page()->dragController()->draggableNode(m_frame, node, m_mouseDownPos, dragState()); else dragState().m_dragSrc = 0; if (!dragState().m_dragSrc) m_mouseDownMayStartDrag = false; // no element is draggable - else { - // remember some facts about this source, while we have a HitTestResult handy - node = result.URLElement(); - dragState().m_dragSrcIsLink = node && node->isLink(); - - node = result.innerNonSharedNode(); - dragState().m_dragSrcIsImage = node && node->renderer() && node->renderer()->isImage(); - - dragState().m_dragSrcInSelection = m_frame->selection()->contains(m_mouseDownPos); - } + else + m_dragMayStartSelectionInstead = (dragState().m_dragType & DragSourceActionSelection); } // For drags starting in the selection, the user must wait between the mousedown and mousedrag, // or else we bail on the dragging stuff and allow selection to occur - if (m_mouseDownMayStartDrag && !dragState().m_dragSrcIsImage && dragState().m_dragSrcInSelection && event.event().timestamp() - m_mouseDownTimestamp < TextDragDelay) { - m_mouseDownMayStartDrag = false; - dragState().m_dragSrc = 0; - // ...but if this was the first click in the window, we don't even want to start selection - if (eventActivatedView(event.event())) - m_mouseDownMayStartSelect = false; + if (m_mouseDownMayStartDrag && m_dragMayStartSelectionInstead && (dragState().m_dragType & DragSourceActionSelection) && event.event().timestamp() - m_mouseDownTimestamp < TextDragDelay) { + ASSERT(event.event().eventType() == MouseEventMoved); + if ((dragState().m_dragType & DragSourceActionImage)) { + // ... unless the mouse is over an image, then we start dragging just the image + dragState().m_dragType = DragSourceActionImage; + } else if (!(dragState().m_dragType & (DragSourceActionDHTML | DragSourceActionLink))) { + // ... but only bail if we're not over an unselectable element. + m_mouseDownMayStartDrag = false; + dragState().m_dragSrc = 0; + // ... but if this was the first click in the window, we don't even want to start selection + if (eventActivatedView(event.event())) + m_mouseDownMayStartSelect = false; + } else { + // Prevent the following case from occuring: + // 1. User starts a drag immediately after mouse down over an unselectable element. + // 2. We enter this block and decided that since we're over an unselectable element, + // don't cancel the drag. + // 3. The drag gets resolved as a potential selection drag below /but/ we haven't + // exceeded the drag hysteresis yet. + // 4. We enter this block again, and since it's now marked as a selection drag, we + // cancel the drag. + m_dragMayStartSelectionInstead = false; + } } if (!m_mouseDownMayStartDrag) return !mouseDownMayStartSelect() && !m_mouseDownMayStartAutoscroll; + if (!ExactlyOneBitSet(dragState().m_dragType)) { + ASSERT((dragState().m_dragType & DragSourceActionSelection)); + ASSERT((dragState().m_dragType & ~DragSourceActionSelection) == DragSourceActionDHTML + || (dragState().m_dragType & ~DragSourceActionSelection) == DragSourceActionImage + || (dragState().m_dragType & ~DragSourceActionSelection) == DragSourceActionLink); + dragState().m_dragType = DragSourceActionSelection; + } + // We are starting a text/image/url drag, so the cursor should be an arrow if (FrameView* view = m_frame->view()) { // FIXME : Custom cursors aren't supported during drag and drop (default to pointer). @@ -2751,10 +2770,10 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event) // to make sure it gets numbified dragState().m_dragClipboard = createDraggingClipboard(); - if (dragState().m_dragSrcMayBeDHTML) { + if (dragState().shouldDispatchEvents()) { // Check to see if the is a DOM based drag, if it is get the DOM specified drag // image and offset - if (dragState().m_dragSrcIsDHTML) { + if (dragState().m_dragType == DragSourceActionDHTML) { if (RenderObject* renderer = dragState().m_dragSrc->renderer()) { // FIXME: This doesn't work correctly with transforms. FloatPoint absPos = renderer->localToAbsolute(); @@ -2790,8 +2809,8 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event) if (m_mouseDownMayStartDrag) { Page* page = m_frame->page(); DragController* dragController = page ? page->dragController() : 0; - bool startedDrag = dragController && dragController->startDrag(m_frame, dragState().m_dragClipboard.get(), srcOp, event.event(), m_mouseDownPos, dragState().m_dragSrcIsDHTML); - if (!startedDrag && dragState().m_dragSrcMayBeDHTML) { + bool startedDrag = dragController && dragController->startDrag(m_frame, dragState(), srcOp, event.event(), m_mouseDownPos); + if (!startedDrag && dragState().shouldDispatchEvents()) { // Drag was canned at the last minute - we owe m_dragSrc a DRAGEND event dispatchDragSrcEvent(eventNames().dragendEvent, event.event()); m_mouseDownMayStartDrag = false; diff --git a/Source/WebCore/page/EventHandler.h b/Source/WebCore/page/EventHandler.h index 5369438bd4701390e71481b9949da2f7c3079444..26e8874aa6c618206abd41f4c9b024320f10bb36 100644 --- a/Source/WebCore/page/EventHandler.h +++ b/Source/WebCore/page/EventHandler.h @@ -27,6 +27,7 @@ #define EventHandler_h #include "DragActions.h" +#include "DragState.h" #include "FocusDirection.h" #include "HitTestRequest.h" #include "PlatformMouseEvent.h" @@ -49,7 +50,6 @@ namespace WebCore { class Clipboard; class Cursor; -struct DragState; class Event; class EventTarget; class FloatPoint; @@ -323,7 +323,7 @@ private: void defaultArrowEventHandler(FocusDirection, KeyboardEvent*); #if ENABLE(DRAG_SUPPORT) - void allowDHTMLDrag(bool& flagDHTML, bool& flagUA) const; + DragSourceAction updateDragSourceActionsAllowed() const; #endif // The following are called at the beginning of handleMouseUp and handleDrag. @@ -360,6 +360,7 @@ private: bool m_mouseDownMayStartSelect; #if ENABLE(DRAG_SUPPORT) bool m_mouseDownMayStartDrag; + bool m_dragMayStartSelectionInstead; #endif bool m_mouseDownWasSingleClickInSelection; bool m_beganSelectingText;