Compare commits

...
12 Commits
32 changed files with 1085 additions and 388 deletions
+2 -2
View File
@@ -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
View File
@@ -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
+37 -8
View File
@@ -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,
+19 -16
View File
@@ -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;
}
}
}
+4
View File
@@ -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
View File
@@ -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");
+1 -1
View File
@@ -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;
}
}
+3 -3
View File
@@ -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);
+1 -11
View File
@@ -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);
}
}
+2 -2
View File
@@ -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(", ");
+9
View File
@@ -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 &&
+46 -4
View File
@@ -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}`
);
+2
View File
@@ -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);
});
+14
View File
@@ -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"]
+2
View File
@@ -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);
});
+5 -1
View File
@@ -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);
},
}));
+1
View File
@@ -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() {
+239
View File
@@ -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);
}
}
}
+1
View File
@@ -8,4 +8,5 @@ EXTRA_JS_MODULES += [
"ZenUBGlobalActions.sys.mjs",
"ZenUBProvider.sys.mjs",
"ZenUBResultsLearner.sys.mjs",
"ZenUBSidebarProvider.sys.mjs",
]
+3 -3
View File
@@ -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"
},