mirror of
https://github.com/zen-browser/desktop.git
synced 2026-01-09 23:03:35 +00:00
test: Started adding tests for window sync, b=no-bug, c=tests, workspaces
This commit is contained in:
@@ -14,6 +14,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
SessionStore: 'resource:///modules/sessionstore/SessionStore.sys.mjs',
|
||||
SessionSaver: 'resource:///modules/sessionstore/SessionSaver.sys.mjs',
|
||||
setTimeout: 'resource://gre/modules/Timer.sys.mjs',
|
||||
gWindowSyncEnabled: 'resource:///modules/zen/ZenWindowSync.sys.mjs',
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(lazy, 'gShouldLog', 'zen.session-store.log', true);
|
||||
@@ -125,6 +126,7 @@ export class nsZenSessionManager {
|
||||
*/
|
||||
async readFile() {
|
||||
try {
|
||||
this.log('Reading Zen session file from disk');
|
||||
let promises = [];
|
||||
promises.push(this.#file.load());
|
||||
if (!Services.prefs.getBoolPref(MIGRATION_PREF, false)) {
|
||||
@@ -145,6 +147,7 @@ export class nsZenSessionManager {
|
||||
* The initial session state read from the session file.
|
||||
*/
|
||||
onFileRead(initialState) {
|
||||
if (!lazy.gWindowSyncEnabled) return;
|
||||
// For the first time after migration, we restore the tabs
|
||||
// That where going to be restored by SessionStore. The sidebar
|
||||
// object will always be empty after migration because we haven't
|
||||
@@ -233,7 +236,7 @@ export class nsZenSessionManager {
|
||||
* @param state The current session state.
|
||||
*/
|
||||
saveState(state) {
|
||||
if (!state?.windows?.length) {
|
||||
if (!state?.windows?.length || !lazy.gWindowSyncEnabled) {
|
||||
// Don't save (or even collect) anything in permanent private
|
||||
// browsing mode. We also don't want to save if there are no windows.
|
||||
return;
|
||||
@@ -350,7 +353,7 @@ export class nsZenSessionManager {
|
||||
* Whether this new window is being restored from a closed window.
|
||||
*/
|
||||
restoreNewWindow(aWindow, SessionStoreInternal, fromClosedWindow = false) {
|
||||
if (aWindow.gZenWorkspaces?.privateWindowOrDisabled) {
|
||||
if (aWindow.gZenWorkspaces?.privateWindowOrDisabled || !lazy.gWindowSyncEnabled) {
|
||||
return;
|
||||
}
|
||||
this.log('Restoring new window with Zen session data');
|
||||
@@ -401,6 +404,7 @@ export class nsZenSessionManager {
|
||||
* @returns
|
||||
*/
|
||||
onNewEmptySession(aWindow) {
|
||||
this.log('Restoring empty session with Zen session data');
|
||||
aWindow.gZenWorkspaces.restoreWorkspacesFromSessionStore({
|
||||
spaces: this.#sidebar.spaces || [],
|
||||
});
|
||||
|
||||
@@ -68,6 +68,12 @@ class nsZenWindowSync {
|
||||
lastHandlerPromise: Promise.resolve(),
|
||||
};
|
||||
|
||||
/**
|
||||
* Map of sync handlers for different event types.
|
||||
* Each handler is a function that takes the event as an argument.
|
||||
*/
|
||||
#syncHandlers = new Set();
|
||||
|
||||
/**
|
||||
* Last focused window.
|
||||
* Used to determine which window to sync tab contents visibility from.
|
||||
@@ -280,6 +286,25 @@ class nsZenWindowSync {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a sync handler for a specific event type.
|
||||
* @param {Function} aHandler - The sync handler function to add.
|
||||
*/
|
||||
addSyncHandler(aHandler) {
|
||||
if (!aHandler || this.#syncHandlers.has(aHandler)) {
|
||||
return;
|
||||
}
|
||||
this.#syncHandlers.add(aHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a sync handler for a specific event type.
|
||||
* @param {Function} aHandler - The sync handler function to remove.
|
||||
*/
|
||||
removeSyncHandler(aHandler) {
|
||||
this.#syncHandlers.delete(aHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the next event by calling the appropriate handler method.
|
||||
*
|
||||
@@ -289,7 +314,17 @@ class nsZenWindowSync {
|
||||
const handler = `on_${aEvent.type}`;
|
||||
try {
|
||||
if (typeof this[handler] === 'function') {
|
||||
return this[handler](aEvent) || Promise.resolve();
|
||||
let promise = this[handler](aEvent) || Promise.resolve();
|
||||
promise.then(() => {
|
||||
for (let syncHandler of this.#syncHandlers) {
|
||||
try {
|
||||
syncHandler(aEvent);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
} else {
|
||||
throw new Error(`No handler for event type: ${aEvent.type}`);
|
||||
}
|
||||
@@ -308,7 +343,7 @@ class nsZenWindowSync {
|
||||
}
|
||||
let permanentKey = aTab.linkedBrowser.permanentKey;
|
||||
this.#runOnAllWindows(null, (win) => {
|
||||
const tab = this.#getItemFromWindow(win, aTab.id);
|
||||
const tab = this.getItemFromWindow(win, aTab.id);
|
||||
if (tab) {
|
||||
tab.linkedBrowser.permanentKey = permanentKey;
|
||||
tab.permanentKey = permanentKey;
|
||||
@@ -323,7 +358,7 @@ class nsZenWindowSync {
|
||||
* @param {string} aItemId - The ID of the item to retrieve.
|
||||
* @returns {MozTabbrowserTab|MozTabbrowserTabGroup|null} The item element if found, otherwise null.
|
||||
*/
|
||||
#getItemFromWindow(aWindow, aItemId) {
|
||||
getItemFromWindow(aWindow, aItemId) {
|
||||
if (!aItemId) {
|
||||
return null;
|
||||
}
|
||||
@@ -453,7 +488,7 @@ class nsZenWindowSync {
|
||||
let container;
|
||||
const parentGroup = aOriginalItem.group;
|
||||
if (parentGroup?.hasAttribute('id')) {
|
||||
container = this.#getItemFromWindow(aWindow, parentGroup.getAttribute('id'));
|
||||
container = this.getItemFromWindow(aWindow, parentGroup.getAttribute('id'));
|
||||
if (container) {
|
||||
if (container?.tabs?.length) {
|
||||
// First tab in folders is the empty tab placeholder.
|
||||
@@ -480,7 +515,7 @@ class nsZenWindowSync {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const relativeTab = this.#getItemFromWindow(aWindow, originalSibling.id);
|
||||
const relativeTab = this.getItemFromWindow(aWindow, originalSibling.id);
|
||||
if (relativeTab) {
|
||||
gBrowser.tabContainer.tabDragAndDrop.handle_drop_transition(
|
||||
relativeTab,
|
||||
@@ -502,7 +537,7 @@ class nsZenWindowSync {
|
||||
#syncItemForAllWindows(aItem, flags = 0) {
|
||||
const window = aItem.ownerGlobal;
|
||||
this.#runOnAllWindows(window, (win) => {
|
||||
this.#syncItemWithOriginal(aItem, this.#getItemFromWindow(win, aItem.id), win, flags);
|
||||
this.#syncItemWithOriginal(aItem, this.getItemFromWindow(win, aItem.id), win, flags);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -713,7 +748,7 @@ class nsZenWindowSync {
|
||||
*/
|
||||
#getActiveTabFromOtherWindows(aWindow, aTabId, filter = (tab) => tab?._zenContentsVisible) {
|
||||
return this.#runOnAllWindows(aWindow, (win) => {
|
||||
const tab = this.#getItemFromWindow(win, aTabId);
|
||||
const tab = this.getItemFromWindow(win, aTabId);
|
||||
if (filter(tab)) {
|
||||
return tab;
|
||||
}
|
||||
@@ -735,7 +770,7 @@ class nsZenWindowSync {
|
||||
(tab) => tab._zenContentsVisible
|
||||
);
|
||||
for (let tab of activeTabsOnClosedWindow) {
|
||||
const targetTab = this.#getItemFromWindow(mostRecentWindow, tab.id);
|
||||
const targetTab = this.getItemFromWindow(mostRecentWindow, tab.id);
|
||||
if (targetTab) {
|
||||
targetTab._zenContentsVisible = true;
|
||||
this.log(`Moving active tab ${tab.id} to most recent window on close`);
|
||||
@@ -830,7 +865,7 @@ class nsZenWindowSync {
|
||||
image: state.image,
|
||||
};
|
||||
this.#runOnAllWindows(null, (win) => {
|
||||
const targetTab = this.#getItemFromWindow(win, aTab.id);
|
||||
const targetTab = this.getItemFromWindow(win, aTab.id);
|
||||
if (targetTab) {
|
||||
targetTab._zenPinnedInitialState = initialState;
|
||||
}
|
||||
@@ -958,7 +993,7 @@ class nsZenWindowSync {
|
||||
on_TabUnpinned(aEvent) {
|
||||
const tab = aEvent.target;
|
||||
this.#runOnAllWindows(null, (win) => {
|
||||
const targetTab = this.#getItemFromWindow(win, tab.id);
|
||||
const targetTab = this.getItemFromWindow(win, tab.id);
|
||||
if (targetTab) {
|
||||
delete targetTab._zenPinnedInitialState;
|
||||
}
|
||||
@@ -978,7 +1013,7 @@ class nsZenWindowSync {
|
||||
const tab = aEvent.target;
|
||||
const window = tab.ownerGlobal;
|
||||
this.#runOnAllWindows(window, (win) => {
|
||||
const targetTab = this.#getItemFromWindow(win, tab.id);
|
||||
const targetTab = this.getItemFromWindow(win, tab.id);
|
||||
if (targetTab) {
|
||||
win.gBrowser.removeTab(targetTab, { animate: true });
|
||||
}
|
||||
@@ -1052,7 +1087,7 @@ class nsZenWindowSync {
|
||||
const tabGroup = aEvent.target;
|
||||
const window = tabGroup.ownerGlobal;
|
||||
this.#runOnAllWindows(window, (win) => {
|
||||
const targetGroup = this.#getItemFromWindow(win, tabGroup.id);
|
||||
const targetGroup = this.getItemFromWindow(win, tabGroup.id);
|
||||
if (targetGroup) {
|
||||
if (targetGroup.isZenFolder) {
|
||||
targetGroup.delete();
|
||||
@@ -1075,7 +1110,7 @@ class nsZenWindowSync {
|
||||
const tab = aEvent.target;
|
||||
const window = tab.ownerGlobal;
|
||||
this.#runOnAllWindows(window, (win) => {
|
||||
const targetTab = this.#getItemFromWindow(win, tab.id);
|
||||
const targetTab = this.getItemFromWindow(win, tab.id);
|
||||
if (targetTab && win.gZenViewSplitter) {
|
||||
win.gZenViewSplitter.removeTabFromGroup(targetTab);
|
||||
}
|
||||
@@ -1088,7 +1123,7 @@ class nsZenWindowSync {
|
||||
const tabs = tabGroup.tabs;
|
||||
this.#runOnAllWindows(window, (win) => {
|
||||
const otherWindowTabs = tabs
|
||||
.map((tab) => this.#getItemFromWindow(win, tab.id))
|
||||
.map((tab) => this.getItemFromWindow(win, tab.id))
|
||||
.filter(Boolean);
|
||||
if (otherWindowTabs.length > 0 && win.gZenViewSplitter) {
|
||||
const group = win.gZenViewSplitter.splitTabs(otherWindowTabs, 'grid', -1);
|
||||
@@ -1104,4 +1139,5 @@ class nsZenWindowSync {
|
||||
}
|
||||
}
|
||||
|
||||
export const gWindowSyncEnabled = lazy.gWindowSyncEnabled;
|
||||
export const ZenWindowSync = new nsZenWindowSync();
|
||||
|
||||
@@ -13,6 +13,7 @@ BROWSER_CHROME_MANIFESTS += [
|
||||
"ub-actions/browser.toml",
|
||||
"urlbar/browser.toml",
|
||||
"welcome/browser.toml",
|
||||
"window_sync/browser.toml",
|
||||
"workspaces/browser.toml",
|
||||
]
|
||||
|
||||
|
||||
12
src/zen/tests/window_sync/browser.toml
Normal file
12
src/zen/tests/window_sync/browser.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
# 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/.
|
||||
|
||||
[DEFAULT]
|
||||
prefs = ["zen.window-sync.enabled=true", "zen.urlbar.replace-newtab=false"]
|
||||
support-files = [
|
||||
"head.js",
|
||||
]
|
||||
|
||||
["browser_sync_tab_open.js"]
|
||||
["browser_sync_tab_label.js"]
|
||||
39
src/zen/tests/window_sync/browser_sync_tab_label.js
Normal file
39
src/zen/tests/window_sync/browser_sync_tab_label.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
'use strict';
|
||||
|
||||
add_task(async function test_SimpleLabelChange() {
|
||||
let newLabel = 'Test Label';
|
||||
await withNewTabAndWindow(async (newTab, win) => {
|
||||
let otherTab = gZenWindowSync.getItemFromWindow(win, newTab.id);
|
||||
await runSyncAction(
|
||||
() => {
|
||||
gBrowser._setTabLabel(newTab, newLabel);
|
||||
Assert.equal(newTab.label, newLabel, 'The original tab label should be changed');
|
||||
},
|
||||
async () => {
|
||||
Assert.equal(
|
||||
otherTab.label,
|
||||
newLabel,
|
||||
'The synced tab label should match the changed label'
|
||||
);
|
||||
},
|
||||
'ZenTabLabelChanged'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_DontChangeBluredTabLabel() {
|
||||
let newLabel = 'Test Label';
|
||||
await withNewTabAndWindow(async (newTab, win) => {
|
||||
let otherTab = gZenWindowSync.getItemFromWindow(win, newTab.id);
|
||||
Assert.ok(!otherTab._zenContentsVisible, 'The synced tab should be blured');
|
||||
gBrowser._setTabLabel(newTab, newLabel);
|
||||
Assert.notEqual(
|
||||
otherTab.label,
|
||||
newLabel,
|
||||
'The synced tab label should NOT match the changed label'
|
||||
);
|
||||
});
|
||||
});
|
||||
14
src/zen/tests/window_sync/browser_sync_tab_open.js
Normal file
14
src/zen/tests/window_sync/browser_sync_tab_open.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
'use strict';
|
||||
|
||||
add_task(async function test_SimpleTabOpen() {
|
||||
await withNewTabAndWindow(async (newTab, win) => {
|
||||
let tabId = newTab.id;
|
||||
let otherTab = gZenWindowSync.getItemFromWindow(win, tabId);
|
||||
Assert.ok(otherTab, 'The opened tab should be found in the synced window');
|
||||
Assert.ok(newTab._zenContentsVisible, 'The opened tab should be visible');
|
||||
Assert.equal(otherTab.id, tabId, 'The opened tab ID should match the synced tab ID');
|
||||
});
|
||||
});
|
||||
47
src/zen/tests/window_sync/head.js
Normal file
47
src/zen/tests/window_sync/head.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/* 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/. */
|
||||
|
||||
async function withNewSyncedWindow(action) {
|
||||
await gZenWorkspaces.promiseInitialized;
|
||||
const win = await BrowserTestUtils.openNewBrowserWindow();
|
||||
await win.gZenWorkspaces.promiseInitialized;
|
||||
await action(win);
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
}
|
||||
|
||||
async function runSyncAction(action, callback, type) {
|
||||
await new Promise((resolve) => {
|
||||
window.gZenWindowSync.addSyncHandler(async function handler(aEvent) {
|
||||
if (aEvent.type === type) {
|
||||
window.gZenWindowSync.removeSyncHandler(handler);
|
||||
await callback(aEvent);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
action();
|
||||
});
|
||||
}
|
||||
|
||||
function getTabState(tab) {
|
||||
return JSON.parse(SessionStore.getTabState(tab));
|
||||
}
|
||||
|
||||
async function withNewTabAndWindow(action) {
|
||||
let newTab = null;
|
||||
await withNewSyncedWindow(async (win) => {
|
||||
await runSyncAction(
|
||||
() => {
|
||||
newTab = gBrowser.addTrustedTab('https://example.com/', { inBackground: true });
|
||||
},
|
||||
async (aEvent) => {
|
||||
Assert.equal(aEvent.type, 'TabOpen', 'Event type should be TabOpen');
|
||||
await action(newTab, win);
|
||||
},
|
||||
'TabOpen'
|
||||
);
|
||||
});
|
||||
let portalTabClosing = BrowserTestUtils.waitForTabClosing(newTab);
|
||||
BrowserTestUtils.removeTab(newTab);
|
||||
await portalTabClosing;
|
||||
}
|
||||
@@ -848,7 +848,7 @@ class nsZenWorkspaces {
|
||||
const spacesFromStore = aWinData.spaces || [];
|
||||
this._workspaceCache = spacesFromStore.length
|
||||
? [...spacesFromStore]
|
||||
: [this.#createWorkspaceData('Space', undefined, true)];
|
||||
: [this.#createWorkspaceData('Space', undefined)];
|
||||
this.activeWorkspace = aWinData.activeZenSpace || this._workspaceCache[0].uuid;
|
||||
let promise = this.#initializeWorkspaces();
|
||||
for (const workspace of spacesFromStore) {
|
||||
@@ -912,7 +912,7 @@ class nsZenWorkspaces {
|
||||
}
|
||||
|
||||
async selectStartPage() {
|
||||
if (!this.workspaceEnabled) {
|
||||
if (!this.workspaceEnabled || gZenUIManager.testingEnabled) {
|
||||
return;
|
||||
}
|
||||
await this.promiseInitialized;
|
||||
@@ -1615,7 +1615,9 @@ class nsZenWorkspaces {
|
||||
// Second pass: Handle tab selection
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
const tabToSelect = await this._handleTabSelection(workspace, onInit, previousWorkspace.uuid);
|
||||
gBrowser.warmupTab(tabToSelect);
|
||||
if (tabToSelect.linkedBrowser) {
|
||||
gBrowser.warmupTab(tabToSelect);
|
||||
}
|
||||
|
||||
// Update UI and state
|
||||
const previousWorkspaceIndex = workspaces.findIndex((w) => w.uuid === previousWorkspace.uuid);
|
||||
|
||||
@@ -326,7 +326,7 @@ fn is_twilight_build() -> bool {
|
||||
if let Ok(content) = fs::read_to_string(&dynamic_config_path) {
|
||||
return !content.contains("\"release\"");
|
||||
}
|
||||
false
|
||||
true
|
||||
}
|
||||
|
||||
fn get_env_values() -> HashMap<String, bool> {
|
||||
|
||||
Reference in New Issue
Block a user