diff --git a/devtools/client/inspector/test/shared-head.js b/devtools/client/inspector/test/shared-head.js index ede6f9b9376e..7b15b7a175df 100644 --- a/devtools/client/inspector/test/shared-head.js +++ b/devtools/client/inspector/test/shared-head.js @@ -383,8 +383,22 @@ var focusEditableField = async function(ruleView, editable, xOffset = 1, yOffset = 1, options = {}) { const onFocus = once(editable.parentNode, "focus", true); info("Clicking on editable field to turn to edit mode"); - EventUtils.synthesizeMouse(editable, xOffset, yOffset, options, - editable.ownerDocument.defaultView); + if (options.type === undefined) { + // "mousedown" and "mouseup" flushes any pending layout. Therefore, + // if the caller wants to click an element, e.g., closebrace to add new + // property, we need to guarantee that the element is clicked here even + // if it's moved by flushing the layout because whether the UI is useful + // or not when there is pending reflow is not scope of the tests. + options.type = "mousedown"; + EventUtils.synthesizeMouse(editable, xOffset, yOffset, options, + editable.ownerGlobal); + options.type = "mouseup"; + EventUtils.synthesizeMouse(editable, xOffset, yOffset, options, + editable.ownerGlobal); + } else { + EventUtils.synthesizeMouse(editable, xOffset, yOffset, options, + editable.ownerGlobal); + } await onFocus; info("Editable field gained focus, returning the input field now"); diff --git a/dom/events/test/mochitest.ini b/dom/events/test/mochitest.ini index 0e17bcc9b8bf..0a145e7ec596 100644 --- a/dom/events/test/mochitest.ini +++ b/dom/events/test/mochitest.ini @@ -153,6 +153,7 @@ subsuite = clipboard [test_bug1514940.html] skip-if = !debug [test_click_on_reframed_generated_text.html] +[test_click_on_restyled_element.html] [test_clickevent_on_input.html] skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM [test_continuous_wheel_events.html] diff --git a/dom/events/test/test_click_on_restyled_element.html b/dom/events/test/test_click_on_restyled_element.html new file mode 100644 index 000000000000..3ecd619ab70d --- /dev/null +++ b/dom/events/test/test_click_on_restyled_element.html @@ -0,0 +1,51 @@ + + + + + Test for clicking on an element which is restyled/reframed by mousedown event + + + + + + +
link
+
span
+
link
+
span
+ + + diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index 59fff385aeb2..6993948a5898 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -6506,6 +6506,16 @@ nsresult PresShell::EventHandler::HandleEvent(nsIFrame* aFrame, } if (aGUIEvent->IsUsingCoordinates()) { + // Flush pending notifications to handle the event with the latest layout. + // But if it causes destroying the frame for mPresShell, stop handling the + // event. (why?) + AutoWeakFrame weakFrame(aFrame); + MaybeFlushPendingNotifications(aGUIEvent); + if (!weakFrame.IsAlive()) { + *aEventStatus = nsEventStatus_eIgnore; + return NS_OK; + } + // XXX Retrieving capturing content here. However, some of the following // methods allow to run script. So, isn't it possible the capturing // content outdated? @@ -6601,18 +6611,11 @@ nsresult PresShell::EventHandler::HandleEvent(nsIFrame* aFrame, frameToHandleEvent = TouchManager::SetupTarget( aGUIEvent->AsTouchEvent(), rootFrameToHandleEvent); } else { - uint32_t flags = 0; - nsPoint eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo( - aGUIEvent, rootFrameToHandleEvent); - - if (mouseEvent && mouseEvent->mClass == eMouseEventClass && - mouseEvent->mIgnoreRootScrollFrame) { - flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME; - } - nsIFrame* target = FindFrameTargetedByInputEvent( - aGUIEvent, rootFrameToHandleEvent, eventPoint, flags); - if (target) { - frameToHandleEvent = target; + frameToHandleEvent = + GetFrameToHandleNonTouchEvent(rootFrameToHandleEvent, aGUIEvent); + if (!frameToHandleEvent) { + *aEventStatus = nsEventStatus_eIgnore; + return NS_OK; } } } @@ -6941,6 +6944,81 @@ nsresult PresShell::EventHandler::HandleEvent(nsIFrame* aFrame, return rv; } +bool PresShell::EventHandler::MaybeFlushPendingNotifications( + WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aGUIEvent); + + switch (aGUIEvent->mMessage) { + case eMouseDown: + case eMouseUp: { + RefPtr presContext = mPresShell->GetPresContext(); + if (NS_WARN_IF(!presContext)) { + return false; + } + uint64_t framesConstructedCount = presContext->FramesConstructedCount(); + uint64_t framesReflowedCount = presContext->FramesReflowedCount(); + + mPresShell->FlushPendingNotifications(FlushType::Layout); + return framesConstructedCount != presContext->FramesConstructedCount() || + framesReflowedCount != presContext->FramesReflowedCount(); + } + default: + return false; + } +} + +nsIFrame* PresShell::EventHandler::GetFrameToHandleNonTouchEvent( + nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aGUIEvent->mClass != eTouchEventClass); + + nsPoint eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo( + aGUIEvent, aRootFrameToHandleEvent); + + uint32_t flags = 0; + if (aGUIEvent->mClass == eMouseEventClass) { + WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent(); + if (mouseEvent && mouseEvent->mIgnoreRootScrollFrame) { + flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME; + } + } + + nsIFrame* targetFrame = FindFrameTargetedByInputEvent( + aGUIEvent, aRootFrameToHandleEvent, eventPoint, flags); + if (!targetFrame) { + return aRootFrameToHandleEvent; + } + + if (targetFrame->PresShell() == mPresShell) { + // If found target is in mPresShell, we've already found it in the latest + // layout so that we can use it. + return targetFrame; + } + + // If target is in a child document, we've not flushed its layout yet. + PresShell* childPresShell = static_cast(targetFrame->PresShell()); + EventHandler childEventHandler(*childPresShell); + AutoWeakFrame weakFrame(aRootFrameToHandleEvent); + bool layoutChanged = + childEventHandler.MaybeFlushPendingNotifications(aGUIEvent); + if (!weakFrame.IsAlive()) { + // Stop handling the event if the root frame to handle event is destroyed + // by the reflow. (but why?) + return nullptr; + } + if (!layoutChanged) { + // If the layout in the child PresShell hasn't been changed, we don't + // need to recompute the target. + return targetFrame; + } + + // Finally, we need to recompute the target with the latest layout. + targetFrame = FindFrameTargetedByInputEvent( + aGUIEvent, aRootFrameToHandleEvent, eventPoint, flags); + + return targetFrame ? targetFrame : aRootFrameToHandleEvent; +} + bool PresShell::EventHandler::MaybeHandleEventWithAccessibleCaret( WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) { MOZ_ASSERT(aGUIEvent); diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h index 1c912a819ef2..bc00b2edf227 100644 --- a/layout/base/PresShell.h +++ b/layout/base/PresShell.h @@ -581,6 +581,32 @@ class PresShell final : public nsIPresShell, static already_AddRefed GetDocumentURIToCompareWithBlacklist( PresShell& aPresShell); + /** + * MaybeFlushPendingNotifications() maybe flush pending notifications if + * aGUIEvent should be handled with the latest layout. + * + * @param aGUIEvent The handling event. + * @return true if this actually flushes pending + * layout and that has caused changing the + * layout. + */ + MOZ_CAN_RUN_SCRIPT + bool MaybeFlushPendingNotifications(WidgetGUIEvent* aGUIEvent); + + /** + * GetFrameToHandleNonTouchEvent() returns a frame to handle the event. + * This may flush pending layout if the target is in child PresShell. + * + * @param aRootFrameToHandleEvent The root frame to handle the event. + * @param aGUIEvent The handling event. + * @return The frame which should handle the + * event. nullptr if the caller should + * stop handling the event. + */ + MOZ_CAN_RUN_SCRIPT + nsIFrame* GetFrameToHandleNonTouchEvent(nsIFrame* aRootFrameToHandleEvent, + WidgetGUIEvent* aGUIEvent); + /** * MaybeDiscardEvent() checks whether it's safe to handle aGUIEvent right * now. If it's not safe, this may notify somebody of discarding event if diff --git a/view/nsViewManager.cpp b/view/nsViewManager.cpp index df7d23b15fa9..ccacb1a7f11b 100644 --- a/view/nsViewManager.cpp +++ b/view/nsViewManager.cpp @@ -752,14 +752,6 @@ void nsViewManager::DispatchEvent(WidgetGUIEvent* aEvent, nsView* aView, // want to cause its destruction in, say, some JavaScript event handler. nsCOMPtr shell = view->GetViewManager()->GetPresShell(); if (shell) { - if (aEvent->mMessage == eMouseDown || aEvent->mMessage == eMouseUp) { - AutoWeakFrame weakFrame(frame); - shell->FlushPendingNotifications(FlushType::Layout); - if (!weakFrame.IsAlive()) { - *aStatus = nsEventStatus_eIgnore; - return; - } - } shell->HandleEvent(frame, aEvent, false, aStatus); return; }