Bug 1569512 - Make `PresShell` ignore synthesized `mousemove` events coming from another process if the child process stores mouse location of synthesized mouse events for tests r=smaug

The reason of intermittent failure of `test_bug656379-2.html` is, synthesized
`mousemove` event coming from the parent process causes `mouseout` and
`mouseleave` events of the last synthesized `mousemove` in the test.  The
reason is, synthesized `mousemove` for tests makes `PresShell` in the content
process record the cursor location, but won't make it `PresShell` in the
parent process do it.  Therefore, parent process may synthesize `mousemove`
event for the system cursor position which does not match with the synthesized
mouse location in the content process.  Therefore, `:hover` state may be
updated unexpectedly.

This patch makes `WidgetEvent::mFlags` have a flag to indicate whether it
came from another process.  Then, makes `PresShell::HandleEvent()` ignore
synthesized `mousemove` events coming from another process only when the
recorded mouse location was set by a mouse event synthesized for tests.

Differential Revision: https://phabricator.services.mozilla.com/D65282

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2020-03-05 21:34:28 +00:00
Родитель 7bd48e79b8
Коммит c90d6c80b3
7 изменённых файлов: 99 добавлений и 10 удалений

Просмотреть файл

@ -28,9 +28,38 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=656379
<pre id="test">
<script type="application/javascript">
function log(aEvent) {
function getPath() {
if (!aEvent.target) {
return "(null)";
}
function getNodeName(aNode) {
if (aNode.id) {
return `${aNode.nodeName}#${aNode.id}`;
}
return aNode.nodeName;
}
let path = getNodeName(aEvent.target);
for (let parent = aEvent.target.parentElement;
parent && document.body != parent;
parent = parent.parentElement) {
path = `${getNodeName(parent)} > ${path}`;
}
return path;
}
info(`${aEvent.type} on ${getPath()}`);
}
window.addEventListener("mousemove", log, {capture: true});
window.addEventListener("mouseenter", log, {capture: true});
window.addEventListener("mouseleave", log, {capture: true});
window.addEventListener("mouseover", log, {capture: true});
window.addEventListener("mouseout", log, {capture: true});
/** Test for Bug 656379 **/
SimpleTest.waitForExplicitFinish();
function* tests() {
info("Synthesizing mousemove on label1...");
synthesizeMouseAtCenter($("label1"), { type: "mousemove" });
yield undefined;
is($("button1").matches(":hover"), true,
@ -41,6 +70,7 @@ function* tests() {
"Button 2 should not be hovered after mousemove over label1");
is($("label2").matches(":hover"), false,
"Label 2 should not be hovered after mousemove over label1");
info("Synthesizing mousemove on button2...");
synthesizeMouseAtCenter($("button2"), { type: "mousemove" });
yield undefined;
is($("button1").matches(":hover"), false,
@ -51,6 +81,7 @@ function* tests() {
"Button 2 should be hovered after mousemove over button2");
is($("label2").matches(":hover"), false,
"Label 2 should not be hovered after mousemove over label2");
info("Synthesizing mousemove on label2...");
synthesizeMouseAtCenter($("label2"), { type: "mousemove" });
yield undefined;
is($("button1").matches(":hover"), false,

Просмотреть файл

@ -842,7 +842,8 @@ PresShell::PresShell(Document* aDocument)
mForceUseLegacyKeyCodeAndCharCodeValues(false),
mInitializedWithKeyPressEventDispatchingBlacklist(false),
mForceUseLegacyNonPrimaryDispatch(false),
mInitializedWithClickEventDispatchingBlacklist(false) {
mInitializedWithClickEventDispatchingBlacklist(false),
mMouseLocationWasSetBySynthesizedMouseEventForTests(false) {
MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this));
MOZ_ASSERT(aDocument);
@ -5072,8 +5073,7 @@ nscolor PresShell::GetDefaultBackgroundColorToDraw() {
// content JS.
Document* doc = GetDocument();
BrowsingContext* bc = doc->GetBrowsingContext();
if (bc && bc->IsTop() && !bc->HasOpener() &&
doc->GetDocumentURI() &&
if (bc && bc->IsTop() && !bc->HasOpener() && doc->GetDocumentURI() &&
NS_IsAboutBlank(doc->GetDocumentURI()) &&
doc->PrefersColorScheme() == StylePrefersColorScheme::Dark) {
// Use --in-content-page-background for prefers-color-scheme: dark.
@ -6289,6 +6289,18 @@ void PresShell::DisableNonTestMouseEvents(bool aDisable) {
sDisableNonTestMouseEvents = aDisable;
}
bool PresShell::MouseLocationWasSetBySynthesizedMouseEventForTests() const {
if (!mPresContext) {
return false;
}
if (mPresContext->IsRoot()) {
return mMouseLocationWasSetBySynthesizedMouseEventForTests;
}
PresShell* rootPresShell = GetRootPresShell();
return rootPresShell &&
rootPresShell->mMouseLocationWasSetBySynthesizedMouseEventForTests;
}
void PresShell::RecordMouseLocation(WidgetGUIEvent* aEvent) {
if (!mPresContext) return;
@ -6315,6 +6327,8 @@ void PresShell::RecordMouseLocation(WidgetGUIEvent* aEvent) {
nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, rootFrame);
mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
}
mMouseLocationWasSetBySynthesizedMouseEventForTests =
aEvent->mFlags.mIsSynthesizedForTests;
#ifdef DEBUG_MOUSE_LOCATION
if (aEvent->mMessage == eMouseEnterIntoWidget) {
printf("[ps=%p]got mouse enter for %p\n", this, aEvent->mWidget);
@ -6333,6 +6347,8 @@ void PresShell::RecordMouseLocation(WidgetGUIEvent* aEvent) {
// the mouse exit when the mouse moves from one of our widgets into another.
mMouseLocation = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
mMouseLocationWasSetBySynthesizedMouseEventForTests =
aEvent->mFlags.mIsSynthesizedForTests;
#ifdef DEBUG_MOUSE_LOCATION
printf("[ps=%p]got mouse exit for %p\n", this, aEvent->mWidget);
printf("[ps=%p]clearing mouse location\n", this);
@ -6439,6 +6455,17 @@ nsresult PresShell::HandleEvent(nsIFrame* aFrameForPresShell,
bool aDontRetargetEvents,
nsEventStatus* aEventStatus) {
MOZ_ASSERT(aGUIEvent);
// If it's synthesized in the parent process and our mouse location was set
// by a mouse event which was synthesized for tests because the test does not
// want to change `:hover` state with the synthesized mouse event for native
// mouse cursor position.
if (aGUIEvent->mMessage == eMouseMove &&
aGUIEvent->CameFromAnotherProcess() && XRE_IsContentProcess() &&
!aGUIEvent->mFlags.mIsSynthesizedForTests &&
MouseLocationWasSetBySynthesizedMouseEventForTests() &&
aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) {
return NS_OK;
}
EventHandler eventHandler(*this);
return eventHandler.HandleEvent(aFrameForPresShell, aGUIEvent,
aDontRetargetEvents, aEventStatus);
@ -10633,7 +10660,7 @@ nsresult PresShell::UpdateImageLockingState() {
return rv;
}
PresShell* PresShell::GetRootPresShell() {
PresShell* PresShell::GetRootPresShell() const {
if (mPresContext) {
nsPresContext* rootPresContext = mPresContext->GetRootPresContext();
if (rootPresContext) {

Просмотреть файл

@ -1939,6 +1939,7 @@ class PresShell final : public nsStubDocumentObserver,
// Check if aEvent is a mouse event and record the mouse location for later
// synth mouse moves.
void RecordMouseLocation(WidgetGUIEvent* aEvent);
inline bool MouseLocationWasSetBySynthesizedMouseEventForTests() const;
class nsSynthMouseMoveEvent final : public nsARefreshObserver {
public:
nsSynthMouseMoveEvent(PresShell* aPresShell, bool aFromScroll)
@ -2758,7 +2759,7 @@ class PresShell final : public nsStubDocumentObserver,
static StaticRefPtr<dom::Element> sLastKeyDownEventTargetElement;
};
PresShell* GetRootPresShell();
PresShell* GetRootPresShell() const;
nscolor GetDefaultBackgroundColorToDraw();
@ -3160,6 +3161,10 @@ class PresShell final : public nsStubDocumentObserver,
// Whether mForceUseLegacyNonPrimaryDispatch is initialised.
bool mInitializedWithClickEventDispatchingBlacklist : 1;
// Set to true if mMouseLocation is set by a mouse event which is synthesized
// for tests.
bool mMouseLocationWasSetBySynthesizedMouseEventForTests : 1;
struct CapturingContentInfo final {
CapturingContentInfo()
: mAllowed(false),

Просмотреть файл

@ -793,7 +793,7 @@ void nsPresContext::UpdateCharSet(NotNull<const Encoding*> aCharSet) {
}
}
nsPresContext* nsPresContext::GetParentPresContext() {
nsPresContext* nsPresContext::GetParentPresContext() const {
mozilla::PresShell* presShell = GetPresShell();
if (presShell) {
nsViewManager* viewManager = presShell->GetViewManager();
@ -848,8 +848,8 @@ nsIWidget* nsPresContext::GetRootWidget() const {
// We may want to replace this with something faster, maybe caching the root
// prescontext
nsRootPresContext* nsPresContext::GetRootPresContext() {
nsPresContext* pc = this;
nsRootPresContext* nsPresContext::GetRootPresContext() const {
nsPresContext* pc = const_cast<nsPresContext*>(this);
for (;;) {
nsPresContext* parent = pc->GetParentPresContext();
if (!parent) break;

Просмотреть файл

@ -181,7 +181,7 @@ class nsPresContext : public nsISupports,
* Returns the parent prescontext for this one. Returns null if this is a
* root.
*/
nsPresContext* GetParentPresContext();
nsPresContext* GetParentPresContext() const;
/**
* Returns the prescontext of the toplevel content document that contains
@ -218,7 +218,7 @@ class nsPresContext : public nsISupports,
* hierarchy that contains this presentation context, or nullptr if it can't
* be found (e.g. it's detached).
*/
nsRootPresContext* GetRootPresContext();
nsRootPresContext* GetRootPresContext() const;
virtual bool IsRoot() { return false; }

Просмотреть файл

@ -176,6 +176,8 @@ struct BaseEventFlags {
// remote process (but it's not handled yet if it's not a duplicated event
// instance).
bool mPostedToRemoteProcess : 1;
// If mCameFromAnotherProcess is true, the event came from another process.
bool mCameFromAnotherProcess : 1;
// At lease one of the event in the event path had non privileged click
// listener.
@ -337,6 +339,16 @@ struct BaseEventFlags {
inline bool HasBeenPostedToRemoteProcess() const {
return mPostedToRemoteProcess;
}
/**
* Return true if the event came from another process.
*/
inline bool CameFromAnotherProcess() const { return mCameFromAnotherProcess; }
/**
* Mark the event as coming from another process.
*/
inline void MarkAsComingFromAnotherProcess() {
mCameFromAnotherProcess = true;
}
/**
* Mark the event is reserved by chrome. I.e., shouldn't be dispatched to
* content because it shouldn't be cancelable.
@ -700,6 +712,18 @@ class WidgetEvent : public WidgetEventTime {
inline bool HasBeenPostedToRemoteProcess() const {
return mFlags.HasBeenPostedToRemoteProcess();
}
/**
* Return true if the event came from another process.
*/
inline bool CameFromAnotherProcess() const {
return mFlags.CameFromAnotherProcess();
}
/**
* Mark the event as coming from another process.
*/
inline void MarkAsComingFromAnotherProcess() {
mFlags.MarkAsComingFromAnotherProcess();
}
/**
* Mark the event is reserved by chrome. I.e., shouldn't be dispatched to
* content because it shouldn't be cancelable.

Просмотреть файл

@ -74,6 +74,8 @@ struct ParamTraits<mozilla::WidgetEvent> {
// Reset cross process dispatching state here because the event has not
// been dispatched to different process from current process.
aResult->ResetCrossProcessDispatchingState();
// Mark the event comes from another process.
aResult->MarkAsComingFromAnotherProcess();
}
return ret;
}