tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit abb3d47ff5271af16eb04978fdd40fd54f2bfd5a
parent 0437f65d7894b30f06df1857d8319a0dda546925
Author: Edgar Chen <echen@mozilla.com>
Date:   Wed,  8 Oct 2025 03:02:10 +0000

Bug 1989416 - Prevent mCallbackId from being copied to generated mouse events; r=smaug

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

Diffstat:
Mdom/ipc/BrowserChild.cpp | 8++++++++
Mdom/ipc/CoalescedMouseData.cpp | 2++
Mtesting/mochitest/tests/browser/browser.toml | 3++-
Mtesting/mochitest/tests/browser/browser_EventUtils.js | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Atesting/mochitest/tests/browser/empty.html | 6++++++
Mwidget/MouseEvents.h | 13+++++++++++++
6 files changed, 146 insertions(+), 26 deletions(-)

diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp @@ -1612,6 +1612,10 @@ mozilla::ipc::IPCResult BrowserChild::RecvRealMouseMoveEvent( // event listener. Therefore, the cloned event in the queue shouldn't // cause eMouseRawUpdate later when it'll be dispatched. WidgetMouseEvent pendingMouseMoveEvent(aEvent); + // We don't want to dispatch aEvent immediately, so the cloned event + // should track the cllback id. And the callback id of cloned event will + // be moved again if there is no coalesced data yet when coalescing. + pendingMouseMoveEvent.mCallbackId = std::move(aEvent.mCallbackId); pendingMouseMoveEvent.convertToPointerRawUpdate = false; data->Coalesce(pendingMouseMoveEvent, aGuid, aInputBlockId); mCoalescedMouseEventFlusher->StartObserver(); @@ -1638,6 +1642,10 @@ mozilla::ipc::IPCResult BrowserChild::RecvRealMouseMoveEvent( // event listener. Therefore, the cloned event in the queue shouldn't // cause eMouseRawUpdate later when it'll be dispatched. WidgetMouseEvent pendingMouseMoveEvent(aEvent); + // The cloned event should track the cllback id. And the callback id of + // cloned event will be moved again if there is no coalesced data yet when + // coalescing. + pendingMouseMoveEvent.mCallbackId = std::move(aEvent.mCallbackId); pendingMouseMoveEvent.convertToPointerRawUpdate = false; newData->Coalesce(pendingMouseMoveEvent, aGuid, aInputBlockId); diff --git a/dom/ipc/CoalescedMouseData.cpp b/dom/ipc/CoalescedMouseData.cpp @@ -19,10 +19,12 @@ void CoalescedMouseData::Coalesce(const WidgetMouseEvent& aEvent, const uint64_t& aInputBlockId) { if (IsEmpty()) { mCoalescedInputEvent = MakeUnique<WidgetMouseEvent>(aEvent); + mCoalescedInputEvent->mCallbackId = std::move(aEvent.mCallbackId); mGuid = aGuid; mInputBlockId = aInputBlockId; MOZ_ASSERT(!mCoalescedInputEvent->mCoalescedWidgetEvents); } else { + MOZ_ASSERT(aEvent.mCallbackId.isNothing()); MOZ_ASSERT(mGuid == aGuid); MOZ_ASSERT(mInputBlockId == aInputBlockId); MOZ_ASSERT(mCoalescedInputEvent->mModifiers == aEvent.mModifiers); diff --git a/testing/mochitest/tests/browser/browser.toml b/testing/mochitest/tests/browser/browser.toml @@ -1,7 +1,8 @@ [DEFAULT] support-files = [ "head.js", - "dummy.html"] + "dummy.html", + "empty.html"] ["browser_BrowserTestUtils.js"] diff --git a/testing/mochitest/tests/browser/browser_EventUtils.js b/testing/mochitest/tests/browser/browser_EventUtils.js @@ -8,6 +8,43 @@ const gBaseURL = gChromeBaseURL.replace( "https://example.com" ); +async function synthesizeMouseFromParent( + aBrowser, + aOffsetX, + aOffsetY, + aAsyncEnabled +) { + info( + `synthesizeMouse with asyncEnabled=${aAsyncEnabled} from parent process` + ); + + let haveReceiveMouseEvent = false; + const onMousemove = event => { + info( + `Received mouse event: ${event.type} ${event.offsetX} ${event.offsetY} ${event.button} ${event.buttons}` + ); + haveReceiveMouseEvent = true; + }; + aBrowser.addEventListener("mousemove", onMousemove, { once: true }); + await new Promise(resolve => { + EventUtils.synthesizeMouse( + aBrowser, + aOffsetX, + aOffsetY, + { + type: "mousemove", + asyncEnabled: aAsyncEnabled, + }, + window, + () => { + ok(haveReceiveMouseEvent, "Should have received mouse event"); + aBrowser.removeEventListener("mousemove", onMousemove); + resolve(); + } + ); + }); +} + add_task(async function synthesizeEventFromParent() { async function testSynthesizeWheelFromParent(aBrowser, aAsyncEnabled) { info(`Testing synthesizeWheel with asyncEnabled=${aAsyncEnabled}`); @@ -68,31 +105,7 @@ add_task(async function synthesizeEventFromParent() { SimpleTest.executeSoon(resolve); }); - let haveReceiveMouseEvent = false; - const onMousemove = event => { - info( - `Received mouse event: ${event.type} ${event.offsetX} ${event.offsetY} ${event.button} ${event.buttons}` - ); - haveReceiveMouseEvent = true; - }; - aBrowser.addEventListener("mousemove", onMousemove, { once: true }); - await new Promise(resolve => { - EventUtils.synthesizeMouse( - aBrowser, - 10, - 10, - { - type: "mousemove", - asyncEnabled: aAsyncEnabled, - }, - window, - () => { - ok(haveReceiveMouseEvent, "Should have received mouse event"); - aBrowser.removeEventListener("mousemove", onMousemove); - resolve(); - } - ); - }); + await synthesizeMouseFromParent(aBrowser, 10, 10, aAsyncEnabled); } await BrowserTestUtils.withNewTab( @@ -184,3 +197,80 @@ add_task(async function synthesizeEventFromContent() { } ); }); + +add_task(async function testCallbackForCrossProcressIframe() { + const iframeBaseURL = gChromeBaseURL.replace( + "chrome://mochitests/content", + "https://example.org/" + ); + + async function synthesizeMouseFromParentAndWait( + aBrowser, + aOffsetX, + aOffsetY, + aBrowsingContext + ) { + let eventPromise = SpecialPowers.spawn(aBrowsingContext, [], async () => { + await new Promise(resolve => { + content.document.addEventListener( + "mousemove", + () => { + info("Received mousemove event in the target browsing context"); + resolve(); + }, + { once: true } + ); + }); + }); + // Enuse the event listener is registered on remote target. + await SpecialPowers.spawn(aBrowsingContext, [], async () => { + await new Promise(resolve => { + SpecialPowers.executeSoon(resolve); + }); + }); + + await Promise.all([ + synthesizeMouseFromParent(aBrowser, aOffsetX, aOffsetY, true), + eventPromise, + ]); + } + + await BrowserTestUtils.withNewTab( + gBaseURL + "empty.html", + async function (browser) { + // Synthesize mouse event to the parent document. + await synthesizeMouseFromParentAndWait( + browser, + 10, + 5, + browser.browsingContext + ); + + // Add an iframe. + await SpecialPowers.spawn( + browser, + [iframeBaseURL + "empty.html"], + async url => { + content.document.body.appendChild( + content.document.createElement("br") + ); + let iframe = content.document.createElement("iframe"); + iframe.src = url; + let loadPromise = new Promise(resolve => { + iframe.addEventListener("load", resolve, { once: true }); + }); + content.document.body.appendChild(iframe); + await loadPromise; + } + ); + + // Synthesize mouse event to the iframe document. + await synthesizeMouseFromParentAndWait( + browser, + 10, + 35, + browser.browsingContext.children[0] + ); + } + ); +}); diff --git a/testing/mochitest/tests/browser/empty.html b/testing/mochitest/tests/browser/empty.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> + <title>This is a empty page</title> + <meta charset="utf-8"> + <body>This is a empty page</body> +</html> diff --git a/widget/MouseEvents.h b/widget/MouseEvents.h @@ -341,6 +341,11 @@ class WidgetMouseEvent : public WidgetMouseEventBase, } } + WidgetMouseEvent(const WidgetMouseEvent& aEvent) + : WidgetMouseEventBase(aEvent), WidgetPointerHelper(aEvent) { + AssignMouseEventDataOnly(aEvent); + } + #ifdef DEBUG virtual ~WidgetMouseEvent() { AssertContextMenuEventButtonConsistency(); } #endif @@ -410,7 +415,12 @@ class WidgetMouseEvent : public WidgetMouseEventBase, void AssignMouseEventData(const WidgetMouseEvent& aEvent, bool aCopyTargets) { AssignMouseEventBaseData(aEvent, aCopyTargets); AssignPointerHelperData(aEvent, /* aCopyCoalescedEvents */ true); + AssignMouseEventDataOnly(aEvent); + } + void AssignMouseEventDataOnly(const WidgetMouseEvent& aEvent) { + // NOTE: Intentionally not copying mClickTarget, it should only be used by + // the original mouseup event to dispatch the click event. mReason = aEvent.mReason; mContextMenuTrigger = aEvent.mContextMenuTrigger; mExitFrom = aEvent.mExitFrom; @@ -418,6 +428,9 @@ class WidgetMouseEvent : public WidgetMouseEventBase, mIgnoreRootScrollFrame = aEvent.mIgnoreRootScrollFrame; mIgnoreCapturingContent = aEvent.mIgnoreCapturingContent; mClickEventPrevented = aEvent.mClickEventPrevented; + // NOTE: Intentionally not copying mSynthesizeMoveAfterDispatch, it should + // only be used by the original event to check whether we need to + // synthesize an additional mousemove or pointermove event. mTriggerEvent = aEvent.mTriggerEvent; // NOTE: Intentionally not copying mCallbackId, it should only be tracked by // the original event or propagated to the cross-process event.