mirror of
https://github.com/zen-browser/desktop.git
synced 2026-09-25 05:36:26 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca522d448d | ||
|
|
9749703d39 | ||
|
|
ef2ea05af5 | ||
|
|
4d8ce9f3eb | ||
|
|
9db97b76fb | ||
|
|
d7393603ee | ||
|
|
3cff4ec8aa | ||
|
|
e0ec6d6a30 | ||
|
|
ec86f7bf88 | ||
|
|
89ff9f3895 | ||
|
|
ad253cf2af | ||
|
|
e74571eead |
@@ -34,8 +34,8 @@ 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 `156.0`!
|
||||
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 156.0`!
|
||||
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `156.0.1`!
|
||||
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 156.0.1`!
|
||||
|
||||
### Contributing
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
07c99209a6e4c794752e9a242215199ad3ea10f6
|
||||
782b15a7c771795a323e9403340c029e7c8d7629
|
||||
@@ -39,3 +39,4 @@ zen-action-switch-to-dark-mode = Switch to Dark Mode
|
||||
zen-action-print = Print
|
||||
zen-action-focus-on = Focus on
|
||||
zen-action-extension = Extension
|
||||
zen-action-open = Open
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/tabbrowser/Tabbrowser.sys.mjs b/browser/components/tabbrowser/Tabbrowser.sys.mjs
|
||||
index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa074f1ac2 100644
|
||||
index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..a9a5dfbc38348f2c070f22af2954a089db94a6d3 100644
|
||||
--- a/browser/components/tabbrowser/Tabbrowser.sys.mjs
|
||||
+++ b/browser/components/tabbrowser/Tabbrowser.sys.mjs
|
||||
@@ -484,6 +484,7 @@ export class Tabbrowser {
|
||||
@@ -10,7 +10,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
const browsers = [];
|
||||
if (this.#activeSplitView) {
|
||||
for (const tab of this.#activeSplitView.tabs) {
|
||||
@@ -582,15 +583,74 @@ export class Tabbrowser {
|
||||
@@ -582,15 +583,86 @@ export class Tabbrowser {
|
||||
return this.tabContainer.visibleTabs;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,18 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
+
|
||||
+ get zenTabProgressListener() { return TabProgressListener; }
|
||||
+
|
||||
+ zenGetTabProgressFilter(tab) {
|
||||
+ return this.#tabFilters.get(tab);
|
||||
+ }
|
||||
+
|
||||
+ zenGetTabProgressListener(tab) {
|
||||
+ return this.#tabListeners.get(tab);
|
||||
+ }
|
||||
+
|
||||
+ zenSetTabProgressListener(tab, listener) {
|
||||
+ this.#tabListeners.set(tab, listener);
|
||||
+ }
|
||||
+
|
||||
+ get TabStateFlusher() {
|
||||
+ return lazy.TabStateFlusher;
|
||||
+ }
|
||||
@@ -87,7 +99,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
setSelectedTab(val, metricsContext = null) {
|
||||
if (this.selectedTab === val) {
|
||||
return;
|
||||
@@ -603,6 +663,9 @@ export class Tabbrowser {
|
||||
@@ -603,6 +675,9 @@ export class Tabbrowser {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -97,7 +109,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
// Update the tab
|
||||
this.tabbox.selectedTab = val;
|
||||
|
||||
@@ -688,6 +751,10 @@ export class Tabbrowser {
|
||||
@@ -688,6 +763,10 @@ export class Tabbrowser {
|
||||
userContextId = parseInt(tabArgument.getAttribute("usercontextid"), 10);
|
||||
}
|
||||
|
||||
@@ -108,7 +120,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (openWindowInfo) {
|
||||
userContextId = openWindowInfo.originAttributes.userContextId;
|
||||
}
|
||||
@@ -786,6 +853,8 @@ export class Tabbrowser {
|
||||
@@ -786,6 +865,8 @@ export class Tabbrowser {
|
||||
this.tabpanels.appendChild(panel);
|
||||
|
||||
let tab = this.tabs[0];
|
||||
@@ -117,7 +129,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
tab.linkedPanel = uniqueId;
|
||||
this.#selectedTab = tab;
|
||||
this.#selectedBrowser = browser;
|
||||
@@ -1234,18 +1303,24 @@ export class Tabbrowser {
|
||||
@@ -1234,18 +1315,24 @@ export class Tabbrowser {
|
||||
* The context for the operation for telemetry purposes, defaults to an unknown context.
|
||||
*/
|
||||
pinTab(aTab, { metricsContext = this.TabMetrics.UNKNOWN_CONTEXT } = {}) {
|
||||
@@ -143,7 +155,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
aTab.setAttribute("pinned", "true");
|
||||
this.#updateTabBarForPinnedTabs();
|
||||
@@ -1262,16 +1337,25 @@ export class Tabbrowser {
|
||||
@@ -1262,16 +1349,25 @@ export class Tabbrowser {
|
||||
* The context for the operation for telemetry purposes, defaults to an unknown context.
|
||||
*/
|
||||
unpinTab(aTab, { metricsContext = this.TabMetrics.UNKNOWN_CONTEXT } = {}) {
|
||||
@@ -170,7 +182,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
});
|
||||
|
||||
aTab.style.marginInlineStart = "";
|
||||
@@ -1494,6 +1578,9 @@ export class Tabbrowser {
|
||||
@@ -1494,6 +1590,9 @@ export class Tabbrowser {
|
||||
|
||||
let LOCAL_PROTOCOLS = ["chrome:", "about:", "resource:", "data:"];
|
||||
|
||||
@@ -180,7 +192,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (
|
||||
aIconURL &&
|
||||
!LOCAL_PROTOCOLS.some(protocol => aIconURL.startsWith(protocol))
|
||||
@@ -1503,6 +1590,9 @@ export class Tabbrowser {
|
||||
@@ -1503,6 +1602,9 @@ export class Tabbrowser {
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -190,7 +202,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
let browser = this.getBrowserForTab(aTab);
|
||||
browser.mIconURL = aIconURL;
|
||||
@@ -1831,7 +1921,6 @@ export class Tabbrowser {
|
||||
@@ -1831,7 +1933,6 @@ export class Tabbrowser {
|
||||
|
||||
// Preview mode should not reset the owner
|
||||
if (!this.#previewMode && !oldTab.selected) {
|
||||
@@ -198,7 +210,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
|
||||
let lastRelatedTab = this.#lastRelatedTabMap.get(oldTab);
|
||||
@@ -1922,6 +2011,7 @@ export class Tabbrowser {
|
||||
@@ -1922,6 +2023,7 @@ export class Tabbrowser {
|
||||
if (!this.#previewMode) {
|
||||
newTab.recordTimeFromUnloadToReload();
|
||||
newTab.updateLastAccessed();
|
||||
@@ -206,7 +218,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
oldTab.updateLastAccessed();
|
||||
// if this is the foreground window, update the last-seen timestamps.
|
||||
if (this.documentGlobal == lazy.BrowserWindowTracker.getTopWindow()) {
|
||||
@@ -2136,6 +2226,9 @@ export class Tabbrowser {
|
||||
@@ -2136,6 +2238,9 @@ export class Tabbrowser {
|
||||
}
|
||||
|
||||
let activeEl = this.document.activeElement;
|
||||
@@ -216,7 +228,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
// If focus is on the old tab, move it to the new tab.
|
||||
if (activeEl == oldTab) {
|
||||
newTab.focus();
|
||||
@@ -2174,7 +2267,7 @@ export class Tabbrowser {
|
||||
@@ -2174,7 +2279,7 @@ export class Tabbrowser {
|
||||
// Focus the location bar if it was previously focused for that tab.
|
||||
// In full screen mode, only bother making the location bar visible
|
||||
// if the tab is a blank one.
|
||||
@@ -225,7 +237,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
let selectURL = () => {
|
||||
if (this.#asyncTabSwitching) {
|
||||
// Set _awaitingSetURI flag to suppress popup notification
|
||||
@@ -2478,6 +2571,10 @@ export class Tabbrowser {
|
||||
@@ -2478,6 +2583,10 @@ export class Tabbrowser {
|
||||
return this.#setTabLabel(aTab, aLabel);
|
||||
}
|
||||
|
||||
@@ -236,7 +248,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
/**
|
||||
* Sets a tab's label, shortened and clamped for display.
|
||||
*
|
||||
@@ -2498,7 +2595,12 @@ export class Tabbrowser {
|
||||
@@ -2498,7 +2607,12 @@ export class Tabbrowser {
|
||||
* @returns {boolean}
|
||||
* Whether the label changed.
|
||||
*/
|
||||
@@ -250,7 +262,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (!aLabel || (isURL && /^about:reader\?url=/.test(aLabel))) {
|
||||
return false;
|
||||
}
|
||||
@@ -2657,7 +2759,7 @@ export class Tabbrowser {
|
||||
@@ -2657,7 +2771,7 @@ export class Tabbrowser {
|
||||
newIndex = this.selectedTab.index + 1;
|
||||
}
|
||||
|
||||
@@ -259,7 +271,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (this.isTabGroupLabel(targetTab)) {
|
||||
throw new Error(
|
||||
"Replacing a tab group label with a tab is not supported"
|
||||
@@ -2971,6 +3073,7 @@ export class Tabbrowser {
|
||||
@@ -2971,6 +3085,7 @@ export class Tabbrowser {
|
||||
uriIsAboutBlank,
|
||||
userContextId,
|
||||
skipLoad,
|
||||
@@ -267,7 +279,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
} = {}) {
|
||||
let b = this.document.createXULElement("browser");
|
||||
// Use the JSM global to create the permanentKey, so that if the
|
||||
@@ -3045,10 +3148,7 @@ export class Tabbrowser {
|
||||
@@ -3045,10 +3160,7 @@ export class Tabbrowser {
|
||||
b.setAttribute("name", name);
|
||||
}
|
||||
|
||||
@@ -279,7 +291,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
b.setAttribute("transparent", "true");
|
||||
}
|
||||
|
||||
@@ -3212,7 +3312,7 @@ export class Tabbrowser {
|
||||
@@ -3212,7 +3324,7 @@ export class Tabbrowser {
|
||||
|
||||
let panel = this.getPanel(browser);
|
||||
let uniqueId = this.#generateUniquePanelID();
|
||||
@@ -288,7 +300,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
aTab.linkedPanel = uniqueId;
|
||||
|
||||
// Inject the <browser> into the DOM if necessary.
|
||||
@@ -3269,8 +3369,8 @@ export class Tabbrowser {
|
||||
@@ -3269,8 +3381,8 @@ export class Tabbrowser {
|
||||
// If we transitioned from one browser to two browsers, we need to set
|
||||
// hasSiblings=false on both the existing browser and the new browser.
|
||||
if (this.tabs.length == 2) {
|
||||
@@ -299,7 +311,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
} else {
|
||||
aTab.linkedBrowser.browsingContext.hasSiblings = this.tabs.length > 1;
|
||||
}
|
||||
@@ -3458,7 +3558,6 @@ export class Tabbrowser {
|
||||
@@ -3458,7 +3570,6 @@ export class Tabbrowser {
|
||||
{
|
||||
tabIndex: tab.index + 1,
|
||||
userContextId: tab.userContextId,
|
||||
@@ -307,7 +319,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
focusUrlBar: true,
|
||||
}
|
||||
);
|
||||
@@ -3667,6 +3766,10 @@ export class Tabbrowser {
|
||||
@@ -3667,6 +3778,10 @@ export class Tabbrowser {
|
||||
schemelessInput,
|
||||
hasValidUserGestureActivation = false,
|
||||
textDirectiveUserActivation = false,
|
||||
@@ -318,7 +330,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
} = {}
|
||||
) {
|
||||
// all callers of addTab that pass a params object need to pass
|
||||
@@ -3677,6 +3780,23 @@ export class Tabbrowser {
|
||||
@@ -3677,6 +3792,23 @@ export class Tabbrowser {
|
||||
);
|
||||
}
|
||||
|
||||
@@ -342,7 +354,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (!UserInteraction.running("browser.tabs.opening", this.documentGlobal)) {
|
||||
UserInteraction.start(
|
||||
"browser.tabs.opening",
|
||||
@@ -3685,6 +3805,7 @@ export class Tabbrowser {
|
||||
@@ -3685,6 +3817,7 @@ export class Tabbrowser {
|
||||
);
|
||||
}
|
||||
|
||||
@@ -350,7 +362,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
// If we're opening a foreground tab, set the owner by default.
|
||||
ownerTab ??= inBackground ? null : this.selectedTab;
|
||||
|
||||
@@ -3692,6 +3813,7 @@ export class Tabbrowser {
|
||||
@@ -3692,6 +3825,7 @@ export class Tabbrowser {
|
||||
if (this.selectedTab.owner) {
|
||||
this.selectedTab.owner = null;
|
||||
}
|
||||
@@ -358,7 +370,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
// Find the tab that opened this one, if any. This is used for
|
||||
// determining positioning, and inherited attributes such as the
|
||||
@@ -3744,6 +3866,22 @@ export class Tabbrowser {
|
||||
@@ -3744,6 +3878,22 @@ export class Tabbrowser {
|
||||
noInitialLabel,
|
||||
skipBackgroundNotify,
|
||||
});
|
||||
@@ -381,7 +393,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (insertTab) {
|
||||
// Insert the tab into the tab container in the correct position.
|
||||
this.#insertTabAtIndex(t, {
|
||||
@@ -3752,6 +3890,7 @@ export class Tabbrowser {
|
||||
@@ -3752,6 +3902,7 @@ export class Tabbrowser {
|
||||
ownerTab,
|
||||
openerTab,
|
||||
pinned,
|
||||
@@ -389,7 +401,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
bulkOrderedOpen,
|
||||
tabGroup: tabGroup ?? openerTab?.group,
|
||||
});
|
||||
@@ -3770,6 +3909,7 @@ export class Tabbrowser {
|
||||
@@ -3770,6 +3921,7 @@ export class Tabbrowser {
|
||||
openWindowInfo,
|
||||
skipLoad,
|
||||
triggeringRemoteType,
|
||||
@@ -397,7 +409,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}));
|
||||
|
||||
if (focusUrlBar) {
|
||||
@@ -3894,6 +4034,12 @@ export class Tabbrowser {
|
||||
@@ -3894,6 +4046,12 @@ export class Tabbrowser {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +422,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
// Additionally send pinned tab events
|
||||
if (pinned) {
|
||||
this.#notifyPinnedStatus(t);
|
||||
@@ -3904,6 +4050,15 @@ export class Tabbrowser {
|
||||
@@ -3904,6 +4062,15 @@ export class Tabbrowser {
|
||||
if (!inBackground) {
|
||||
this.selectedTab = t;
|
||||
}
|
||||
@@ -426,7 +438,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -4125,6 +4280,7 @@ export class Tabbrowser {
|
||||
@@ -4125,6 +4292,7 @@ export class Tabbrowser {
|
||||
insertBefore = null,
|
||||
isAdoptingGroup = false,
|
||||
metricsContext = this.TabMetrics.UNKNOWN_CONTEXT,
|
||||
@@ -434,7 +446,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
} = {}
|
||||
) {
|
||||
if (
|
||||
@@ -4135,7 +4291,6 @@ export class Tabbrowser {
|
||||
@@ -4135,7 +4303,6 @@ export class Tabbrowser {
|
||||
!this.isSplitViewWrapper(tabOrSplitView)
|
||||
)
|
||||
) {
|
||||
@@ -442,7 +454,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
|
||||
if (!color) {
|
||||
@@ -4150,7 +4305,15 @@ export class Tabbrowser {
|
||||
@@ -4150,7 +4317,15 @@ export class Tabbrowser {
|
||||
id = `${Date.now()}-${Math.round(Math.random() * 100)}`;
|
||||
}
|
||||
let group = this.#createTabGroup(id, color, false, label, isAdoptingGroup);
|
||||
@@ -459,7 +471,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
group.addTabs(tabsAndSplitViews, metricsContext);
|
||||
|
||||
// Bail out if the group is empty at this point. This can happen if all
|
||||
@@ -4253,7 +4416,7 @@ export class Tabbrowser {
|
||||
@@ -4253,7 +4428,7 @@ export class Tabbrowser {
|
||||
}
|
||||
|
||||
this.#handleTabMove(tab, () =>
|
||||
@@ -468,7 +480,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4337,6 +4500,7 @@ export class Tabbrowser {
|
||||
@@ -4337,6 +4512,7 @@ export class Tabbrowser {
|
||||
color: group.color,
|
||||
insertBefore: newTabs[0],
|
||||
isAdoptingGroup: true,
|
||||
@@ -476,7 +488,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4597,6 +4761,7 @@ export class Tabbrowser {
|
||||
@@ -4597,6 +4773,7 @@ export class Tabbrowser {
|
||||
openWindowInfo,
|
||||
skipLoad,
|
||||
triggeringRemoteType,
|
||||
@@ -484,7 +496,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
) {
|
||||
// If we don't have a preferred remote type (or it is `NOT_REMOTE`), and
|
||||
@@ -4660,6 +4825,7 @@ export class Tabbrowser {
|
||||
@@ -4660,6 +4837,7 @@ export class Tabbrowser {
|
||||
openWindowInfo,
|
||||
name,
|
||||
skipLoad,
|
||||
@@ -492,7 +504,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4943,8 +5109,9 @@ export class Tabbrowser {
|
||||
@@ -4943,8 +5121,9 @@ export class Tabbrowser {
|
||||
}
|
||||
|
||||
// Add a new tab if needed.
|
||||
@@ -504,7 +516,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
let url = "about:blank";
|
||||
if (tabData.entries?.length) {
|
||||
@@ -4977,8 +5144,10 @@ export class Tabbrowser {
|
||||
@@ -4977,8 +5156,10 @@ export class Tabbrowser {
|
||||
insertTab: false,
|
||||
skipLoad: true,
|
||||
preferredRemoteType,
|
||||
@@ -516,7 +528,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (select) {
|
||||
tabToSelect = tab;
|
||||
}
|
||||
@@ -5000,7 +5169,8 @@ export class Tabbrowser {
|
||||
@@ -5000,7 +5181,8 @@ export class Tabbrowser {
|
||||
this.pinTab(tab);
|
||||
// Then ensure all the tab open/pinning information is sent.
|
||||
this.#fireTabOpen(tab, {});
|
||||
@@ -526,7 +538,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
let { groupId } = tabData;
|
||||
const tabGroup = tabGroupWorkingData.get(groupId);
|
||||
// if a tab refers to a tab group we don't know, skip any group
|
||||
@@ -5020,7 +5190,10 @@ export class Tabbrowser {
|
||||
@@ -5020,7 +5202,10 @@ export class Tabbrowser {
|
||||
tabGroup.stateData.id,
|
||||
tabGroup.stateData.color,
|
||||
tabGroup.stateData.collapsed,
|
||||
@@ -538,7 +550,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
);
|
||||
tabsFragment.appendChild(tabGroup.node);
|
||||
}
|
||||
@@ -5080,9 +5253,21 @@ export class Tabbrowser {
|
||||
@@ -5080,9 +5265,18 @@ export class Tabbrowser {
|
||||
// to remove the old selected tab.
|
||||
if (tabToSelect) {
|
||||
let leftoverTab = this.selectedTab;
|
||||
@@ -547,9 +559,6 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
this.removeTab(leftoverTab);
|
||||
+ } else {
|
||||
+ this.documentGlobal.gZenWorkspaces._tabToRemoveForEmpty = leftoverTab;
|
||||
+ if (Services.prefs.getBoolPref("zen.workspaces.continue-where-left-off")) {
|
||||
+ this.documentGlobal.gZenWorkspaces._tabToSelect = selectTab - 1; // -1 for the empty tab.
|
||||
+ }
|
||||
+ if (this.documentGlobal.gZenWorkspaces._initialTab && !this.documentGlobal.gZenVerticalTabsManager._canReplaceNewTab) {
|
||||
+ this.documentGlobal.gZenWorkspaces._initialTab._shouldRemove = true;
|
||||
+ }
|
||||
@@ -560,7 +569,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
if (tabs.length > 1 || !tabs[0].selected) {
|
||||
this.#updateTabsAfterInsert();
|
||||
@@ -5313,11 +5498,17 @@ export class Tabbrowser {
|
||||
@@ -5313,11 +5507,17 @@ export class Tabbrowser {
|
||||
if (ownerTab) {
|
||||
tab.owner = ownerTab;
|
||||
}
|
||||
@@ -579,7 +588,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
let insertRelatedAfterCurrent = Services.prefs.getBoolPref(
|
||||
"browser.tabs.insertRelatedAfterCurrent"
|
||||
);
|
||||
@@ -5332,7 +5523,7 @@ export class Tabbrowser {
|
||||
@@ -5332,7 +5532,7 @@ export class Tabbrowser {
|
||||
(insertRelatedAfterCurrent && lastRelatedTab) ||
|
||||
openerTab ||
|
||||
this.selectedTab;
|
||||
@@ -588,7 +597,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
tabGroup = previousTab.group;
|
||||
}
|
||||
if (
|
||||
@@ -5348,7 +5539,7 @@ export class Tabbrowser {
|
||||
@@ -5348,7 +5548,7 @@ export class Tabbrowser {
|
||||
previousTab.splitview
|
||||
) + 1;
|
||||
} else if (previousTab.visible) {
|
||||
@@ -597,7 +606,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
} else if (previousTab == this.documentGlobal.FirefoxViewHandler.tab) {
|
||||
elementIndex = 0;
|
||||
}
|
||||
@@ -5376,14 +5567,14 @@ export class Tabbrowser {
|
||||
@@ -5376,14 +5576,14 @@ export class Tabbrowser {
|
||||
}
|
||||
// Ensure index is within bounds.
|
||||
if (tab.pinned) {
|
||||
@@ -616,7 +625,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
if (pinned && !itemAfter?.pinned) {
|
||||
itemAfter = null;
|
||||
@@ -5400,7 +5591,7 @@ export class Tabbrowser {
|
||||
@@ -5400,7 +5600,7 @@ export class Tabbrowser {
|
||||
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
|
||||
@@ -625,7 +634,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (
|
||||
(this.isTab(itemAfter) && itemAfter.group == tabGroup) ||
|
||||
this.isSplitViewWrapper(itemAfter)
|
||||
@@ -5431,7 +5622,11 @@ export class Tabbrowser {
|
||||
@@ -5431,7 +5631,11 @@ export class Tabbrowser {
|
||||
const tabContainer = pinned
|
||||
? this.tabContainer.pinnedTabsContainer
|
||||
: this.tabContainer;
|
||||
@@ -637,7 +646,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
|
||||
if (tab.group?.collapsed) {
|
||||
@@ -5446,6 +5641,7 @@ export class Tabbrowser {
|
||||
@@ -5446,6 +5650,7 @@ export class Tabbrowser {
|
||||
if (pinned) {
|
||||
this.#updateTabBarForPinnedTabs();
|
||||
}
|
||||
@@ -645,7 +654,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
this.documentGlobal.TabBarVisibility.update();
|
||||
}
|
||||
@@ -6025,6 +6221,7 @@ export class Tabbrowser {
|
||||
@@ -6025,6 +6230,7 @@ export class Tabbrowser {
|
||||
metricsContext,
|
||||
} = {}
|
||||
) {
|
||||
@@ -653,7 +662,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs
|
||||
// can be considered equivalent to closing the window.
|
||||
if (
|
||||
@@ -6135,6 +6332,7 @@ export class Tabbrowser {
|
||||
@@ -6135,6 +6341,7 @@ export class Tabbrowser {
|
||||
closedTabCount -= 1;
|
||||
}
|
||||
}
|
||||
@@ -661,7 +670,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
if (closedTabCount > 0) {
|
||||
this.recordTabMetrics(
|
||||
@@ -6236,6 +6434,14 @@ export class Tabbrowser {
|
||||
@@ -6236,6 +6443,14 @@ export class Tabbrowser {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -676,7 +685,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
let isVisibleTab = aTab.visible;
|
||||
// We have to sample the tab width now, since #beginRemoveTab might
|
||||
// end up modifying the DOM in such a way that aTab gets a new
|
||||
@@ -6244,6 +6450,9 @@ export class Tabbrowser {
|
||||
@@ -6244,6 +6459,9 @@ export class Tabbrowser {
|
||||
let tabWidth =
|
||||
this.documentGlobal.windowUtils.getBoundsWithoutFlushing(aTab).width;
|
||||
let isLastTab = this.#isLastTabInWindow(aTab);
|
||||
@@ -686,7 +695,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (
|
||||
!this.#beginRemoveTab(aTab, {
|
||||
closeWindowFastpath: true,
|
||||
@@ -6254,13 +6463,14 @@ export class Tabbrowser {
|
||||
@@ -6254,13 +6472,14 @@ export class Tabbrowser {
|
||||
metricsContext,
|
||||
})
|
||||
) {
|
||||
@@ -702,7 +711,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
let lockTabSizing =
|
||||
!this.tabContainer.verticalMode &&
|
||||
!aTab.pinned &&
|
||||
@@ -6291,7 +6501,13 @@ export class Tabbrowser {
|
||||
@@ -6291,7 +6510,13 @@ export class Tabbrowser {
|
||||
// We're not animating, so we can cancel the animation stopwatch.
|
||||
Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId);
|
||||
aTab._closeTimeAnimTimerId = null;
|
||||
@@ -717,7 +726,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6331,7 +6547,9 @@ export class Tabbrowser {
|
||||
@@ -6331,7 +6556,9 @@ export class Tabbrowser {
|
||||
get #shouldCloseWindowWithLastTab() {
|
||||
return (
|
||||
!this.documentGlobal.toolbar.visible ||
|
||||
@@ -728,7 +737,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6347,7 +6565,7 @@ export class Tabbrowser {
|
||||
@@ -6347,7 +6574,7 @@ export class Tabbrowser {
|
||||
*/
|
||||
#isLastTabInWindow(tab) {
|
||||
for (const otherTab of this.tabs) {
|
||||
@@ -737,7 +746,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -6488,6 +6706,7 @@ export class Tabbrowser {
|
||||
@@ -6488,6 +6715,7 @@ export class Tabbrowser {
|
||||
|
||||
newTab = true;
|
||||
}
|
||||
@@ -745,7 +754,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
aTab._endRemoveArgs = [closeWindow, newTab];
|
||||
|
||||
// swapBrowsersAndCloseOther will take care of closing the window without animation.
|
||||
@@ -6542,13 +6761,7 @@ export class Tabbrowser {
|
||||
@@ -6542,13 +6770,7 @@ export class Tabbrowser {
|
||||
}
|
||||
|
||||
if (newTab) {
|
||||
@@ -760,7 +769,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
} else {
|
||||
this.documentGlobal.TabBarVisibility.update();
|
||||
}
|
||||
@@ -6701,6 +6914,7 @@ export class Tabbrowser {
|
||||
@@ -6701,6 +6923,7 @@ export class Tabbrowser {
|
||||
this.tabs[i]._index = i;
|
||||
}
|
||||
|
||||
@@ -768,7 +777,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (!this.#windowIsClosing) {
|
||||
// update tab close buttons state
|
||||
this.tabContainer._updateCloseButtons();
|
||||
@@ -6891,6 +7105,7 @@ export class Tabbrowser {
|
||||
@@ -6891,6 +7114,7 @@ export class Tabbrowser {
|
||||
memory_after: await getTotalMemoryUsage(),
|
||||
time_to_unload_in_ms: timeElapsed,
|
||||
});
|
||||
@@ -776,7 +785,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6937,11 +7152,12 @@ export class Tabbrowser {
|
||||
@@ -6937,11 +7161,12 @@ export class Tabbrowser {
|
||||
}
|
||||
|
||||
let excludeTabs = new Set(aExcludeTabs);
|
||||
@@ -790,7 +799,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -6949,13 +7165,13 @@ export class Tabbrowser {
|
||||
@@ -6949,13 +7174,13 @@ export class Tabbrowser {
|
||||
!excludeTabs.has(aTab.owner) &&
|
||||
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
|
||||
) {
|
||||
@@ -806,7 +815,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
);
|
||||
|
||||
if (Services.prefs.getBoolPref("browser.tabs.selectMRUOnClose", false)) {
|
||||
@@ -6970,6 +7186,13 @@ export class Tabbrowser {
|
||||
@@ -6970,6 +7195,13 @@ export class Tabbrowser {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,7 +829,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
let tab = this.tabContainer.findNextTab(aTab, {
|
||||
direction: 1,
|
||||
filter: _tab => remainingTabs.includes(_tab),
|
||||
@@ -6983,7 +7206,7 @@ export class Tabbrowser {
|
||||
@@ -6983,7 +7215,7 @@ export class Tabbrowser {
|
||||
}
|
||||
|
||||
if (tab) {
|
||||
@@ -829,7 +838,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
|
||||
// If no qualifying visible tab was found, see if there is a tab in
|
||||
@@ -7004,7 +7227,7 @@ export class Tabbrowser {
|
||||
@@ -7004,7 +7236,7 @@ export class Tabbrowser {
|
||||
});
|
||||
}
|
||||
|
||||
@@ -838,7 +847,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
|
||||
#blurTab(aTab) {
|
||||
@@ -7021,7 +7244,7 @@ export class Tabbrowser {
|
||||
@@ -7021,7 +7253,7 @@ export class Tabbrowser {
|
||||
* @returns {boolean}
|
||||
* False if swapping isn't permitted, true otherwise.
|
||||
*/
|
||||
@@ -847,7 +856,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
// Do not allow transfering a private tab to a non-private window
|
||||
// and vice versa.
|
||||
if (
|
||||
@@ -7080,6 +7303,7 @@ export class Tabbrowser {
|
||||
@@ -7080,6 +7312,7 @@ export class Tabbrowser {
|
||||
// fire the beforeunload event in the process. Close the other
|
||||
// window if this was its last tab.
|
||||
if (
|
||||
@@ -855,7 +864,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
!remoteBrowser.#beginRemoveTab(aOtherTab, {
|
||||
adoptedByTab: aOurTab,
|
||||
closeWindowWithLastTab: true,
|
||||
@@ -7091,7 +7315,7 @@ export class Tabbrowser {
|
||||
@@ -7091,7 +7324,7 @@ export class Tabbrowser {
|
||||
// If this is the last tab of the window, hide the window
|
||||
// immediately without animation before the docshell swap, to avoid
|
||||
// about:blank being painted.
|
||||
@@ -864,7 +873,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (closeWindow) {
|
||||
let win = aOtherTab.documentGlobal;
|
||||
win.windowUtils.suppressAnimation(true);
|
||||
@@ -7231,11 +7455,13 @@ export class Tabbrowser {
|
||||
@@ -7231,11 +7464,13 @@ export class Tabbrowser {
|
||||
}
|
||||
|
||||
// Finish tearing down the tab that's going away.
|
||||
@@ -878,7 +887,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
this.setTabTitle(aOurTab);
|
||||
|
||||
@@ -7484,10 +7710,10 @@ export class Tabbrowser {
|
||||
@@ -7484,10 +7719,10 @@ export class Tabbrowser {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -891,7 +900,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
aTab.selected ||
|
||||
aTab.closing ||
|
||||
// Tabs that are sharing the screen, microphone or camera cannot be hidden.
|
||||
@@ -7576,7 +7802,8 @@ export class Tabbrowser {
|
||||
@@ -7576,7 +7811,8 @@ export class Tabbrowser {
|
||||
* @param {object} [aOptions={}]
|
||||
* Key-value pairs that will be serialized into the features string.
|
||||
*/
|
||||
@@ -901,7 +910,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
if (this.tabs.length == 1) {
|
||||
return null;
|
||||
}
|
||||
@@ -7593,7 +7820,7 @@ export class Tabbrowser {
|
||||
@@ -7593,7 +7829,7 @@ export class Tabbrowser {
|
||||
// tell a new window to take the "dropped" tab
|
||||
let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
args.appendElement(aTab.splitview ?? aTab);
|
||||
@@ -910,7 +919,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
private: lazy.PrivateBrowsingUtils.isWindowPrivate(this.documentGlobal),
|
||||
features: Object.entries(aOptions)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
@@ -7601,6 +7828,8 @@ export class Tabbrowser {
|
||||
@@ -7601,6 +7837,8 @@ export class Tabbrowser {
|
||||
openerWindow: this.documentGlobal,
|
||||
args,
|
||||
});
|
||||
@@ -919,7 +928,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7734,7 +7963,7 @@ export class Tabbrowser {
|
||||
@@ -7734,7 +7972,7 @@ export class Tabbrowser {
|
||||
* @returns {element is MozTabbrowserTabGroup}
|
||||
*/
|
||||
isTabGroup(element) {
|
||||
@@ -928,7 +937,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7807,8 +8036,8 @@ export class Tabbrowser {
|
||||
@@ -7807,8 +8045,8 @@ export class Tabbrowser {
|
||||
}
|
||||
|
||||
// Don't allow mixing pinned and unpinned tabs.
|
||||
@@ -939,7 +948,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
} else {
|
||||
tabIndex = Math.max(tabIndex, this.pinnedTabCount);
|
||||
}
|
||||
@@ -7854,8 +8083,8 @@ export class Tabbrowser {
|
||||
@@ -7854,8 +8092,8 @@ export class Tabbrowser {
|
||||
this.#handleTabMove(
|
||||
element,
|
||||
() => {
|
||||
@@ -950,7 +959,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
neighbor = neighbor.group;
|
||||
}
|
||||
if (neighbor?.splitview) {
|
||||
@@ -7866,6 +8095,12 @@ export class Tabbrowser {
|
||||
@@ -7866,6 +8104,12 @@ export class Tabbrowser {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -963,7 +972,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
if (movingForwards && neighbor) {
|
||||
neighbor.after(element);
|
||||
@@ -7939,23 +8174,31 @@ export class Tabbrowser {
|
||||
@@ -7939,23 +8183,31 @@ export class Tabbrowser {
|
||||
) {
|
||||
if (this.isTabGroupLabel(targetElement)) {
|
||||
targetElement = targetElement.group;
|
||||
@@ -1001,7 +1010,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
} else if (!element.pinned && targetElement && targetElement.pinned) {
|
||||
// If the caller asks to move an unpinned element next to a pinned
|
||||
// tab, move the unpinned element to be the first unpinned element
|
||||
@@ -7968,12 +8211,35 @@ export class Tabbrowser {
|
||||
@@ -7968,12 +8220,35 @@ export class Tabbrowser {
|
||||
// move the tab group right before the first unpinned tab.
|
||||
// 4. Moving a tab group and the first unpinned tab is grouped:
|
||||
// move the tab group right before the first unpinned tab's tab group.
|
||||
@@ -1038,7 +1047,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
// We want to include the splitview wrapper if it's the targetElement, but
|
||||
// not in the case where we want to reverse tabs within the same splitview.
|
||||
@@ -7982,6 +8248,7 @@ export class Tabbrowser {
|
||||
@@ -7982,6 +8257,7 @@ export class Tabbrowser {
|
||||
}
|
||||
|
||||
let getContainer = () =>
|
||||
@@ -1046,7 +1055,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
element.pinned
|
||||
? this.tabContainer.pinnedTabsContainer
|
||||
: this.tabContainer;
|
||||
@@ -7990,11 +8257,15 @@ export class Tabbrowser {
|
||||
@@ -7990,11 +8266,15 @@ export class Tabbrowser {
|
||||
element,
|
||||
() => {
|
||||
if (moveBefore) {
|
||||
@@ -1063,7 +1072,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
},
|
||||
{ metricsContext }
|
||||
@@ -8070,11 +8341,15 @@ export class Tabbrowser {
|
||||
@@ -8070,11 +8350,15 @@ export class Tabbrowser {
|
||||
* The context for the operation for telemetry purposes.
|
||||
*/
|
||||
moveTabToExistingGroup(aTab, aGroup, { metricsContext } = {}) {
|
||||
@@ -1082,7 +1091,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
}
|
||||
if (aTab.group && aTab.group.id === aGroup.id) {
|
||||
return;
|
||||
@@ -8153,6 +8428,7 @@ export class Tabbrowser {
|
||||
@@ -8153,6 +8437,7 @@ export class Tabbrowser {
|
||||
|
||||
let state = {
|
||||
tabIndex: tab.index,
|
||||
@@ -1090,7 +1099,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
};
|
||||
if (tab.visible) {
|
||||
state.elementIndex = tab.elementIndex;
|
||||
@@ -8190,7 +8466,7 @@ export class Tabbrowser {
|
||||
@@ -8190,7 +8475,7 @@ export class Tabbrowser {
|
||||
let changedSplitView =
|
||||
previousTabState.splitViewId != currentTabState.splitViewId;
|
||||
|
||||
@@ -1099,7 +1108,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
tab.dispatchEvent(
|
||||
new this.documentGlobal.CustomEvent("TabMove", {
|
||||
bubbles: true,
|
||||
@@ -8248,6 +8524,10 @@ export class Tabbrowser {
|
||||
@@ -8248,6 +8533,10 @@ export class Tabbrowser {
|
||||
|
||||
moveActionCallback();
|
||||
|
||||
@@ -1110,7 +1119,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
// Clear tabs cache after moving nodes because the order of tabs may have
|
||||
// changed.
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
@@ -8307,7 +8587,22 @@ export class Tabbrowser {
|
||||
@@ -8307,7 +8596,22 @@ export class Tabbrowser {
|
||||
* @returns {object}
|
||||
* The new tab in the current window, null if the tab couldn't be adopted.
|
||||
*/
|
||||
@@ -1134,7 +1143,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
// Swap the dropped tab with a new one we create and then close
|
||||
// it in the other window (making it seem to have moved between
|
||||
// windows). We also ensure that the tab we create to swap into has
|
||||
@@ -8350,6 +8645,8 @@ export class Tabbrowser {
|
||||
@@ -8350,6 +8654,8 @@ export class Tabbrowser {
|
||||
}
|
||||
params.skipLoad = true;
|
||||
let newTab = this.addWebTab("about:blank", params);
|
||||
@@ -1143,7 +1152,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
aTab.container.tabDragAndDrop.finishAnimateTabMove();
|
||||
|
||||
@@ -9189,7 +9486,7 @@ export class Tabbrowser {
|
||||
@@ -9189,7 +9495,7 @@ export class Tabbrowser {
|
||||
// preventDefault(). It will still raise the window if appropriate.
|
||||
return;
|
||||
}
|
||||
@@ -1152,7 +1161,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
this.documentGlobal.focus();
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
@@ -9206,7 +9503,6 @@ export class Tabbrowser {
|
||||
@@ -9206,7 +9512,6 @@ export class Tabbrowser {
|
||||
|
||||
on_TabGroupCollapse(aEvent) {
|
||||
aEvent.target.tabs.forEach(tab => {
|
||||
@@ -1160,7 +1169,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9554,7 +9850,9 @@ export class Tabbrowser {
|
||||
@@ -9554,7 +9859,9 @@ export class Tabbrowser {
|
||||
|
||||
let filter = this.#tabFilters.get(tab);
|
||||
if (filter) {
|
||||
@@ -1170,7 +1179,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
|
||||
let listener = this.#tabListeners.get(tab);
|
||||
if (listener) {
|
||||
@@ -10354,6 +10652,7 @@ class TabProgressListener {
|
||||
@@ -10354,6 +10661,7 @@ class TabProgressListener {
|
||||
aWebProgress.isTopLevel
|
||||
) {
|
||||
this._tab.setAttribute("busy", "true");
|
||||
@@ -1178,7 +1187,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
this.#tabbrowser._tabAttrModified(this._tab, ["busy"]);
|
||||
this._tab._notselectedsinceload = !this._tab.selected;
|
||||
}
|
||||
@@ -10434,6 +10733,7 @@ class TabProgressListener {
|
||||
@@ -10434,6 +10742,7 @@ class TabProgressListener {
|
||||
// known defaults. Note we use the original URL since about:newtab
|
||||
// redirects to a prerendered page.
|
||||
const shouldRemoveFavicon =
|
||||
@@ -1186,7 +1195,7 @@ index f68e37926001384d0c8e80a3c163dc89e9c4ee5c..350cebd50aa6ee58fbbbb53d0f5334aa
|
||||
!this._browser.mIconURL &&
|
||||
!ignoreBlank &&
|
||||
!(originalLocation.spec in FAVICON_DEFAULTS);
|
||||
@@ -10610,13 +10910,6 @@ class TabProgressListener {
|
||||
@@ -10610,13 +10919,6 @@ class TabProgressListener {
|
||||
this._browser.originalURI = aRequest.originalURI;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/urlbar/content/UrlbarInputBase.mjs b/browser/components/urlbar/content/UrlbarInputBase.mjs
|
||||
index 8be35b58c5369e123ca69426e852e3849bf75513..170cffbdd12a72dab15976cdab9207dc4292d4e6 100644
|
||||
index 8be35b58c5369e123ca69426e852e3849bf75513..778d8aecce4da6748ecbf8b86a38c2da6e01bec5 100644
|
||||
--- a/browser/components/urlbar/content/UrlbarInputBase.mjs
|
||||
+++ b/browser/components/urlbar/content/UrlbarInputBase.mjs
|
||||
@@ -83,6 +83,12 @@ if (lazy) {
|
||||
@@ -347,7 +347,7 @@ index 8be35b58c5369e123ca69426e852e3849bf75513..170cffbdd12a72dab15976cdab9207dc
|
||||
// Also ignore mousedown on the urlbarView context menu: opening/closing the
|
||||
// view is already handled by the result opening flow.
|
||||
- if (event.target.closest?.("tab, #urlbarView-context-menu")) {
|
||||
+ if (event.target.closest?.("tab, #urlbarView-context-menu") || event.target.closest?.("#tabs-newtab-button")) {
|
||||
+ if (event.target.closest?.("tab, #urlbarView-context-menu") || event.target.closest?.("menupopup") || event.target.closest?.("#tabs-newtab-button")) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/urlbar/content/UrlbarShared.mjs b/browser/components/urlbar/content/UrlbarShared.mjs
|
||||
index 0ac5330223c84fbb98880a5c2d83ae58a8f3079e..f9d3d27333f5552404bd8a1138fb1e5d6150a3d8 100644
|
||||
index 0ac5330223c84fbb98880a5c2d83ae58a8f3079e..ff488cf5a2c8b7857a8e145e40aaf861cfa655ec 100644
|
||||
--- a/browser/components/urlbar/content/UrlbarShared.mjs
|
||||
+++ b/browser/components/urlbar/content/UrlbarShared.mjs
|
||||
@@ -158,6 +158,7 @@ export const UrlbarShared = {
|
||||
@@ -71,3 +71,11 @@ index 0ac5330223c84fbb98880a5c2d83ae58a8f3079e..f9d3d27333f5552404bd8a1138fb1e5d
|
||||
case "UrlbarProviderOmnibox":
|
||||
return UrlbarShared.RESULT_GROUP.HEURISTIC_OMNIBOX;
|
||||
case "UrlbarProviderRestrictKeywordsAutofill":
|
||||
@@ -1060,6 +1080,7 @@ export const UrlbarShared = {
|
||||
case "UrlbarProviderAboutPages":
|
||||
return UrlbarShared.RESULT_GROUP.ABOUT_PAGES;
|
||||
case "UrlbarProviderInputHistory":
|
||||
+ case "ZenUrlbarProviderSidebar":
|
||||
return UrlbarShared.RESULT_GROUP.INPUT_HISTORY;
|
||||
case "UrlbarProviderQuickSuggest":
|
||||
return UrlbarShared.RESULT_GROUP.GENERAL_PARENT;
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp
|
||||
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
|
||||
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
|
||||
@@ -298,11 +298,13 @@
|
||||
panic_on_gl_error, picTileWidth, picTileHeight,
|
||||
gfx::gfxVars::WebRenderRequiresHardwareDriver(),
|
||||
StaticPrefs::gfx_webrender_low_quality_pinch_zoom_AtStartup(),
|
||||
StaticPrefs::gfx_webrender_max_shared_surface_size_AtStartup(),
|
||||
StaticPrefs::gfx_webrender_enable_subpixel_aa_AtStartup(),
|
||||
- compositor->ShouldUseLayerCompositor())) {
|
||||
+ compositor->ShouldUseLayerCompositor(),
|
||||
+ StaticPrefs::
|
||||
+ gfx_webrender_opaque_backdrop_fallback_AtStartup())) {
|
||||
// wr_window_new puts a message into gfxCriticalNote if it returns
|
||||
// false
|
||||
MOZ_ASSERT(errorMessage);
|
||||
error.AssignASCII(errorMessage);
|
||||
wr_api_free_error_msg(errorMessage);
|
||||
diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs
|
||||
--- a/gfx/webrender_bindings/src/bindings.rs
|
||||
+++ b/gfx/webrender_bindings/src/bindings.rs
|
||||
@@ -1998,10 +1998,11 @@
|
||||
reject_software_rasterizer: bool,
|
||||
low_quality_pinch_zoom: bool,
|
||||
max_shared_surface_size: i32,
|
||||
enable_subpixel_aa: bool,
|
||||
use_layer_compositor: bool,
|
||||
+ opaque_backdrop_fallback: bool,
|
||||
) -> bool {
|
||||
assert!(unsafe { is_in_render_thread() });
|
||||
|
||||
// Ensure the WR profiler callbacks are hooked up to the Gecko profiler.
|
||||
set_profiler_hooks(Some(&PROFILER_HOOKS));
|
||||
@@ -2164,11 +2165,12 @@
|
||||
texture_cache_config,
|
||||
reject_software_rasterizer,
|
||||
low_quality_pinch_zoom,
|
||||
max_shared_surface_size,
|
||||
enable_dithering,
|
||||
enable_shared_instance_buffer,
|
||||
+ opaque_backdrop_fallback,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let window_size = DeviceIntSize::new(window_width, window_height);
|
||||
let notifier = Box::new(CppNotifier { window_id });
|
||||
diff --git a/gfx/wr/webrender/src/device/gl.rs b/gfx/wr/webrender/src/device/gl.rs
|
||||
--- a/gfx/wr/webrender/src/device/gl.rs
|
||||
+++ b/gfx/wr/webrender/src/device/gl.rs
|
||||
@@ -3982,10 +3982,14 @@
|
||||
|
||||
pub fn disable_color_write(&self) {
|
||||
self.gl.color_mask(false, false, false, false);
|
||||
}
|
||||
|
||||
+ pub fn set_color_mask(&self, r: bool, g: bool, b: bool, a: bool) {
|
||||
+ self.gl.color_mask(r, g, b, a);
|
||||
+ }
|
||||
+
|
||||
pub fn set_blend(&mut self, enable: bool) {
|
||||
if enable {
|
||||
self.gl.enable(gl::BLEND);
|
||||
} else {
|
||||
self.gl.disable(gl::BLEND);
|
||||
diff --git a/gfx/wr/webrender/src/renderer/init.rs b/gfx/wr/webrender/src/renderer/init.rs
|
||||
--- a/gfx/wr/webrender/src/renderer/init.rs
|
||||
+++ b/gfx/wr/webrender/src/renderer/init.rs
|
||||
@@ -204,6 +204,8 @@
|
||||
pub low_quality_pinch_zoom: bool,
|
||||
pub max_shared_surface_size: i32,
|
||||
/// If true, open a debug socket to listen for remote debugger.
|
||||
/// Relies on `debugger` cargo feature being enabled.
|
||||
pub enable_debugger: bool,
|
||||
+ /// See explanation of `gfx.webrender.opaque-backdrop-fallback`.
|
||||
+ pub opaque_backdrop_fallback: bool,
|
||||
}
|
||||
@@ -277,10 +279,11 @@
|
||||
enable_instancing: true,
|
||||
enable_shared_instance_buffer: false,
|
||||
reject_software_rasterizer: false,
|
||||
low_quality_pinch_zoom: false,
|
||||
max_shared_surface_size: 2048,
|
||||
enable_debugger: true,
|
||||
+ opaque_backdrop_fallback: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -802,11 +805,12 @@
|
||||
allocated_native_surfaces: FastHashSet::default(),
|
||||
debug_overlay_state: DebugOverlayState::new(),
|
||||
buffer_damage_tracker: BufferDamageTracker::default(),
|
||||
max_primitive_instance_count,
|
||||
enable_instancing: options.enable_instancing,
|
||||
use_shared_instance_buffer,
|
||||
+ opaque_backdrop_fallback: options.opaque_backdrop_fallback,
|
||||
consecutive_oom_frames: 0,
|
||||
target_frame_publish_id: None,
|
||||
pending_result_msg: None,
|
||||
layer_compositor_frame_state_in_prev_frame: None,
|
||||
external_composite_debug_items: Vec::new(),
|
||||
diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs
|
||||
--- a/gfx/wr/webrender/src/renderer/mod.rs
|
||||
+++ b/gfx/wr/webrender/src/renderer/mod.rs
|
||||
@@ -867,13 +867,15 @@
|
||||
buffer_damage_tracker: BufferDamageTracker,
|
||||
|
||||
max_primitive_instance_count: usize,
|
||||
enable_instancing: bool,
|
||||
/// If true, stream instance data into large buffers shared by multiple
|
||||
/// draws, rather than allocating a new instance buffer for each draw.
|
||||
use_shared_instance_buffer: bool,
|
||||
|
||||
+ opaque_backdrop_fallback: bool,
|
||||
+
|
||||
/// Count consecutive oom frames to detectif we are stuck unable to render
|
||||
/// in a loop.
|
||||
consecutive_oom_frames: u32,
|
||||
|
||||
/// update() defers processing of ResultMsg, if frame_publish_id of
|
||||
@@ -2787,18 +2789,29 @@
|
||||
let read_target = ReadTarget::from_texture(cache_texture);
|
||||
|
||||
// Should always be drawing to picture cache tiles or off-screen surface!
|
||||
debug_assert!(!draw_target.is_default());
|
||||
let device_to_framebuffer = Scale::new(1i32);
|
||||
+ let dest_fb_rect = dest * device_to_framebuffer;
|
||||
|
||||
self.device.blit_render_target(
|
||||
read_target,
|
||||
src * device_to_framebuffer,
|
||||
draw_target,
|
||||
- dest * device_to_framebuffer,
|
||||
+ dest_fb_rect,
|
||||
TextureFilter::Linear,
|
||||
);
|
||||
+
|
||||
+ if self.opaque_backdrop_fallback {
|
||||
+ self.device.set_color_mask(false, false, false, true);
|
||||
+ self.device.clear_target(
|
||||
+ Some([0.0, 0.0, 0.0, 1.0]),
|
||||
+ None,
|
||||
+ Some(dest_fb_rect),
|
||||
+ );
|
||||
+ self.device.set_color_mask(true, true, true, true);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_picture_cache_target(
|
||||
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
|
||||
--- a/modules/libpref/init/StaticPrefList.yaml
|
||||
+++ b/modules/libpref/init/StaticPrefList.yaml
|
||||
@@ -8439,10 +8439,17 @@
|
||||
#else
|
||||
value: false
|
||||
#endif
|
||||
mirror: once
|
||||
|
||||
+# Make backdrop-filter treat its captured backdrop as if it had been
|
||||
+# composited over an opaque-black background. (See bug 2036640)
|
||||
+- name: gfx.webrender.opaque-backdrop-fallback
|
||||
+ type: bool
|
||||
+ value: true
|
||||
+ mirror: once
|
||||
+
|
||||
# Disable wait of GPU execution completion
|
||||
- name: gfx.webrender.wait-gpu-finished.disabled
|
||||
type: bool
|
||||
value: false
|
||||
mirror: once
|
||||
|
||||
@@ -146,6 +146,20 @@ window.gZenUIManager = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Shakes an element from side to side to catch the user's eye.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {number} delay Milliseconds to wait before shaking.
|
||||
*/
|
||||
shakeElement(element, delay = 0) {
|
||||
return this.elementAnimate(
|
||||
element,
|
||||
{ x: [0, -12, 8, -4, 2, 0] },
|
||||
{ duration: 600, delay, easing: "ease-out" }
|
||||
);
|
||||
},
|
||||
|
||||
_addNewCustomizableButtonsIfNeeded() {
|
||||
const kPref = "zen.ui.migration.compact-mode-button-added";
|
||||
let navbarPlacements = CustomizableUI.getWidgetIdsInArea(
|
||||
@@ -1404,18 +1418,37 @@ window.gZenVerticalTabsManager = {
|
||||
|
||||
if (isSingleToolbar) {
|
||||
this._navbarParent = navBar.parentElement;
|
||||
let elements = document.querySelectorAll(
|
||||
'#nav-bar-customization-target > :is([cui-areatype="toolbar"], .chromeclass-toolbar-additional):not(#urlbar-container):not(toolbarspring)'
|
||||
let elements = Array.from(
|
||||
document.querySelectorAll(
|
||||
'#nav-bar-customization-target > :is([cui-areatype="toolbar"], .chromeclass-toolbar-additional):not(#urlbar-container):not(toolbarspring)'
|
||||
)
|
||||
);
|
||||
elements = Array.from(elements).reverse();
|
||||
let normalButtons = [];
|
||||
let extensionButtons = [];
|
||||
for (const element of elements) {
|
||||
if (
|
||||
element.hasAttribute("data-extensionid") &&
|
||||
Services.prefs.getBoolPref("zen.view.overflow-webext-toolbar", true)
|
||||
) {
|
||||
extensionButtons.push(element);
|
||||
} else {
|
||||
normalButtons.push(element);
|
||||
}
|
||||
}
|
||||
// Add separator if it doesn't exist
|
||||
if (!this._hasSetSingleToolbar) {
|
||||
buttonsTarget.append(this._topButtonsSeparatorElement);
|
||||
}
|
||||
this._hasSetSingleToolbar = true;
|
||||
for (const button of elements) {
|
||||
for (const button of normalButtons.reverse()) {
|
||||
this.appendCustomizableItem(this._topButtonsSeparatorElement, button);
|
||||
}
|
||||
for (const extension of extensionButtons) {
|
||||
this.appendCustomizableItem(
|
||||
this._topButtonsSeparatorElement,
|
||||
extension
|
||||
);
|
||||
}
|
||||
buttonsTarget.prepend(
|
||||
document.getElementById("unified-extensions-button")
|
||||
);
|
||||
@@ -1576,10 +1609,6 @@ window.gZenVerticalTabsManager = {
|
||||
},
|
||||
|
||||
rebuildURLBarMenus() {
|
||||
if (document.getElementById("paste-and-go")) {
|
||||
return;
|
||||
}
|
||||
gURLBar._initCopyCutController();
|
||||
gURLBar.updatePlaceholder();
|
||||
},
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
.tab-group-overflow-count,
|
||||
.tab-group-editor-swatch,
|
||||
.tab-preview-container-indicator,
|
||||
.tab-preview-thumbnail-container > canvas,
|
||||
#tabbrowser-tabs[orient='vertical']:not([expanded]) .tab-close-button,
|
||||
.all-tabs-item > toolbarbutton.all-tabs-container-indicator::before,
|
||||
.urlbarView-button,
|
||||
|
||||
@@ -164,11 +164,12 @@
|
||||
}
|
||||
|
||||
#navigator-toolbox[zen-has-implicit-hover="true"] &,
|
||||
&[open],
|
||||
&:is([open], [showing]),
|
||||
#urlbar[has-popup-open="true"] &,
|
||||
:root[zen-has-empty-tab="true"] & {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: opacity 0.15s !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -558,22 +559,24 @@
|
||||
}
|
||||
|
||||
.urlbarView-prettyName,
|
||||
.urlbarView-tabGroup,
|
||||
.urlbarView-shortcutContent {
|
||||
border-radius: 4px;
|
||||
border-radius: 4px !important;
|
||||
font-weight: 600;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-top: auto !important;
|
||||
margin-bottom: auto !important;
|
||||
}
|
||||
|
||||
.urlbarView-prettyName {
|
||||
padding: 4px 6px;
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 90%);
|
||||
margin-left: 6px;
|
||||
.urlbarView-prettyName,
|
||||
.urlbarView-tabGroup {
|
||||
padding: 4px 6px !important;
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 90%) !important;
|
||||
margin-left: 6px !important;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
display: flex;
|
||||
color: color-mix(in srgb, var(--zen-primary-color), currentColor 95%);
|
||||
color: color-mix(in srgb, var(--zen-primary-color), currentColor 95%) !important;
|
||||
|
||||
& img {
|
||||
-moz-context-properties: fill;
|
||||
@@ -652,11 +655,10 @@
|
||||
--urlbarview-row-padding-block: 10px;
|
||||
color: light-dark(rgba(0, 0, 0, 0.7), rgba(255, 255, 255, 0.7)) !important;
|
||||
|
||||
&:hover {
|
||||
&,
|
||||
& .urlbarView-favicon {
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse) 5%, transparent 95%) !important;
|
||||
}
|
||||
&:hover,
|
||||
&:hover .urlbarView-favicon,
|
||||
.urlbarView-action-favicon {
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse) 5%, transparent 95%) !important;
|
||||
}
|
||||
|
||||
&[selected] {
|
||||
@@ -684,8 +686,9 @@
|
||||
background-color: rgba(255, 255, 255, 0.9) !important;
|
||||
}
|
||||
|
||||
& .urlbarView-prettyName {
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 80%);
|
||||
& .urlbarView-prettyName,
|
||||
& .urlbarView-tabGroup {
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 80%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,3 +411,7 @@ menuseparator {
|
||||
#window-modal-dialog {
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.tab-preview-thumbnail-container > canvas {
|
||||
border-radius: calc(var(--border-radius-medium) / var(--zen-squircle-value));
|
||||
}
|
||||
|
||||
+123
-56
@@ -404,13 +404,18 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
return;
|
||||
}
|
||||
|
||||
const collapsedRoot = group.rootMostCollapsedFolder;
|
||||
let collapsedRoot = group.rootMostCollapsedFolder;
|
||||
if (!collapsedRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
collapsedRoot.setAttribute("has-active", "true");
|
||||
await this.animateSelect(collapsedRoot);
|
||||
// If the folder got expanded instead, the tab may still be
|
||||
// inside of another collapsed subfolder.
|
||||
while (collapsedRoot) {
|
||||
collapsedRoot.setAttribute("has-active", "true");
|
||||
const expanded = await this.animateSelect(collapsedRoot);
|
||||
collapsedRoot = expanded ? group.rootMostCollapsedFolder : null;
|
||||
}
|
||||
gBrowser.tabContainer._invalidateCachedTabs();
|
||||
}
|
||||
|
||||
@@ -1505,22 +1510,28 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
#createAnimation(items, targetState, opts, callback = () => {}) {
|
||||
items = Array.isArray(items) ? items : [items];
|
||||
return items.map(item =>
|
||||
this.#animateItem(item, targetState, opts).then(callback)
|
||||
this.#animateItem(item, targetState, opts).then(
|
||||
finished => finished && callback()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#itemAnimations = new WeakMap();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* values are left applied as inline styles. A running animation on the
|
||||
* same element is replaced, continuing from its current values.
|
||||
*
|
||||
* @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.
|
||||
* @returns {Promise<boolean>} Resolves when the animation has finished,
|
||||
* false if it got replaced by another one before that.
|
||||
*/
|
||||
async #animateItem(item, targetState, { duration = 0.18, ease } = {}) {
|
||||
const computed = window.getComputedStyle(item);
|
||||
@@ -1539,13 +1550,18 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
const to = {};
|
||||
const finalStyles = new Map();
|
||||
for (const [prop, value] of Object.entries(targetState)) {
|
||||
let [start, end] = Array.isArray(value) ? value : [undefined, value];
|
||||
const start = Array.isArray(value) ? value[0] : undefined;
|
||||
from[prop] =
|
||||
start === undefined || start === "auto"
|
||||
? measure(prop)
|
||||
: toCssValue(prop, start);
|
||||
}
|
||||
// The start values are taken, the natural values need to be measured
|
||||
// without the previous animation applying.
|
||||
this.#itemAnimations.get(item)?.cancel();
|
||||
for (const [prop, value] of Object.entries(targetState)) {
|
||||
let end = Array.isArray(value) ? value[1] : 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);
|
||||
@@ -1555,18 +1571,18 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
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",
|
||||
});
|
||||
this.#itemAnimations.set(item, animation);
|
||||
try {
|
||||
await animation.finished;
|
||||
} catch (e) {
|
||||
// The animation was cancelled, leave the element as-is.
|
||||
return;
|
||||
// The animation was replaced, leave the element to the new one.
|
||||
return false;
|
||||
}
|
||||
for (const [cssProp, value] of finalStyles) {
|
||||
if (value === null) {
|
||||
@@ -1575,6 +1591,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
item.style.setProperty(cssProp, value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#calculateHeightShift(tabsContainer, selectedTabs) {
|
||||
@@ -1757,49 +1774,53 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
}
|
||||
}
|
||||
|
||||
const afterMarginTop = () => {
|
||||
tabsContainer.style.overflowY = "";
|
||||
if (group.hasAttribute("has-active")) {
|
||||
const activeTabs = group.activeTabs;
|
||||
const folders = new Map();
|
||||
group.removeAttribute("has-active");
|
||||
for (let tab of activeTabs) {
|
||||
const tabGroup = tab?.group?.hasAttribute("split-view-group")
|
||||
? tab?.group?.group
|
||||
: tab?.group;
|
||||
if (!folders.has(tabGroup?.id)) {
|
||||
folders.set(tabGroup?.id, tabGroup?.activeGroups?.at(-1));
|
||||
}
|
||||
let activeGroup = folders.get(tabGroup?.id);
|
||||
if (activeGroup) {
|
||||
this.setFolderIndentation(
|
||||
[tab],
|
||||
activeGroup,
|
||||
/* for collapse = */ true
|
||||
);
|
||||
// Update the active state and indentation right away instead of when
|
||||
// the animation ends, it would otherwise be lost if the folder gets
|
||||
// collapsed again before that.
|
||||
if (group.hasAttribute("has-active")) {
|
||||
const activeTabs = group.activeTabs;
|
||||
group.removeAttribute("has-active");
|
||||
for (const tab of activeTabs) {
|
||||
const tabGroup = tab?.group?.hasAttribute("split-view-group")
|
||||
? tab?.group?.group
|
||||
: tab?.group;
|
||||
const activeGroup = tabGroup?.activeGroups?.at(-1);
|
||||
if (activeGroup) {
|
||||
this.setFolderIndentation(
|
||||
[tab],
|
||||
activeGroup,
|
||||
/* for collapse = */ true
|
||||
);
|
||||
} else {
|
||||
// Since the folder is now expanded, we should remove active attribute
|
||||
// to the tab that was previously visible
|
||||
tab.removeAttribute("folder-active");
|
||||
if (tab.group?.hasAttribute("split-view-group")) {
|
||||
tab.group.style.removeProperty("--zen-folder-indent");
|
||||
} else {
|
||||
// Since the folder is now expanded, we should remove active attribute
|
||||
// to the tab that was previously visible
|
||||
tab.removeAttribute("folder-active");
|
||||
if (tab.group?.hasAttribute("split-view-group")) {
|
||||
tab.group.style.removeProperty("--zen-folder-indent");
|
||||
} else {
|
||||
tab.style.removeProperty("--zen-folder-indent");
|
||||
}
|
||||
tab.style.removeProperty("--zen-folder-indent");
|
||||
}
|
||||
}
|
||||
folders.clear();
|
||||
}
|
||||
// Folder has been expanded and has no active tabs
|
||||
group.activeTabs = [];
|
||||
}
|
||||
// Folder has been expanded and has no active tabs
|
||||
group.activeTabs = [];
|
||||
|
||||
const afterMarginTop = () => {
|
||||
tabsContainer.style.overflowY = "";
|
||||
};
|
||||
|
||||
let duration = this.#folderAnimationDuration;
|
||||
|
||||
// Items of collapsed subfolders must stay hidden, don't animate them twice.
|
||||
const itemsToReveal = itemsToShow.filter(
|
||||
item => !itemsToHide.includes(item)
|
||||
);
|
||||
|
||||
animations.push(
|
||||
...this.#createAnimation(
|
||||
itemsToShow,
|
||||
{ opacity: "", height: "" },
|
||||
itemsToReveal,
|
||||
{ opacity: "", height: "", minHeight: "" },
|
||||
{ duration, ease: "easeInOut" }
|
||||
),
|
||||
...this.#createAnimation(
|
||||
@@ -1821,10 +1842,6 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
this.#animationCount += 1;
|
||||
await Promise.all(animations);
|
||||
this.#animationCount -= 1;
|
||||
|
||||
// Cleanup
|
||||
this.styleCleanup(itemsToShow);
|
||||
this.styleCleanup(itemsToHide);
|
||||
}
|
||||
|
||||
async animateUnloadAll(group) {
|
||||
@@ -1969,9 +1986,16 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
gBrowser.tabContainer._invalidateCachedTabs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the selected tabs of a collapsed folder.
|
||||
*
|
||||
* @param {MozTabbrowserTabGroup} group - The collapsed folder.
|
||||
* @returns {Promise<boolean>} True if the folder got expanded instead,
|
||||
* since all of its tabs are active.
|
||||
*/
|
||||
async animateSelect(group) {
|
||||
if (!group?.isZenFolder) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.cancelPopupTimer();
|
||||
@@ -1989,7 +2013,24 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
|
||||
if (group.collapsed && selectedTabs.length) {
|
||||
const active = new Set([...(group.activeTabs ?? []), ...selectedTabs]);
|
||||
const tabs = group.tabs.filter(tab => !tab.hasAttribute("zen-empty-tab"));
|
||||
// Tabs inside of collapsed subfolders would stay hidden if the folder
|
||||
// gets expanded, so they don't need to be active.
|
||||
const isInsideCollapsedSubfolder = tab => {
|
||||
let folder = tab.group?.hasAttribute("split-view-group")
|
||||
? tab.group.group
|
||||
: tab.group;
|
||||
while (folder && folder !== group) {
|
||||
if (folder.collapsed) {
|
||||
return true;
|
||||
}
|
||||
folder = folder.group;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const tabs = group.tabs.filter(
|
||||
tab =>
|
||||
!tab.hasAttribute("zen-empty-tab") && !isInsideCollapsedSubfolder(tab)
|
||||
);
|
||||
if (
|
||||
tabs.length &&
|
||||
tabs.every(
|
||||
@@ -2001,7 +2042,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
)
|
||||
) {
|
||||
group.collapsed = false;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2160,11 +2201,12 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
await Promise.all(animations);
|
||||
this.#animationCount -= 1;
|
||||
if (this.#animationCount) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
this.styleCleanup(selectedTabs);
|
||||
return false;
|
||||
}
|
||||
|
||||
animateGroupMove(group, expand = false) {
|
||||
@@ -2187,6 +2229,31 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches to the folder's space, expands its parents (root first) and the
|
||||
* folder itself, then jiggles it so it's easy to spot.
|
||||
*
|
||||
* @param {MozTabbrowserTabGroup} folder The folder to reveal.
|
||||
*/
|
||||
async revealFolder(folder) {
|
||||
const workspaceId = folder.getAttribute("zen-workspace-id");
|
||||
if (workspaceId && workspaceId != gZenWorkspaces.activeWorkspace) {
|
||||
await gZenWorkspaces.changeWorkspaceWithID(workspaceId);
|
||||
}
|
||||
let collapsedRoot = folder.rootMostCollapsedFolder;
|
||||
const wasCollapsed = !!collapsedRoot;
|
||||
while (collapsedRoot) {
|
||||
collapsedRoot.collapsed = false;
|
||||
collapsedRoot = folder.rootMostCollapsedFolder;
|
||||
}
|
||||
const label = folder.labelElement.parentElement;
|
||||
label.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||
await gZenUIManager.shakeElement(
|
||||
label,
|
||||
wasCollapsed ? this.#folderRevealDuration * 1000 : 0
|
||||
);
|
||||
}
|
||||
|
||||
styleCleanup(items) {
|
||||
items.forEach(item => {
|
||||
item.style.removeProperty("opacity");
|
||||
|
||||
@@ -381,7 +381,7 @@
|
||||
|
||||
/* Hide .zen-media-focus-button if it doesn't fit in the toolbar */
|
||||
@container media-controls (max-width: 165px) {
|
||||
.zen-media-focus-button {
|
||||
.zen-media-card:not([media-sharing]) .zen-media-focus-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,8 +773,8 @@ class nsZenWindowSync {
|
||||
const otherBrowser = aTab.linkedBrowser;
|
||||
|
||||
// We aren't closing the other tab so, we also need to swap its tablisteners.
|
||||
let filter = otherTabBrowser._getTabProgressFilter(aTab);
|
||||
let tabListener = otherTabBrowser._getTabProgressListener(aTab);
|
||||
let filter = otherTabBrowser.zenGetTabProgressFilter(aTab);
|
||||
let tabListener = otherTabBrowser.zenGetTabProgressListener(aTab);
|
||||
try {
|
||||
otherBrowser.webProgress.removeProgressListener(filter);
|
||||
filter.removeProgressListener(tabListener);
|
||||
@@ -796,7 +796,7 @@ class nsZenWindowSync {
|
||||
true,
|
||||
false
|
||||
);
|
||||
otherTabBrowser._setTabProgressListener(aTab, tabListener);
|
||||
otherTabBrowser.zenSetTabProgressListener(aTab, tabListener);
|
||||
|
||||
const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
|
||||
filter.addProgressListener(tabListener, notifyAll);
|
||||
|
||||
@@ -437,17 +437,7 @@ class nsZenShareManager extends nsZenDOMOperatedFeature {
|
||||
// Shake the new split group in the sidebar.
|
||||
const group = tabs[0]?.group;
|
||||
if (group?.hasAttribute("split-view-group")) {
|
||||
requestAnimationFrame(() => {
|
||||
gZenUIManager.motion
|
||||
.animate(
|
||||
group,
|
||||
{ x: [-32, 0] },
|
||||
{ type: "spring", bounce: 0.5, duration: 1.5 }
|
||||
)
|
||||
.then(() => {
|
||||
group.style.removeProperty("transform");
|
||||
});
|
||||
});
|
||||
gZenUIManager.shakeElement(group);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)} 20%, transparent 100%)`,
|
||||
`linear-gradient(${rotation + 180}deg, ${this.#getSingleRGBColor(themedColors[0], forToolbar)} 20%, transparent 100%)`,
|
||||
`linear-gradient(${rotation}deg, ${this.#getSingleRGBColor(themedColors[1], forToolbar)} 30%, transparent 120%)`,
|
||||
`linear-gradient(${rotation + 180}deg, ${this.#getSingleRGBColor(themedColors[0], forToolbar)} 30%, transparent 120%)`,
|
||||
]
|
||||
.reverse()
|
||||
.join(", ");
|
||||
|
||||
@@ -757,6 +757,9 @@ class nsZenWorkspaces {
|
||||
: [this.#createWorkspaceData("Space", undefined)];
|
||||
this.activeWorkspace =
|
||||
aWinData.activeZenSpace || this._workspaceCache[0].uuid;
|
||||
if (aWinData.selected) {
|
||||
this._sessionSelected = aWinData.selected;
|
||||
}
|
||||
let promise = this.#initializeWorkspaces();
|
||||
for (const workspace of spacesFromStore) {
|
||||
const element = this.workspaceElement(workspace.uuid);
|
||||
@@ -834,6 +837,7 @@ class nsZenWorkspaces {
|
||||
});
|
||||
|
||||
const cleanup = () => {
|
||||
delete this._sessionSelected;
|
||||
delete this._tabToSelect;
|
||||
delete this._tabToRemoveForEmpty;
|
||||
delete this._shouldOverrideTabs;
|
||||
@@ -862,6 +866,11 @@ class nsZenWorkspaces {
|
||||
!this._shouldOverrideTabs
|
||||
) {
|
||||
const tabs = gBrowser.tabs.filter(tab => !tab.collapsed);
|
||||
if (
|
||||
Services.prefs.getBoolPref("zen.workspaces.continue-where-left-off")
|
||||
) {
|
||||
this._tabToSelect = this._sessionSelected - 1;
|
||||
}
|
||||
if (
|
||||
typeof this._tabToSelect === "number" &&
|
||||
this._tabToSelect >= 0 &&
|
||||
|
||||
@@ -145,6 +145,36 @@ export function recordDigest(kind, data) {
|
||||
class nsZenSpacesSyncModel {
|
||||
#file = null;
|
||||
#cache = null;
|
||||
/**
|
||||
* Ids applied from an incoming batch, keyed to the projection generation
|
||||
* (sidebar lastCollected) that was current when they were applied. The
|
||||
* projection only refreshes on the delayed session collection that runs
|
||||
* after the sync, so within the same sync the just-applied ids are in the
|
||||
* uploaded snapshot (noteApplied) but not yet in the projection. Without
|
||||
* this, the outgoing diff would read that gap as local deletions and
|
||||
* upload tombstones for records it just downloaded, wiping them on every
|
||||
* other device (gh-15426).
|
||||
*/
|
||||
#appliedStamp = new Map();
|
||||
#appliedStampGen = 0;
|
||||
|
||||
/** The projection generation the current sidebar data belongs to. */
|
||||
#currentStamp() {
|
||||
return lazy.ZenSessionStore.getSidebarData()?.lastCollected || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current projection generation, dropping held ids once a newer
|
||||
* collection has caught up (they can no longer be in the skew window).
|
||||
*/
|
||||
#appliedStampNow() {
|
||||
const stamp = this.#currentStamp();
|
||||
if (stamp !== this.#appliedStampGen) {
|
||||
this.#appliedStamp.clear();
|
||||
this.#appliedStampGen = stamp;
|
||||
}
|
||||
return stamp;
|
||||
}
|
||||
|
||||
#data() {
|
||||
if (!this.#file) {
|
||||
@@ -743,15 +773,20 @@ class nsZenSpacesSyncModel {
|
||||
const uploaded = this.#data().uploaded;
|
||||
const current = this.#digestAll();
|
||||
const pending = this.#pendingIds();
|
||||
const stamp = this.#appliedStampNow();
|
||||
const now = Date.now() / 1000;
|
||||
const changes = {};
|
||||
for (const [id, digest] of current) {
|
||||
if (uploaded[id] !== digest) {
|
||||
if (uploaded[id] !== digest && this.#appliedStamp.get(id) !== stamp) {
|
||||
changes[id] = now;
|
||||
}
|
||||
}
|
||||
for (const id of Object.keys(uploaded)) {
|
||||
if (!current.has(id) && !pending.has(id)) {
|
||||
if (
|
||||
!current.has(id) &&
|
||||
!pending.has(id) &&
|
||||
this.#appliedStamp.get(id) !== stamp
|
||||
) {
|
||||
changes[id] = now;
|
||||
}
|
||||
}
|
||||
@@ -774,13 +809,18 @@ class nsZenSpacesSyncModel {
|
||||
const uploaded = this.#data().uploaded;
|
||||
const current = this.#digestAll();
|
||||
const pending = this.#pendingIds();
|
||||
const stamp = this.#appliedStampNow();
|
||||
for (const [id, digest] of current) {
|
||||
if (uploaded[id] !== digest) {
|
||||
if (uploaded[id] !== digest && this.#appliedStamp.get(id) !== stamp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const id of Object.keys(uploaded)) {
|
||||
if (!current.has(id) && !pending.has(id)) {
|
||||
if (
|
||||
!current.has(id) &&
|
||||
!pending.has(id) &&
|
||||
this.#appliedStamp.get(id) !== stamp
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -823,11 +863,13 @@ class nsZenSpacesSyncModel {
|
||||
*/
|
||||
noteApplied(id, cleartext) {
|
||||
const data = this.#data();
|
||||
const stamp = this.#appliedStampNow();
|
||||
if (!cleartext) {
|
||||
delete data.uploaded[id];
|
||||
} else {
|
||||
data.uploaded[id] = recordDigest(cleartext.kind, cleartext.data);
|
||||
}
|
||||
this.#appliedStamp.set(id, stamp);
|
||||
syncLog(
|
||||
`acknowledged incoming ${cleartext ? cleartext.kind : "tombstone"} ${id}`
|
||||
);
|
||||
|
||||
@@ -12,6 +12,8 @@ support-files = [
|
||||
"head.js",
|
||||
]
|
||||
|
||||
["browser_apply_skew.js"]
|
||||
|
||||
["browser_folder_delete.js"]
|
||||
|
||||
["browser_normal_tabs_apply.js"]
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_AppliedCreateNotTombstonedBeforeCollection() {
|
||||
await gZenWorkspaces.promiseInitialized;
|
||||
await collectProjections();
|
||||
|
||||
const id = "test-skew-create";
|
||||
ZenSpacesSyncModel.noteApplied(id, tabRecord(id, { pinned: true }).cleartext);
|
||||
Assert.ok(
|
||||
!ZenSpacesSyncModel.itemExists(id),
|
||||
"the id is acknowledged but not yet projected (the skew window)"
|
||||
);
|
||||
|
||||
const changes = ZenSpacesSyncModel.computeChangedIDs();
|
||||
Assert.ok(
|
||||
!(id in changes),
|
||||
"a just-applied create is not tombstoned before the projection catches up"
|
||||
);
|
||||
|
||||
ZenSpacesSyncModel.noteApplied(id, null);
|
||||
});
|
||||
|
||||
add_task(async function test_AppliedDeletionNotResurrectedBeforeCollection() {
|
||||
await gZenWorkspaces.promiseInitialized;
|
||||
|
||||
const tab = await openSyncableTab("https://example.com/?skew-delete", {
|
||||
pinned: true,
|
||||
});
|
||||
const id = tab.id;
|
||||
await collectProjections();
|
||||
ZenSpacesSyncModel.markUploaded([id]);
|
||||
Assert.ok(
|
||||
!(id in ZenSpacesSyncModel.computeChangedIDs()),
|
||||
"the uploaded tab is clean before the deletion"
|
||||
);
|
||||
|
||||
const failed = await ZenSpacesSyncApplier.applyBatch([tombstone(id)]);
|
||||
Assert.deepEqual(failed, [], "the tombstone applies cleanly");
|
||||
Assert.ok(!document.getElementById(id), "the tab is removed");
|
||||
|
||||
const changes = ZenSpacesSyncModel.computeChangedIDs();
|
||||
Assert.ok(
|
||||
!(id in changes),
|
||||
"a just-applied deletion is not re-uploaded before the projection catches up"
|
||||
);
|
||||
|
||||
ZenSpacesSyncModel.noteApplied(id, null);
|
||||
});
|
||||
@@ -44,10 +44,7 @@ add_task(async function test_Issue_9981() {
|
||||
|
||||
const tab2ResetButton = tab2.querySelector(".tab-reset-button");
|
||||
tab2ResetButton.style.display = "flex";
|
||||
await TestUtils.waitForCondition(
|
||||
() => tab2ResetButton.getBoundingClientRect().width != 0
|
||||
);
|
||||
EventUtils.synthesizeMouseAtCenter(tab2ResetButton, {});
|
||||
await clickWhenReady(tab2ResetButton);
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => !tab2.hasAttribute("folder-active"),
|
||||
|
||||
@@ -42,7 +42,10 @@ add_task(async function test_Issue_() {
|
||||
"Tab 2 should be in the active folder"
|
||||
);
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(folder.resetButton, {});
|
||||
EventUtils.synthesizeMouseAtCenter(folder.labelElement, {
|
||||
type: "mousemove",
|
||||
});
|
||||
await clickWhenReady(folder.resetButton);
|
||||
|
||||
await new Promise(resolve =>
|
||||
/* eslint-disable mozilla/no-arbitrary-setTimeout */
|
||||
|
||||
@@ -26,3 +26,175 @@ add_task(async function test_Create_Folder() {
|
||||
await removeFolder(subfolder);
|
||||
await removeFolder(parent);
|
||||
});
|
||||
|
||||
add_task(async function test_Collapsed_Subfolder_Stays_Collapsed() {
|
||||
const originalTab = gBrowser.selectedTab;
|
||||
const activeTab = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab1");
|
||||
const hiddenTab = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab2");
|
||||
const parentTab = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab3");
|
||||
const subfolder = await gZenFolders.createFolder([activeTab, hiddenTab], {
|
||||
renameFolder: false,
|
||||
label: "subfolder",
|
||||
});
|
||||
const parent = await gZenFolders.createFolder([parentTab], {
|
||||
renameFolder: false,
|
||||
label: "parent",
|
||||
});
|
||||
parentTab.after(subfolder);
|
||||
gBrowser.selectedTab = activeTab;
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
const height = tab => tab.getBoundingClientRect().height;
|
||||
|
||||
subfolder.collapsed = true;
|
||||
await TestUtils.waitForCondition(
|
||||
() => !height(hiddenTab),
|
||||
"Unselected tab is hidden when the subfolder collapses"
|
||||
);
|
||||
|
||||
parent.collapsed = true;
|
||||
await TestUtils.waitForCondition(
|
||||
() => !height(parentTab),
|
||||
"Parent tab is hidden when the parent folder collapses"
|
||||
);
|
||||
|
||||
parent.collapsed = false;
|
||||
await TestUtils.waitForCondition(
|
||||
() => height(parentTab) && !parent.hasAttribute("has-active"),
|
||||
"Parent folder is expanded again"
|
||||
);
|
||||
await TestUtils.waitForTick();
|
||||
|
||||
ok(subfolder.collapsed, "Subfolder is still collapsed");
|
||||
ok(height(activeTab), "Active tab of the subfolder is still visible");
|
||||
Assert.equal(
|
||||
height(hiddenTab),
|
||||
0,
|
||||
"Unselected tab of the collapsed subfolder stays hidden"
|
||||
);
|
||||
|
||||
gBrowser.selectedTab = originalTab;
|
||||
await removeFolder(subfolder);
|
||||
await removeFolder(parent);
|
||||
});
|
||||
|
||||
add_task(async function test_Spam_Toggle_Parent_Folder() {
|
||||
const originalTab = gBrowser.selectedTab;
|
||||
const activeTab = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab1");
|
||||
const hiddenTab = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab2");
|
||||
const parentTab = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab3");
|
||||
const subfolder = await gZenFolders.createFolder([activeTab, hiddenTab], {
|
||||
renameFolder: false,
|
||||
label: "subfolder",
|
||||
});
|
||||
const parent = await gZenFolders.createFolder([parentTab], {
|
||||
renameFolder: false,
|
||||
label: "parent",
|
||||
});
|
||||
parentTab.after(subfolder);
|
||||
gBrowser.selectedTab = activeTab;
|
||||
// createFolder sets the initial collapsed state on a timeout.
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
const height = tab => tab.getBoundingClientRect().height;
|
||||
const indent = tab => tab.style.getPropertyValue("--zen-folder-indent");
|
||||
|
||||
subfolder.collapsed = true;
|
||||
await TestUtils.waitForCondition(
|
||||
() => !height(hiddenTab),
|
||||
"Unselected tab is hidden when the subfolder collapses"
|
||||
);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
parent.collapsed = !parent.collapsed;
|
||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||
}
|
||||
await TestUtils.waitForCondition(
|
||||
() => !height(parentTab),
|
||||
"Parent tab is hidden when the parent folder ends up collapsed"
|
||||
);
|
||||
|
||||
ok(parent.collapsed, "Parent folder is collapsed");
|
||||
ok(parent.hasAttribute("has-active"), "Parent folder is still active");
|
||||
Assert.deepEqual(parent.activeTabs, [activeTab], "Active tab is kept");
|
||||
ok(height(activeTab), "Active tab is still visible");
|
||||
Assert.equal(indent(activeTab), "0px", "Active tab lost its indentation");
|
||||
Assert.equal(height(hiddenTab), 0, "Unselected tab stays hidden");
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
parent.collapsed = !parent.collapsed;
|
||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||
}
|
||||
await TestUtils.waitForCondition(
|
||||
() => height(parentTab),
|
||||
"Parent tab is visible when the parent folder ends up expanded"
|
||||
);
|
||||
|
||||
ok(!parent.hasAttribute("has-active"), "Parent folder is not active");
|
||||
ok(subfolder.hasAttribute("has-active"), "Subfolder is still active");
|
||||
Assert.equal(indent(activeTab), "14px", "Active tab is indented again");
|
||||
Assert.equal(height(hiddenTab), 0, "Unselected tab stays hidden");
|
||||
|
||||
gBrowser.selectedTab = originalTab;
|
||||
await removeFolder(subfolder);
|
||||
await removeFolder(parent);
|
||||
});
|
||||
|
||||
add_task(async function test_Select_All_Tabs_Expands_Subfolder() {
|
||||
const originalTab = gBrowser.selectedTab;
|
||||
const tab1 = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab1");
|
||||
const tab2 = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab2");
|
||||
const tab3 = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab3");
|
||||
const subfolder = await gZenFolders.createFolder([tab2, tab3], {
|
||||
renameFolder: false,
|
||||
label: "subfolder",
|
||||
});
|
||||
const parent = await gZenFolders.createFolder([tab1], {
|
||||
renameFolder: false,
|
||||
label: "parent",
|
||||
});
|
||||
tab1.after(subfolder);
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
const height = tab => tab.getBoundingClientRect().height;
|
||||
|
||||
subfolder.collapsed = true;
|
||||
gBrowser.selectedTab = tab1;
|
||||
parent.collapsed = true;
|
||||
await TestUtils.waitForCondition(
|
||||
() => !height(tab2) && !height(tab3),
|
||||
"Subfolder tabs are hidden when the parent folder collapses"
|
||||
);
|
||||
|
||||
gBrowser.selectedTab = tab3;
|
||||
await TestUtils.waitForCondition(
|
||||
() => height(tab3) && !parent.collapsed,
|
||||
"Parent folder is expanded when tab 3 is selected"
|
||||
);
|
||||
await TestUtils.waitForCondition(
|
||||
() => !height(tab2),
|
||||
"Tab 2 stays hidden inside of the collapsed subfolder"
|
||||
);
|
||||
ok(!parent.hasAttribute("has-active"), "Parent folder is not active");
|
||||
ok(subfolder.collapsed, "Subfolder is still collapsed");
|
||||
ok(subfolder.hasAttribute("has-active"), "Subfolder is active");
|
||||
Assert.deepEqual(subfolder.activeTabs, [tab3], "Tab 3 is the active tab");
|
||||
ok(!tab1.hasAttribute("folder-active"), "Tab 1 is not folder-active");
|
||||
|
||||
gBrowser.selectedTab = tab2;
|
||||
await TestUtils.waitForCondition(
|
||||
() => height(tab2),
|
||||
"Tab 2 is visible when selected"
|
||||
);
|
||||
ok(!parent.collapsed, "Parent folder is expanded");
|
||||
ok(!subfolder.collapsed, "Subfolder is expanded");
|
||||
ok(!parent.hasAttribute("has-active"), "Parent folder is not active");
|
||||
ok(!subfolder.hasAttribute("has-active"), "Subfolder is not active");
|
||||
for (const tab of [tab1, tab2, tab3]) {
|
||||
ok(!tab.hasAttribute("folder-active"), "Tab is not folder-active");
|
||||
}
|
||||
|
||||
gBrowser.selectedTab = originalTab;
|
||||
await removeFolder(subfolder);
|
||||
await removeFolder(parent);
|
||||
});
|
||||
|
||||
@@ -32,3 +32,17 @@ async function addTabTo(
|
||||
await BrowserTestUtils.browserLoaded(browser);
|
||||
return tab;
|
||||
}
|
||||
|
||||
async function clickWhenReady(button) {
|
||||
await TestUtils.waitForCondition(() => {
|
||||
const rect = button.getBoundingClientRect();
|
||||
return (
|
||||
rect.width &&
|
||||
document.elementFromPoint(
|
||||
rect.x + rect.width / 2,
|
||||
rect.y + rect.height / 2
|
||||
) === button
|
||||
);
|
||||
}, "Button should be clickable");
|
||||
EventUtils.synthesizeMouseAtCenter(button, {});
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -13,4 +13,6 @@ support-files = [
|
||||
|
||||
["browser_issue_7385.js"]
|
||||
|
||||
["browser_sidebar_provider.js"]
|
||||
|
||||
["browser_urlbar_close_token.js"]
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
UrlbarShared: "chrome://browser/content/urlbar/UrlbarShared.mjs",
|
||||
UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
|
||||
SessionSaver:
|
||||
"moz-src:///browser/components/sessionstore/SessionSaver.sys.mjs",
|
||||
TabStateFlusher:
|
||||
"moz-src:///browser/components/sessionstore/TabStateFlusher.sys.mjs",
|
||||
});
|
||||
|
||||
const PROVIDER_NAME = "ZenUrlbarProviderSidebar";
|
||||
const TAB_URL = "https://example.com/";
|
||||
|
||||
async function collectSidebarData() {
|
||||
await TabStateFlusher.flushWindow(window);
|
||||
await SessionSaver.run();
|
||||
}
|
||||
|
||||
async function searchSidebarRows(value) {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
waitForFocus,
|
||||
value,
|
||||
});
|
||||
const rows = [];
|
||||
for (let index = 0; index < UrlbarTestUtils.getResultCount(window); index++) {
|
||||
const { result } = await UrlbarTestUtils.getRowAt(window, index);
|
||||
if (result.providerName == PROVIDER_NAME) {
|
||||
rows.push({ index, result });
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function addLabelledTab(label) {
|
||||
const tab = BrowserTestUtils.addTab(gBrowser, TAB_URL, {
|
||||
skipAnimation: true,
|
||||
});
|
||||
await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.zenStaticLabel = label;
|
||||
gBrowser._setTabLabel(tab, label);
|
||||
return tab;
|
||||
}
|
||||
|
||||
async function removeFolder(folder) {
|
||||
const removeEvent = BrowserTestUtils.waitForEvent(folder, "TabGroupRemoved");
|
||||
folder.delete();
|
||||
await removeEvent;
|
||||
}
|
||||
|
||||
add_task(async function test_custom_label_is_searchable() {
|
||||
const tab = await addLabelledTab("Quarterly zeninvoices");
|
||||
await collectSidebarData();
|
||||
|
||||
const rows = await searchSidebarRows("zeninvoices quarterly");
|
||||
Assert.equal(rows.length, 1, "The renamed tab is the only match");
|
||||
const { result } = rows[0];
|
||||
Assert.equal(result.type, UrlbarShared.RESULT_TYPE.TAB_SWITCH);
|
||||
Assert.equal(result.payload.url, TAB_URL);
|
||||
Assert.equal(
|
||||
result.payload.title,
|
||||
"Quarterly zeninvoices",
|
||||
"The custom label is shown instead of the page title"
|
||||
);
|
||||
|
||||
Assert.deepEqual(
|
||||
await searchSidebarRows("zeninvoices yearly"),
|
||||
[],
|
||||
"Every token needs to be part of the label"
|
||||
);
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_current_tab_is_not_suggested() {
|
||||
const tab = await addLabelledTab("zencurrenttab");
|
||||
gBrowser.selectedTab = tab;
|
||||
await collectSidebarData();
|
||||
|
||||
Assert.deepEqual(
|
||||
await searchSidebarRows("zencurrenttab"),
|
||||
[],
|
||||
"There is no point in switching to the tab we are already in"
|
||||
);
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_stale_sidebar_url_is_not_suggested() {
|
||||
const tab = await addLabelledTab("zenstaletab");
|
||||
await collectSidebarData();
|
||||
|
||||
BrowserTestUtils.startLoadingURIString(
|
||||
tab.linkedBrowser,
|
||||
"https://example.org/"
|
||||
);
|
||||
await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
|
||||
Assert.deepEqual(
|
||||
await searchSidebarRows("zenstaletab"),
|
||||
[],
|
||||
"A url that is not open anymore must not be offered as switch to tab"
|
||||
);
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_only_active_space_tabs() {
|
||||
const originalSpace = gZenWorkspaces.activeWorkspace;
|
||||
const tab = await addLabelledTab("zenotherspace");
|
||||
await gZenWorkspaces.createAndSaveWorkspace("Sidebar Provider Space");
|
||||
Assert.notEqual(
|
||||
gZenWorkspaces.activeWorkspace,
|
||||
originalSpace,
|
||||
"The new space is the active one"
|
||||
);
|
||||
await collectSidebarData();
|
||||
|
||||
Assert.deepEqual(
|
||||
await searchSidebarRows("zenotherspace"),
|
||||
[],
|
||||
"Tabs from other spaces are not matched"
|
||||
);
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
|
||||
await gZenWorkspaces.removeWorkspace(gZenWorkspaces.activeWorkspace);
|
||||
Assert.equal(gZenWorkspaces.activeWorkspace, originalSpace);
|
||||
await collectSidebarData();
|
||||
|
||||
const rows = await searchSidebarRows("zenotherspace");
|
||||
Assert.equal(rows.length, 1, "The tab is matched again in its own space");
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_folders_path_and_ranking() {
|
||||
const tab = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab1");
|
||||
const tab2 = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab2");
|
||||
const subfolder = await gZenFolders.createFolder([tab], {
|
||||
renameFolder: false,
|
||||
label: "zenfold archive notes",
|
||||
});
|
||||
const parent = await gZenFolders.createFolder([tab2], {
|
||||
renameFolder: false,
|
||||
label: "zenfold",
|
||||
});
|
||||
parent.tabs[0].after(subfolder);
|
||||
await collectSidebarData();
|
||||
|
||||
const rows = await searchSidebarRows("zenfold");
|
||||
Assert.equal(rows.length, 2, "Both folders are matched");
|
||||
for (const { result } of rows) {
|
||||
Assert.equal(result.type, UrlbarShared.RESULT_TYPE.DYNAMIC);
|
||||
}
|
||||
const [best, worst] = rows;
|
||||
const space = gZenWorkspaces.getWorkspaceFromId(
|
||||
gZenWorkspaces.activeWorkspace
|
||||
);
|
||||
Assert.equal(best.result.payload.zenFolderId, parent.id);
|
||||
Assert.equal(
|
||||
best.result.payload.path,
|
||||
[space.name, "zenfold"].join(" / "),
|
||||
"A root folder only shows its space and name"
|
||||
);
|
||||
Assert.equal(best.index, 1, "A full match sits right below the heuristic");
|
||||
Assert.equal(worst.result.payload.zenFolderId, subfolder.id);
|
||||
Assert.equal(
|
||||
worst.result.payload.path,
|
||||
[space.name, "zenfold", "zenfold archive notes"].join(" / "),
|
||||
"A subfolder shows every parent folder"
|
||||
);
|
||||
Assert.greater(
|
||||
worst.index,
|
||||
best.index,
|
||||
"The weaker the match, the further down the folder goes"
|
||||
);
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
await removeFolder(subfolder);
|
||||
await removeFolder(parent);
|
||||
});
|
||||
|
||||
add_task(async function test_picking_a_folder_reveals_it() {
|
||||
const tab = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab1");
|
||||
const tab2 = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab2");
|
||||
const subfolder = await gZenFolders.createFolder([tab], {
|
||||
renameFolder: false,
|
||||
label: "zenreveal",
|
||||
});
|
||||
const parent = await gZenFolders.createFolder([tab2], {
|
||||
renameFolder: false,
|
||||
label: "parent",
|
||||
});
|
||||
parent.tabs[0].after(subfolder);
|
||||
subfolder.collapsed = true;
|
||||
parent.collapsed = true;
|
||||
await collectSidebarData();
|
||||
|
||||
const rows = await searchSidebarRows("zenreveal");
|
||||
Assert.equal(rows.length, 1, "The subfolder is matched");
|
||||
UrlbarTestUtils.setSelectedRowIndex(window, rows[0].index);
|
||||
await UrlbarTestUtils.promisePopupClose(window, () =>
|
||||
EventUtils.synthesizeKey("KEY_Enter")
|
||||
);
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => !parent.collapsed && !subfolder.collapsed,
|
||||
"The folder and its parent get expanded"
|
||||
);
|
||||
|
||||
await removeFolder(subfolder);
|
||||
await removeFolder(parent);
|
||||
});
|
||||
@@ -302,6 +302,10 @@ const globalActionsTemplate = [
|
||||
},
|
||||
];
|
||||
|
||||
export function formatValueSync(l10nId) {
|
||||
return lazy.l10n.formatValueSync(l10nId);
|
||||
}
|
||||
|
||||
export const globalActions = globalActionsTemplate.map(action => ({
|
||||
isAvailable: window => {
|
||||
return (
|
||||
@@ -317,6 +321,6 @@ export const globalActions = globalActionsTemplate.map(action => ({
|
||||
extraPayload: {},
|
||||
...action,
|
||||
get label() {
|
||||
return lazy.l10n.formatValueSync(action.l10nId);
|
||||
return formatValueSync(action.l10nId);
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -9,6 +9,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
/* eslint-disable mozilla/valid-lazy */
|
||||
ZenUrlbarProviderGlobalActions:
|
||||
"resource:///modules/ZenUBActionsProvider.sys.mjs",
|
||||
ZenUrlbarProviderSidebar: "resource:///modules/ZenUBSidebarProvider.sys.mjs",
|
||||
});
|
||||
|
||||
export function registerZenUrlbarProviders() {
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
/* 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/. */
|
||||
|
||||
import {
|
||||
UrlbarProvider,
|
||||
UrlbarUtils,
|
||||
} from "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs";
|
||||
import { UrlbarShared } from "chrome://browser/content/urlbar/UrlbarShared.mjs";
|
||||
import { formatValueSync } from "resource:///modules/ZenUBGlobalActions.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
UrlbarResult: "chrome://browser/content/urlbar/UrlbarResult.mjs",
|
||||
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
|
||||
UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs",
|
||||
UrlbarProviderOpenTabs:
|
||||
"moz-src:///browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs",
|
||||
ZenSessionStore: "resource:///modules/zen/ZenSessionManager.sys.mjs",
|
||||
});
|
||||
|
||||
const DYNAMIC_TYPE_NAME = "zen-folders";
|
||||
|
||||
export class ZenUrlbarProviderSidebar extends UrlbarProvider {
|
||||
get name() {
|
||||
return "ZenUrlbarProviderSidebar";
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Values<typeof UrlbarShared.PROVIDER_TYPE>}
|
||||
*/
|
||||
get type() {
|
||||
return UrlbarShared.PROVIDER_TYPE.PROFILE;
|
||||
}
|
||||
|
||||
async isActive(queryContext) {
|
||||
return (
|
||||
!queryContext.isPrivate &&
|
||||
!!queryContext.tokens.length &&
|
||||
queryContext.sources.includes(UrlbarShared.RESULT_SOURCE.TABS) &&
|
||||
lazy.UrlbarPrefs.get("suggest.openpage")
|
||||
);
|
||||
}
|
||||
|
||||
async startQuery(queryContext, addCallback) {
|
||||
const sidebar = lazy.ZenSessionStore.getSidebarData();
|
||||
if (!sidebar) {
|
||||
return;
|
||||
}
|
||||
const tokens = queryContext.tokens.map(t => t.lowerCaseValue);
|
||||
const score = text => {
|
||||
text = text.toLowerCase();
|
||||
if (!tokens.every(token => text.includes(token))) {
|
||||
return 0;
|
||||
}
|
||||
return tokens.reduce((sum, token) => sum + token.length, 0) / text.length;
|
||||
};
|
||||
this.#addFolders(sidebar, score, queryContext, addCallback);
|
||||
this.#addTabs(sidebar, score, queryContext, addCallback);
|
||||
}
|
||||
|
||||
#addTabs(sidebar, score, queryContext, addCallback) {
|
||||
const openTabUrls = lazy.UrlbarProviderOpenTabs.getOpenTabUrls();
|
||||
const activeSpace =
|
||||
lazy.BrowserWindowTracker.getTopWindow().gZenWorkspaces.activeWorkspace;
|
||||
let resultsAdded = 0;
|
||||
for (const tabData of sidebar.tabs || []) {
|
||||
// Essentials have no space, they are part of every one of them.
|
||||
if (!tabData.zenEssential && tabData.zenWorkspace != activeSpace) {
|
||||
continue;
|
||||
}
|
||||
const entries = tabData.entries || [];
|
||||
const entry = entries[(tabData.index || entries.length) - 1];
|
||||
const label = tabData.zenStaticLabel || entry?.title;
|
||||
if (typeof label !== "string" || !label || !score(label)) {
|
||||
continue;
|
||||
}
|
||||
const url = entry?.url;
|
||||
if (!url || url == queryContext.currentPage) {
|
||||
// Don't suggest switching to the current tab.
|
||||
continue;
|
||||
}
|
||||
const userContextId = tabData.userContextId || 0;
|
||||
// The sidebar data can lag behind a navigation, only offer tabs that
|
||||
// are still registered as open with this url.
|
||||
const openTab = [...(openTabUrls.get(url) || [])].find(
|
||||
([contextId]) => contextId == userContextId
|
||||
);
|
||||
if (!openTab) {
|
||||
continue;
|
||||
}
|
||||
addCallback(
|
||||
this,
|
||||
new lazy.UrlbarResult({
|
||||
type: UrlbarShared.RESULT_TYPE.TAB_SWITCH,
|
||||
source: UrlbarShared.RESULT_SOURCE.TABS,
|
||||
payload: {
|
||||
url,
|
||||
title: label,
|
||||
icon: UrlbarShared.getIconForUrl(url),
|
||||
userContext: UrlbarUtils.getUserContextData(userContextId),
|
||||
tabGroup: openTab[1],
|
||||
action: lazy.UrlbarPrefs.get("secondaryActions.switchToTab")
|
||||
? UrlbarUtils.createTabSwitchSecondaryAction(userContextId)
|
||||
: undefined,
|
||||
},
|
||||
highlights: {
|
||||
url: UrlbarShared.HIGHLIGHT.TYPED,
|
||||
title: UrlbarShared.HIGHLIGHT.TYPED,
|
||||
},
|
||||
})
|
||||
);
|
||||
if (++resultsAdded == queryContext.maxResults) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#addFolders(sidebar, score, queryContext, addCallback) {
|
||||
const folders = new Map(
|
||||
(sidebar.folders || []).map(folder => [folder.id, folder])
|
||||
);
|
||||
const matched = [...folders.values()]
|
||||
.filter(folder => !folder.splitViewGroup && folder.name)
|
||||
.map(folder => ({ folder, score: score(folder.name) }))
|
||||
.filter(match => match.score)
|
||||
.sort((a, b) => b.score - a.score);
|
||||
for (const match of matched) {
|
||||
const { folder } = match;
|
||||
const space = sidebar.spaces?.find(s => s.uuid == folder.workspaceId);
|
||||
const path = [folder.name];
|
||||
for (
|
||||
let parent = folders.get(folder.parentId);
|
||||
parent;
|
||||
parent = folders.get(parent.parentId)
|
||||
) {
|
||||
path.unshift(parent.name);
|
||||
}
|
||||
if (space?.name) {
|
||||
path.unshift(space.name);
|
||||
}
|
||||
addCallback(
|
||||
this,
|
||||
new lazy.UrlbarResult({
|
||||
type: UrlbarShared.RESULT_TYPE.DYNAMIC,
|
||||
source: UrlbarShared.RESULT_SOURCE.TABS,
|
||||
payload: {
|
||||
dynamicType: DYNAMIC_TYPE_NAME,
|
||||
zenFolderId: folder.id,
|
||||
titleL10n: "zen-action-open",
|
||||
userIcon: folder.userIcon,
|
||||
icon: "chrome://browser/skin/zen-icons/folder.svg",
|
||||
path: path.join(" / "),
|
||||
},
|
||||
suggestedIndex:
|
||||
1 +
|
||||
Math.round(
|
||||
(1 - Math.min(match.score, 1)) * (queryContext.maxResults - 2)
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getViewUpdate(result) {
|
||||
return {
|
||||
icon: { attributes: { src: result.payload.icon } },
|
||||
titleStrong: {
|
||||
textContent: formatValueSync(result.payload.titleL10n),
|
||||
attributes: { dir: "ltr" },
|
||||
},
|
||||
path: {
|
||||
textContent: result.payload.path,
|
||||
attributes: { dir: "ltr", hidden: !result.payload.path },
|
||||
},
|
||||
userIcon: {
|
||||
attributes: {
|
||||
src: result.payload.userIcon,
|
||||
hidden: !result.payload.userIcon,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getViewTemplate() {
|
||||
return {
|
||||
attributes: {
|
||||
selectable: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: "icon",
|
||||
tag: "img",
|
||||
classList: ["urlbarView-favicon", "urlbarView-action-favicon"],
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
tag: "span",
|
||||
classList: ["urlbarView-title"],
|
||||
children: [
|
||||
{
|
||||
name: "titleStrong",
|
||||
tag: "strong",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tag: "span",
|
||||
classList: ["urlbarView-prettyName"],
|
||||
children: [
|
||||
{
|
||||
name: "userIcon",
|
||||
tag: "img",
|
||||
attributes: { hidden: true },
|
||||
},
|
||||
{
|
||||
name: "path",
|
||||
tag: "span",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
onEngagement(queryContext, controller, details) {
|
||||
const { zenFolderId } = details.result.payload;
|
||||
if (!zenFolderId) {
|
||||
return;
|
||||
}
|
||||
const ownerGlobal = details.element.documentGlobal;
|
||||
ownerGlobal.gBrowser.selectedBrowser.focus();
|
||||
const folder = ownerGlobal.document.getElementById(zenFolderId);
|
||||
if (folder?.isZenFolder) {
|
||||
ownerGlobal.gZenFolders.revealFolder(folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,5 @@ EXTRA_JS_MODULES += [
|
||||
"ZenUBGlobalActions.sys.mjs",
|
||||
"ZenUBProvider.sys.mjs",
|
||||
"ZenUBResultsLearner.sys.mjs",
|
||||
"ZenUBSidebarProvider.sys.mjs",
|
||||
]
|
||||
|
||||
+3
-3
@@ -5,8 +5,8 @@
|
||||
"binaryName": "zen",
|
||||
"version": {
|
||||
"product": "firefox",
|
||||
"version": "156.0",
|
||||
"candidate": "156.0",
|
||||
"version": "156.0.1",
|
||||
"candidate": "156.0.1",
|
||||
"candidateBuild": 1
|
||||
},
|
||||
"buildOptions": {
|
||||
@@ -20,7 +20,7 @@
|
||||
"brandShortName": "Zen",
|
||||
"brandFullName": "Zen Browser",
|
||||
"release": {
|
||||
"displayVersion": "1.22.2b",
|
||||
"displayVersion": "1.22.3b",
|
||||
"github": {
|
||||
"repo": "zen-browser/desktop"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user