From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../browser_click_event_during_autoscrolling.js | 577 +++++++++++++++++++++ 1 file changed, 577 insertions(+) create mode 100644 toolkit/content/tests/browser/browser_click_event_during_autoscrolling.js (limited to 'toolkit/content/tests/browser/browser_click_event_during_autoscrolling.js') diff --git a/toolkit/content/tests/browser/browser_click_event_during_autoscrolling.js b/toolkit/content/tests/browser/browser_click_event_during_autoscrolling.js new file mode 100644 index 0000000000..b47f72ed5b --- /dev/null +++ b/toolkit/content/tests/browser/browser_click_event_during_autoscrolling.js @@ -0,0 +1,577 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["general.autoScroll", true], + ["middlemouse.contentLoadURL", false], + ["test.events.async.enabled", false], + ], + }); + + await BrowserTestUtils.withNewTab( + "https://example.com/browser/toolkit/content/tests/browser/file_empty.html", + async function (browser) { + ok(browser.isRemoteBrowser, "This test passes only in e10s mode"); + await SpecialPowers.spawn(browser, [], () => { + content.document.body.innerHTML = + '
'; + content.document.documentElement.scrollTop = 500; + content.document.documentElement.scrollTop; // Flush layout. + // Prevent to open context menu when testing the secondary button click. + content.window.addEventListener( + "contextmenu", + event => event.preventDefault(), + { capture: true } + ); + }); + + function promiseFlushLayoutInContent() { + return SpecialPowers.spawn(browser, [], () => { + content.document.documentElement.scrollTop; // Flush layout in the remote content. + }); + } + + function promiseContentTick() { + return SpecialPowers.spawn(browser, [], async () => { + await new Promise(r => { + content.requestAnimationFrame(() => { + content.requestAnimationFrame(r); + }); + }); + }); + } + + let autoScroller; + function promiseWaitForAutoScrollerOpen() { + if (autoScroller?.state == "open") { + info("The autoscroller has already been open"); + return Promise.resolve(); + } + return BrowserTestUtils.waitForEvent( + window, + "popupshown", + { capture: true }, + event => { + if (event.originalTarget.id != "autoscroller") { + return false; + } + autoScroller = event.originalTarget; + info('"popupshown" event is fired'); + autoScroller.getBoundingClientRect(); // Flush layout of the autoscroller + return true; + } + ); + } + + function promiseWaitForAutoScrollerClosed() { + if (!autoScroller || autoScroller.state == "closed") { + info("The autoscroller has already been closed"); + return Promise.resolve(); + } + return BrowserTestUtils.waitForEvent( + autoScroller, + "popuphidden", + { capture: true }, + () => { + info('"popuphidden" event is fired'); + return true; + } + ); + } + + // Unfortunately, we cannot use synthesized mouse events for starting and + // stopping autoscrolling because they may run different path from user + // operation especially when there is a popup. + + /** + * Instead of using `waitForContentEvent`, we use `addContentEventListener` + * for checking which events are fired because `waitForContentEvent` cannot + * detect redundant event since it's removed automatically at first event + * or timeout if the expected count is 0. + */ + class ContentEventCounter { + constructor(aBrowser, aEventTypes) { + this.eventData = new Map(); + for (let eventType of aEventTypes) { + const removeEventListener = + BrowserTestUtils.addContentEventListener( + aBrowser, + eventType, + () => { + let eventData = this.eventData.get(eventType); + eventData.count++; + }, + { capture: true } + ); + this.eventData.set(eventType, { + count: 0, // how many times the event fired. + removeEventListener, // function to remove the event listener. + }); + } + } + + getCountAndRemoveEventListener(aEventType) { + let eventData = this.eventData.get(aEventType); + if (eventData.removeEventListener) { + eventData.removeEventListener(); + eventData.removeEventListener = null; + } + return eventData.count; + } + + promiseMouseEvents(aEventTypes, aMessage) { + let needsToWait = []; + for (const eventType of aEventTypes) { + let eventData = this.eventData.get(eventType); + if (eventData.count > 0) { + info(`${aMessage}: Waiting "${eventType}" event in content...`); + needsToWait.push( + // Let's use `waitForCondition` here. "timeout" is not worthwhile + // to debug this test. We want clearer failure log. + TestUtils.waitForCondition( + () => eventData.count > 0, + `${aMessage}: "${eventType}" should be fired, but timed-out` + ) + ); + break; + } + } + return Promise.all(needsToWait); + } + } + + await SpecialPowers.pushPrefEnv({ set: [["middlemouse.paste", true]] }); + await (async function testMouseEventsAtStartingAutoScrolling() { + info( + "Waiting autoscroller popup for testing mouse events at starting autoscrolling" + ); + await promiseFlushLayoutInContent(); + let eventsInContent = new ContentEventCounter(browser, [ + "click", + "auxclick", + "mousedown", + "mouseup", + "paste", + ]); + // Ensure that the event listeners added in the content with accessing + // the remote content. + await promiseFlushLayoutInContent(); + await EventUtils.promiseNativeMouseEvent({ + type: "mousemove", + target: browser, + atCenter: true, + }); + const waitForOpenAutoScroll = promiseWaitForAutoScrollerOpen(); + await EventUtils.promiseNativeMouseEvent({ + type: "mousedown", + target: browser, + atCenter: true, + button: 1, // middle button + }); + await waitForOpenAutoScroll; + // In the wild, native "mouseup" event occurs after the popup is open. + await EventUtils.promiseNativeMouseEvent({ + type: "mouseup", + target: browser, + atCenter: true, + button: 1, // middle button + }); + await promiseFlushLayoutInContent(); + await promiseContentTick(); + await eventsInContent.promiseMouseEvents( + ["mouseup"], + "At starting autoscrolling" + ); + for (let eventType of ["click", "auxclick", "paste"]) { + is( + eventsInContent.getCountAndRemoveEventListener(eventType), + 0, + `"${eventType}" event shouldn't be fired in the content when a middle click starts autoscrolling` + ); + } + for (let eventType of ["mousedown", "mouseup"]) { + is( + eventsInContent.getCountAndRemoveEventListener(eventType), + 1, + `"${eventType}" event should be fired in the content when a middle click starts autoscrolling` + ); + } + info("Waiting autoscroller close for preparing the following tests"); + let waitForAutoScrollEnd = promiseWaitForAutoScrollerClosed(); + EventUtils.synthesizeKey("KEY_Escape"); + await waitForAutoScrollEnd; + })(); + + if ( + // Bug 1693240: We don't support setting modifiers while posting a mouse event on Windows. + !navigator.platform.includes("Win") && + // Bug 1693237: We don't support setting modifiers on Android. + !navigator.appVersion.includes("Android") && + // In Headless mode, modifiers are not supported by this kind of APIs. + !Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless + ) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["general.autoscroll.prevent_to_start.shiftKey", true], + ["general.autoscroll.prevent_to_start.altKey", true], + ["general.autoscroll.prevent_to_start.ctrlKey", true], + ["general.autoscroll.prevent_to_start.metaKey", true], + ], + }); + for (const modifier of ["Shift", "Control", "Alt", "Meta"]) { + if (modifier == "Meta" && !navigator.platform.includes("Mac")) { + continue; // Delete this after fixing bug 1232918. + } + await (async function modifiersPreventToStartAutoScrolling() { + info( + `Waiting to check not to open autoscroller popup with middle button click with ${modifier}` + ); + await promiseFlushLayoutInContent(); + let eventsInContent = new ContentEventCounter(browser, [ + "click", + "auxclick", + "mousedown", + "mouseup", + "paste", + ]); + // Ensure that the event listeners added in the content with accessing + // the remote content. + await promiseFlushLayoutInContent(); + await EventUtils.promiseNativeMouseEvent({ + type: "mousemove", + target: browser, + atCenter: true, + }); + info( + `Waiting to MozAutoScrollNoStart event for the middle button click with ${modifier}` + ); + await EventUtils.promiseNativeMouseEvent({ + type: "mousedown", + target: browser, + atCenter: true, + button: 1, // middle button + modifiers: { + altKey: modifier == "Alt", + ctrlKey: modifier == "Control", + metaKey: modifier == "Meta", + shiftKey: modifier == "Shift", + }, + }); + try { + await TestUtils.waitForCondition( + () => autoScroller?.state == "open", + `Waiting to check not to open autoscroller popup with ${modifier}`, + 100, + 10 + ); + ok( + false, + `The autoscroller popup shouldn't be opened by middle click with ${modifier}` + ); + } catch (ex) { + ok( + true, + `The autoscroller popup was not open as expected after middle click with ${modifier}` + ); + } + // In the wild, native "mouseup" event occurs after the popup is open. + await EventUtils.promiseNativeMouseEvent({ + type: "mouseup", + target: browser, + atCenter: true, + button: 1, // middle button + }); + await promiseFlushLayoutInContent(); + await promiseContentTick(); + await eventsInContent.promiseMouseEvents( + ["paste"], + `At middle clicking with ${modifier}` + ); + for (let eventType of [ + "mousedown", + "mouseup", + "click", + "auxclick", + "paste", + ]) { + is( + eventsInContent.getCountAndRemoveEventListener(eventType), + 1, + `"${eventType}" event should be fired in the content when a middle click with ${modifier}` + ); + } + info( + "Waiting autoscroller close for preparing the following tests" + ); + })(); + } + } + + async function doTestMouseEventsAtStoppingAutoScrolling({ + aButton = 0, + aClickOutsideAutoScroller = false, + aDescription = "Unspecified", + }) { + info( + `Starting autoscrolling for testing to stop autoscrolling with ${aDescription}` + ); + await promiseFlushLayoutInContent(); + await EventUtils.promiseNativeMouseEvent({ + type: "mousemove", + target: browser, + atCenter: true, + }); + const waitForOpenAutoScroll = promiseWaitForAutoScrollerOpen(); + await EventUtils.promiseNativeMouseEvent({ + type: "mousedown", + target: browser, + atCenter: true, + button: 1, // middle button + }); + // In the wild, native "mouseup" event occurs after the popup is open. + await waitForOpenAutoScroll; + await EventUtils.promiseNativeMouseEvent({ + type: "mouseup", + target: browser, + atCenter: true, + button: 1, // middle button + }); + await promiseFlushLayoutInContent(); + // Just to be sure, wait for a tick for wait APZ stable. + await TestUtils.waitForTick(); + + let eventsInContent = new ContentEventCounter(browser, [ + "click", + "auxclick", + "mousedown", + "mouseup", + "paste", + "contextmenu", + ]); + // Ensure that the event listeners added in the content with accessing + // the remote content. + await promiseFlushLayoutInContent(); + + aDescription = `Stop autoscrolling with ${aDescription}`; + info( + `${aDescription}: Synthesizing primary mouse button event on the autoscroller` + ); + const autoScrollerRect = autoScroller.getOuterScreenRect(); + info( + `${aDescription}: autoScroller: { left: ${autoScrollerRect.left}, top: ${autoScrollerRect.top}, width: ${autoScrollerRect.width}, height: ${autoScrollerRect.height} }` + ); + const waitForCloseAutoScroller = promiseWaitForAutoScrollerClosed(); + if (aClickOutsideAutoScroller) { + info( + `${aDescription}: Synthesizing mousemove move cursor outside the autoscroller...` + ); + await EventUtils.promiseNativeMouseEvent({ + type: "mousemove", + target: autoScroller, + offsetX: -10, + offsetY: -10, + elementOnWidget: browser, // use widget for the parent window of the autoscroller + }); + info( + `${aDescription}: Synthesizing mousedown to stop autoscrolling...` + ); + await EventUtils.promiseNativeMouseEvent({ + type: "mousedown", + target: autoScroller, + offsetX: -10, + offsetY: -10, + button: aButton, + elementOnWidget: browser, // use widget for the parent window of the autoscroller + }); + } else { + info( + `${aDescription}: Synthesizing mousemove move cursor onto the autoscroller...` + ); + await EventUtils.promiseNativeMouseEvent({ + type: "mousemove", + target: autoScroller, + atCenter: true, + elementOnWidget: browser, // use widget for the parent window of the autoscroller + }); + info( + `${aDescription}: Synthesizing mousedown to stop autoscrolling...` + ); + await EventUtils.promiseNativeMouseEvent({ + type: "mousedown", + target: autoScroller, + atCenter: true, + button: aButton, + elementOnWidget: browser, // use widget for the parent window of the autoscroller + }); + } + // In the wild, native "mouseup" event occurs after the popup is closed. + await waitForCloseAutoScroller; + info( + `${aDescription}: Synthesizing mouseup event for preceding mousedown which is for stopping autoscrolling` + ); + await EventUtils.promiseNativeMouseEvent({ + type: "mouseup", + target: browser, + atCenter: true, + button: aButton, + }); + await promiseFlushLayoutInContent(); + await promiseContentTick(); + await eventsInContent.promiseMouseEvents( + aButton != 2 ? ["mouseup"] : ["mouseup", "contextmenu"], + aDescription + ); + is( + autoScroller.state, + "closed", + `${aDescription}: The autoscroller should've been closed` + ); + // - On macOS, when clicking outside autoscroller, nsChildView + // intentionally blocks both "mousedown" and "mouseup" events in the + // case of the primary button click, and only "mousedown" for the + // middle button when the "mousedown". I'm not sure how it should work + // on macOS for conforming to the platform manner. Note that autoscroll + // isn't available on the other browsers on macOS. So, there is no + // reference, but for consistency between platforms, it may be better + // to ignore the platform manner. + // - On Windows, when clicking outside autoscroller, nsWindow + // intentionally blocks only "mousedown" events for the primary button + // and the middle button. But this behavior is different from Chrome + // so that we need to fix this in the future. + // - On Linux, when clicking outside autoscroller, nsWindow + // intentionally blocks only "mousedown" events for any buttons. But + // on Linux, autoscroll isn't available by the default settings. So, + // not so urgent, but should be fixed in the future for consistency + // between platforms and compatibility with Chrome on Windows. + const rollingUpPopupConsumeMouseDown = + aClickOutsideAutoScroller && + (aButton != 2 || navigator.platform.includes("Linux")); + const rollingUpPopupConsumeMouseUp = + aClickOutsideAutoScroller && + aButton == 0 && + navigator.platform.includes("Mac"); + const checkFuncForClick = + aClickOutsideAutoScroller && + aButton == 2 && + !navigator.platform.includes("Linux") + ? todo_is + : is; + for (let eventType of ["click", "auxclick"]) { + checkFuncForClick( + eventsInContent.getCountAndRemoveEventListener(eventType), + 0, + `${aDescription}: "${eventType}" event shouldn't be fired in the remote content` + ); + } + is( + eventsInContent.getCountAndRemoveEventListener("paste"), + 0, + `${aDescription}: "paste" event shouldn't be fired in the remote content` + ); + const checkFuncForMouseDown = rollingUpPopupConsumeMouseDown + ? todo_is + : is; + checkFuncForMouseDown( + eventsInContent.getCountAndRemoveEventListener("mousedown"), + 1, + `${aDescription}: "mousedown" event should be fired in the remote content` + ); + const checkFuncForMouseUp = rollingUpPopupConsumeMouseUp ? todo_is : is; + checkFuncForMouseUp( + eventsInContent.getCountAndRemoveEventListener("mouseup"), + 1, + `${aDescription}: "mouseup" event should be fired in the remote content` + ); + const checkFuncForContextMenu = + aButton == 2 && + aClickOutsideAutoScroller && + navigator.platform.includes("Linux") + ? todo_is + : is; + checkFuncForContextMenu( + eventsInContent.getCountAndRemoveEventListener("contextmenu"), + aButton == 2 ? 1 : 0, + `${aDescription}: "contextmenu" event should${ + aButton != 2 ? " not" : "" + } be fired in the remote content` + ); + + const promiseClickEvent = BrowserTestUtils.waitForContentEvent( + browser, + "click", + { + capture: true, + } + ); + await promiseFlushLayoutInContent(); + info(`${aDescription}: Waiting for click event in the remote content`); + EventUtils.synthesizeNativeMouseEvent({ + type: "click", + target: browser, + atCenter: true, + }); + await promiseClickEvent; + ok( + true, + `${aDescription}: click event is fired in the remote content after stopping autoscrolling` + ); + } + + // Clicking the primary button to stop autoscrolling. + await doTestMouseEventsAtStoppingAutoScrolling({ + aButton: 0, + aClickOutsideAutoScroller: false, + aDescription: "a primary button click on autoscroller", + }); + await doTestMouseEventsAtStoppingAutoScrolling({ + aButton: 0, + aClickOutsideAutoScroller: true, + aDescription: "a primary button click outside autoscroller", + }); + + // Clicking the secondary button to stop autoscrolling. + await doTestMouseEventsAtStoppingAutoScrolling({ + aButton: 2, + aClickOutsideAutoScroller: false, + aDescription: "a secondary button click on autoscroller", + }); + await doTestMouseEventsAtStoppingAutoScrolling({ + aButton: 2, + aClickOutsideAutoScroller: true, + aDescription: "a secondary button click outside autoscroller", + }); + + // Clicking the middle button to stop autoscrolling. + await SpecialPowers.pushPrefEnv({ set: [["middlemouse.paste", true]] }); + await doTestMouseEventsAtStoppingAutoScrolling({ + aButton: 1, + aClickOutsideAutoScroller: false, + aDescription: + "a middle button click on autoscroller (middle click paste enabled)", + }); + await doTestMouseEventsAtStoppingAutoScrolling({ + aButton: 1, + aClickOutsideAutoScroller: true, + aDescription: + "a middle button click outside autoscroller (middle click paste enabled)", + }); + await SpecialPowers.pushPrefEnv({ set: [["middlemouse.paste", false]] }); + await doTestMouseEventsAtStoppingAutoScrolling({ + aButton: 1, + aClickOutsideAutoScroller: false, + aDescription: + "a middle button click on autoscroller (middle click paste disabled)", + }); + await doTestMouseEventsAtStoppingAutoScrolling({ + aButton: 1, + aClickOutsideAutoScroller: true, + aDescription: + "a middle button click outside autoscroller (middle click paste disabled)", + }); + } + ); +}); -- cgit v1.2.3