From fa09c95758c6350741c5ffd5f7a924e2cc747a12 Mon Sep 17 00:00:00 2001 From: Sandor Molnar Date: Sat, 27 Jan 2024 03:18:38 +0200 Subject: [PATCH] Backed out changeset 99babcb9f349 (bug 1855704) for causing mochitest failures at dom/animation/test/chrome/test_animation_observers_async.html CLOSED TREE --- browser/components/firefoxview/OpenTabs.sys.mjs | 398 --------------- browser/components/firefoxview/opentabs.mjs | 207 +++++--- .../firefoxview/tests/browser/browser.toml | 2 - .../tests/browser/browser_firefoxview_paused.js | 46 +- .../tests/browser/browser_opentabs_cards.js | 88 +--- .../tests/browser/browser_opentabs_changes.js | 541 --------------------- .../tests/browser/browser_opentabs_firefoxview.js | 163 ++----- .../tests/browser/browser_opentabs_recency.js | 54 -- browser/components/firefoxview/viewpage.mjs | 7 +- browser/modules/EveryWindow.sys.mjs | 9 - 10 files changed, 210 insertions(+), 1305 deletions(-) delete mode 100644 browser/components/firefoxview/OpenTabs.sys.mjs delete mode 100644 browser/components/firefoxview/tests/browser/browser_opentabs_changes.js diff --git a/browser/components/firefoxview/OpenTabs.sys.mjs b/browser/components/firefoxview/OpenTabs.sys.mjs deleted file mode 100644 index 92ba888449b2..000000000000 --- a/browser/components/firefoxview/OpenTabs.sys.mjs +++ /dev/null @@ -1,398 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * This module provides the means to monitor and query for tab collections against open - * browser windows and allow listeners to be notified of changes to those collections. - */ - -const lazy = {}; - -ChromeUtils.defineESModuleGetters(lazy, { - DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs", - EveryWindow: "resource:///modules/EveryWindow.sys.mjs", - PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", -}); - -const TAB_ATTRS_TO_WATCH = Object.freeze(["image", "label"]); -const TAB_CHANGE_EVENTS = Object.freeze([ - "TabAttrModified", - "TabClose", - "TabMove", - "TabOpen", - "TabPinned", - "TabUnpinned", -]); -const TAB_RECENCY_CHANGE_EVENTS = Object.freeze([ - "activate", - "TabAttrModified", - "TabClose", - "TabOpen", - "TabSelect", -]); - -// Debounce tab/tab recency changes and dispatch max once per frame at 60fps -const CHANGES_DEBOUNCE_MS = 1000 / 60; - -/** - * A sort function used to order tabs by most-recently seen and active. - */ -export function lastSeenActiveSort(a, b) { - let dt = b.lastSeenActive - a.lastSeenActive; - if (dt) { - return dt; - } - // try to break a deadlock by sorting the selected tab higher - if (!(a.selected || b.selected)) { - return 0; - } - return a.selected ? -1 : 1; -} - -/** - * Provides a object capable of monitoring and accessing tab collections for either - * private or non-private browser windows. As the class extends EventTarget, consumers - * should add event listeners for the change events. - * - * @param {boolean} options.usePrivateWindows - Constrain to only windows that match this privateness. Defaults to false. - * @param {Window | null} options.exclusiveWindow - * Constrain to only a specific window. - */ -class OpenTabsTarget extends EventTarget { - #changedWindowsByType = { - TabChange: new Set(), - TabRecencyChange: new Set(), - }; - #dispatchChangesTask; - #started = false; - #watchedWindows = new Set(); - - #exclusiveWindowWeakRef = null; - usePrivateWindows = false; - - constructor(options = {}) { - super(); - this.usePrivateWindows = !!options.usePrivateWindows; - - if (options.exclusiveWindow) { - this.exclusiveWindow = options.exclusiveWindow; - this.everyWindowCallbackId = `opentabs-${this.exclusiveWindow.windowGlobalChild.innerWindowId}`; - } else { - this.everyWindowCallbackId = `opentabs-${ - this.usePrivateWindows ? "private" : "non-private" - }`; - } - } - - get exclusiveWindow() { - return this.#exclusiveWindowWeakRef?.get(); - } - set exclusiveWindow(newValue) { - if (newValue) { - this.#exclusiveWindowWeakRef = Cu.getWeakReference(newValue); - } else { - this.#exclusiveWindowWeakRef = null; - } - } - - includeWindowFilter(win) { - if (this.#exclusiveWindowWeakRef) { - return win == this.exclusiveWindow; - } - return ( - win.gBrowser && - !win.closed && - this.usePrivateWindows == lazy.PrivateBrowsingUtils.isWindowPrivate(win) - ); - } - - get currentWindows() { - return lazy.EveryWindow.readyWindows.filter(win => - this.includeWindowFilter(win) - ); - } - - /** - * A promise that resolves to all matched windows once their delayedStartupPromise resolves - */ - get readyWindowsPromise() { - let windowList = Array.from( - Services.wm.getEnumerator("navigator:browser") - ).filter(win => { - // avoid waiting for windows we definitely don't care about - if (this.#exclusiveWindowWeakRef) { - return this.exclusiveWindow == win; - } - return ( - this.usePrivateWindows == lazy.PrivateBrowsingUtils.isWindowPrivate(win) - ); - }); - return Promise.allSettled( - windowList.map(win => win.delayedStartupPromise) - ).then(() => { - // re-filter the list as properties might have changed in the interim - return windowList.filter(win => this.includeWindowFilter); - }); - } - - haveListenersForEvent(eventType) { - switch (eventType) { - case "TabChange": - return Services.els.hasListenersFor(this, "TabChange"); - case "TabRecencyChange": - return Services.els.hasListenersFor(this, "TabRecencyChange"); - default: - return false; - } - } - - get haveAnyListeners() { - return ( - this.haveListenersForEvent("TabChange") || - this.haveListenersForEvent("TabRecencyChange") - ); - } - - /* - * @param {string} type - * Either "TabChange" or "TabRecencyChange" - * @param {Object|Function} listener - * @param {Object} [options] - */ - addEventListener(type, listener, options) { - let hadListeners = this.haveAnyListeners; - super.addEventListener(type, listener, options); - - // if this is the first listener, start up all the window & tab monitoring - if (!hadListeners && this.haveAnyListeners) { - this.start(); - } - } - - /* - * @param {string} type - * Either "TabChange" or "TabRecencyChange" - * @param {Object|Function} listener - */ - removeEventListener(type, listener) { - let hadListeners = this.haveAnyListeners; - super.removeEventListener(type, listener); - - // if this was the last listener, we can stop all the window & tab monitoring - if (hadListeners && !this.haveAnyListeners) { - this.stop(); - } - } - - /** - * Begin watching for tab-related events from all browser windows matching the instance's private property - */ - start() { - if (this.#started) { - return; - } - // EveryWindow will call #watchWindow for each open window once its delayedStartupPromise resolves. - lazy.EveryWindow.registerCallback( - this.everyWindowCallbackId, - win => this.#watchWindow(win), - win => this.#unwatchWindow(win) - ); - this.#started = true; - } - - /** - * Stop watching for tab-related events from all browser windows and clean up. - */ - stop() { - if (this.#started) { - lazy.EveryWindow.unregisterCallback(this.everyWindowCallbackId); - this.#started = false; - } - for (let changedWindows of Object.values(this.#changedWindowsByType)) { - changedWindows.clear(); - } - this.#watchedWindows.clear(); - this.#dispatchChangesTask?.disarm(); - } - - /** - * Add listeners for tab-related events from the given window. The consumer's - * listeners will always be notified at least once for newly-watched window. - */ - #watchWindow(win) { - if (!this.includeWindowFilter(win)) { - return; - } - this.#watchedWindows.add(win); - const { tabContainer } = win.gBrowser; - tabContainer.addEventListener("TabAttrModified", this); - tabContainer.addEventListener("TabClose", this); - tabContainer.addEventListener("TabMove", this); - tabContainer.addEventListener("TabOpen", this); - tabContainer.addEventListener("TabPinned", this); - tabContainer.addEventListener("TabUnpinned", this); - tabContainer.addEventListener("TabSelect", this); - win.addEventListener("activate", this); - - this.#scheduleEventDispatch("TabChange", {}); - this.#scheduleEventDispatch("TabRecencyChange", {}); - } - - /** - * Remove all listeners for tab-related events from the given window. - * Consumers will always be notified at least once for unwatched window. - */ - #unwatchWindow(win) { - // We check the window is in our watchedWindows collection rather than currentWindows - // as the unwatched window may not match the criteria we used to watch it anymore, - // and we need to unhook our event listeners regardless. - if (this.#watchedWindows.has(win)) { - this.#watchedWindows.delete(win); - - const { tabContainer } = win.gBrowser; - tabContainer.removeEventListener("TabAttrModified", this); - tabContainer.removeEventListener("TabClose", this); - tabContainer.removeEventListener("TabMove", this); - tabContainer.removeEventListener("TabOpen", this); - tabContainer.removeEventListener("TabPinned", this); - tabContainer.removeEventListener("TabSelect", this); - tabContainer.removeEventListener("TabUnpinned", this); - win.removeEventListener("activate", this); - - this.#scheduleEventDispatch("TabChange", {}); - this.#scheduleEventDispatch("TabRecencyChange", {}); - } - } - - /** - * Flag the need to notify all our consumers of a change to open tabs. - * Repeated calls within approx 16ms will be consolidated - * into one event dispatch. - */ - #scheduleEventDispatch(eventType, { sourceWindowId } = {}) { - if (!this.haveListenersForEvent(eventType)) { - return; - } - - this.#changedWindowsByType[eventType].add(sourceWindowId); - // Queue up an event dispatch - we use a deferred task to make this less noisy by - // consolidating multiple change events into one. - if (!this.#dispatchChangesTask) { - this.#dispatchChangesTask = new lazy.DeferredTask(() => { - this.#dispatchChanges(); - }, CHANGES_DEBOUNCE_MS); - } - this.#dispatchChangesTask.arm(); - } - - #dispatchChanges() { - this.#dispatchChangesTask?.disarm(); - for (let [eventType, changedWindowIds] of Object.entries( - this.#changedWindowsByType - )) { - if (this.haveListenersForEvent(eventType) && changedWindowIds.size) { - this.dispatchEvent( - new CustomEvent(eventType, { - detail: { - windowIds: [...changedWindowIds], - }, - }) - ); - changedWindowIds.clear(); - } - } - } - - /* - * @param {Window} win - * @returns {Array} - * The list of visible tabs for the browser window - */ - getTabsForWindow(win) { - if (this.currentWindows.includes(win)) { - return [...win.gBrowser.visibleTabs]; - } - return []; - } - - /* - * @returns {Array} - * A by-recency-sorted, aggregated list of tabs from all the same-privateness browser windows. - */ - getRecentTabs() { - const tabs = []; - for (let win of this.currentWindows) { - tabs.push(...this.getTabsForWindow(win)); - } - tabs.sort(lastSeenActiveSort); - return tabs; - } - - handleEvent({ detail, target, type }) { - const win = target.ownerGlobal; - // NOTE: we already filtered on privateness by not listening for those events - // from private/not-private windows - if ( - type == "TabAttrModified" && - !detail.changed.some(attr => TAB_ATTRS_TO_WATCH.includes(attr)) - ) { - return; - } - - if (TAB_RECENCY_CHANGE_EVENTS.includes(type)) { - this.#scheduleEventDispatch("TabRecencyChange", { - sourceWindowId: win.windowGlobalChild.innerWindowId, - }); - } - if (TAB_CHANGE_EVENTS.includes(type)) { - this.#scheduleEventDispatch("TabChange", { - sourceWindowId: win.windowGlobalChild.innerWindowId, - }); - } - } -} - -const gExclusiveWindows = new (class { - perWindowInstances = new WeakMap(); - constructor() { - Services.obs.addObserver(this, "domwindowclosed"); - } - observe(subject, topic, data) { - let win = subject; - let winTarget = this.perWindowInstances.get(win); - if (winTarget) { - winTarget.stop(); - this.perWindowInstances.delete(win); - } - } -})(); - -/** - * Get an OpenTabsTarget instance constrained to a specific window. - * - * @param {Window} exclusiveWindow - * @returns {OpenTabsTarget} - */ -const getTabsTargetForWindow = function (exclusiveWindow) { - let instance = gExclusiveWindows.perWindowInstances.get(exclusiveWindow); - if (instance) { - return instance; - } - instance = new OpenTabsTarget({ - exclusiveWindow, - }); - gExclusiveWindows.perWindowInstances.set(exclusiveWindow, instance); - return instance; -}; - -const NonPrivateTabs = new OpenTabsTarget({ - usePrivateWindows: false, -}); - -const PrivateTabs = new OpenTabsTarget({ - usePrivateWindows: true, -}); - -export { NonPrivateTabs, PrivateTabs, getTabsTargetForWindow }; diff --git a/browser/components/firefoxview/opentabs.mjs b/browser/components/firefoxview/opentabs.mjs index 5f4f8e970eea..9d9cb404352c 100644 --- a/browser/components/firefoxview/opentabs.mjs +++ b/browser/components/firefoxview/opentabs.mjs @@ -21,8 +21,7 @@ import { ViewPage, ViewPageContent } from "./viewpage.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { - NonPrivateTabs: "resource:///modules/OpenTabs.sys.mjs", - getTabsTargetForWindow: "resource:///modules/OpenTabs.sys.mjs", + EveryWindow: "resource:///modules/EveryWindow.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", }); @@ -32,16 +31,18 @@ ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => { ).getFxAccountsSingleton(); }); +const TOPIC_CURRENT_BROWSER_CHANGED = "net:current-browser-id"; + /** * A collection of open tabs grouped by window. * - * @property {Array} windows - * A list of windows with the same privateness + * @property {Map} windows + * A mapping of windows to their respective list of open tabs. */ class OpenTabsInView extends ViewPage { static properties = { ...ViewPage.properties, - windows: { type: Array }, + windows: { type: Map }, searchQuery: { type: String }, }; static queries = { @@ -49,20 +50,18 @@ class OpenTabsInView extends ViewPage { searchTextbox: "fxview-search-textbox", }; - initialWindowsReady = false; - currentWindow = null; - openTabsTarget = null; + static TAB_ATTRS_TO_WATCH = Object.freeze(["image", "label"]); constructor() { super(); this._started = false; - this.windows = []; + this.everyWindowCallbackId = `firefoxview-${Services.uuid.generateUUID()}`; + this.windows = new Map(); this.currentWindow = this.getWindow(); - if (lazy.PrivateBrowsingUtils.isWindowPrivate(this.currentWindow)) { - this.openTabsTarget = lazy.getTabsTargetForWindow(this.currentWindow); - } else { - this.openTabsTarget = lazy.NonPrivateTabs; - } + this.isPrivateWindow = lazy.PrivateBrowsingUtils.isWindowPrivate( + this.currentWindow + ); + this.boundObserve = (...args) => this.observe(...args); this.searchQuery = ""; } @@ -72,19 +71,43 @@ class OpenTabsInView extends ViewPage { } this._started = true; - if (this.recentBrowsing) { - this.openTabsTarget.addEventListener("TabRecencyChange", this); - } else { - this.openTabsTarget.addEventListener("TabChange", this); - } - - // To resolve the race between this component wanting to render all the windows' - // tabs, while those windows are still potentially opening, flip this property - // once the promise resolves and we'll bail out of rendering until then. - this.openTabsTarget.readyWindowsPromise.finally(() => { - this.initialWindowsReady = true; - this._updateWindowList(); - }); + Services.obs.addObserver(this.boundObserve, TOPIC_CURRENT_BROWSER_CHANGED); + + lazy.EveryWindow.registerCallback( + this.everyWindowCallbackId, + win => { + if (win.gBrowser && this._shouldShowOpenTabs(win) && !win.closed) { + const { tabContainer } = win.gBrowser; + tabContainer.addEventListener("TabSelect", this); + tabContainer.addEventListener("TabAttrModified", this); + tabContainer.addEventListener("TabClose", this); + tabContainer.addEventListener("TabMove", this); + tabContainer.addEventListener("TabOpen", this); + tabContainer.addEventListener("TabPinned", this); + tabContainer.addEventListener("TabUnpinned", this); + // BrowserWindowWatcher doesnt always notify "net:current-browser-id" when + // restoring a window, so we need to listen for "activate" events here as well. + win.addEventListener("activate", this); + this._updateOpenTabsList(); + } + }, + win => { + if (win.gBrowser && this._shouldShowOpenTabs(win)) { + const { tabContainer } = win.gBrowser; + tabContainer.removeEventListener("TabSelect", this); + tabContainer.removeEventListener("TabAttrModified", this); + tabContainer.removeEventListener("TabClose", this); + tabContainer.removeEventListener("TabMove", this); + tabContainer.removeEventListener("TabOpen", this); + tabContainer.removeEventListener("TabPinned", this); + tabContainer.removeEventListener("TabUnpinned", this); + win.removeEventListener("activate", this); + this._updateOpenTabsList(); + } + } + ); + // EveryWindow will invoke the callback for existing windows - including this one + // So this._updateOpenTabsList will get called for the already-open window for (let card of this.viewCards) { card.paused = false; @@ -99,13 +122,6 @@ class OpenTabsInView extends ViewPage { } } - shouldUpdate(changedProperties) { - if (!this.initialWindowsReady) { - return false; - } - return super.shouldUpdate(changedProperties); - } - disconnectedCallback() { super.disconnectedCallback(); this.stop(); @@ -118,8 +134,12 @@ class OpenTabsInView extends ViewPage { this._started = false; this.paused = true; - this.openTabsTarget.removeEventListener("TabChange", this); - this.openTabsTarget.removeEventListener("TabRecencyChange", this); + lazy.EveryWindow.unregisterCallback(this.everyWindowCallbackId); + + Services.obs.removeObserver( + this.boundObserve, + TOPIC_CURRENT_BROWSER_CHANGED + ); for (let card of this.viewCards) { card.paused = true; @@ -142,6 +162,14 @@ class OpenTabsInView extends ViewPage { this.stop(); } + async observe(subject, topic, data) { + switch (topic) { + case TOPIC_CURRENT_BROWSER_CHANGED: + this.requestUpdate(); + break; + } + } + render() { if (this.recentBrowsing) { return this.getRecentBrowsingTemplate(); @@ -149,8 +177,7 @@ class OpenTabsInView extends ViewPage { let currentWindowIndex, currentWindowTabs; let index = 1; const otherWindows = []; - this.windows.forEach(win => { - const tabs = this.openTabsTarget.getTabsForWindow(win); + this.windows.forEach((tabs, win) => { if (win === this.currentWindow) { currentWindowIndex = index++; currentWindowTabs = tabs; @@ -160,13 +187,13 @@ class OpenTabsInView extends ViewPage { }); const cardClasses = classMap({ - "height-limited": this.windows.length > 3, - "width-limited": this.windows.length > 1, + "height-limited": this.windows.size > 3, + "width-limited": this.windows.size > 1, }); let cardCount; - if (this.windows.length <= 1) { + if (this.windows.size <= 1) { cardCount = "one"; - } else if (this.windows.length === 2) { + } else if (this.windows.size === 2) { cardCount = "two"; } else { cardCount = "three-or-more"; @@ -250,7 +277,19 @@ class OpenTabsInView extends ViewPage { * The recent browsing template. */ getRecentBrowsingTemplate() { - const tabs = this.openTabsTarget.getRecentTabs(); + const tabs = Array.from(this.windows.values()) + .flat() + .sort((a, b) => { + let dt = b.lastSeenActive - a.lastSeenActive; + if (dt) { + return dt; + } + // try to break a deadlock by sorting the selected tab higher + if (!(a.selected || b.selected)) { + return 0; + } + return a.selected ? -1 : 1; + }); return html` + OpenTabsInView.TAB_ATTRS_TO_WATCH.includes(attr) + ) + ) { + // We don't care about this attr, bail out to avoid change detection. return; } - windowIds = detail.windowIds; - this._updateWindowList(); + break; + case "TabClose": + tabs.splice(target._tPos, 1); + break; + case "TabMove": + [tabs[detail], tabs[target._tPos]] = [tabs[target._tPos], tabs[detail]]; + break; + case "TabOpen": + tabs.splice(target._tPos, 0, target); + break; + case "TabPinned": + case "TabUnpinned": + this.windows.set(win, [...win.gBrowser.tabs]); break; } - if (this.recentBrowsing) { - return; - } - if (windowIds?.length) { - // there were tab changes to one or more windows - for (let winId of windowIds) { - const cardForWin = this.shadowRoot.querySelector( - `view-opentabs-card[data-inner-id="${winId}"]` - ); - if (this.searchQuery) { - cardForWin?.updateSearchResults(); - } - cardForWin?.requestUpdate(); - } - } else { - let winId = window.windowGlobalChild.innerWindowId; - let cardForWin = this.shadowRoot.querySelector( - `view-opentabs-card[data-inner-id="${winId}"]` + this.requestUpdate(); + if (!this.recentBrowsing) { + const cardForWin = this.shadowRoot.querySelector( + `view-opentabs-card[data-inner-id="${win.windowGlobalChild.innerWindowId}"]` ); if (this.searchQuery) { cardForWin?.updateSearchResults(); } + cardForWin?.requestUpdate(); } } - async _updateWindowList() { - this.windows = this.openTabsTarget.currentWindows; + _updateOpenTabsList() { + this.windows = this._getOpenTabsPerWindow(); + } + + /** + * Get a list of open tabs for each window. + * + * @returns {Map} + */ + _getOpenTabsPerWindow() { + return new Map( + Array.from(Services.wm.getEnumerator("navigator:browser")) + .filter( + win => win.gBrowser && this._shouldShowOpenTabs(win) && !win.closed + ) + .map(win => [win, [...win.gBrowser.tabs]]) + ); + } + + _shouldShowOpenTabs(win) { + return ( + win == this.currentWindow || + (!this.isPrivateWindow && !lazy.PrivateBrowsingUtils.isWindowPrivate(win)) + ); } } customElements.define("view-opentabs", OpenTabsInView); diff --git a/browser/components/firefoxview/tests/browser/browser.toml b/browser/components/firefoxview/tests/browser/browser.toml index daf042303541..38924c7fed96 100644 --- a/browser/components/firefoxview/tests/browser/browser.toml +++ b/browser/components/firefoxview/tests/browser/browser.toml @@ -32,8 +32,6 @@ skip-if = ["true"] # Bug 1869605 and # Bug 1870296 ["browser_notification_dot.js"] skip-if = ["true"] # Bug 1851453 -["browser_opentabs_changes.js"] - ["browser_reload_firefoxview.js"] ["browser_tab_close_last_tab.js"] diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js index c95ac4fcf595..e8a8fc7ed88f 100644 --- a/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js +++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js @@ -5,9 +5,6 @@ const tabURL1 = "data:,Tab1"; const tabURL2 = "data:,Tab2"; const tabURL3 = "data:,Tab3"; -const { NonPrivateTabs } = ChromeUtils.importESModule( - "resource:///modules/OpenTabs.sys.mjs" -); const TestTabs = {}; function getTopLevelViewElements(document) { @@ -41,8 +38,6 @@ async function getElements(document) { await TestUtils.waitForCondition(() => recentlyClosedView.fullyUpdated); } let recentlyClosedList = recentlyClosedView.tabList; - await openTabsView.openTabsTarget.readyWindowsPromise; - await openTabsView.updateComplete; let openTabsList = openTabsView.shadowRoot.querySelector("view-opentabs-card")?.tabList; @@ -89,17 +84,6 @@ async function setupOpenAndClosedTabs() { await SessionStoreTestUtils.closeTab(TestTabs.tab3); } -function assertSpiesCalled(spiesMap, expectCalled) { - let message = expectCalled ? "to be called" : "to not be called"; - for (let [elem, renderSpy] of spiesMap.entries()) { - is( - expectCalled, - renderSpy.called, - `Expected the render method spy on element ${elem.localName} ${message}` - ); - } -} - async function checkFxRenderCalls(browser, elements, selectedView) { const sandbox = sinon.createSandbox(); const topLevelViews = getTopLevelViewElements(browser.contentDocument); @@ -135,20 +119,7 @@ async function checkFxRenderCalls(browser, elements, selectedView) { } info("test switches to tab2"); - let tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); await BrowserTestUtils.switchTab(gBrowser, TestTabs.tab2); - await tabChangeRaised; - info( - "TabRecencyChange event was raised, check no render() methods were called" - ); - assertSpiesCalled(viewSpies, false); - assertSpiesCalled(elementSpies, false); - for (let renderSpy of [...viewSpies.values(), ...elementSpies.values()]) { - renderSpy.resetHistory(); - } // check all the top-level views are paused ok( @@ -161,14 +132,21 @@ async function checkFxRenderCalls(browser, elements, selectedView) { ); ok(topLevelViews.openTabsView.paused, "The open tabs view is paused"); + function assertSpiesCalled(spiesMap, expectCalled) { + let message = expectCalled ? "to be called" : "to not be called"; + for (let [elem, renderSpy] of spiesMap.entries()) { + is( + expectCalled, + renderSpy.called, + `Expected the render method spy on element ${elem.localName} ${message}` + ); + } + } + await nextFrame(); info("test removes tab1"); - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); await BrowserTestUtils.removeTab(TestTabs.tab1); - await tabChangeRaised; + await nextFrame(); assertSpiesCalled(viewSpies, false); assertSpiesCalled(elementSpies, false); diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js b/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js index 7a34d6c2306d..7e762e811ebf 100644 --- a/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js +++ b/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js @@ -7,9 +7,6 @@ const ROW_DATE_ID = "fxview-tab-row-date"; let gInitialTab; let gInitialTabURL; -const { NonPrivateTabs } = ChromeUtils.importESModule( - "resource:///modules/OpenTabs.sys.mjs" -); add_setup(function () { // This test opens a lot of windows and tabs and might run long on slower configurations @@ -68,8 +65,7 @@ add_task(async function open_tab_same_window() { const browser = viewTab.linkedBrowser; await navigateToOpenTabs(browser); const openTabs = getOpenTabsComponent(browser); - await openTabs.openTabsTarget.readyWindowsPromise; - await openTabs.updateComplete; + await openTabs.getUpdateComplete(); const cards = getCards(browser); is(cards.length, 1, "There is one window."); @@ -84,23 +80,15 @@ add_task(async function open_tab_same_window() { browser.contentDocument, "visibilitychange" ); - let tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabChange" - ); await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); await promiseHidden; - await tabChangeRaised; }); const [originalTab, newTab] = gBrowser.visibleTabs; await openFirefoxViewTab(window).then(async viewTab => { const browser = viewTab.linkedBrowser; - const openTabs = getOpenTabsComponent(browser); - await openTabs.openTabsTarget.readyWindowsPromise; - await openTabs.updateComplete; - + await TestUtils.waitForTick(); const cards = getCards(browser); is(cards.length, 1, "There is one window."); let tabItems = await getRowsForCard(cards[0]); @@ -143,15 +131,10 @@ add_task(async function open_tab_same_window() { const browser = viewTab.linkedBrowser; const cards = getCards(browser); let tabItems; - let tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabChange" - ); info("Bring the new tab to the front."); gBrowser.moveTabTo(newTab, 0); - await tabChangeRaised; await BrowserTestUtils.waitForMutationCondition( cards[0].shadowRoot, { childList: true, subtree: true }, @@ -160,12 +143,7 @@ add_task(async function open_tab_same_window() { return tabItems[0].url === TEST_URL; } ); - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabChange" - ); await BrowserTestUtils.removeTab(newTab); - await tabChangeRaised; const [card] = getCards(browser); await TestUtils.waitForCondition( @@ -196,8 +174,7 @@ add_task(async function open_tab_new_window() { const browser = viewTab.linkedBrowser; await navigateToOpenTabs(browser); const openTabs = getOpenTabsComponent(browser); - await openTabs.openTabsTarget.readyWindowsPromise; - await openTabs.updateComplete; + await openTabs.getUpdateComplete(); const cards = getCards(browser); is(cards.length, 2, "There are two windows."); @@ -220,13 +197,8 @@ add_task(async function open_tab_new_window() { "The date is hidden, since we have two windows." ); info("Select a tab from the original window."); - let tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); winFocused = BrowserTestUtils.waitForEvent(window, "focus", true); originalWinRows[0].mainEl.click(); - await tabChangeRaised; }); info("Wait for the original window to be focused"); @@ -236,8 +208,7 @@ add_task(async function open_tab_new_window() { const browser = viewTab.linkedBrowser; await navigateToOpenTabs(browser); const openTabs = getOpenTabsComponent(browser); - await openTabs.openTabsTarget.readyWindowsPromise; - await openTabs.updateComplete; + await openTabs.getUpdateComplete(); const cards = getCards(browser); is(cards.length, 2, "There are two windows."); @@ -245,12 +216,7 @@ add_task(async function open_tab_new_window() { info("Select a tab from the new window."); winFocused = BrowserTestUtils.waitForEvent(win, "focus", true); - let tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); newWinRows[0].mainEl.click(); - await tabChangeRaised; }); info("Wait for the new window to be focused"); await winFocused; @@ -265,8 +231,7 @@ add_task(async function open_tab_new_private_window() { const browser = viewTab.linkedBrowser; await navigateToOpenTabs(browser); const openTabs = getOpenTabsComponent(browser); - await openTabs.openTabsTarget.readyWindowsPromise; - await openTabs.updateComplete; + await openTabs.getUpdateComplete(); const cards = getCards(browser); is(cards.length, 1, "The private window is not displayed."); @@ -279,61 +244,33 @@ add_task(async function styling_for_multiple_windows() { const browser = viewTab.linkedBrowser; await navigateToOpenTabs(browser); const openTabs = getOpenTabsComponent(browser); - await openTabs.openTabsTarget.readyWindowsPromise; - await openTabs.updateComplete; + await openTabs.getUpdateComplete(); ok( openTabs.shadowRoot.querySelector("[card-count=one]"), "The container shows one column when one window is open." ); }); - await BrowserTestUtils.openNewBrowserWindow(); - let tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabChange" - ); - await NonPrivateTabs.readyWindowsPromise; - await tabChangeRaised; - is( - NonPrivateTabs.currentWindows.length, - 2, - "NonPrivateTabs now has 2 currentWindows" - ); info("switch to firefox view in the first window"); SimpleTest.promiseFocus(window); await openFirefoxViewTab(window).then(async viewTab => { const browser = viewTab.linkedBrowser; const openTabs = getOpenTabsComponent(browser); - await openTabs.openTabsTarget.readyWindowsPromise; - await openTabs.updateComplete; - is( - openTabs.openTabsTarget.currentWindows.length, - 2, - "There should be 2 current windows" - ); + await openTabs.getUpdateComplete(); ok( openTabs.shadowRoot.querySelector("[card-count=two]"), "The container shows two columns when two windows are open." ); }); await BrowserTestUtils.openNewBrowserWindow(); - tabChangeRaised = BrowserTestUtils.waitForEvent(NonPrivateTabs, "TabChange"); - await NonPrivateTabs.readyWindowsPromise; - await tabChangeRaised; - is( - NonPrivateTabs.currentWindows.length, - 3, - "NonPrivateTabs now has 2 currentWindows" - ); SimpleTest.promiseFocus(window); await openFirefoxViewTab(window).then(async viewTab => { const browser = viewTab.linkedBrowser; const openTabs = getOpenTabsComponent(browser); - await openTabs.openTabsTarget.readyWindowsPromise; - await openTabs.updateComplete; + await openTabs.getUpdateComplete(); ok( openTabs.shadowRoot.querySelector("[card-count=three-or-more]"), @@ -369,8 +306,7 @@ add_task(async function toggle_show_more_link() { const browser = viewTab.linkedBrowser; await navigateToOpenTabs(browser); const openTabs = getOpenTabsComponent(browser); - await openTabs.openTabsTarget.readyWindowsPromise; - await openTabs.updateComplete; + await openTabs.getUpdateComplete(); const cards = getCards(browser); is(cards.length, NUMBER_OF_WINDOWS, "There are four windows."); @@ -385,8 +321,7 @@ add_task(async function toggle_show_more_link() { await openFirefoxViewTab(window).then(async viewTab => { const browser = viewTab.linkedBrowser; const openTabs = getOpenTabsComponent(browser); - await openTabs.openTabsTarget.readyWindowsPromise; - await openTabs.updateComplete; + await openTabs.getUpdateComplete(); Assert.less( (await getRowsForCard(lastCard)).length, NUMBER_OF_TABS, @@ -457,8 +392,7 @@ add_task(async function search_open_tabs() { const browser = viewTab.linkedBrowser; await navigateToOpenTabs(browser); const openTabs = getOpenTabsComponent(browser); - await openTabs.openTabsTarget.readyWindowsPromise; - await openTabs.updateComplete; + await openTabs.getUpdateComplete(); const cards = getCards(browser); is(cards.length, 2, "There are two windows."); diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_changes.js b/browser/components/firefoxview/tests/browser/browser_opentabs_changes.js deleted file mode 100644 index c293afa8cd04..000000000000 --- a/browser/components/firefoxview/tests/browser/browser_opentabs_changes.js +++ /dev/null @@ -1,541 +0,0 @@ -const { NonPrivateTabs, getTabsTargetForWindow } = ChromeUtils.importESModule( - "resource:///modules/OpenTabs.sys.mjs" -); -let privateTabsChanges; - -const tabURL1 = "data:text/html,Tab1Tab1"; -const tabURL2 = "data:text/html,Tab2Tab2"; -const tabURL3 = "data:text/html,Tab3Tab3"; -const tabURL4 = "data:text/html,Tab4Tab4"; - -const nonPrivateListener = sinon.stub(); -const privateListener = sinon.stub(); - -function tabUrl(tab) { - return tab.linkedBrowser.currentURI?.spec; -} - -function getWindowId(win) { - return win.windowGlobalChild.innerWindowId; -} - -async function setup(tabChangeEventName) { - nonPrivateListener.resetHistory(); - privateListener.resetHistory(); - - NonPrivateTabs.addEventListener(tabChangeEventName, nonPrivateListener); - - await TestUtils.waitForTick(); - is( - NonPrivateTabs.currentWindows.length, - 1, - "NonPrivateTabs has 1 window a tick after adding the event listener" - ); - - info("Opening new windows"); - let win0 = window, - win1 = await BrowserTestUtils.openNewBrowserWindow(), - privateWin = await BrowserTestUtils.openNewBrowserWindow({ - private: true, - }); - BrowserTestUtils.startLoadingURIString( - win1.gBrowser.selectedBrowser, - tabURL1 - ); - await BrowserTestUtils.browserLoaded(win1.gBrowser.selectedBrowser); - - // load a tab with a title/label we can easily verify - BrowserTestUtils.startLoadingURIString( - privateWin.gBrowser.selectedBrowser, - tabURL2 - ); - await BrowserTestUtils.browserLoaded(privateWin.gBrowser.selectedBrowser); - - is( - win1.gBrowser.selectedTab.label, - "Tab1", - "Check the tab label in the new non-private window" - ); - is( - privateWin.gBrowser.selectedTab.label, - "Tab2", - "Check the tab label in the new private window" - ); - - privateTabsChanges = getTabsTargetForWindow(privateWin); - privateTabsChanges.addEventListener(tabChangeEventName, privateListener); - is( - privateTabsChanges, - getTabsTargetForWindow(privateWin), - "getTabsTargetForWindow reuses a single instance per exclusive window" - ); - - await TestUtils.waitForTick(); - is( - NonPrivateTabs.currentWindows.length, - 2, - "NonPrivateTabs has 2 windows once openNewBrowserWindow resolves" - ); - is( - privateTabsChanges.currentWindows.length, - 1, - "privateTabsChanges has 1 window once openNewBrowserWindow resolves" - ); - - await SimpleTest.promiseFocus(win0); - info("setup, win0 has id: " + getWindowId(win0)); - info("setup, win1 has id: " + getWindowId(win1)); - info("setup, privateWin has id: " + getWindowId(privateWin)); - info("setup,waiting for both private and nonPrivateListener to be called"); - await TestUtils.waitForCondition(() => { - return nonPrivateListener.called && privateListener.called; - }); - nonPrivateListener.resetHistory(); - privateListener.resetHistory(); - - const cleanup = async eventName => { - NonPrivateTabs.removeEventListener(eventName, nonPrivateListener); - privateTabsChanges.removeEventListener(eventName, privateListener); - await SimpleTest.promiseFocus(window); - await promiseAllButPrimaryWindowClosed(); - }; - return { windows: [win0, win1, privateWin], cleanup }; -} - -add_task(async function test_TabChanges() { - const { windows, cleanup } = await setup("TabChange"); - const [win0, win1, privateWin] = windows; - let tabChangeRaised; - let changeEvent; - - info( - "Verify that manipulating tabs in a non-private window dispatches events on the correct target" - ); - for (let win of [win0, win1]) { - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabChange" - ); - let newTab = await BrowserTestUtils.openNewForegroundTab( - win.gBrowser, - tabURL1 - ); - changeEvent = await tabChangeRaised; - Assert.deepEqual( - changeEvent.detail.windowIds, - [getWindowId(win)], - "The event had the correct window id" - ); - - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabChange" - ); - const navigateUrl = "https://example.org/"; - BrowserTestUtils.startLoadingURIString(newTab.linkedBrowser, navigateUrl); - await BrowserTestUtils.browserLoaded( - newTab.linkedBrowser, - null, - navigateUrl - ); - // navigation in a tab changes the label which should produce a change event - changeEvent = await tabChangeRaised; - Assert.deepEqual( - changeEvent.detail.windowIds, - [getWindowId(win)], - "The event had the correct window id" - ); - - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabChange" - ); - BrowserTestUtils.removeTab(newTab); - // navigation in a tab changes the label which should produce a change event - changeEvent = await tabChangeRaised; - Assert.deepEqual( - changeEvent.detail.windowIds, - [getWindowId(win)], - "The event had the correct window id" - ); - } - - info( - "make sure a change to a private window doesnt dispatch on a nonprivate target" - ); - nonPrivateListener.resetHistory(); - privateListener.resetHistory(); - - tabChangeRaised = BrowserTestUtils.waitForEvent( - privateTabsChanges, - "TabChange" - ); - BrowserTestUtils.addTab(privateWin.gBrowser, tabURL1); - changeEvent = await tabChangeRaised; - info( - `Check windowIds adding tab to private window: ${getWindowId( - privateWin - )}: ${JSON.stringify(changeEvent.detail.windowIds)}` - ); - Assert.deepEqual( - changeEvent.detail.windowIds, - [getWindowId(privateWin)], - "The event had the correct window id" - ); - await TestUtils.waitForTick(); - Assert.ok( - nonPrivateListener.notCalled, - "A private tab change shouldnt raise a tab change event on the non-private target" - ); - - info("testTabChanges complete"); - await cleanup("TabChange"); -}); - -add_task(async function test_TabRecencyChange() { - const { windows, cleanup } = await setup("TabRecencyChange"); - const [win0, win1, privateWin] = windows; - - let tabChangeRaised; - let changeEvent; - let sortedTabs; - - info("Open some tabs in the non-private windows"); - for (let win of [win0, win1]) { - for (let url of [tabURL1, tabURL2]) { - let tab = BrowserTestUtils.addTab(win.gBrowser, url); - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabChange" - ); - await BrowserTestUtils.browserLoaded(tab.linkedBrowser); - await tabChangeRaised; - } - } - - info("Verify switching tabs produces the expected event and result"); - nonPrivateListener.resetHistory(); - privateListener.resetHistory(); - - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); - BrowserTestUtils.switchTab(win0.gBrowser, win0.gBrowser.tabs.at(-1)); - changeEvent = await tabChangeRaised; - - Assert.deepEqual( - changeEvent.detail.windowIds, - [getWindowId(win0)], - "The recency change event had the correct window id" - ); - Assert.ok( - nonPrivateListener.called, - "Sanity check that the non-private tabs listener was called" - ); - Assert.ok( - privateListener.notCalled, - "The private tabs listener was not called" - ); - - sortedTabs = NonPrivateTabs.getRecentTabs(); - is( - sortedTabs[0], - win0.gBrowser.selectedTab, - "The most-recent tab is the selected tab" - ); - - info("Verify switching window produces the expected event and result"); - nonPrivateListener.resetHistory(); - privateListener.resetHistory(); - - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); - await SimpleTest.promiseFocus(win1); - changeEvent = await tabChangeRaised; - Assert.deepEqual( - changeEvent.detail.windowIds, - [getWindowId(win1)], - "The recency change event had the correct window id" - ); - Assert.ok( - nonPrivateListener.called, - "Sanity check that the non-private tabs listener was called" - ); - Assert.ok( - privateListener.notCalled, - "The private tabs listener was not called" - ); - - sortedTabs = NonPrivateTabs.getRecentTabs(); - is( - sortedTabs[0], - win1.gBrowser.selectedTab, - "The most-recent tab is the selected tab in the current window" - ); - - info("Verify behavior with private window changes"); - nonPrivateListener.resetHistory(); - privateListener.resetHistory(); - - tabChangeRaised = BrowserTestUtils.waitForEvent( - privateTabsChanges, - "TabRecencyChange" - ); - await SimpleTest.promiseFocus(privateWin); - changeEvent = await tabChangeRaised; - Assert.deepEqual( - changeEvent.detail.windowIds, - [getWindowId(privateWin)], - "The recency change event had the correct window id" - ); - Assert.ok( - nonPrivateListener.notCalled, - "The non-private listener got no recency-change events from the private window" - ); - Assert.ok( - privateListener.called, - "Sanity check the private tabs listener was called" - ); - - sortedTabs = privateTabsChanges.getRecentTabs(); - is( - sortedTabs[0], - privateWin.gBrowser.selectedTab, - "The most-recent tab is the selected tab in the current window" - ); - sortedTabs = NonPrivateTabs.getRecentTabs(); - is( - sortedTabs[0], - win1.gBrowser.selectedTab, - "The most-recent non-private tab is still the selected tab in the previous non-private window" - ); - - info("Verify adding a tab to a private window does the right thing"); - nonPrivateListener.resetHistory(); - privateListener.resetHistory(); - - tabChangeRaised = BrowserTestUtils.waitForEvent( - privateTabsChanges, - "TabRecencyChange" - ); - await BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, tabURL3); - changeEvent = await tabChangeRaised; - Assert.deepEqual( - changeEvent.detail.windowIds, - [getWindowId(privateWin)], - "The event had the correct window id" - ); - Assert.ok( - nonPrivateListener.notCalled, - "The non-private listener got no recency-change events from the private window" - ); - sortedTabs = privateTabsChanges.getRecentTabs(); - is( - tabUrl(sortedTabs[0]), - tabURL3, - "The most-recent tab is the tab we just opened in the private window" - ); - - nonPrivateListener.resetHistory(); - privateListener.resetHistory(); - - tabChangeRaised = BrowserTestUtils.waitForEvent( - privateTabsChanges, - "TabRecencyChange" - ); - BrowserTestUtils.switchTab(privateWin.gBrowser, privateWin.gBrowser.tabs[0]); - changeEvent = await tabChangeRaised; - Assert.deepEqual( - changeEvent.detail.windowIds, - [getWindowId(privateWin)], - "The event had the correct window id" - ); - Assert.ok( - nonPrivateListener.notCalled, - "The non-private listener got no recency-change events from the private window" - ); - sortedTabs = privateTabsChanges.getRecentTabs(); - is( - sortedTabs[0], - privateWin.gBrowser.selectedTab, - "The most-recent tab is the selected tab in the private window" - ); - - info("Verify switching back to a non-private does the right thing"); - nonPrivateListener.resetHistory(); - privateListener.resetHistory(); - - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); - await SimpleTest.promiseFocus(win1); - await tabChangeRaised; - if (privateListener.called) { - info(`The private listener was called ${privateListener.callCount} times`); - } - Assert.ok( - privateListener.notCalled, - "The private listener got no recency-change events for the non-private window" - ); - Assert.ok( - nonPrivateListener.called, - "Sanity-check the non-private listener got a recency-change event for the non-private window" - ); - - sortedTabs = privateTabsChanges.getRecentTabs(); - is( - sortedTabs[0], - privateWin.gBrowser.selectedTab, - "The most-recent private tab is unchanged" - ); - - sortedTabs = NonPrivateTabs.getRecentTabs(); - is( - sortedTabs[0], - win1.gBrowser.selectedTab, - "The most-recent non-private tab is the selected tab in the current window" - ); - - await cleanup("TabRecencyChange"); - while (win0.gBrowser.tabs.length > 1) { - info( - "Removing last tab:" + - win0.gBrowser.tabs.at(-1).linkedBrowser.currentURI.spec - ); - BrowserTestUtils.removeTab(win0.gBrowser.tabs.at(-1)); - info("Removed, tabs.length:" + win0.gBrowser.tabs.length); - } -}); - -add_task(async function test_tabNavigations() { - const { windows, cleanup } = await setup("TabChange"); - const [, win1, privateWin] = windows; - - // also listen for TabRecencyChange events - const nonPrivateRecencyListener = sinon.stub(); - const privateRecencyListener = sinon.stub(); - privateTabsChanges.addEventListener( - "TabRecencyChange", - privateRecencyListener - ); - NonPrivateTabs.addEventListener( - "TabRecencyChange", - nonPrivateRecencyListener - ); - - info( - `Verify navigating in tab generates TabChange & TabRecencyChange events` - ); - let loaded = BrowserTestUtils.browserLoaded(win1.gBrowser.selectedBrowser); - win1.gBrowser.selectedBrowser.loadURI(Services.io.newURI(tabURL4), { - triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), - }); - info("waiting for the load into win1 tab to complete"); - await loaded; - info("waiting for listeners to be called"); - await BrowserTestUtils.waitForCondition(() => { - return nonPrivateListener.called && nonPrivateRecencyListener.called; - }); - ok(!privateListener.called, "The private TabChange listener was not called"); - ok( - !privateRecencyListener.called, - "The private TabRecencyChange listener was not called" - ); - - nonPrivateListener.resetHistory(); - privateListener.resetHistory(); - nonPrivateRecencyListener.resetHistory(); - privateRecencyListener.resetHistory(); - - // Now verify the same with a private window - info( - `Verify navigating in private tab generates TabChange & TabRecencyChange events` - ); - ok( - !nonPrivateListener.called, - "The non-private TabChange listener is not yet called" - ); - - loaded = BrowserTestUtils.browserLoaded(privateWin.gBrowser.selectedBrowser); - privateWin.gBrowser.selectedBrowser.loadURI( - Services.io.newURI("about:robots"), - { - triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), - } - ); - info("waiting for the load into privateWin tab to complete"); - await loaded; - info("waiting for the privateListeners to be called"); - await BrowserTestUtils.waitForCondition(() => { - return privateListener.called && privateRecencyListener.called; - }); - ok( - !nonPrivateListener.called, - "The non-private TabChange listener was not called" - ); - ok( - !nonPrivateRecencyListener.called, - "The non-private TabRecencyChange listener was not called" - ); - - // cleanup - privateTabsChanges.removeEventListener( - "TabRecencyChange", - privateRecencyListener - ); - NonPrivateTabs.removeEventListener( - "TabRecencyChange", - nonPrivateRecencyListener - ); - - await cleanup(); -}); - -add_task(async function test_tabsFromPrivateWindows() { - const { cleanup } = await setup("TabChange"); - const private2Listener = sinon.stub(); - - const private2Win = await BrowserTestUtils.openNewBrowserWindow({ - private: true, - waitForTabURL: "about:privatebrowsing", - }); - const private2TabsChanges = getTabsTargetForWindow(private2Win); - private2TabsChanges.addEventListener("TabChange", private2Listener); - ok( - privateTabsChanges !== getTabsTargetForWindow(private2Win), - "getTabsTargetForWindow creates a distinct instance for a different private window" - ); - - await BrowserTestUtils.waitForCondition(() => private2Listener.called); - - ok( - !privateListener.called, - "No TabChange event was raised by opening a different private window" - ); - privateListener.resetHistory(); - private2Listener.resetHistory(); - - BrowserTestUtils.addTab(private2Win.gBrowser, tabURL1); - await BrowserTestUtils.waitForCondition(() => private2Listener.called); - ok( - !privateListener.called, - "No TabChange event was raised by adding tab to a different private window" - ); - - is( - privateTabsChanges.getRecentTabs().length, - 1, - "The recent tab count for the first private window tab target only reports the tabs for its associated windodw" - ); - is( - private2TabsChanges.getRecentTabs().length, - 2, - "The recent tab count for a 2nd private window tab target only reports the tabs for its associated windodw" - ); - - await cleanup("TabChange"); -}); diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js index 6f31d5ee14b3..137176c3b51e 100644 --- a/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js +++ b/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js @@ -20,10 +20,6 @@ const fxaDevicesWithCommands = [ }, ]; -const { NonPrivateTabs } = ChromeUtils.importESModule( - "resource:///modules/OpenTabs.sys.mjs" -); - function getCards(openTabs) { return openTabs.shadowRoot.querySelectorAll("view-opentabs-card"); } @@ -33,64 +29,6 @@ async function getRowsForCard(card) { return card.tabList.rowEls; } -async function add_new_tab(URL) { - let tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabChange" - ); - let tab = BrowserTestUtils.addTab(gBrowser, URL); - // wait so we can reliably compare the tab URL - await BrowserTestUtils.browserLoaded(tab.linkedBrowser); - await tabChangeRaised; - return tab; -} - -function getVisibleTabURLs(win = window) { - return win.gBrowser.visibleTabs.map(tab => tab.linkedBrowser.currentURI.spec); -} - -function getTabRowURLs(rows) { - return Array.from(rows).map(row => row.url); -} - -async function waitUntilRowsMatch(openTabs, cardIndex, expectedURLs) { - let card; - - info( - "moreMenuSetup: openTabs has openTabsTarget?:" + !!openTabs?.openTabsTarget - ); - //await openTabs.openTabsTarget.readyWindowsPromise; - info( - `waitUntilRowsMatch, wait for there to be at least ${cardIndex + 1} cards` - ); - await BrowserTestUtils.waitForCondition(() => { - if (!openTabs.initialWindowsReady) { - info("openTabs.initialWindowsReady isn't true"); - return false; - } - try { - card = getCards(openTabs)[cardIndex]; - } catch (ex) { - info("Calling getCards produced exception: " + ex.message); - } - return !!card; - }, "Waiting for openTabs to be ready and to get the cards"); - - const expectedURLsAsString = JSON.stringify(expectedURLs); - info(`Waiting for row URLs to match ${expectedURLs.join(", ")}`); - await BrowserTestUtils.waitForMutationCondition( - card.shadowRoot, - { characterData: true, childList: true, subtree: true }, - async () => { - let rows = await getRowsForCard(card); - return ( - rows.length == expectedURLs.length && - JSON.stringify(getTabRowURLs(rows)) == expectedURLsAsString - ); - } - ); -} - async function getContextMenuPanelListForCard(card) { let menuContainer = card.shadowRoot.querySelector( "view-opentabs-contextmenu" @@ -118,26 +56,25 @@ async function openContextMenuForItem(tabItem, card) { return panelList; } -async function moreMenuSetup() { - await add_new_tab(TEST_URL2); - await add_new_tab(TEST_URL3); +async function moreMenuSetup(document) { + await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL2); + await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL3); // once we've opened a few tabs, navigate to the open tabs section in firefox view await clickFirefoxViewButton(window); - const document = window.FirefoxViewHandler.tab.linkedBrowser.contentDocument; - - await navigateToCategoryAndWait(document, "opentabs"); + navigateToCategory(document, "opentabs"); let openTabs = document.querySelector("view-opentabs[name=opentabs]"); - await openTabs.openTabsTarget.readyWindowsPromise; - - info("waiting for openTabs' first card rows"); - await waitUntilRowsMatch(openTabs, 0, getVisibleTabURLs()); - let cards = getCards(openTabs); + let cards; + await TestUtils.waitForCondition(() => { + cards = getCards(openTabs); + return cards.length == 1; + }); is(cards.length, 1, "There is one open window."); let rows = await getRowsForCard(cards[0]); + is(rows.length, 3, "There are three tabs in the open tabs list."); let firstTab = rows[0]; @@ -153,6 +90,7 @@ async function moreMenuSetup() { add_task(async function test_more_menus() { await withFirefoxView({}, async browser => { + const { document } = browser.contentWindow; let win = browser.ownerGlobal; let shown, menuHidden; @@ -163,22 +101,11 @@ add_task(async function test_more_menus() { "Selected tab is about:blank" ); - info(`Loading ${TEST_URL1} into the selected about:blank tab`); - let tabLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); - win.gURLBar.focus(); win.gURLBar.value = TEST_URL1; EventUtils.synthesizeKey("KEY_Enter", {}, win); - await tabLoaded; - - info("Waiting for moreMenuSetup to resolve"); - let [cards, rows] = await moreMenuSetup(); - Assert.deepEqual( - getVisibleTabURLs(), - [TEST_URL1, TEST_URL2, TEST_URL3], - "Prepared 3 open tabs" - ); + let [cards, rows] = await moreMenuSetup(document); let firstTab = rows[0]; // Open the panel list (more menu) from the first list item let panelList = await openContextMenuForItem(firstTab, cards[0]); @@ -209,33 +136,30 @@ add_task(async function test_more_menus() { ]; // close a tab via the menu - let tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabChange" - ); menuHidden = BrowserTestUtils.waitForEvent(panelList, "hidden"); panelItemButton.click(); - info("Waiting for result of closing a tab via the menu"); - await tabChangeRaised; await cards[0].getUpdateComplete(); await menuHidden; await telemetryEvent(contextMenuEvent); - Assert.deepEqual( - getVisibleTabURLs(), - [TEST_URL2, TEST_URL3], - "Got the expected 2 open tabs" - ); - - let openTabs = cards[0].ownerDocument.querySelector( - "view-opentabs[name=opentabs]" - ); - await waitUntilRowsMatch(openTabs, 0, [TEST_URL2, TEST_URL3]); + let visibleTabs = gBrowser.visibleTabs; + is(visibleTabs.length, 2, "Expected to now have 2 open tabs"); // Move Tab submenu item firstTab = rows[0]; is(firstTab.url, TEST_URL2, `First tab list item is ${TEST_URL2}`); + is( + visibleTabs[0].linkedBrowser.currentURI.spec, + TEST_URL2, + `First tab in tab strip is ${TEST_URL2}` + ); + is( + visibleTabs[visibleTabs.length - 1].linkedBrowser.currentURI.spec, + TEST_URL3, + `Last tab in tab strip is ${TEST_URL3}` + ); + panelList = await openContextMenuForItem(firstTab, cards[0]); let moveTabsPanelItem = panelList.querySelector( "panel-item[data-l10n-id=fxviewtabrow-move-tab]" @@ -267,27 +191,35 @@ add_task(async function test_more_menus() { // click on the first option, which should be "Move to the end" since // this is the first tab menuHidden = BrowserTestUtils.waitForEvent(panelList, "hidden"); - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabChange" - ); EventUtils.synthesizeKey("KEY_Enter", {}); - info("Waiting for result of moving a tab via the menu"); await telemetryEvent(contextMenuEvent); await menuHidden; - await tabChangeRaised; - Assert.deepEqual( - getVisibleTabURLs(), - [TEST_URL3, TEST_URL2], - "The last tab became the first tab" + visibleTabs = gBrowser.visibleTabs; + is( + visibleTabs[0].linkedBrowser.currentURI.spec, + TEST_URL3, + `First tab in tab strip is now ${TEST_URL3}` + ); + is( + visibleTabs[visibleTabs.length - 1].linkedBrowser.currentURI.spec, + TEST_URL2, + `Last tab in tab strip is now ${TEST_URL2}` ); // this entire "move tabs" submenu test can be reordered above // closing a tab since it very clearly reveals the issues // outlined in bug 1852622 when there are 3 or more tabs open // and one is moved via the more menus. - await waitUntilRowsMatch(openTabs, 0, [TEST_URL3, TEST_URL2]); + await BrowserTestUtils.waitForMutationCondition( + cards[0].shadowRoot, + { characterData: true, childList: true, subtree: true }, + async () => { + rows = await getRowsForCard(cards[0]); + firstTab = rows[0]; + return firstTab.url == TEST_URL3; + } + ); // Copy Link menu item (copyLink function that's called is a member of Viewpage.mjs) panelList = await openContextMenuForItem(firstTab, cards[0]); @@ -352,12 +284,7 @@ add_task(async function test_send_device_submenu() { .callsFake(() => fxaDevicesWithCommands); await withFirefoxView({}, async browser => { - // TEST_URL2 is our only tab, left over from previous test - Assert.deepEqual( - getVisibleTabURLs(), - [TEST_URL2], - `We initially have a single ${TEST_URL2} tab` - ); + const { document } = browser.contentWindow; let shown; Services.obs.notifyObservers(null, UIState.ON_UPDATE); diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js b/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js index 63574ae23664..d5b71050d464 100644 --- a/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js +++ b/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js @@ -13,9 +13,6 @@ const tabURL4 = "data:,Tab4"; let gInitialTab; let gInitialTabURL; -const { NonPrivateTabs } = ChromeUtils.importESModule( - "resource:///modules/OpenTabs.sys.mjs" -); add_setup(function () { gInitialTab = gBrowser.selectedTab; @@ -166,14 +163,8 @@ add_task(async function test_single_window_tabs() { browser.contentDocument, "visibilitychange" ); - - let tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); await BrowserTestUtils.switchTab(gBrowser, gBrowser.visibleTabs[0]); await promiseHidden; - await tabChangeRaised; }); // and check the results in the open tabs section of Recent Browsing @@ -187,7 +178,6 @@ add_task(async function test_single_window_tabs() { add_task(async function test_multiple_window_tabs() { const fxViewURL = getFirefoxViewURL(); const win1 = window; - let tabChangeRaised; await prepareOpenTabs([tabURL1, tabURL2]); const win2 = await BrowserTestUtils.openNewBrowserWindow(); await prepareOpenTabs([tabURL3, tabURL4], win2); @@ -206,10 +196,6 @@ add_task(async function test_multiple_window_tabs() { ); info("Switching to first tab (tab3) in win2"); - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); let promiseHidden = BrowserTestUtils.waitForEvent( browser.contentDocument, "visibilitychange" @@ -223,7 +209,6 @@ add_task(async function test_multiple_window_tabs() { tabURL3, `The selected tab in window 2 is ${tabURL3}` ); - await tabChangeRaised; await promiseHidden; }); @@ -235,12 +220,7 @@ add_task(async function test_multiple_window_tabs() { }); info("Focusing win1, where tab2 should be selected"); - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); await SimpleTest.promiseFocus(win1); - await tabChangeRaised; Assert.equal( tabUrl(win1.gBrowser.selectedTab), tabURL2, @@ -259,17 +239,12 @@ add_task(async function test_multiple_window_tabs() { browser.contentDocument, "visibilitychange" ); - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); info("Switching to first visible tab (tab1) in win1"); await BrowserTestUtils.switchTab( win1.gBrowser, win1.gBrowser.visibleTabs[0] ); await promiseHidden; - await tabChangeRaised; }); // check result in the fxview in the 1st window @@ -287,41 +262,27 @@ add_task(async function test_windows_activation() { const win1 = window; await prepareOpenTabs([tabURL1], win1); let fxViewTab; - let tabChangeRaised; info("switch to firefox-view and leave it selected"); await openFirefoxViewTab(win1).then(tab => (fxViewTab = tab)); const win2 = await BrowserTestUtils.openNewBrowserWindow(); await prepareOpenTabs([tabURL2], win2); - const win3 = await BrowserTestUtils.openNewBrowserWindow(); await prepareOpenTabs([tabURL3], win3); - await tabChangeRaised; - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); await SimpleTest.promiseFocus(win1); - await tabChangeRaised; const browser = fxViewTab.linkedBrowser; await checkTabList(browser, [tabURL3, tabURL2, tabURL1]); info("switch to win2 and confirm its selected tab becomes most recent"); - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); await SimpleTest.promiseFocus(win2); - await tabChangeRaised; await checkTabList(browser, [tabURL2, tabURL3, tabURL1]); await cleanup(win2, win3); }); add_task(async function test_minimize_restore_windows() { const win1 = window; - let tabChangeRaised; await prepareOpenTabs([tabURL1, tabURL2]); const win2 = await BrowserTestUtils.openNewBrowserWindow(); await prepareOpenTabs([tabURL3, tabURL4], win2); @@ -337,29 +298,19 @@ add_task(async function test_minimize_restore_windows() { browser.contentDocument, "visibilitychange" ); - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); info("Switching to the first tab (tab3) in 2nd window"); await BrowserTestUtils.switchTab( win2.gBrowser, win2.gBrowser.visibleTabs[0] ); await promiseHidden; - await tabChangeRaised; }); // then minimize the window, focusing the 1st window info("Minimizing win2, leaving tab 3 selected"); - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); await minimizeWindow(win2); info("Focusing win1, where tab2 is selected - making it most recent"); await SimpleTest.promiseFocus(win1); - await tabChangeRaised; Assert.equal( tabUrl(win1.gBrowser.selectedTab), @@ -374,13 +325,8 @@ add_task(async function test_minimize_restore_windows() { info( "Restoring win2 and focusing it - which should make its selected tab most recent" ); - tabChangeRaised = BrowserTestUtils.waitForEvent( - NonPrivateTabs, - "TabRecencyChange" - ); await restoreWindow(win2); await SimpleTest.promiseFocus(win2); - await tabChangeRaised; info( "Checking tab order in fxview in win1, to confirm tab3 is most recent" diff --git a/browser/components/firefoxview/viewpage.mjs b/browser/components/firefoxview/viewpage.mjs index fee02b49d65b..b4bdaadc0b53 100644 --- a/browser/components/firefoxview/viewpage.mjs +++ b/browser/components/firefoxview/viewpage.mjs @@ -70,9 +70,10 @@ export class ViewPageContent extends MozLitElement { return window.browsingContext.embedderWindowGlobal.browsingContext.window; } - get isSelectedBrowserTab() { - const { gBrowser } = this.getWindow(); - return gBrowser.selectedBrowser.browsingContext == window.browsingContext; + getBrowserTab() { + return this.getWindow().gBrowser.getTabForBrowser( + window.browsingContext.embedderElement + ); } copyLink(e) { diff --git a/browser/modules/EveryWindow.sys.mjs b/browser/modules/EveryWindow.sys.mjs index 704240b54f92..7df784a6e57a 100644 --- a/browser/modules/EveryWindow.sys.mjs +++ b/browser/modules/EveryWindow.sys.mjs @@ -38,15 +38,6 @@ function callForEveryWindow(callback) { export const EveryWindow = { /** - * The current list of all browser windows whose delayedStartupPromise has resolved - */ - get readyWindows() { - return Array.from(Services.wm.getEnumerator("navigator:browser")).filter( - win => win.gBrowserInit?.delayedStartupFinished - ); - }, - - /** * Registers init and uninit functions to be called on every window. * * @param {string} id A unique identifier for the consumer, to be -- 2.11.4.GIT