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:
@@ -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 };
|
||||
@@ -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);
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user