Backed out changeset 99babcb9f349 (bug 1855704) for causing mochitest failures at dom/animation/test/chrome/test_animation_observers_async.html CLOSED TREE

This commit is contained in:
Sandor Molnar
2024-01-27 03:18:38 +02:00
parent b81d2e8be2
commit 943300c574
10 changed files with 209 additions and 1304 deletions

View File

@@ -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<Tab>}
* The list of visible tabs for the browser window
*/
getTabsForWindow(win) {
if (this.currentWindows.includes(win)) {
return [...win.gBrowser.visibleTabs];
}
return [];
}
/*
* @returns {Array<Tab>}
* 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 };

View File

@@ -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<Window>} windows
* A list of windows with the same privateness
* @property {Map<Window, MozTabbrowserTab[]>} 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);
}
Services.obs.addObserver(this.boundObserve, TOPIC_CURRENT_BROWSER_CHANGED);
// 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();
});
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`<view-opentabs-card
.tabs=${tabs}
.recentBrowsing=${true}
@@ -264,46 +303,76 @@ class OpenTabsInView extends ViewPage {
this.onSearchQuery({ detail });
return;
}
let windowIds;
const win = target.ownerGlobal;
const tabs = this.windows.get(win);
switch (type) {
case "TabRecencyChange":
case "TabChange":
case "TabSelect": {
// if we're switching away from our tab, we can halt any updates immediately
if (!this.isSelectedBrowserTab) {
if (detail.previousTab == this.getBrowserTab()) {
this.stop();
}
return;
}
case "TabAttrModified":
if (
!detail.changed.some(attr =>
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<Window, MozTabbrowserTab[]>}
*/
_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);

View File

@@ -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"]

View File

@@ -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);

View File

@@ -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.");

View File

@@ -1,541 +0,0 @@
const { NonPrivateTabs, getTabsTargetForWindow } = ChromeUtils.importESModule(
"resource:///modules/OpenTabs.sys.mjs"
);
let privateTabsChanges;
const tabURL1 = "data:text/html,<title>Tab1</title>Tab1";
const tabURL2 = "data:text/html,<title>Tab2</title>Tab2";
const tabURL3 = "data:text/html,<title>Tab3</title>Tab3";
const tabURL4 = "data:text/html,<title>Tab4</title>Tab4";
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");
});

View File

@@ -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);

View File

@@ -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"

View File

@@ -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) {