From dfd2830ceb5ef68ebcc290dce76c66c150668266 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Mon, 28 Jul 2025 17:05:23 +0200
Subject: [PATCH] test: Added tests for split view, p=#9667, c=tests
---
src/browser/base/content/browser-js.patch | 12 +-
.../base/content/zen-commands.inc.xhtml | 60 ++++++++
.../base/content/zen-keysets.inc.xhtml | 57 +------
src/zen/common/ZenUIManager.mjs | 8 +-
src/zen/glance/ZenGlanceManager.mjs | 13 +-
src/zen/glance/moz.build | 3 +-
src/zen/glance/tests/GlanceTestUtils.sys.mjs | 30 ++++
src/zen/split-view/ZenViewSplitter.mjs | 8 +-
src/zen/tests/glance/head.js | 33 +----
src/zen/tests/moz.build | 1 +
src/zen/tests/split_view/browser.toml | 15 ++
.../split_view/browser_basic_split_view.js | 31 ++++
.../browser_split_browser_duplication.js | 140 ++++++++++++++++++
.../tests/split_view/browser_split_groups.js | 43 ++++++
.../split_view/browser_split_inset_checks.js | 68 +++++++++
.../browser_split_view_with_glance.js | 104 +++++++++++++
src/zen/tests/split_view/head.js | 43 ++++++
src/zen/tests/tabs/head.js | 5 +
.../workspaces/browser_change_to_empty.js | 24 +++
src/zen/workspaces/ZenWorkspaces.mjs | 9 +-
20 files changed, 599 insertions(+), 108 deletions(-)
create mode 100644 src/browser/base/content/zen-commands.inc.xhtml
create mode 100644 src/zen/glance/tests/GlanceTestUtils.sys.mjs
create mode 100644 src/zen/tests/split_view/browser.toml
create mode 100644 src/zen/tests/split_view/browser_basic_split_view.js
create mode 100644 src/zen/tests/split_view/browser_split_browser_duplication.js
create mode 100644 src/zen/tests/split_view/browser_split_groups.js
create mode 100644 src/zen/tests/split_view/browser_split_inset_checks.js
create mode 100644 src/zen/tests/split_view/browser_split_view_with_glance.js
create mode 100644 src/zen/tests/split_view/head.js
create mode 100644 src/zen/tests/workspaces/browser_change_to_empty.js
diff --git a/src/browser/base/content/browser-js.patch b/src/browser/base/content/browser-js.patch
index d8ed9df04..d64624dd9 100644
--- a/src/browser/base/content/browser-js.patch
+++ b/src/browser/base/content/browser-js.patch
@@ -1,5 +1,5 @@
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
-index 8e839c497bba9de04948ad8759679b6a6f61a65f..877ec60553fc64fea860de297dc0858eb05bae7f 100644
+index 8e839c497bba9de04948ad8759679b6a6f61a65f..f94a160427b7e465e2c7134fbaf876f589a3fcce 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -29,6 +29,7 @@ ChromeUtils.defineESModuleGetters(this, {
@@ -10,18 +10,16 @@ index 8e839c497bba9de04948ad8759679b6a6f61a65f..877ec60553fc64fea860de297dc0858e
DevToolsSocketStatus:
"resource://devtools/shared/security/DevToolsSocketStatus.sys.mjs",
DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
-@@ -2282,6 +2283,10 @@ var XULBrowserWindow = {
+@@ -2282,6 +2283,8 @@ var XULBrowserWindow = {
AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
TranslationsParent.onLocationChange(gBrowser.selectedBrowser);
-+ gZenViewSplitter.onLocationChange(gBrowser.selectedBrowser);
-+ gZenWorkspaces.onLocationChange(gBrowser.selectedBrowser);
+ gZenPinnedTabManager.onLocationChange(gBrowser.selectedBrowser);
+
PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser);
if (!gMultiProcessBrowser) {
-@@ -4617,7 +4622,7 @@ function switchToTabHavingURI(
+@@ -4617,7 +4620,7 @@ function switchToTabHavingURI(
ignoreQueryString || replaceQueryString,
ignoreFragmentWhenComparing
);
@@ -30,7 +28,7 @@ index 8e839c497bba9de04948ad8759679b6a6f61a65f..877ec60553fc64fea860de297dc0858e
for (let i = 0; i < browsers.length; i++) {
let browser = browsers[i];
let browserCompare = cleanURL(
-@@ -4660,7 +4665,7 @@ function switchToTabHavingURI(
+@@ -4660,7 +4663,7 @@ function switchToTabHavingURI(
}
if (!doAdopt) {
@@ -39,7 +37,7 @@ index 8e839c497bba9de04948ad8759679b6a6f61a65f..877ec60553fc64fea860de297dc0858e
}
return true;
-@@ -5476,6 +5481,9 @@ var ConfirmationHint = {
+@@ -5476,6 +5479,9 @@ var ConfirmationHint = {
MozXULElement.insertFTLIfNeeded("toolkit/branding/brandings.ftl");
MozXULElement.insertFTLIfNeeded("browser/confirmationHints.ftl");
document.l10n.setAttributes(this._message, messageId, options.l10nArgs);
diff --git a/src/browser/base/content/zen-commands.inc.xhtml b/src/browser/base/content/zen-commands.inc.xhtml
new file mode 100644
index 000000000..0bd5f6020
--- /dev/null
+++ b/src/browser/base/content/zen-commands.inc.xhtml
@@ -0,0 +1,60 @@
+# 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/.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/browser/base/content/zen-keysets.inc.xhtml b/src/browser/base/content/zen-keysets.inc.xhtml
index f34bb1191..2e8122fb5 100644
--- a/src/browser/base/content/zen-keysets.inc.xhtml
+++ b/src/browser/base/content/zen-keysets.inc.xhtml
@@ -2,61 +2,6 @@
# 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/.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+#include zen-commands.inc.xhtml
diff --git a/src/zen/common/ZenUIManager.mjs b/src/zen/common/ZenUIManager.mjs
index 8173287d4..6650435cf 100644
--- a/src/zen/common/ZenUIManager.mjs
+++ b/src/zen/common/ZenUIManager.mjs
@@ -603,7 +603,13 @@ var gZenVerticalTabsManager = {
},
animateTab(aTab) {
- if (!gZenUIManager.motion || !aTab || !gZenUIManager._hasLoadedDOM || !aTab.isConnected) {
+ if (
+ !gZenUIManager.motion ||
+ !aTab ||
+ !gZenUIManager._hasLoadedDOM ||
+ !aTab.isConnected ||
+ gZenUIManager.testingEnabled
+ ) {
return;
}
// get next visible tab
diff --git a/src/zen/glance/ZenGlanceManager.mjs b/src/zen/glance/ZenGlanceManager.mjs
index 5f54a6058..3e4bc6aee 100644
--- a/src/zen/glance/ZenGlanceManager.mjs
+++ b/src/zen/glance/ZenGlanceManager.mjs
@@ -223,6 +223,7 @@
this.browserWrapper.setAttribute('has-finished-animation', true);
this._animating = false;
this.animatingOpen = false;
+ this.#currentTab.dispatchEvent(new Event('GlanceOpen', { bubbles: true }));
resolve(this.#currentTab);
});
});
@@ -358,12 +359,12 @@
this.overlay.removeAttribute('fade-out');
this.browserWrapper.removeAttribute('animate');
- this.lastCurrentTab = this.#currentTab;
+ const lastCurrentTab = this.#currentTab;
this.overlay.classList.remove('zen-glance-overlay');
gBrowser
._getSwitcher()
- .setTabStateNoAction(this.lastCurrentTab, gBrowser.AsyncTabSwitcher.STATE_UNLOADED);
+ .setTabStateNoAction(lastCurrentTab, gBrowser.AsyncTabSwitcher.STATE_UNLOADED);
if (!onTabClose) {
this.#currentParentTab._visuallySelected = false;
@@ -381,14 +382,15 @@
this.overlay = null;
this.contentWrapper = null;
- this.lastCurrentTab.removeAttribute('zen-glance-tab');
- this.lastCurrentTab._closingGlance = true;
+ lastCurrentTab.removeAttribute('zen-glance-tab');
+ lastCurrentTab._closingGlance = true;
if (!isDifferent) {
gBrowser.selectedTab = this.#currentParentTab;
}
this._ignoreClose = true;
- gBrowser.removeTab(this.lastCurrentTab, { animate: true, skipPermitUnload: true });
+ lastCurrentTab.dispatchEvent(new Event('GlanceClose', { bubbles: true }));
+ gBrowser.removeTab(lastCurrentTab, { animate: true, skipPermitUnload: true });
gBrowser.tabContainer._invalidateCachedTabs();
this.#currentParentTab.removeAttribute('glance-id');
@@ -396,7 +398,6 @@
this.#glances.delete(this.#currentGlanceID);
this.#currentGlanceID = setNewID;
- this.lastCurrentTab = null;
this._duringOpening = false;
this._animating = false;
diff --git a/src/zen/glance/moz.build b/src/zen/glance/moz.build
index 3f787914b..9588459dc 100644
--- a/src/zen/glance/moz.build
+++ b/src/zen/glance/moz.build
@@ -3,8 +3,9 @@
# 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/.
-
FINAL_TARGET_FILES.actors += [
"actors/ZenGlanceChild.sys.mjs",
"actors/ZenGlanceParent.sys.mjs",
]
+
+TESTING_JS_MODULES += ["tests/GlanceTestUtils.sys.mjs"]
diff --git a/src/zen/glance/tests/GlanceTestUtils.sys.mjs b/src/zen/glance/tests/GlanceTestUtils.sys.mjs
new file mode 100644
index 000000000..08fbe8dee
--- /dev/null
+++ b/src/zen/glance/tests/GlanceTestUtils.sys.mjs
@@ -0,0 +1,30 @@
+/* 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/. */
+
+export function openGlanceOnTab(window, callback, close = true) {
+ return new Promise(async (resolve) => {
+ window.gZenGlanceManager
+ .openGlance({
+ url: 'https://example.com',
+ clientX: 0,
+ clientY: 0,
+ width: 0,
+ height: 0,
+ })
+ .then(async (glanceTab) => {
+ await callback(glanceTab);
+ if (close) {
+ window.gZenGlanceManager
+ .closeGlance({
+ onTabClose: true,
+ })
+ .then(() => {
+ resolve();
+ });
+ } else {
+ resolve();
+ }
+ });
+ });
+}
diff --git a/src/zen/split-view/ZenViewSplitter.mjs b/src/zen/split-view/ZenViewSplitter.mjs
index cd7c61207..b0271c823 100644
--- a/src/zen/split-view/ZenViewSplitter.mjs
+++ b/src/zen/split-view/ZenViewSplitter.mjs
@@ -177,6 +177,7 @@ class nsZenViewSplitter extends ZenDOMOperatedFeature {
if (previousTab && !previousTab.hasAttribute('zen-empty-tab')) {
this._lastOpenedTab = previousTab;
}
+ this.onLocationChange(event.target.linkedBrowser);
}
/**
@@ -527,7 +528,6 @@ class nsZenViewSplitter extends ZenDOMOperatedFeature {
this._thumnailCanvas.width = 280 * devicePixelRatio;
this._thumnailCanvas.height = 140 * devicePixelRatio;
}
-
const browsers = this._data[this.currentView].tabs.map((t) => t.linkedBrowser);
browsers.forEach((b) => {
b.style.pointerEvents = 'none';
@@ -1108,11 +1108,6 @@ class nsZenViewSplitter extends ZenDOMOperatedFeature {
}
}
}
-
- if (this._sessionRestoring) {
- return;
- }
- this.activateSplitView(splitData);
}
addTabToSplit(tab, splitNode, prepend = true) {
@@ -1181,6 +1176,7 @@ class nsZenViewSplitter extends ZenDOMOperatedFeature {
this.applyGridLayout(splitData.layoutTree);
this.setTabsDocShellState(splitData.tabs, true);
this.toggleWrapperDisplay(true);
+ window.dispatchEvent(new CustomEvent('ZenViewSplitter:SplitViewActivated'));
}
calculateLayoutTree(tabs, gridType) {
diff --git a/src/zen/tests/glance/head.js b/src/zen/tests/glance/head.js
index 963fad248..2d884456e 100644
--- a/src/zen/tests/glance/head.js
+++ b/src/zen/tests/glance/head.js
@@ -2,33 +2,10 @@
* 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/. */
+const { openGlanceOnTab: internalGlanceHandle } = ChromeUtils.importESModule(
+ 'resource://testing-common/GlanceTestUtils.sys.mjs'
+);
+
function openGlanceOnTab(callback, close = true) {
- return new Promise(async (resolve) => {
- setTimeout(() => {
- gZenGlanceManager
- .openGlance({
- url: 'https://example.com',
- clientX: 0,
- clientY: 0,
- width: 0,
- height: 0,
- })
- .then(async (glanceTab) => {
- await callback(glanceTab);
- if (close) {
- setTimeout(() => {
- gZenGlanceManager
- .closeGlance({
- onTabClose: true,
- })
- .then(() => {
- resolve();
- });
- }, 500); // Give tons of time for the glance to close
- } else {
- resolve();
- }
- });
- }, 500); // Give tons of time for the glance to open
- });
+ return internalGlanceHandle(window, callback, close);
}
diff --git a/src/zen/tests/moz.build b/src/zen/tests/moz.build
index ae9b9a17e..881368a49 100644
--- a/src/zen/tests/moz.build
+++ b/src/zen/tests/moz.build
@@ -7,6 +7,7 @@ BROWSER_CHROME_MANIFESTS += [
"container_essentials/browser.toml",
"glance/browser.toml",
"pinned/browser.toml",
+ "split_view/browser.toml",
"tabs/browser.toml",
"urlbar/browser.toml",
"welcome/browser.toml",
diff --git a/src/zen/tests/split_view/browser.toml b/src/zen/tests/split_view/browser.toml
new file mode 100644
index 000000000..94b4a923c
--- /dev/null
+++ b/src/zen/tests/split_view/browser.toml
@@ -0,0 +1,15 @@
+# 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]
+support-files = [
+ "head.js",
+ "!/zen/tests/glance/head.js",
+]
+
+["browser_basic_split_view.js"]
+["browser_split_inset_checks.js"]
+["browser_split_groups.js"]
+["browser_split_browser_duplication.js"]
+["browser_split_view_with_glance.js"]
diff --git a/src/zen/tests/split_view/browser_basic_split_view.js b/src/zen/tests/split_view/browser_basic_split_view.js
new file mode 100644
index 000000000..2beb8a291
--- /dev/null
+++ b/src/zen/tests/split_view/browser_basic_split_view.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+add_task(async function test_Basic_Split_View() {
+ await basicSplitNTabs(async (tabs) => {
+ ok(
+ gBrowser.tabpanels.hasAttribute('zen-split-view'),
+ 'The split view should not have crashed with two tabs in it'
+ );
+ });
+ ok(
+ !gBrowser.tabpanels.hasAttribute('zen-split-view'),
+ 'Unsplit view should not have crashed with two tabs in it'
+ );
+});
+
+add_task(async function test_Browser_Elements_Attributes() {
+ await basicSplitNTabs(async (tabs) => {
+ Assert.equal(
+ document.querySelectorAll('.browserSidebarContainer[zen-split="true"]').length,
+ 2,
+ 'There should be two split browser sidebars'
+ );
+ });
+ ok(
+ !document.querySelector('.browserSidebarContainer[zen-split="true"]'),
+ 'There should be no split browser sidebars in unsplit view'
+ );
+});
diff --git a/src/zen/tests/split_view/browser_split_browser_duplication.js b/src/zen/tests/split_view/browser_split_browser_duplication.js
new file mode 100644
index 000000000..968e58a8a
--- /dev/null
+++ b/src/zen/tests/split_view/browser_split_browser_duplication.js
@@ -0,0 +1,140 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+add_task(async function test_Basic_Split_View_Duplication() {
+ const [normal, pinned] = await Promise.all([
+ addTabTo(gBrowser, getUrlForNthTab(1)),
+ addTabTo(gBrowser, getUrlForNthTab(2)),
+ ]);
+ const pinEvent = BrowserTestUtils.waitForEvent(pinned, 'TabPinned');
+ gBrowser.pinTab(pinned);
+ await pinEvent;
+ Assert.ok(
+ gBrowser.tabs.length === 4, // empty + initial + 2 split tabs
+ 'There should be four tabs after pinning the second tab'
+ );
+ await createSplitView([normal, pinned], 'grid');
+ ok(!pinned.group, 'The pinned tab should not be in a split group after duplication');
+ ok(
+ normal.group.hasAttribute('split-view-group'),
+ 'The normal tab should be in a split group after duplication'
+ );
+ const group = normal.group;
+ for (const tab of group.tabs) {
+ Assert.ok(!tab.pinned, 'All tabs in the split group should not be pinned after duplication');
+ Assert.ok(
+ tab.splitView,
+ 'All tabs in the split group should be in a split view after duplication'
+ );
+ }
+ Assert.ok(!group.pinned, 'The split group should not be pinned after duplication');
+ for (const tab of [pinned, ...group.tabs]) {
+ await BrowserTestUtils.removeTab(tab);
+ }
+});
+
+add_task(async function test_Split_View_Duplication_Both_Pinned() {
+ const [tab1, tab2] = await Promise.all([
+ addTabTo(gBrowser, getUrlForNthTab(1)),
+ addTabTo(gBrowser, getUrlForNthTab(2)),
+ ]);
+ const pinEvent1 = BrowserTestUtils.waitForEvent(tab1, 'TabPinned');
+ const pinEvent2 = BrowserTestUtils.waitForEvent(tab2, 'TabPinned');
+ gBrowser.pinTab(tab1);
+ gBrowser.pinTab(tab2);
+ await Promise.all([pinEvent1, pinEvent2]);
+ Assert.ok(
+ gBrowser.tabs.length === 4, // empty + initial + 2 split tabs
+ 'There should be four tabs after pinning both tabs'
+ );
+ await createSplitView([tab1, tab2], 'grid');
+ ok(tab1.group, 'The first pinned tab should be in a split group after duplication');
+ ok(
+ tab2.group === tab1.group,
+ 'The second pinned tab should be in the same split group after duplication'
+ );
+ Assert.equal(
+ gBrowser.tabs.length,
+ 4,
+ 'There should not be any duplicate tabs after pinning both tabs'
+ );
+ for (const tab of tab1.group.tabs) {
+ Assert.ok(tab.pinned, 'All tabs in the split group should be pinned after duplication');
+ Assert.ok(
+ tab.splitView,
+ 'All tabs in the split group should be in a split view after duplication'
+ );
+ }
+ Assert.ok(tab1.group.pinned, 'The split group should be pinned after duplication of both tabs');
+ for (const tab of tab1.group.tabs) {
+ await BrowserTestUtils.removeTab(tab);
+ }
+ await BrowserTestUtils.removeTab(tab2);
+ await BrowserTestUtils.removeTab(tab1);
+});
+
+add_task(async function test_Split_View_Duplication_Pinned_Essential() {
+ const existingTabs = gBrowser.tabs;
+ const [pinned, essential] = await Promise.all([
+ addTabTo(gBrowser, getUrlForNthTab(1)),
+ addTabTo(gBrowser, getUrlForNthTab(2)),
+ ]);
+ const pinEvent = BrowserTestUtils.waitForEvent(pinned, 'TabPinned');
+ gBrowser.pinTab(pinned);
+ await pinEvent;
+ gZenPinnedTabManager.addToEssentials(essential);
+ Assert.ok(
+ gBrowser.tabs.length === 4, // empty + initial + 2 split tabs
+ 'There should be four tabs after pinning the first tab and adding the second to essentials'
+ );
+ await createSplitView([pinned, essential], 'grid');
+ ok(
+ gBrowser.tabs.length === 4 + 2,
+ 'There should be six tabs after creating a split view with the pinned and essential tabs'
+ );
+ ok(
+ !pinned.group,
+ 'The pinned tab should not be in a split group after duplication with an essential tab'
+ );
+ ok(
+ !essential.group,
+ 'The essential tab should not be in a split group after duplication with a pinned tab'
+ );
+ for (const tab of gBrowser.tabs) {
+ if (existingTabs.includes(tab)) {
+ continue; // Skip if the tab was already present before the test
+ }
+ await BrowserTestUtils.removeTab(tab);
+ }
+});
+
+add_task(async function test_Split_View_Duplication_Essential() {
+ const existingTabs = gBrowser.tabs;
+ const essentials = await Promise.all(
+ [...Array(2)].map((_, i) => addTabTo(gBrowser, getUrlForNthTab(i + 1)))
+ );
+ essentials.forEach((tab) => {
+ gZenPinnedTabManager.addToEssentials(tab);
+ });
+ ok(
+ gBrowser.tabs.length === 4, // empty + initial + 2 essential tabs
+ 'There should be four tabs after adding two essential tabs'
+ );
+ await createSplitView(essentials, 'grid');
+ ok(
+ gBrowser.tabs.length === 4 + 2,
+ 'There should be six tabs after creating a split view with two essential tabs'
+ );
+ for (const tab of essentials) {
+ ok(!tab.group, 'Each essential tab should not be in a split group after duplication');
+ ok(!tab.splitView, 'Each essential tab should not be in a split view after duplication');
+ }
+ for (const tab of gBrowser.tabs) {
+ if (existingTabs.includes(tab)) {
+ continue; // Skip if the tab was already present before the test
+ }
+ await BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/src/zen/tests/split_view/browser_split_groups.js b/src/zen/tests/split_view/browser_split_groups.js
new file mode 100644
index 000000000..ab5ab8b9b
--- /dev/null
+++ b/src/zen/tests/split_view/browser_split_groups.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+add_task(async function test_Basic_Split_Groups() {
+ await basicSplitNTabs(async (tabs) => {
+ ok(tabs[0].group.hasAttribute('split-view-group'), 'The first tab should be in a split group');
+ Assert.equal(tabs[0].group.tabs.length, 2, 'The first split group should contain two tabs');
+ });
+});
+
+add_task(async function test_Basic_Split_Groups_Pinning() {
+ await basicSplitNTabs(async (tabs) => {
+ const group = tabs[0].group;
+ ok(group.hasAttribute('split-view-group'), 'The first tab should be in a split group');
+ const pinEvent = BrowserTestUtils.waitForEvent(tabs[0], 'TabPinned');
+ gBrowser.pinTab(tabs[0]);
+ await pinEvent;
+ for (const tab of tabs) {
+ ok(tab.pinned, 'All tabs in the split group should be pinned after pinning the first tab');
+ ok(
+ tab.group === group,
+ 'All tabs in the split group should remain in the same group after pinning'
+ );
+ }
+ ok(group.pinned, 'The split group should be pinned after pinning a tab');
+ const unpinEvent = BrowserTestUtils.waitForEvent(tabs[0], 'TabUnpinned');
+ gBrowser.unpinTab(tabs[0]);
+ await unpinEvent;
+ for (const tab of tabs) {
+ ok(
+ !tab.pinned,
+ 'All tabs in the split group should be unpinned after unpinning the first tab'
+ );
+ ok(
+ tab.group === group,
+ 'All tabs in the split group should remain in the same group after unpinning'
+ );
+ }
+ ok(!group.pinned, 'The split group should be unpinned after unpinning a tab');
+ });
+});
diff --git a/src/zen/tests/split_view/browser_split_inset_checks.js b/src/zen/tests/split_view/browser_split_inset_checks.js
new file mode 100644
index 000000000..0ceb0950d
--- /dev/null
+++ b/src/zen/tests/split_view/browser_split_inset_checks.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+add_task(async function test_Basic_Split_View_Inset() {
+ let viewsToCheck = [];
+ await basicSplitNTabs(async (tabs) => {
+ viewsToCheck = document.querySelectorAll('.browserSidebarContainer[zen-split="true"]');
+ ok(viewsToCheck.length, 'There should be split views present');
+ Assert.equal(
+ viewsToCheck[0].style.inset,
+ '0% 50% 0% 0%',
+ 'The split view should have correct inset style'
+ );
+ Assert.equal(
+ viewsToCheck[1].style.inset,
+ '0% 0% 0% 50%',
+ 'The second split view should have correct inset style'
+ );
+ });
+ for (const view of viewsToCheck) {
+ Assert.equal(view.style.inset, '', 'The unsplit view should not have correct inset style');
+ }
+});
+
+add_task(async function test_Horizontal_Split_Inset() {
+ await basicSplitNTabs(async (tabs) => {
+ const viewsToCheck = document.querySelectorAll('.browserSidebarContainer[zen-split="true"]');
+ ok(viewsToCheck.length, 'There should be split views present');
+ Assert.equal(
+ viewsToCheck[0].style.inset,
+ '0% 50% 0% 0%',
+ 'The horizontal split view should have correct inset style'
+ );
+ Assert.equal(
+ viewsToCheck[1].style.inset,
+ '0% 0% 0% 50%',
+ 'The second horizontal split view should have correct inset style'
+ );
+ });
+});
+
+add_task(async function test_3_Splits_Grid_Inset() {
+ await basicSplitNTabs(
+ async (tabs) => {
+ const viewsToCheck = document.querySelectorAll('.browserSidebarContainer[zen-split="true"]');
+ ok(viewsToCheck.length, 'There should be split views present');
+ Assert.equal(
+ viewsToCheck[0].style.inset,
+ '0% 0% 50% 50%',
+ 'The first split view should have correct inset style'
+ );
+ Assert.equal(
+ viewsToCheck[1].style.inset,
+ '50% 0% 0% 50%',
+ 'The second split view should have correct inset style'
+ );
+ Assert.equal(
+ viewsToCheck[2].style.inset,
+ '0% 50% 0% 0%',
+ 'The third split view should have correct inset style'
+ );
+ },
+ 'grid',
+ 3
+ );
+});
diff --git a/src/zen/tests/split_view/browser_split_view_with_glance.js b/src/zen/tests/split_view/browser_split_view_with_glance.js
new file mode 100644
index 000000000..20a592221
--- /dev/null
+++ b/src/zen/tests/split_view/browser_split_view_with_glance.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const { openGlanceOnTab } = ChromeUtils.importESModule(
+ 'resource://testing-common/GlanceTestUtils.sys.mjs'
+);
+
+add_task(async function test_Basic_Split_View_Glance() {
+ await basicSplitNTabs(async (tabs) => {
+ await openGlanceOnTab(window, async (glanceTab) => {
+ ok(
+ glanceTab.hasAttribute('zen-glance-tab'),
+ 'The glance tab should have the zen-glance-tab attribute'
+ );
+ ok(
+ gBrowser.tabpanels.hasAttribute('zen-split-view'),
+ 'The split view should not have crashed with two tabs in it'
+ );
+ });
+ });
+});
+
+add_task(async function test_Basic_Split_View_Glance_Expand() {
+ await basicSplitNTabs(async (tabs) => {
+ await openGlanceOnTab(
+ window,
+ async (glanceTab) => {
+ await gZenGlanceManager.fullyOpenGlance();
+ ok(
+ !glanceTab.hasAttribute('zen-glance-tab'),
+ 'The glance tab should not have the zen-glance-tab attribute after expanding'
+ );
+ ok(!glanceTab.group, 'The glance tab should not be in a split group after expanding');
+ for (const tab of tabs) {
+ ok(
+ tab.group.hasAttribute('split-view-group'),
+ 'All tabs in the split view should still be in a split group after expanding glance'
+ );
+ }
+ const selectedBrowser = document.querySelectorAll('.browserSidebarContainer.deck-selected');
+ Assert.equal(
+ selectedBrowser.length,
+ 1,
+ 'There should be one selected browser sidebar after expanding glance'
+ );
+ BrowserTestUtils.removeTab(glanceTab);
+ },
+ false
+ );
+ });
+});
+
+add_task(async function test_Basic_Split_View_Glance_No_More_Split() {
+ await basicSplitNTabs(
+ async (tabs) => {
+ await openGlanceOnTab(window, async (glanceTab) => {
+ ok(
+ document.getElementById('cmd_zenGlanceSplit').getAttribute('disabled') === 'true',
+ 'The split command should be disabled when glance is open'
+ );
+ });
+ },
+ 'grid',
+ 4
+ );
+});
+
+add_task(async function test_Basic_Split_View_Glance_Split() {
+ const tab = await addTabTo(gBrowser, getUrlForNthTab(1));
+ gBrowser.selectedTab = tab;
+ await openGlanceOnTab(
+ window,
+ async (glanceTab) => {
+ const waitForSplitPromise = BrowserTestUtils.waitForEvent(
+ window,
+ 'ZenViewSplitter:SplitViewActivated'
+ );
+ document.getElementById('cmd_zenGlanceSplit').doCommand();
+ await waitForSplitPromise;
+ ok(
+ !glanceTab.hasAttribute('zen-glance-tab'),
+ 'The glance tab should not have the zen-glance-tab attribute after splitting'
+ );
+ ok(
+ gBrowser.tabpanels.hasAttribute('zen-split-view'),
+ 'The split view should not have crashed with two tabs in it'
+ );
+ ok(
+ glanceTab.group.hasAttribute('split-view-group'),
+ 'The glance tab should be in a split group after splitting'
+ );
+ Assert.equal(
+ tab.group,
+ glanceTab.group,
+ 'The original tab should be in the same split group as the glance tab after splitting'
+ );
+ BrowserTestUtils.removeTab(glanceTab);
+ },
+ false
+ );
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/src/zen/tests/split_view/head.js b/src/zen/tests/split_view/head.js
new file mode 100644
index 000000000..5748459e7
--- /dev/null
+++ b/src/zen/tests/split_view/head.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+async function addTabTo(targetBrowser, url = 'http://mochi.test:8888/', params = {}) {
+ params.skipAnimation = true;
+ const tab = BrowserTestUtils.addTab(targetBrowser, url, params);
+ const browser = targetBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+ return tab;
+}
+
+function getUrlForNthTab(n) {
+ return `data:text/plain,tab${n}`;
+}
+
+async function createSplitView(tabs, type = 'grid') {
+ const waitForActivationPromise = BrowserTestUtils.waitForEvent(
+ window,
+ 'ZenViewSplitter:SplitViewActivated'
+ );
+ gZenViewSplitter.splitTabs(tabs, type);
+ await waitForActivationPromise;
+ await new Promise((resolve) => {
+ setTimeout(async () => {
+ resolve();
+ }, 100);
+ });
+}
+
+async function basicSplitNTabs(callback, type = 'grid', n = 2) {
+ Assert.greater(n, 1, 'There should be at least two tabs');
+ Assert.less(n, 5, 'There should be at most four tabs');
+ const tabs = await Promise.all(
+ [...Array(n)].map((_, i) => addTabTo(gBrowser, getUrlForNthTab(i + 1)))
+ );
+ await createSplitView(tabs, type);
+ await callback(tabs);
+ for (const tab of tabs) {
+ await BrowserTestUtils.removeTab(tab);
+ }
+}
diff --git a/src/zen/tests/tabs/head.js b/src/zen/tests/tabs/head.js
index 0784bcafa..ba758a0fc 100644
--- a/src/zen/tests/tabs/head.js
+++ b/src/zen/tests/tabs/head.js
@@ -1,3 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
const { TabGroupTestUtils } = ChromeUtils.importESModule(
'resource://testing-common/TabGroupTestUtils.sys.mjs'
);
diff --git a/src/zen/tests/workspaces/browser_change_to_empty.js b/src/zen/tests/workspaces/browser_change_to_empty.js
new file mode 100644
index 000000000..c7f6f90b7
--- /dev/null
+++ b/src/zen/tests/workspaces/browser_change_to_empty.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+add_task(async function test_Change_To_Empty() {
+ const currentWorkspaceUUID = gZenWorkspaces.activeWorkspace;
+ await gZenWorkspaces.createAndSaveWorkspace('Test Workspace 2');
+ const workspaces = await gZenWorkspaces._workspaces();
+ const secondWorkspace = workspaces.workspaces[1];
+
+ await gZenWorkspaces.changeWorkspace(secondWorkspace.uuid);
+ ok(gBrowser.selectedTab === gZenWorkspaces._emptyTab, 'The empty tab should be selected.');
+
+ await gZenWorkspaces.removeWorkspace(gZenWorkspaces.activeWorkspace);
+ ok(
+ gBrowser.selectedTab !== gZenWorkspaces._emptyTab,
+ 'The empty tab should not be selected anymore.'
+ );
+
+ const workspacesAfterRemove = await gZenWorkspaces._workspaces();
+ ok(workspacesAfterRemove.workspaces.length === 1, 'One workspace should exist.');
+ ok(gBrowser.tabs.length === 2, 'There should be two tabs.');
+});
diff --git a/src/zen/workspaces/ZenWorkspaces.mjs b/src/zen/workspaces/ZenWorkspaces.mjs
index 6fe257018..714450ce3 100644
--- a/src/zen/workspaces/ZenWorkspaces.mjs
+++ b/src/zen/workspaces/ZenWorkspaces.mjs
@@ -922,6 +922,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
window.addEventListener('TabPinned', tabUpdateListener);
window.addEventListener('TabUnpinned', tabUpdateListener);
window.addEventListener('aftercustomization', tabUpdateListener);
+ window.addEventListener('TabSelect', this.onLocationChange.bind(this));
window.addEventListener('TabBrowserInserted', this.onTabBrowserInserted.bind(this));
}
@@ -1621,7 +1622,9 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
}
);
} else {
- workspaceElement.style.paddingTop = essentialsHeight + 'px';
+ window.requestAnimationFrame(() => {
+ workspaceElement.style.paddingTop = essentialsHeight + 'px';
+ });
}
}
}
@@ -2448,7 +2451,8 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
tab.setAttribute('zen-workspace-id', activeWorkspace.uuid);
}
- async onLocationChange(browser) {
+ async onLocationChange(event) {
+ let tab = event.target;
gZenCompactModeManager.sidebar.toggleAttribute(
'zen-has-empty-tab',
gBrowser.selectedTab.hasAttribute('zen-empty-tab')
@@ -2457,7 +2461,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
return;
}
- let tab = gBrowser.getTabForBrowser(browser);
if (tab.hasAttribute('zen-glance-tab')) {
// Extract from parent node so we are not selecting the wrong (current) tab
tab = tab.parentNode.closest('.tabbrowser-tab');