Skip to content

Commit

Permalink
Bug 1528289 - part 2: Dispatch same events on the web contents when a…
Browse files Browse the repository at this point in the history
…utoscroll is canceled with a click r=Gijs,edgar

Chrome behaves like this:

1. When user starts autoscroll with a middle click, `mousedown` and `mouseup`
   are fired, but `auxclick` nor `paste` event is not fired.
2. When user ends autoscroll with a left click, only `mouseup` event is fired.
   I.e, `mousedown` nor `click` event is not fired.
3. When user ends autoscroll with a middle click, only `mouseup` event is fired.
   I.e., `mousedown`, `auxclick` nor `paste` events is not fired.
4. When user ends autoscroll with a right click, `mouseup` and `contextmenu`
   events are fired, but `mousedown` and `auxclick` events are not fired.

This patch emulates these Chrome's behavior as far as possible.  However,
unfortunately, we cannot do exactly same behavior without some big patches
because each widget (`nsWindow` or `nsChildView`) discards a mouse event
which rolled up a widget before dispatching it into the DOM.  Therefore,
for now, this patch does not fix the following issues:

1. `mousedown` event is not fired in content when clicking outside the
   autoscroller to close it except when pressing the secondary button or on any
   buttons on Linux.
2. `mouseup` event is not fired in content when clicking outside the
   autoscroller to close it except when pressing the primary button macOS.
3. `click` event and `auxclick` events are fired when clicking outside the
   autoscroller with the secondary button.

So, the middle button `click`/`auxclick` events and `paste` event which is
reported to the bug won't be fired with this patch.  I'll file follow up bugs.

Differential Revision: https://phabricator.services.mozilla.com/D104652
  • Loading branch information
masayuki-nakano committed Mar 1, 2021
1 parent 2461631 commit 77d74d9
Show file tree
Hide file tree
Showing 10 changed files with 563 additions and 38 deletions.
55 changes: 46 additions & 9 deletions dom/events/EventStateManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3232,6 +3232,30 @@ nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
}
}

// If MouseEvent::PreventClickEvent() was called by chrome script,
// we need to forget the clicking content and click count for the
// following eMouseUp event.
if (mouseEvent->mClickEventPrevented) {
RefPtr<EventStateManager> esm =
ESMFromContentOrThis(aOverrideClickTarget);
switch (mouseEvent->mButton) {
case MouseButton::ePrimary:
esm->mLastLeftMouseDownContent = nullptr;
esm->mLClickCount = 0;
break;
case MouseButton::eSecondary:
esm->mLastMiddleMouseDownContent = nullptr;
esm->mMClickCount = 0;
break;
case MouseButton::eMiddle:
esm->mLastRightMouseDownContent = nullptr;
esm->mRClickCount = 0;
break;
default:
break;
}
}

nsCOMPtr<nsIContent> activeContent;
// When content calls PreventDefault on pointerdown, we also call
// PreventDefault on the subsequent mouse events to suppress default
Expand Down Expand Up @@ -4917,11 +4941,14 @@ nsresult EventStateManager::SetClickCount(WidgetMouseEvent* aEvent,
switch (aEvent->mButton) {
case MouseButton::ePrimary:
if (aEvent->mMessage == eMouseDown) {
mLastLeftMouseDownContent = mouseContent;
mLastLeftMouseDownContent =
!aEvent->mClickEventPrevented ? mouseContent : nullptr;
} else if (aEvent->mMessage == eMouseUp) {
aEvent->mClickTarget =
nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastLeftMouseDownContent);
!aEvent->mClickEventPrevented
? nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastLeftMouseDownContent)
: nullptr;
if (aEvent->mClickTarget) {
aEvent->mClickCount = mLClickCount;
mLClickCount = 0;
Expand All @@ -4934,11 +4961,14 @@ nsresult EventStateManager::SetClickCount(WidgetMouseEvent* aEvent,

case MouseButton::eMiddle:
if (aEvent->mMessage == eMouseDown) {
mLastMiddleMouseDownContent = mouseContent;
mLastMiddleMouseDownContent =
!aEvent->mClickEventPrevented ? mouseContent : nullptr;
} else if (aEvent->mMessage == eMouseUp) {
aEvent->mClickTarget =
nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastMiddleMouseDownContent);
!aEvent->mClickEventPrevented
? nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastMiddleMouseDownContent)
: nullptr;
if (aEvent->mClickTarget) {
aEvent->mClickCount = mMClickCount;
mMClickCount = 0;
Expand All @@ -4951,11 +4981,14 @@ nsresult EventStateManager::SetClickCount(WidgetMouseEvent* aEvent,

case MouseButton::eSecondary:
if (aEvent->mMessage == eMouseDown) {
mLastRightMouseDownContent = mouseContent;
mLastRightMouseDownContent =
!aEvent->mClickEventPrevented ? mouseContent : nullptr;
} else if (aEvent->mMessage == eMouseUp) {
aEvent->mClickTarget =
nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastRightMouseDownContent);
!aEvent->mClickEventPrevented
? nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastRightMouseDownContent)
: nullptr;
if (aEvent->mClickTarget) {
aEvent->mClickCount = mRClickCount;
mRClickCount = 0;
Expand Down Expand Up @@ -4986,6 +5019,10 @@ bool EventStateManager::EventCausesClickEvents(
if (!aMouseEvent.mClickCount || !aMouseEvent.mClickTarget) {
return false;
}
// If click event was explicitly prevented, we shouldn't dispatch it.
if (aMouseEvent.mClickEventPrevented) {
return false;
}
// Check that the window isn't disabled before firing a click
// (see bug 366544).
return !(aMouseEvent.mWidget && !aMouseEvent.mWidget->IsEnabled());
Expand Down
6 changes: 6 additions & 0 deletions dom/events/MouseEvent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,12 @@ void MouseEvent::InitNSMouseEvent(const nsAString& aType, bool aCanBubble,
mouseEventBase->mInputSource = aInputSource;
}

void MouseEvent::PreventClickEvent() {
if (WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent()) {
mouseEvent->mClickEventPrevented = true;
}
}

int16_t MouseEvent::Button() {
switch (mEvent->mClass) {
case eMouseEventClass:
Expand Down
1 change: 1 addition & 0 deletions dom/events/MouseEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class MouseEvent : public UIEvent {
bool aAltKey, bool aShiftKey, bool aMetaKey,
uint16_t aButton, EventTarget* aRelatedTarget,
float aPressure, uint16_t aInputSource);
void PreventClickEvent();

protected:
~MouseEvent() = default;
Expand Down
6 changes: 6 additions & 0 deletions dom/webidl/MouseEvent.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,11 @@ partial interface MouseEvent
optional float pressure = 0,
optional unsigned short inputSourceArg = 0);

/**
* preventClickEvent() prevents the following "click", "auxclick" and
* "dblclick" events of "mousedown" and "mouseup" events.
*/
[ChromeOnly]
void preventClickEvent();
};

57 changes: 38 additions & 19 deletions toolkit/actors/AutoScrollChild.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ class AutoScrollChild extends JSWindowActorChild {
}

Services.els.addSystemEventListener(this.document, "mousemove", this, true);
Services.els.addSystemEventListener(this.document, "mouseup", this, true);
this.document.addEventListener("pagehide", this, true);

this._ignoreMouseEvents = true;
Expand Down Expand Up @@ -253,6 +254,12 @@ class AutoScrollChild extends JSWindowActorChild {
this,
true
);
Services.els.removeSystemEventListener(
this.document,
"mouseup",
this,
true
);
this.document.removeEventListener("pagehide", this, true);
if (this._autoscrollHandledByApz) {
Services.obs.removeObserver(
Expand Down Expand Up @@ -324,26 +331,38 @@ class AutoScrollChild extends JSWindowActorChild {
}

handleEvent(event) {
if (event.type == "mousemove") {
this._screenX = event.screenX;
this._screenY = event.screenY;
} else if (event.type == "mousedown") {
if (
event.isTrusted & !event.defaultPrevented &&
event.button == 1 &&
!this._scrollable &&
!this.isAutoscrollBlocker(event.originalTarget)
) {
this.startScroll(event);
}
} else if (event.type == "pagehide") {
if (this._scrollable) {
var doc = this._scrollable.ownerDocument || this._scrollable.document;
if (doc == event.target) {
this.sendAsyncMessage("Autoscroll:Cancel");
this.stopScroll();
switch (event.type) {
case "mousemove":
this._screenX = event.screenX;
this._screenY = event.screenY;
break;
case "mousedown":
if (
event.isTrusted &&
!event.defaultPrevented &&
event.button === 1 &&
!this._scrollable &&
!this.isAutoscrollBlocker(event.originalTarget)
) {
this.startScroll(event);
}
}
// fallthrough
case "mouseup":
if (this._ignoreMouseEvents) {
// Middle mouse click event shouldn't be fired in web content for
// compatibility with Chrome.
event.preventClickEvent();
}
break;
case "pagehide":
if (this._scrollable) {
var doc = this._scrollable.ownerDocument || this._scrollable.document;
if (doc == event.target) {
this.sendAsyncMessage("Autoscroll:Cancel");
this.stopScroll();
}
}
break;
}
}

Expand Down
2 changes: 2 additions & 0 deletions toolkit/content/tests/browser/browser.ini
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ skip-if = true # bug 1399845 tracks re-enabling this test.
[browser_bug594509.js]
[browser_bug982298.js]
[browser_charsetMenu_swapBrowsers.js]
[browser_click_event_during_autoscrolling.js]
skip-if = !e10s
[browser_content_url_annotation.js]
skip-if = !e10s || !crashreporter
[browser_contentTitle.js]
Expand Down
Loading

0 comments on commit 77d74d9

Please sign in to comment.