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;
}