gh-15384: Sync upstream Firefox to version 156.0 (gh-15409)

This commit is contained in:
mr. m
2026-09-15 23:06:35 +02:00
committed by GitHub
parent 644bf48b6c
commit 04e7db5d3a
20 changed files with 410 additions and 148 deletions

View File

@@ -34,7 +34,7 @@ Zen is a firefox-based browser with the aim of pushing your productivity to a ne
### Firefox Versions
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `155.0.1`!
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `156.0`!
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 156.0`!
### Contributing

View File

@@ -1 +1 @@
931c2f71dc0b1e11668a5ca165dd75e2e4297174
07c99209a6e4c794752e9a242215199ad3ea10f6

View File

@@ -1,13 +1,7 @@
diff --git a/toolkit/modules/subprocess/subprocess_shared_unix.js b/toolkit/modules/subprocess/subprocess_shared_unix.js
--- a/toolkit/modules/subprocess/subprocess_shared_unix.js
+++ b/toolkit/modules/subprocess/subprocess_shared_unix.js
@@ -52,16 +52,17 @@
close: [ctypes.default_abi, ctypes.int, ctypes.int /* fildes */],
dup: [ctypes.default_abi, ctypes.int, ctypes.int],
+ // Variadic arguments use a different calling convention on Apple silicon.
@@ -57,11 +57,11 @@
fcntl: [
ctypes.default_abi,
ctypes.int,
@@ -64,79 +58,62 @@ diff --git a/toolkit/modules/subprocess/test/xpcshell/test_subprocess_pipe_flags
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/subprocess/test/xpcshell/test_subprocess_pipe_flags.js
@@ -0,0 +1,73 @@
@@ -0,0 +1,56 @@
+/* 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/. */
+
+"use strict";
+
+// fcntl commands for reading the flags set by the subprocess modules. The
+// values are the same on Linux and macOS but are not exposed by
+// ChromeUtils.getLibcConstants().
+const F_GETFD = 1;
+const F_GETFL = 3;
+
+add_task(async function test_subprocess_pipe_flags() {
+ const { Subprocess, getSubprocessImplForTest } = ChromeUtils.importESModule(
+ const { getSubprocessImplForTest } = ChromeUtils.importESModule(
+ "resource://gre/modules/Subprocess.sys.mjs"
+ );
+ const { ctypes } = ChromeUtils.importESModule(
+ "resource://gre/modules/ctypes.sys.mjs"
+ const { libc } = ChromeUtils.importESModule(
+ "resource://gre/modules/subprocess/subprocess_unix.sys.mjs"
+ );
+ const constants = ChromeUtils.getLibcConstants();
+ const library = ctypes.open("a.out");
+ const fcntl = library.declare(
+ "fcntl",
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.int,
+ ctypes.int,
+ "..."
+ const { FD_CLOEXEC, O_NONBLOCK } = ChromeUtils.getLibcConstants();
+
+ const worker = getSubprocessImplForTest().Process.getWorker();
+ equal(
+ libc.fcntl(worker.signalFd, F_GETFD),
+ FD_CLOEXEC,
+ "The main-thread signal pipe has exactly FD_CLOEXEC set"
+ );
+ // Darwin's fcntl commands for reading descriptor and file status flags.
+ const F_GETFD = 1;
+ const F_GETFL = 3;
+ let process;
+
+ try {
+ const worker = getSubprocessImplForTest().Process.getWorker();
+ Assert.equal(
+ fcntl(worker.signalFd, F_GETFD),
+ constants.FD_CLOEXEC,
+ "The main-thread signal pipe has exactly FD_CLOEXEC set"
+ const proc = await Subprocess.call({
+ command: await Subprocess.pathSearch("cat"),
+ stderr: "pipe",
+ });
+ const fds = await worker.call("getFds", [proc.id]);
+ ok(
+ fds.every(Number.isInteger),
+ "The worker owns the stdin, stdout and stderr pipes"
+ );
+ for (const fd of fds) {
+ equal(
+ libc.fcntl(fd, F_GETFD),
+ FD_CLOEXEC,
+ "Worker pipes have exactly FD_CLOEXEC set"
+ );
+
+ process = await Subprocess.call({
+ command: "/bin/cat",
+ stderr: "pipe",
+ disclaim: true,
+ });
+ const fds = await worker.call("getFds", [process.id]);
+ // Only inspect the descriptors while the worker owns them and cat is alive.
+ // Exact flags also rule out an unintended FD_CLOFORK on macOS.
+ for (const fd of fds) {
+ Assert.equal(
+ fcntl(fd, F_GETFD),
+ constants.FD_CLOEXEC,
+ "Worker pipes have exactly FD_CLOEXEC set"
+ );
+ Assert.equal(
+ fcntl(fd, F_GETFL) & constants.O_NONBLOCK,
+ constants.O_NONBLOCK,
+ "Worker pipes are nonblocking"
+ );
+ }
+
+ const output = process.stdout.readString(5);
+ await process.stdin.write("hello");
+ Assert.equal(await output, "hello", "The subprocess pipes transfer data");
+ await process.stdin.close();
+ Assert.equal(
+ (await process.wait()).exitCode,
+ 0,
+ "The subprocess exits cleanly"
+ equal(
+ libc.fcntl(fd, F_GETFL) & O_NONBLOCK,
+ O_NONBLOCK,
+ "Worker pipes are nonblocking"
+ );
+ } finally {
+ if (process && process.exitCode === null) {
+ await process.kill();
+ }
+ library.close();
+ }
+
+ const output = proc.stdout.readString(5);
+ await proc.stdin.write("hello");
+ equal(await output, "hello", "The subprocess pipes transfer data");
+ await proc.stdin.close();
+ equal((await proc.wait()).exitCode, 0, "The subprocess exits cleanly");
+});
diff --git a/toolkit/modules/subprocess/test/xpcshell/xpcshell.toml b/toolkit/modules/subprocess/test/xpcshell/xpcshell.toml
--- a/toolkit/modules/subprocess/test/xpcshell/xpcshell.toml
@@ -148,7 +125,7 @@ diff --git a/toolkit/modules/subprocess/test/xpcshell/xpcshell.toml b/toolkit/mo
requesttimeoutfactor = 2 # Slow on Windows
+["test_subprocess_pipe_flags.js"]
+run-if = ["os == 'mac'"]
+skip-if = ["os == 'win'"]
+
["test_subprocess_polling.js"]

View File

@@ -61,7 +61,7 @@ body,
background: var(--zen-main-browser-background);
opacity: var(--zen-background-opacity);
transition: 0s;
background-blend-mode: screen;
background-blend-mode: lighten;
}
&:is(.zen-toolbar-background) {
@@ -74,7 +74,7 @@ body,
background: var(--zen-main-browser-background-old);
opacity: calc(1 - var(--zen-background-opacity));
transition: 0s;
background-blend-mode: screen;
background-blend-mode: lighten;
}
&:is(.zen-toolbar-background) {

View File

@@ -17,7 +17,7 @@
}
.urlbar {
--urlbarview-separator-color: light-dark(hsl(0, 0%, 80%), hsl(0, 0%, 20%));
--urlbarview-separator-color: light-dark(hsl(0, 0%, 85%), hsl(0, 0%, 15%));
--urlbarview-background-color-hover: var(--toolbarbutton-background-color-hover);
border-radius: calc(var(--toolbarbutton-border-radius) - 2px);
--urlbarView-results-padding: 10px !important;
@@ -284,7 +284,7 @@
background-color: var(--zen-urlbar-background-transparent, var(--zen-urlbar-background-base)) !important;
box-shadow: 0 30px 140px -15px light-dark(rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.6)) !important;
backdrop-filter: none !important;
outline: 0.5px solid light-dark(rgba(0, 0, 0, 0.2), rgba(255, 255, 255, 0.2)) !important;
outline: 0.5px solid light-dark(rgba(0, 0, 0, 0.2), rgba(255, 255, 255, 0.1)) !important;
outline-offset: var(--zen-urlbar-outline-offset) !important;
/* stylelint-disable-next-line media-query-no-invalid */
@@ -295,7 +295,7 @@
&,
.urlbar-background {
border-radius: 12px !important;
border-radius: 14px !important;
}
&[breakout-extend][animate-searchmode="true"]::before {

View File

@@ -109,7 +109,7 @@
if (AppConstants.platform == "macosx") {
const targetRadius = window.matchMedia("(-moz-mac-tahoe-theme)")
.matches
? 12
? 11
: 10;
document.documentElement.style.setProperty(
"--zen-border-radius",

View File

@@ -541,7 +541,7 @@ window.gZenCompactModeManager = {
ease: "easeIn",
type: "spring",
bounce: 0,
duration: 0.12,
duration: 0.1,
}
)
.then(() => {
@@ -597,7 +597,7 @@ window.gZenCompactModeManager = {
ease: "easeOut",
type: "spring",
bounce: 0,
duration: 0.12,
duration: 0.1,
}
)
.then(() => {

View File

@@ -158,9 +158,9 @@
if (tab.hasAttribute("zen-essential")) {
tab.style.visibility = "hidden";
}
this.ZenDragAndDropService.armDropLanding(
!args[0]?.fromTabList && this.getDropEffectForTabDrag(event) == "move"
);
this._dropLandingArmed =
!args[0]?.fromTabList && this.getDropEffectForTabDrag(event) == "move";
this.ZenDragAndDropService.armDropLanding(this._dropLandingArmed);
}
#createDragImageForTabs(movingTabs) {
@@ -907,6 +907,8 @@
this.#dragOverSplit.fakeTab = element;
this.#dragOverSplit.canDrop = true;
// A drop into the split takes the drag image away at once.
this.ZenDragAndDropService.armDropLanding(false);
}
_clearDragOverSplit() {
@@ -914,6 +916,9 @@
clearTimeout(this.#dragOverSplit.timer);
}
this.#dragOverSplit.fakeTab?.remove();
if (this.#dragOverSplit.canDrop && this._dropLandingArmed) {
this.ZenDragAndDropService.armDropLanding(true);
}
this.#dragOverSplit.timer = null;
this.#dragOverSplit.fakeTab = null;
@@ -1040,34 +1045,57 @@
}
this.clearSpaceSwitchTimer();
gZenFolders.highlightGroupOnDragOver(null);
// A drop into a split merges the tab away; nothing lands on it.
const toSplit = !!this.#dragOverSplit.canDrop;
const placesBefore = new Map(
this.#draggedElements(event).map(element => [
element,
this.#placeOf(element),
])
);
super.handle_drop(event);
this.#maybeClearVerticalPinnedGridDragOver();
this.#handle_dropSwitchSpace(event);
this.#handle_dropCreateSplit(event);
this._clearDragOverSplit();
if (!toSplit) {
this.#landDragImage(event);
this.#landDragImage(event, placesBefore);
}
}
/**
* @param {DragEvent} event
* @returns {Element[]} The elements of the dragged tabs, in the order of
* their drag images, when they are this window's
*/
#draggedElements(event) {
const draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
if (draggedTab?.documentGlobal !== window) {
return [];
}
return (this._dragImageTabs ?? [draggedTab]).map(elementToMove);
}
#placeOf(element) {
return `${element.screenX},${element.screenY}`;
}
/**
* Has the drag image land on the dropped tab rather than vanish. The OS
* slides it onto the tab's new place and ends the drag once it is there,
* which is when the tab shows again. Only macOS does so.
*
* @param {DragEvent} event - The drop
* @param {Map<Element, string>} placesBefore - Where each dragged element
* was before the drop.
*/
#landDragImage(event) {
#landDragImage(event, placesBefore) {
// The drop is done, so its indicator goes before the image lands.
this.clearDragOverVisuals();
const draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
if (draggedTab?.documentGlobal !== window || gReduceMotion) {
if (gReduceMotion) {
return;
}
const elements = (this._dragImageTabs ?? [draggedTab]).map(elementToMove);
for (const element of elements) {
const landing = [];
for (const element of this.#draggedElements(event)) {
const { width, height } = element.getBoundingClientRect();
this.ZenDragAndDropService.addDropLandingRect(
Math.round(element.screenX),
@@ -1075,9 +1103,12 @@
Math.round(width),
Math.round(height)
);
element.style.visibility = "hidden";
if (placesBefore.get(element) !== this.#placeOf(element)) {
element.style.visibility = "hidden";
landing.push(element);
}
}
this._landingElements = elements;
this._landingElements = landing;
}
#handle_dropSwitchSpace(event) {
@@ -1303,6 +1334,7 @@
thisFromGlobal._clearDragOverSplit();
this.#maybeClearVerticalPinnedGridDragOver();
thisFromGlobal.originalDragImageArgs = [];
thisFromGlobal._dropLandingArmed = false;
this.#firstHapticFeedbackPlayed = false;
window.removeEventListener(
"dragenter",

View File

@@ -1505,10 +1505,78 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
#createAnimation(items, targetState, opts, callback = () => {}) {
items = Array.isArray(items) ? items : [items];
return items.map(item =>
gZenUIManager.motion.animate(item, targetState, opts).then(callback)
this.#animateItem(item, targetState, opts).then(callback)
);
}
/**
* Animates an element to the given target state. An array value is a
* [from, to] pair, "auto" endpoints are resolved by measuring and an
* empty string animates back to the element's natural value. The final
* values are left applied as inline styles.
*
* @param {Element} item - The element to animate.
* @param {object} targetState - Property to value (or [from, to]) map.
* @param {object} opts - The animation options.
* @param {number} opts.duration - The duration in seconds.
* @param {string} opts.ease - The easing name.
* @returns {Promise} Resolves when the animation has finished.
*/
async #animateItem(item, targetState, { duration = 0.18, ease } = {}) {
const computed = window.getComputedStyle(item);
const toCssValue = (prop, value) =>
typeof value === "number" && prop !== "opacity"
? `${value}px`
: String(value);
const measure = prop => {
const value = computed[prop];
if (value === "auto") {
return `${item.getBoundingClientRect()[prop] ?? 0}px`;
}
return value;
};
const from = {};
const to = {};
const finalStyles = new Map();
for (const [prop, value] of Object.entries(targetState)) {
let [start, end] = Array.isArray(value) ? value : [undefined, value];
const cssProp = prop.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`);
if (start === undefined || start === "auto") {
start = measure(prop);
} else {
start = toCssValue(prop, start);
}
if (end === "" || end === "auto") {
// Resolve the natural value by clearing any inline override.
item.style.removeProperty(cssProp);
end = measure(prop);
finalStyles.set(cssProp, null);
} else {
end = toCssValue(prop, end);
finalStyles.set(cssProp, end);
}
from[prop] = start;
to[prop] = end;
}
const animation = item.animate([from, to], {
duration: duration * 1000,
easing: ease === "easeInOut" ? "ease-in-out" : "ease",
});
try {
await animation.finished;
} catch (e) {
// The animation was cancelled, leave the element as-is.
return;
}
for (const [cssProp, value] of finalStyles) {
if (value === null) {
item.style.removeProperty(cssProp);
} else {
item.style.setProperty(cssProp, value);
}
}
}
#calculateHeightShift(tabsContainer, selectedTabs) {
let heightShift = 0;
if (selectedTabs.length) {
@@ -1526,7 +1594,11 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
}
get #folderAnimationDuration() {
return this._dontAnimateFolder ? 0 : 0.12;
return this._dontAnimateFolder ? 0 : 0.18;
}
get #folderRevealDuration() {
return this._dontAnimateFolder ? 0 : 0.22;
}
async animateCollapse(group) {
@@ -1546,6 +1618,9 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
splitViewIds,
activeFoldersIds,
});
if (selectedTabs.length) {
tabsContainer.removeAttribute("hidden");
}
const collapsedHeight = this.#calculateHeightShift(
tabsContainer,
selectedTabs
@@ -1764,6 +1839,9 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
// Set correct margin-top after animation
const afterAnimate = () => {
if (folder.hasAttribute("has-active")) {
return;
}
groupStart.style.removeProperty("margin-top");
this.styleCleanup(groupItems);
// Trigger the recalculation so that zen returns
@@ -1785,7 +1863,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
{
marginTop: -(collapsedHeight + 4),
},
{ duration: 0.12, ease: "easeInOut" },
{ duration: this.#folderAnimationDuration, ease: "easeInOut" },
afterAnimate
)
);
@@ -1821,6 +1899,9 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
// Set correct margin-top after animation
const afterAnimate = () => {
if (folder.hasAttribute("has-active")) {
return;
}
groupStart.style.removeProperty("margin-top");
this.styleCleanup(groupItems);
// Trigger the recalculation so that zen returns
@@ -1845,7 +1926,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
{
marginTop: -(collapsedHeight + 4),
},
{ duration: 0.12, ease: "easeInOut" },
{ duration: this.#folderAnimationDuration, ease: "easeInOut" },
afterAnimate
),
];
@@ -1870,7 +1951,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
height: 0,
},
{
duration: 0.12,
duration: this.#folderAnimationDuration,
ease: "easeInOut",
}
);
@@ -1899,12 +1980,31 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
const selectedTabs = [];
const splitViewIds = new Set();
const itemsToHide = [];
const newlyActive = new Set();
const groupItems = this.#collectGroupItems(group, {
selectedTabs,
splitViewIds,
});
if (group.collapsed && selectedTabs.length) {
const active = new Set([...(group.activeTabs ?? []), ...selectedTabs]);
const tabs = group.tabs.filter(tab => !tab.hasAttribute("zen-empty-tab"));
if (
tabs.length &&
tabs.every(
tab =>
active.has(tab) ||
tab.selected ||
tab.multiselected ||
tab.hasAttribute("folder-active")
)
) {
group.collapsed = false;
return;
}
}
for (const tab of selectedTabs) {
let currentGroup = tab?.group?.hasAttribute("split-view-group")
? tab.group.group
@@ -1916,6 +2016,11 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
if (activeTabs.length) {
if (currentGroup.collapsed) {
if (currentGroup.hasAttribute("has-active")) {
for (const activeTab of activeTabs) {
if (!currentGroup.activeTabs.includes(activeTab)) {
newlyActive.add(activeTab);
}
}
// It is important to keep the sequence of elements as in the DOM
currentGroup.activeTabs = [
...new Set([...currentGroup.activeTabs, ...activeTabs]),
@@ -1944,7 +2049,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
{
marginTop: 0,
},
{ duration: 0.12, ease: "easeInOut" },
{ duration: this.#folderAnimationDuration, ease: "easeInOut" },
afterMarginTop
)
);
@@ -1963,13 +2068,14 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
}
const itemsToShow = [];
const itemsToReveal = [];
if (selectedTabs.length) {
for (let i = 0; i < groupItems.length; i++) {
const { item, splitViewId } = groupItems[i];
let itemVisible = item.visible;
if (itemVisible) {
itemsToShow.push(item);
(newlyActive.has(item) ? itemsToReveal : itemsToShow).push(item);
}
// Skip selected items
@@ -2003,18 +2109,48 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
height: "",
},
{
duration: 0.12,
duration: this.#folderAnimationDuration,
ease: "easeInOut",
}
),
...itemsToReveal.flatMap(item => {
const state = {
opacity: [0, ""],
height: [0, ""],
minHeight: [0, ""],
};
const folder = item.group?.hasAttribute("split-view-group")
? item.group.group
: item.group;
const slidesFromFolder = !(folder?.activeTabs ?? []).some(
active =>
!newlyActive.has(active) &&
active.compareDocumentPosition(item) &
Node.DOCUMENT_POSITION_FOLLOWING
);
if (slidesFromFolder) {
const height =
item.getBoundingClientRect().height ||
(folder?.activeTabs ?? []).reduce(
(found, active) => found || active.getBoundingClientRect().height,
0
);
state.transform = [`translateY(-${height}px)`, ""];
}
return this.#createAnimation(item, state, {
duration: this.#folderRevealDuration,
ease: "easeInOut",
});
}),
...this.#createAnimation(
itemsToHide,
{
opacity: 0,
height: 0,
minHeight: 0,
},
{
duration: 0.12,
duration: this.#folderAnimationDuration,
ease: "easeInOut",
}
)
@@ -2047,7 +2183,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
{
marginTop: expand ? 0 : -(heightContainer + 4),
},
{ duration: 0.12, ease: "easeInOut" }
{ duration: this.#folderAnimationDuration, ease: "easeInOut" }
);
}
@@ -2055,6 +2191,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
items.forEach(item => {
item.style.removeProperty("opacity");
item.style.removeProperty("height");
item.style.removeProperty("min-height");
});
}
}

View File

@@ -193,15 +193,8 @@ zen-folder {
}
}
:root[zen-sidebar-expanded] &[has-active] > .tab-group-label-container {
& .tab-reset-button {
display: flex;
opacity: 0;
}
&:hover .tab-reset-button {
opacity: 1;
}
:root[zen-sidebar-expanded] &[has-active] > .tab-group-label-container:hover .tab-reset-button {
display: flex;
}
}

View File

@@ -498,41 +498,51 @@ class nsZenLiveFoldersManager {
);
})
.map(item => {
const tab = this.window.gBrowser.addTrustedTab(item.url, {
createLazyBrowser: true,
inBackground: true,
skipAnimation: true,
noInitialLabel: true,
lazyTabTitle: item.title,
userContextId,
});
// createLazyBrowser can't be pinned by default
this.window.gBrowser.pinTab(tab);
if (userContextId) {
tab.setAttribute("zenDefaultUserContextId", "true");
}
if (item.icon) {
this.window.gBrowser.setIcon(tab, item.icon);
if (tab.linkedBrowser) {
lazy.TabStateCache.update(tab.linkedBrowser.permanentKey, {
image: null,
try {
const tab = this.window.gBrowser.addTrustedTab(item.url, {
createLazyBrowser: true,
inBackground: true,
skipAnimation: true,
noInitialLabel: true,
lazyTabTitle: item.title,
userContextId,
});
// createLazyBrowser can't be pinned by default
this.window.gBrowser.pinTab(tab);
if (userContextId) {
tab.setAttribute("zenDefaultUserContextId", "true");
}
if (item.icon) {
this.window.gBrowser.setIcon(tab, item.icon);
if (tab.linkedBrowser) {
lazy.TabStateCache.update(tab.linkedBrowser.permanentKey, {
image: null,
});
}
}
tab.setAttribute(
"zen-live-folder-item-id",
this.#makeCompositeId(liveFolder.id, item.id)
);
if (item.subtitle) {
tab.setAttribute("zen-show-sublabel", item.subtitle);
const tabLabel = tab.querySelector(".zen-tab-sublabel");
this.window.document.l10n.setArgs(tabLabel, {
tabSubtitle: item.subtitle,
});
}
}
tab.setAttribute(
"zen-live-folder-item-id",
this.#makeCompositeId(liveFolder.id, item.id)
);
if (item.subtitle) {
tab.setAttribute("zen-show-sublabel", item.subtitle);
const tabLabel = tab.querySelector(".zen-tab-sublabel");
this.window.document.l10n.setArgs(tabLabel, {
tabSubtitle: item.subtitle,
});
}
return tab;
});
return tab;
} catch (e) {
console.error(
"ZenLiveFoldersManager: Failed to add tab for item",
item.url,
e
);
return null;
}
})
.filter(tab => tab);
// Wait for tabs to (hopefully) be initialized on all windows
lazy.setTimeout(() => {

View File

@@ -330,7 +330,7 @@ class nsZenWindowSync {
// browser/components/extensions/parent/ext-browser.js.
// See: Bug 1960104 - Improve tab group ID generation in addTabGroup
// This is implemented from gBrowser.addTabGroup.
return `${Date.now()}-${Math.round(Math.random() * 100)}`;
return `${Date.now()}-${Services.uuid.generateUUID().toString().slice(1, -1)}`;
}
/**

View File

@@ -1337,7 +1337,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
this.useAlgo = themedColors[0]?.algorithm ?? "";
this.#currentLightness = themedColors[0]?.lightness ?? 50;
const rotation = -45; // TODO: Detect rotation based on the accent color
const rotation = -30; // TODO: Detect rotation based on the accent color
if (themedColors.length === 0) {
const getBrowserBg = () => {
if (this.canBeTransparent) {
@@ -1367,8 +1367,8 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
if (themedColors.length === 2) {
if (!forToolbar) {
return [
`linear-gradient(${rotation}deg, ${this.#getSingleRGBColor(themedColors[1], forToolbar)} 0%, transparent 100%)`,
`linear-gradient(${rotation + 180}deg, ${this.#getSingleRGBColor(themedColors[0], forToolbar)} 0%, transparent 100%)`,
`linear-gradient(${rotation}deg, ${this.#getSingleRGBColor(themedColors[1], forToolbar)} 20%, transparent 100%)`,
`linear-gradient(${rotation + 180}deg, ${this.#getSingleRGBColor(themedColors[0], forToolbar)} 20%, transparent 100%)`,
]
.reverse()
.join(", ");

View File

@@ -880,7 +880,7 @@ class nsZenSpacesSyncApplier {
prev &&
prev.parentNode &&
prev.parentNode === el.parentNode &&
prev.nextElementSibling !== el
prev.compareDocumentPosition(el) & win.Node.DOCUMENT_POSITION_PRECEDING
) {
win.gBrowser.zenHandleTabMove(el, () => prev.after(el));
}

View File

@@ -374,6 +374,14 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
case "reset-switch":
case "switch":
if (behavior.includes("unload")) {
if (pinnedTabs.some(tab => tab.selected)) {
const selectedTabs = pinnedTabs.filter(tab => tab.selected);
const tabToBlurTo = gBrowser._findTabToBlurTo(
selectedTabs[0],
pinnedTabs
);
gBrowser.selectedTab = tabToBlurTo;
}
for (const tab of pinnedTabs) {
if (tab.hasAttribute("glance-id")) {
// We have a glance tab inside the tab we are trying to unload,

View File

@@ -629,6 +629,8 @@
--tab-icon-end-margin: 8.5px;
& .tabbrowser-tab {
overflow: visible;
& .tab-label-container {
opacity: 1;
display: flex;

View File

@@ -16,4 +16,6 @@ support-files = [
["browser_normal_tabs_apply.js"]
["browser_normal_tabs_order.js"]
["browser_normal_tabs_projection.js"]

View File

@@ -0,0 +1,101 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function test_RapidTabsGetUniqueSyncIds() {
await gZenWorkspaces.promiseInitialized;
const tabs = [];
for (let i = 0; i < 60; i++) {
tabs.push(
gBrowser.addTrustedTab(`https://example.com/?rapid-${i}`, {
inBackground: true,
skipAnimation: true,
})
);
}
await TestUtils.waitForCondition(
() => tabs.every(tab => tab.id),
"every rapidly-opened tab is assigned a sync id"
);
const ids = tabs.map(tab => tab.id);
Assert.equal(
new Set(ids).size,
ids.length,
"every rapidly-opened tab has a unique sync id"
);
for (const tab of tabs) {
BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_IncomingOrderKeepsUnlistedTabInPlace() {
await gZenWorkspaces.promiseInitialized;
await SpecialPowers.pushPrefEnv({ set: [[NORMAL_TABS_PREF, true]] });
const uuid = gZenWorkspaces.activeWorkspace;
const opened = [
await openSyncableTab("https://example.com/?order-a"),
await openSyncableTab("https://example.com/?order-b"),
await openSyncableTab("https://example.com/?order-c"),
];
const container = opened[0].parentNode;
Assert.ok(
opened.every(tab => tab.parentNode === container),
"the three tabs share a container"
);
const domOrder = () =>
[...container.children].filter(node => opened.includes(node));
const [first, middle, last] = domOrder();
const spaceData = (await collectProjections()).get(uuid)?.data;
Assert.ok(spaceData, "the active space projects");
const record = {
id: uuid,
deleted: false,
cleartext: {
kind: "space",
data: { ...spaceData, children: [first.id, last.id] },
},
};
const failed = await ZenSpacesSyncApplier.applyBatch([record]);
Assert.deepEqual(failed, [], "the space order applies cleanly");
Assert.deepEqual(
domOrder(),
[first, middle, last],
"the unlisted tab stays between its neighbours, not pushed to the bottom"
);
ZenSpacesSyncModel.noteApplied(uuid, null);
for (const tab of opened) {
BrowserTestUtils.removeTab(tab);
}
await SpecialPowers.popPrefEnv();
});
add_task(async function test_AppliedNormalTabReprojectsFaithfully() {
await gZenWorkspaces.promiseInitialized;
await SpecialPowers.pushPrefEnv({ set: [[NORMAL_TABS_PREF, true]] });
const id = "test-sync-normal-faithful";
const failed = await ZenSpacesSyncApplier.applyBatch([
tabRecord(id, { pinned: false, url: "https://example.com/?faithful" }),
]);
Assert.deepEqual(failed, [], "the normal-tab record applies cleanly");
const tab = document.getElementById(id);
Assert.ok(gBrowser.isTab(tab), "the tab materializes");
const projections = await collectProjections();
const changes = ZenSpacesSyncModel.computeChangedIDs();
Assert.ok(
projections.get(id),
"the applied normal tab re-projects (is not dropped from the sidebar)"
);
Assert.ok(!(id in changes) || projections.get(id), "and is not tombstoned");
ZenSpacesSyncModel.noteApplied(id, null);
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});

View File

@@ -38,12 +38,12 @@ https_first_disabled = true
["browser_mute.js"]
["browser_mute2.js"]
["browser_mute_persist_navigation.js"]
["browser_mute_restore_closed_audible_tab.js"]
["browser_mute2.js"]
["browser_mute_webAudio.js"]
["browser_sound_indicator_silent_video.js"]

View File

@@ -5,7 +5,7 @@
"binaryName": "zen",
"version": {
"product": "firefox",
"version": "155.0.1",
"version": "156.0",
"candidate": "156.0",
"candidateBuild": 1
},
@@ -20,7 +20,7 @@
"brandShortName": "Zen",
"brandFullName": "Zen Browser",
"release": {
"displayVersion": "1.22.1b",
"displayVersion": "1.22.2b",
"github": {
"repo": "zen-browser/desktop"
},