Compare commits

...

11 Commits
1.21.2b ... dev

Author SHA1 Message Date
mr. m
9f9ae52c63 gh-14351: Sync upstream Firefox to version 152.0.2 (gh-14350)
This PR syncs the upstream Firefox to version 152.0.2.

*  All patches applied cleanly.
2026-06-23 16:33:12 +02:00
fen4flo
3c3322058e gh-14044: Implement 'Add Route for Domain' in tab context menu (gh-14279)
Signed-off-by: fen4flo <75260616+FlorianButz@users.noreply.github.com>
Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2026-06-23 16:07:17 +02:00
Bernhard
31bb9d606d gh-12153: edit pinned-page url directly via submenu (gh-14329) 2026-06-23 15:43:57 +02:00
moshyfawn
b6cb8338e3 gh-14341: Fix about dialog footer alignment and restore legal links (gh-14342) 2026-06-23 15:39:29 +02:00
Bernhard
183c583841 gh-14320: fix workspace change icon picker state when opening (gh-14321)
The picker reset its page with a smooth scrollIntoView while the popup
was hiding, and positioned the page before the popup was laid out, so it
could animate/jump on open. Reset instantly on popupshown (after layout)
instead, and drop the on-hide scroll so there's no visible animation.
2026-06-21 21:43:44 +02:00
fen4flo
40080a25dc gh-11245: Fix tab placement with Move Tab / Space Routing (gh-14324)
Fixes #11245
2026-06-21 15:18:02 +02:00
Connor Griffin
733061fbe3 gh-11766: Fixed video fullscreen not working while in glance (gh-14272)
Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2026-06-21 11:20:37 +02:00
Andrey Bochkarev
61e631902c gh-14131: Prevent sidebar from flickering when moving a tab (gh-14293)
Co-authored-by: mr. m <mr.m@tuta.com>
2026-06-20 17:18:41 +02:00
mr. m
0cd67f882a gh-14266: Sync upstream Firefox to version 152.0.1 (gh-14265)
Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2026-06-18 10:36:03 +02:00
mr. m
bc6e4676f4 gh-14208: Set default prefs category to tabs&browsing (gh-14264) 2026-06-18 09:15:50 +02:00
mr. m
e331f07265 gh-14259: Fixed transparency not working after update (gh-14262) 2026-06-18 09:04:09 +02:00
24 changed files with 794 additions and 106 deletions

View File

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

View File

@@ -1 +1 @@
01604ce201d4de3c6d4b930d271ce4fe05e8d0c8 16659f37ea7611e9444cacbbdb33831d4dbbe0db

View File

@@ -19,13 +19,19 @@ tab-context-zen-add-essential-badge = { $num } / { $max }
tab-context-zen-remove-essential = tab-context-zen-remove-essential =
.label = Remove from Essentials .label = Remove from Essentials
.accesskey = R .accesskey = R
tab-context-zen-replace-pinned-url-with-current = tab-context-zen-edit-pinned-page =
.label = .label =
{ $isEssential -> { $isEssential ->
[true] Replace Essential URL with Current [true] Edit Essential URL
*[false] Replace Pinned URL with Current *[false] Edit Pinned URL
} }
.accesskey = P
tab-context-zen-replace-pinned-url-with-current =
.label = Replace with Current URL
.accesskey = C .accesskey = C
tab-context-zen-edit-pinned-url =
.label = Edit...
.accesskey = E
tab-context-zen-edit-title = tab-context-zen-edit-title =
.label = Change Label... .label = Change Label...
tab-context-zen-edit-icon = tab-context-zen-edit-icon =
@@ -55,6 +61,10 @@ zen-general-confirm =
.label = Confirm .label = Confirm
zen-pinned-tab-replaced = Pinned tab URL has been replaced with the current URL! zen-pinned-tab-replaced = Pinned tab URL has been replaced with the current URL!
zen-pinned-tab-url-edited = Pinned tab URL has been updated!
zen-pinned-tab-url-invalid = That doesn't look like a valid URL.
zen-pinned-tab-edit-url-title = Edit Pinned URL
zen-pinned-tab-edit-url-label = Enter the URL this pinned tab should point to:
zen-tabs-renamed = Tab has been successfully renamed! zen-tabs-renamed = Tab has been successfully renamed!
zen-background-tab-opened-toast = New background tab opened! zen-background-tab-opened-toast = New background tab opened!
zen-workspace-renamed-toast = Workspace has been successfully renamed! zen-workspace-renamed-toast = Workspace has been successfully renamed!

View File

@@ -25,3 +25,9 @@ zen-space-routing-open-in = Open In
zen-space-routing-url = URL zen-space-routing-url = URL
zen-space-routing-tab-routed-toast = New tab opened in { $targetWorkspace } zen-space-routing-tab-routed-toast = New tab opened in { $targetWorkspace }
tab-context-zen-add-domain-to-sr =
.label =
{ $tabCount ->
[one] Add Route for Domain
*[other] Add Route for Domains
}

View File

@@ -7,10 +7,6 @@
value: true value: true
condition: "defined(XP_WIN)" condition: "defined(XP_WIN)"
- name: widget.windows.mica.popups
value: true
condition: "defined(XP_WIN)"
# 1 = DWMSBT_MAINWINDOW # 1 = DWMSBT_MAINWINDOW
# 2 = DWMSBT_TRANSIENTWINDOW (default, also used for popups) # 2 = DWMSBT_TRANSIENTWINDOW (default, also used for popups)
# 3 = DWMSBT_TABBEDWINDOW # 3 = DWMSBT_TABBEDWINDOW

View File

@@ -0,0 +1,18 @@
diff --git a/browser/base/content/aboutDialog.css b/browser/base/content/aboutDialog.css
index 017125bc2510e5f5e317a5e78c40d6aa9ded76ca..d343d8c62a2251e3c3a33ae8f2ab9c4c68218c22 100644
--- a/browser/base/content/aboutDialog.css
+++ b/browser/base/content/aboutDialog.css
@@ -135,6 +135,13 @@
margin: 0 40px;
}
+#trademark {
+ font-size: xx-small;
+ text-align: center;
+ margin-block: 10px;
+ color: var(--text-color-deemphasized);
+}
+
#currentChannel {
margin: 0;
padding: 0;

View File

@@ -1,5 +1,5 @@
diff --git a/browser/base/content/aboutDialog.xhtml b/browser/base/content/aboutDialog.xhtml diff --git a/browser/base/content/aboutDialog.xhtml b/browser/base/content/aboutDialog.xhtml
index 3ffd464b960a4299a7dd0cd87e4fc2f781b9d593..ef9f42d1f0196902b4af31f4496891fcd6319831 100644 index 3ffd464b960a4299a7dd0cd87e4fc2f781b9d593..7a831c8ee2b73bb89bf8a82ac24958b55c16a5aa 100644
--- a/browser/base/content/aboutDialog.xhtml --- a/browser/base/content/aboutDialog.xhtml
+++ b/browser/base/content/aboutDialog.xhtml +++ b/browser/base/content/aboutDialog.xhtml
@@ -102,10 +102,6 @@ @@ -102,10 +102,6 @@
@@ -39,8 +39,8 @@ index 3ffd464b960a4299a7dd0cd87e4fc2f781b9d593..ef9f42d1f0196902b4af31f4496891fc
<label is="text-link" class="bottom-link" useoriginprincipal="true" href="about:license" data-l10n-id="bottomLinks-license"/> <label is="text-link" class="bottom-link" useoriginprincipal="true" href="about:license" data-l10n-id="bottomLinks-license"/>
- <label is="text-link" class="bottom-link" href="https://www.mozilla.org/about/legal/terms/firefox/" data-l10n-id="bottom-links-terms"/> - <label is="text-link" class="bottom-link" href="https://www.mozilla.org/about/legal/terms/firefox/" data-l10n-id="bottom-links-terms"/>
- <label is="text-link" class="bottom-link" href="https://www.mozilla.org/privacy/firefox/?utm_source=firefox-browser&#38;utm_medium=firefox-desktop&#38;utm_campaign=about-dialog" data-l10n-id="bottom-links-privacy"/> - <label is="text-link" class="bottom-link" href="https://www.mozilla.org/privacy/firefox/?utm_source=firefox-browser&#38;utm_medium=firefox-desktop&#38;utm_campaign=about-dialog" data-l10n-id="bottom-links-privacy"/>
+ <label is="text-link" class="bottom-link" href="about:rights" data-l10n-id="bottomLinks-rights"/> + <label is="text-link" class="bottom-link" href="about:rights" data-l10n-id="bottom-links-terms"/>
+ <label is="text-link" class="bottom-link" href="https://www.zen-browser.app/privacy-policy/" data-l10n-id="bottomLinks-privacy"/> + <label is="text-link" class="bottom-link" href="https://www.zen-browser.app/privacy-policy/" data-l10n-id="bottom-links-privacy"/>
</hbox> </hbox>
<description id="trademark" data-l10n-id="trademarkInfo"></description> <description id="trademark" data-l10n-id="trademarkInfo"></description>
</vbox> </vbox>

View File

@@ -35,6 +35,7 @@
<command id="cmd_zenToggleTabsOnRight" /> <command id="cmd_zenToggleTabsOnRight" />
<command id="cmd_zenReplacePinnedUrlWithCurrent" /> <command id="cmd_zenReplacePinnedUrlWithCurrent" />
<command id="cmd_zenEditPinnedUrl" />
<command id="cmd_contextZenAddToEssentials" /> <command id="cmd_contextZenAddToEssentials" />
<command id="cmd_contextZenRemoveFromEssentials" /> <command id="cmd_contextZenRemoveFromEssentials" />

View File

@@ -0,0 +1,13 @@
diff --git a/browser/components/nova/NovaPrefs.sys.mjs b/browser/components/nova/NovaPrefs.sys.mjs
index 3d22c881c481643fcffbc581523905a1847a7d41..453dd4d9c43d7483c037a993afbf2b854533497c 100644
--- a/browser/components/nova/NovaPrefs.sys.mjs
+++ b/browser/components/nova/NovaPrefs.sys.mjs
@@ -18,7 +18,7 @@ const PLATFORM_PREFS = (() => {
})();
function applyNovaPlatformDefaults() {
- const on = Services.prefs.getBoolPref("browser.nova.enabled", false);
+ const on = true;
const defaults = Services.prefs.getDefaultBranch("");
for (const pref of PLATFORM_PREFS) {
defaults.setBoolPref(pref, on);

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js
index 57add34d876fb885275f1147209c6fbeee367a7c..5f4616d5f7d3d077326246e843775f58c293ee48 100644 index 57add34d876fb885275f1147209c6fbeee367a7c..be0ab43b299317c0022a5e719f47a070c1574714 100644
--- a/browser/components/preferences/preferences.js --- a/browser/components/preferences/preferences.js
+++ b/browser/components/preferences/preferences.js +++ b/browser/components/preferences/preferences.js
@@ -132,6 +132,7 @@ ChromeUtils.defineLazyGetter(this, "gSubDialog", function () { @@ -132,6 +132,7 @@ ChromeUtils.defineLazyGetter(this, "gSubDialog", function () {
@@ -39,9 +39,12 @@ index 57add34d876fb885275f1147209c6fbeee367a7c..5f4616d5f7d3d077326246e843775f58
// Restore the cached Firefox Labs nav button visibility so it shows // Restore the cached Firefox Labs nav button visibility so it shows
// immediately when recipes are expected to be available, before // immediately when recipes are expected to be available, before
@@ -653,7 +660,7 @@ async function gotoPref( @@ -651,9 +658,9 @@ async function gotoPref(
let redesignEnabled = srdSectionPrefs.all;
let categories = document.getElementById("categories");
const kDefaultCategoryInternalName = redesignEnabled const kDefaultCategoryInternalName = redesignEnabled
? "paneSync" - ? "paneSync"
+ ? "paneTabsBrowsing"
: "paneGeneral"; : "paneGeneral";
- const kDefaultCategory = redesignEnabled ? "sync" : "general"; - const kDefaultCategory = redesignEnabled ? "sync" : "general";
+ const kDefaultCategory = redesignEnabled ? "tabsBrowsing" : "general"; + const kDefaultCategory = redesignEnabled ? "tabsBrowsing" : "general";

View File

@@ -1,8 +1,23 @@
diff --git a/browser/components/tabbrowser/content/tabs.js b/browser/components/tabbrowser/content/tabs.js diff --git a/browser/components/tabbrowser/content/tabs.js b/browser/components/tabbrowser/content/tabs.js
index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9ea21b6f6 100644 index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..b9a1cfe3a4a5035d9b06b0b3826a97c52cfcb39e 100644
--- a/browser/components/tabbrowser/content/tabs.js --- a/browser/components/tabbrowser/content/tabs.js
+++ b/browser/components/tabbrowser/content/tabs.js +++ b/browser/components/tabbrowser/content/tabs.js
@@ -220,7 +220,7 @@ @@ -197,8 +197,12 @@
XPCOMUtils.defineLazyPreferenceGetter(
this,
"_sidebarPositionStart",
- "sidebar.position_start",
- true
+ "zen.tabs.vertical.right-side",
+ true,
+ null,
+ newValue => {
+ return !newValue;
+ }
);
if (gMultiProcessBrowser) {
@@ -220,7 +224,7 @@
this.tooltip = "tabbrowser-tab-tooltip"; this.tooltip = "tabbrowser-tab-tooltip";
@@ -11,7 +26,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
this.tabDragAndDrop.init(); this.tabDragAndDrop.init();
} }
@@ -444,7 +444,7 @@ @@ -444,7 +448,7 @@
// and we're not hitting the scroll buttons. // and we're not hitting the scroll buttons.
if ( if (
event.button != 0 || event.button != 0 ||
@@ -20,7 +35,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
event.composedTarget.localName == "toolbarbutton" event.composedTarget.localName == "toolbarbutton"
) { ) {
return; return;
@@ -525,7 +525,6 @@ @@ -525,7 +529,6 @@
}); });
} }
} else if (isTabGroupLabel(event.target)) { } else if (isTabGroupLabel(event.target)) {
@@ -28,7 +43,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
} else if ( } else if (
event.originalTarget.closest("scrollbox") && event.originalTarget.closest("scrollbox") &&
!Services.prefs.getBoolPref( !Services.prefs.getBoolPref(
@@ -561,6 +560,9 @@ @@ -561,6 +564,9 @@
} }
on_keydown(event) { on_keydown(event) {
@@ -38,7 +53,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
let { altKey, shiftKey } = event; let { altKey, shiftKey } = event;
let [accel, nonAccel] = let [accel, nonAccel] =
AppConstants.platform == "macosx" AppConstants.platform == "macosx"
@@ -755,7 +757,6 @@ @@ -755,7 +761,6 @@
this._updateCloseButtons(); this._updateCloseButtons();
if (!this.#animatingGroups.size) { if (!this.#animatingGroups.size) {
@@ -46,7 +61,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
} }
document document
@@ -822,7 +823,7 @@ @@ -822,7 +827,7 @@
} }
get newTabButton() { get newTabButton() {
@@ -55,7 +70,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
} }
get verticalMode() { get verticalMode() {
@@ -838,6 +839,7 @@ @@ -838,6 +843,7 @@
} }
get overflowing() { get overflowing() {
@@ -63,7 +78,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
return this.hasAttribute("overflow"); return this.hasAttribute("overflow");
} }
@@ -851,29 +853,56 @@ @@ -851,29 +857,56 @@
if (pinnedChildren?.at(-1)?.id == "pinned-tabs-container-periphery") { if (pinnedChildren?.at(-1)?.id == "pinned-tabs-container-periphery") {
pinnedChildren.pop(); pinnedChildren.pop();
} }
@@ -93,7 +108,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
+ } else if (!isTab(tab)) { + } else if (!isTab(tab)) {
+ tabs.splice(i, 1); + tabs.splice(i, 1);
+ } + }
} + }
+ }; + };
+ expandTabs(pinnedTabs); + expandTabs(pinnedTabs);
+ expandTabs(unpinnedChildren); + expandTabs(unpinnedChildren);
@@ -114,7 +129,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
+ // remove the separator from the list + // remove the separator from the list
+ allTabs.splice(i, 1); + allTabs.splice(i, 1);
+ i--; + i--;
+ } }
+ i++; + i++;
} }
- -
@@ -130,7 +145,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
} }
get allSplitViews() { get allSplitViews() {
@@ -958,29 +987,28 @@ @@ -958,29 +991,28 @@
return this.#focusableItems; return this.#focusableItems;
} }
@@ -170,7 +185,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
this.#focusableItems = focusableItems; this.#focusableItems = focusableItems;
return this.#focusableItems; return this.#focusableItems;
@@ -993,6 +1021,7 @@ @@ -993,6 +1025,7 @@
* focusable (ex, we don't want the splitview container to be focusable, only its children). * focusable (ex, we don't want the splitview container to be focusable, only its children).
*/ */
get dragAndDropElements() { get dragAndDropElements() {
@@ -178,7 +193,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
if (this.#dragAndDropElements) { if (this.#dragAndDropElements) {
return this.#dragAndDropElements; return this.#dragAndDropElements;
} }
@@ -1063,6 +1092,7 @@ @@ -1063,6 +1096,7 @@
_invalidateCachedTabs() { _invalidateCachedTabs() {
this.#allTabs = null; this.#allTabs = null;
this._invalidateCachedVisibleTabs(); this._invalidateCachedVisibleTabs();
@@ -186,7 +201,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
} }
_invalidateCachedVisibleTabs() { _invalidateCachedVisibleTabs() {
@@ -1082,7 +1112,8 @@ @@ -1082,7 +1116,8 @@
isContainerVerticalPinnedGrid(tab) { isContainerVerticalPinnedGrid(tab) {
return ( return (
@@ -196,7 +211,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
this.verticalMode && this.verticalMode &&
this.hasAttribute("expanded") && this.hasAttribute("expanded") &&
!this.expandOnHover !this.expandOnHover
@@ -1176,7 +1207,7 @@ @@ -1176,7 +1211,7 @@
if (node == null) { if (node == null) {
// We have a container for non-tab elements at the end of the scrollbox. // We have a container for non-tab elements at the end of the scrollbox.
@@ -205,7 +220,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
} }
node.before(tab); node.before(tab);
@@ -1271,7 +1302,7 @@ @@ -1271,7 +1306,7 @@
// There are separate "new tab" buttons for horizontal tabs toolbar, vertical tabs and // There are separate "new tab" buttons for horizontal tabs toolbar, vertical tabs and
// for when the tab strip is overflowed (which is shared by vertical and horizontal tabs); // for when the tab strip is overflowed (which is shared by vertical and horizontal tabs);
// Attach the long click popup to all of them. // Attach the long click popup to all of them.
@@ -214,7 +229,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
const newTab2 = this.newTabButton; const newTab2 = this.newTabButton;
const newTabVertical = document.getElementById( const newTabVertical = document.getElementById(
"vertical-tabs-newtab-button" "vertical-tabs-newtab-button"
@@ -1376,8 +1407,10 @@ @@ -1376,8 +1411,10 @@
*/ */
_handleTabSelect(aInstant) { _handleTabSelect(aInstant) {
let selectedTab = this.selectedItem; let selectedTab = this.selectedItem;
@@ -225,7 +240,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
selectedTab._notselectedsinceload = false; selectedTab._notselectedsinceload = false;
} }
@@ -1386,7 +1419,7 @@ @@ -1386,7 +1423,7 @@
* @param {boolean} [shouldScrollInstantly=false] * @param {boolean} [shouldScrollInstantly=false]
*/ */
#ensureTabIsVisible(tab, shouldScrollInstantly = false) { #ensureTabIsVisible(tab, shouldScrollInstantly = false) {
@@ -234,7 +249,7 @@ index 568f3a7cc7051ff8cb569f6bcb8018a5212f7072..3036768b8911b4fbc28df7528f7189d9
if (arrowScrollbox?.overflowing) { if (arrowScrollbox?.overflowing) {
arrowScrollbox.ensureElementIsVisible(tab, shouldScrollInstantly); arrowScrollbox.ensureElementIsVisible(tab, shouldScrollInstantly);
} }
@@ -1513,7 +1546,7 @@ @@ -1513,7 +1550,7 @@
} }
_notifyBackgroundTab(aTab) { _notifyBackgroundTab(aTab) {

View File

@@ -16,3 +16,4 @@ category app-startup nsBrowserGlue @mozilla.org/browser/browserglue;1 applicatio
#include common/Components.manifest #include common/Components.manifest
#include sessionstore/SessionComponents.manifest #include sessionstore/SessionComponents.manifest
#include live-folders/LiveFoldersComponents.manifest #include live-folders/LiveFoldersComponents.manifest
#include space-routing/SpaceRoutingComponents.manifest

View File

@@ -47,6 +47,7 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature {
init() { init() {
this.#panel = document.getElementById("PanelUI-zen-emojis-picker"); this.#panel = document.getElementById("PanelUI-zen-emojis-picker");
this.#panel.addEventListener("popupshowing", this); this.#panel.addEventListener("popupshowing", this);
this.#panel.addEventListener("popupshown", this);
this.#panel.addEventListener("popuphidden", this); this.#panel.addEventListener("popuphidden", this);
this.#panel.addEventListener("command", this); this.#panel.addEventListener("command", this);
this.searchInput.addEventListener("input", this); this.searchInput.addEventListener("input", this);
@@ -57,6 +58,9 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature {
case "popupshowing": case "popupshowing":
this.#onPopupShowing(event); this.#onPopupShowing(event);
break; break;
case "popupshown":
this.#onPopupShown(event);
break;
case "popuphidden": case "popuphidden":
this.#onPopupHidden(event); this.#onPopupHidden(event);
break; break;
@@ -103,17 +107,20 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature {
return document.getElementById("PanelUI-zen-emojis-picker-search"); return document.getElementById("PanelUI-zen-emojis-picker-search");
} }
#changePage(toSvg = false) { #changePage(toSvg = false, { animate = true } = {}) {
const pages = document.getElementById("PanelUI-zen-emojis-picker-pages");
const itemToScroll = toSvg const itemToScroll = toSvg
? this.svgList ? this.svgList
: document : pages.querySelector('[emojis="true"]');
.getElementById("PanelUI-zen-emojis-picker-pages") if (animate) {
.querySelector('[emojis="true"]');
itemToScroll.scrollIntoView({ itemToScroll.scrollIntoView({
behavior: "smooth", behavior: "smooth",
block: "nearest", block: "nearest",
inline: "start", inline: "start",
}); });
} else {
pages.scrollLeft = toSvg ? itemToScroll.offsetLeft : 0;
}
const button = document.getElementById( const button = document.getElementById(
`PanelUI-zen-emojis-picker-change-${toSvg ? "svg" : "emojis"}` `PanelUI-zen-emojis-picker-change-${toSvg ? "svg" : "emojis"}`
); );
@@ -180,9 +187,6 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature {
}); });
emojiList.appendChild(item); emojiList.appendChild(item);
} }
setTimeout(() => {
this.searchInput.focus();
}, 500);
} }
const svgList = this.svgList; const svgList = this.svgList;
for (const icon of SVG_ICONS) { for (const icon of SVG_ICONS) {
@@ -199,14 +203,23 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature {
} }
} }
#onPopupShown(event) {
if (event.target !== this.#panel) {
return;
}
const allowEmojis = !this.#panel.hasAttribute("only-svg-icons");
if (allowEmojis) {
this.searchInput.focus({ preventScroll: true });
}
this.#changePage(false, { animate: false });
}
#onPopupHidden(event) { #onPopupHidden(event) {
if (event.target !== this.#panel) { if (event.target !== this.#panel) {
return; return;
} }
this.#clearEmojis(); this.#clearEmojis();
this.#changePage(false);
const emojiList = this.emojiList; const emojiList = this.emojiList;
emojiList.innerHTML = ""; emojiList.innerHTML = "";

View File

@@ -78,6 +78,9 @@ document.addEventListener(
case "cmd_zenReplacePinnedUrlWithCurrent": case "cmd_zenReplacePinnedUrlWithCurrent":
gZenPinnedTabManager.replacePinnedUrlWithCurrent(); gZenPinnedTabManager.replacePinnedUrlWithCurrent();
break; break;
case "cmd_zenEditPinnedUrl":
gZenPinnedTabManager.editPinnedUrl();
break;
case "cmd_contextZenAddToEssentials": case "cmd_contextZenAddToEssentials":
gZenPinnedTabManager.addToEssentials(); gZenPinnedTabManager.addToEssentials();
break; break;

View File

@@ -723,8 +723,11 @@
const { isNearLeftEdge, isNearRightEdge } = const { isNearLeftEdge, isNearRightEdge } =
this.#shouldSwitchSpace(event); this.#shouldSwitchSpace(event);
if (isNearLeftEdge || isNearRightEdge) { if (isNearLeftEdge || isNearRightEdge) {
if (!this.#changeSpaceTimer) { if (!this.#changeSpaceTimer && !this.#isOutOfWindow) {
this.#changeSpaceTimer = setTimeout(() => { this.#changeSpaceTimer = setTimeout(() => {
if (this.#isOutOfWindow) {
return;
}
this.clearDragOverVisuals(); this.clearDragOverVisuals();
gZenWorkspaces gZenWorkspaces
.changeWorkspaceShortcut( .changeWorkspaceShortcut(
@@ -956,8 +959,10 @@
if (ownerGlobal?.gZenCompactModeManager) { if (ownerGlobal?.gZenCompactModeManager) {
// Sometimes, dragend doesn't always get called when dragging // Sometimes, dragend doesn't always get called when dragging
// to different windows, see gh-8643. // to different windows, see gh-8643.
requestAnimationFrame(() => {
delete ownerGlobal.gZenCompactModeManager._isTabBeingDragged; delete ownerGlobal.gZenCompactModeManager._isTabBeingDragged;
ownerGlobal.gZenCompactModeManager._clearAllHoverStates(); ownerGlobal.gZenCompactModeManager._clearAllHoverStates();
});
} }
this.clearSpaceSwitchTimer(); this.clearSpaceSwitchTimer();
gZenFolders.highlightGroupOnDragOver(null); gZenFolders.highlightGroupOnDragOver(null);

View File

@@ -53,6 +53,10 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
#setupEventListeners() { #setupEventListeners() {
window.addEventListener("TabClose", this.onTabClose.bind(this)); window.addEventListener("TabClose", this.onTabClose.bind(this));
window.addEventListener("TabSelect", this.onLocationChange.bind(this)); window.addEventListener("TabSelect", this.onLocationChange.bind(this));
window.addEventListener(
"MozDOMFullscreen:Entered",
this.onFullscreenEntered.bind(this)
);
document document
.getElementById("tabbrowser-tabpanels") .getElementById("tabbrowser-tabpanels")
@@ -1414,6 +1418,23 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
} }
} }
/**
* Handle DOM Fullscreen request while inside glance
*
* @param {Event} event - The MozDOMFullscreen:Entered event
*/
onFullscreenEntered(event) {
const browser = this.#currentBrowser;
if (!browser) {
return;
}
if (event.target === browser) {
this.fullyOpenGlance();
}
}
/** /**
* Manage tab close for glance tabs * Manage tab close for glance tabs
* *

View File

@@ -1232,20 +1232,39 @@ class nsZenWindowSync {
activeIndex = Math.min(activeIndex, entries.length - 1); activeIndex = Math.min(activeIndex, entries.length - 1);
activeIndex = Math.max(activeIndex, 0); activeIndex = Math.max(activeIndex, 0);
let entryToUse = (entries[activeIndex] || entries[0]) ?? null; let entryToUse = (entries[activeIndex] || entries[0]) ?? null;
const initialState = { this.#setPinnedInitialState(
entry: { aTab,
url: entryToUse?.url, { url: entryToUse?.url, title: entryToUse?.title },
title: entryToUse?.title, image
}, );
image, });
}; }
/**
* Sets the canonical pinned URL for a tab across all windows. Used to let the
* user edit a pinned tab's URL directly.
*
* @param {object} aTab - The tab to set the pinned URL for.
* @param {string} aUrl - The URL to store as the canonical pinned URL.
* @param {string} [aImage] - Optional Icon to store.
*/
setPinnedUrl(aTab, aUrl, aImage) {
this.log(`Setting pinned url for tab ${aTab.id}`);
this.#setPinnedInitialState(
aTab,
{ url: aUrl, title: aTab.zenStaticLabel },
aImage
);
}
#setPinnedInitialState(aTab, aEntry, aImage) {
const initialState = { entry: aEntry, image: aImage };
this.#runOnAllWindows(null, win => { this.#runOnAllWindows(null, win => {
const targetTab = this.getItemFromWindow(win, aTab.id); const targetTab = this.getItemFromWindow(win, aTab.id);
if (targetTab) { if (targetTab) {
targetTab._zenPinnedInitialState = initialState; targetTab._zenPinnedInitialState = initialState;
} }
}); });
});
} }
/** /**

View File

@@ -0,0 +1,5 @@
# 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/.
category browser-window-delayed-startup resource:///modules/zen/spacerouting/ZenSpaceRoutingManager.sys.mjs gZenSpaceRoutingManager.onDelayedBrowserStartup

View File

@@ -18,6 +18,58 @@ class nsZenSpaceRoutingManager {
this.#readFromDisk(); this.#readFromDisk();
} }
/**
* Auto invoked for every window on delayed startup
*
* @param {nsIDOMWindow} window - The browser window that just started up
*/
onDelayedBrowserStartup(window) {
const element = window.MozXULElement.parseXULToFragment(`
<menuseparator/>
<menuitem id="context_zen-add-domain-to-routing"
data-lazy-l10n-id="tab-context-zen-add-domain-to-sr"
data-l10n-args='{"tabCount": 1}'/>
`);
window.document.getElementById("context_undoCloseTab").after(element);
window.document
.getElementById("context_zen-add-domain-to-routing")
.addEventListener("command", this.#onAddSelectedToRouting.bind(this));
window.document
.getElementById("tabContextMenu")
.addEventListener(
"popupshowing",
this.#updateTabCloseCountState.bind(this)
);
}
/**
* Updates the "context_zen-add-domain-to-routing" command
* to reflect the number of selected tabs, when applicable.
*
* @param {Event} event - The event param
*/
#updateTabCloseCountState(event) {
const window = event.target.documentGlobal;
window.document.l10n.setArgs(
window.document.getElementById("context_zen-add-domain-to-routing"),
{ tabCount: window.gBrowser.selectedTabs.length }
);
}
/**
* Callback for whenever the menuitem command is ran
*
* @param {Event} event - The event parameter
*/
#onAddSelectedToRouting(event) {
const window = event.target.documentGlobal;
const tabs = window.TabContextMenu.contextTab.multiselected
? window.gBrowser.selectedTabs
: [window.TabContextMenu.contextTab];
this.addRouteForSelected(tabs, window);
}
/** /**
* Callback that will be executed from tabbrowser.js * Callback that will be executed from tabbrowser.js
* This method can be used to stop the tab from being created. * This method can be used to stop the tab from being created.
@@ -403,6 +455,37 @@ class nsZenSpaceRoutingManager {
this.#file.data.defaultRouteExternal = routeType; this.#file.data.defaultRouteExternal = routeType;
} }
/**
* Adds a new route for all given tabs
*
* @param {Array<object>} selectedTabs - The tabs that should be routed
* @param {Window} parentWindow - The window from which this is being executed
*/
addRouteForSelected(selectedTabs, parentWindow) {
const newRoute = this.createNewRoute();
let routeReference = "";
if (selectedTabs.length == 1) {
newRoute.matchType = "contains";
routeReference = selectedTabs[0].linkedBrowser.currentURI.host;
} else {
newRoute.matchType = "regex";
routeReference = "(";
for (let i = 0; i < selectedTabs.length; i++) {
const domain = selectedTabs[i].linkedBrowser.currentURI.host;
routeReference += domain.replaceAll(".", "\.");
if (i != selectedTabs.length - 1) {
routeReference += "|";
}
}
routeReference += ")";
}
newRoute.reference = routeReference;
this.updateRoute(newRoute);
this.openSpaceRoutingDialog(parentWindow);
}
/** /**
* Saves all routes. The list of * Saves all routes. The list of
* routes is stripped of empty routes * routes is stripped of empty routes

View File

@@ -1506,19 +1506,27 @@ class nsZenWorkspaces {
continue; continue;
} }
const newtabPlacement = Services.prefs.getBoolPref(
"zen.view.show-newtab-button-top",
false
);
const insertElement = newtabPlacement
? container.firstChild
: container.lastChild;
if (container) { if (container) {
if (tab.group?.hasAttribute("split-view-group")) { if (tab.group?.hasAttribute("split-view-group")) {
gBrowser.zenHandleTabMove(tab.group, () => { gBrowser.zenHandleTabMove(tab.group, () => {
for (const subTab of tab.group.tabs) { for (const subTab of tab.group.tabs) {
subTab.setAttribute("zen-workspace-id", workspaceID); subTab.setAttribute("zen-workspace-id", workspaceID);
} }
container.insertBefore(tab.group, container.lastChild); container.insertBefore(tab.group, insertElement);
}); });
continue; continue;
} }
gBrowser.zenHandleTabMove(tab, () => { gBrowser.zenHandleTabMove(tab, () => {
tab.setAttribute("zen-workspace-id", workspaceID); tab.setAttribute("zen-workspace-id", workspaceID);
container.insertBefore(tab, container.lastChild); container.insertBefore(tab, insertElement);
}); });
} }
// also change glance tab if it's the same tab // also change glance tab if it's the same tab

View File

@@ -246,6 +246,66 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
gZenUIManager.showToast("zen-pinned-tab-replaced"); gZenUIManager.showToast("zen-pinned-tab-replaced");
} }
async editPinnedUrl(tab = undefined) {
tab ??= TabContextMenu.contextTab;
if (!tab || !tab.pinned) {
return;
}
const initialUrl =
tab._zenPinnedInitialState?.entry?.url ||
tab.linkedBrowser?.currentURI?.spec;
const [title, label] = await document.l10n.formatValues([
{ id: "zen-pinned-tab-edit-url-title" },
{ id: "zen-pinned-tab-edit-url-label" },
]);
const result = { value: initialUrl ?? "" };
const confirmed = Services.prompt.prompt(
window,
title,
label,
result,
null,
{ value: false }
);
if (!confirmed) {
return;
}
let uri;
try {
uri = Services.uriFixup.getFixupURIInfo(
result.value.trim(),
Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
).preferredURI;
} catch (_) {}
if (!uri) {
gZenUIManager.showToast("zen-pinned-tab-url-invalid");
return;
}
const url = uri.spec;
// Skip when the value wasn't actually changed from what was prefilled.
if (!url || url === initialUrl) {
return;
}
const image = tab.zenStaticIcon || (await this.#getCachedFavicon(uri));
window.gZenWindowSync.setPinnedUrl(tab, url, image);
this.#resetTabToStoredState(tab);
gZenUIManager.showToast("zen-pinned-tab-url-edited");
}
async #getCachedFavicon(uri) {
try {
const favicon = await PlacesUtils.favicons.getFaviconForPage(uri);
return favicon?.dataURI?.spec;
} catch (ex) {
console.error("Failed to get favicon for edited pinned url:", ex);
return null;
}
}
_initClosePinnedTabShortcut() { _initClosePinnedTabShortcut() {
let cmdClose = document.getElementById("cmd_close"); let cmdClose = document.getElementById("cmd_close");
@@ -545,11 +605,20 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
} }
const elements = window.MozXULElement.parseXULToFragment(` const elements = window.MozXULElement.parseXULToFragment(`
<menuseparator id="context_zen-pinned-tab-separator" hidden="true"/> <menuseparator id="context_zen-pinned-tab-separator" hidden="true"/>
<menu id="context_zen-edit-pinned-page"
data-lazy-l10n-id="tab-context-zen-edit-pinned-page"
data-l10n-args="{&quot;isEssential&quot;:&quot;&quot;}"
hidden="true">
<menupopup>
<menuitem id="context_zen-replace-pinned-url-with-current" <menuitem id="context_zen-replace-pinned-url-with-current"
data-lazy-l10n-id="tab-context-zen-replace-pinned-url-with-current" data-lazy-l10n-id="tab-context-zen-replace-pinned-url-with-current"
data-l10n-args="{&quot;isEssential&quot;:&quot;&quot;}" data-l10n-args="{&quot;isEssential&quot;:&quot;&quot;}"
hidden="true"
command="cmd_zenReplacePinnedUrlWithCurrent"/> command="cmd_zenReplacePinnedUrlWithCurrent"/>
<menuitem id="context_zen-edit-pinned-url"
data-lazy-l10n-id="tab-context-zen-edit-pinned-url"
command="cmd_zenEditPinnedUrl"/>
</menupopup>
</menu>
<menuitem id="context_zen-reset-pinned-tab" <menuitem id="context_zen-reset-pinned-tab"
data-lazy-l10n-id="tab-context-zen-reset-pinned-tab" data-lazy-l10n-id="tab-context-zen-reset-pinned-tab"
data-l10n-args="{&quot;isEssential&quot;:&quot;&quot;}" data-l10n-args="{&quot;isEssential&quot;:&quot;&quot;}"
@@ -619,15 +688,24 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
const zenResetPinnedTab = document.getElementById( const zenResetPinnedTab = document.getElementById(
"context_zen-reset-pinned-tab" "context_zen-reset-pinned-tab"
); );
const zenEditPinnedPage = document.getElementById(
"context_zen-edit-pinned-page"
);
const zenReplacePinnedUrl = document.getElementById( const zenReplacePinnedUrl = document.getElementById(
"context_zen-replace-pinned-url-with-current" "context_zen-replace-pinned-url-with-current"
); );
[zenResetPinnedTab, zenReplacePinnedUrl].forEach(element => { [zenResetPinnedTab, zenEditPinnedPage].forEach(element => {
if (element) { if (element) {
element.hidden = !isVisible; element.hidden = !isVisible;
document.l10n.setArgs(element, { isEssential });
} }
}); });
[zenResetPinnedTab, zenEditPinnedPage, zenReplacePinnedUrl].forEach(
element => {
if (element) {
document.l10n.setArgs(element, { isEssential });
}
}
);
zenAddEssential.hidden = isEssential || !!contextTab.group; zenAddEssential.hidden = isEssential || !!contextTab.group;
document.l10n document.l10n
.formatValue("tab-context-zen-add-essential-badge", { .formatValue("tab-context-zen-add-essential-badge", {
@@ -857,7 +935,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
) { ) {
return; return;
} }
// Remove # and ? from the URL // Remove # from the URL
const pinUrl = tab._zenPinnedInitialState.entry.url.split("#")[0]; const pinUrl = tab._zenPinnedInitialState.entry.url.split("#")[0];
const currentUrl = location.split("#")[0]; const currentUrl = location.split("#")[0];
// Add an indicator that the pin has been changed // Add an indicator that the pin has been changed
@@ -897,10 +975,14 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
} else { } else {
tab.setAttribute("zen-pinned-changed", "true"); tab.setAttribute("zen-pinned-changed", "true");
} }
if (tab._zenPinnedInitialState.image) {
tab.style.setProperty( tab.style.setProperty(
"--zen-original-tab-icon", "--zen-original-tab-icon",
`url(${tab._zenPinnedInitialState.image})` `url(${tab._zenPinnedInitialState.image})`
); );
} else {
tab.style.removeProperty("--zen-original-tab-icon");
}
} }
removeTabContainersDragoverClass(hideIndicator = true) { removeTabContainersDragoverClass(hideIndicator = true) {

View File

@@ -13,6 +13,8 @@ prefs = ["zen.workspaces.separate-essentials=false"]
["browser_pinned_created.js"] ["browser_pinned_created.js"]
["browser_pinned_edit_url.js"]
["browser_pinned_nounload_reset.js"] ["browser_pinned_nounload_reset.js"]
["browser_pinned_reset_button.js"] ["browser_pinned_reset_button.js"]

View File

@@ -0,0 +1,384 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
async function pinTab(url) {
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
gBrowser.pinTab(tab);
await gBrowser.TabStateFlusher.flush(tab.linkedBrowser);
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(r => setTimeout(r, 500));
return tab;
}
// XPCOM service methods can't be stubbed in place (non-configurable), so we
// swap the whole service object out for a mock and restore it afterwards.
function mockPrompt(value) {
const original = Services.prompt;
Services.prompt = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptService]),
prompt(win, title, label, result) {
if (value === null) {
return false; // user cancelled
}
result.value = value;
return true;
},
};
return () => {
Services.prompt = original;
};
}
function mockFavicons(faviconSpec) {
const original = PlacesUtils.favicons;
const mock = {
callCount: 0,
defaultFavicon: { spec: "data:image/png;base64,DEFAULT" },
getFaviconForPage() {
mock.callCount++;
return Promise.resolve(
faviconSpec ? { dataURI: { spec: faviconSpec } } : null
);
},
};
PlacesUtils.favicons = mock;
return {
mock,
restore: () => {
PlacesUtils.favicons = original;
},
};
}
add_task(async function test_EditPinnedUrl_SurvivesRebuild() {
// Pinned tab at url1 (loaded), then select a different tab (unfocus it).
const tab = await pinTab("https://example.com/1");
const other = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"https://example.com/other"
);
const editedUrl = "https://example.com/edited";
const restorePrompt = mockPrompt(editedUrl);
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
// Close + re-open rebuilds the tab: the in-memory _zenPinnedInitialState is
// gone and gets reconstructed from the persisted session via
// setPinnedTabState (exactly what #onSessionStoreInitialized does).
delete tab._zenPinnedInitialState;
await window.gZenWindowSync.setPinnedTabState(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
editedUrl,
"After the tab is rebuilt, the pinned URL should still be the edited one"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(other);
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_ActiveTabNavigates() {
// Editing the active (focused) pinned tab applies the new URL immediately:
// the live tab navigates to it (matching Arc's behavior).
const tab = await pinTab("https://example.com/1");
Assert.equal(gBrowser.selectedTab, tab, "the pinned tab should be active");
const editedUrl = "https://example.com/edited";
const restorePrompt = mockPrompt(editedUrl);
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
await BrowserTestUtils.waitForCondition(
() => tab.linkedBrowser.currentURI.spec === editedUrl,
"the active pinned tab to navigate to the edited URL"
);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
editedUrl,
"Editing the active pinned tab should navigate it to the new URL"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(
async function test_EditPinnedUrl_FaviconLookupErrorLeavesImageEmpty() {
const tab = await pinTab("https://example.com/1");
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons(null);
// Simulate a Places DB failure so #getCachedFavicon hits its catch branch.
favicons.mock.getFaviconForPage = () =>
Promise.reject(new Error("simulated favicon DB failure"));
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
"https://example.org/edited",
"The URL should still be updated when the favicon lookup fails"
);
ok(
!tab._zenPinnedInitialState.image,
"The image should be left empty (populated by the next navigation)"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
}
);
add_task(async function test_EditPinnedUrl_UpdatesUrlAndFavicon() {
const tab = await pinTab("https://example.com/1");
const faviconSpec = "data:image/png;base64,iVBORw0KGgo=";
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons(faviconSpec);
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
"https://example.org/edited",
"The pinned URL should be updated to the edited value"
);
Assert.equal(
tab._zenPinnedInitialState.image,
faviconSpec,
"The stored icon should be the cached favicon for the new URL"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_NoCachedFaviconLeavesImageEmpty() {
const tab = await pinTab("https://example.com/1");
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons(null); // no cached favicon for the new URL
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
ok(
!tab._zenPinnedInitialState.image,
"Without a cached favicon the image is left empty, not the default; the " +
"next navigation captures the real icon"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_ClearsStaleTitle() {
const tab = await pinTab("https://example.com/1");
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
ok(
!tab._zenPinnedInitialState.entry.title,
"The previous title is cleared so the new page's title is used on load"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_KeepsCustomLabel() {
const tab = await pinTab("https://example.com/1");
tab.zenStaticLabel = "My Pinned Tab";
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.title,
"My Pinned Tab",
"An explicit custom label is preserved across a URL edit"
);
} finally {
restorePrompt();
favicons.restore();
delete tab.zenStaticLabel;
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_KeepsCustomIcon() {
const tab = await pinTab("https://example.com/1");
const customIcon = "data:image/svg+xml,custom-icon";
tab.zenStaticIcon = customIcon;
const restorePrompt = mockPrompt("https://example.org/edited");
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
"https://example.org/edited",
"The pinned URL should still be updated when a custom icon is set"
);
Assert.equal(
tab._zenPinnedInitialState.image,
customIcon,
"A user-set custom icon should be preserved, not overridden by a favicon"
);
Assert.equal(
favicons.mock.callCount,
0,
"Favicon lookup should be skipped when a custom icon is set"
);
} finally {
restorePrompt();
favicons.restore();
delete tab.zenStaticIcon;
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_InvalidUrlKeepsState() {
const tab = await pinTab("https://example.com/1");
const originalUrl = tab._zenPinnedInitialState.entry.url;
const restorePrompt = mockPrompt(" "); // whitespace only -> not a valid URL
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
originalUrl,
"The pinned URL should be unchanged for invalid input"
);
ok(
!tab.hasAttribute("zen-pinned-changed"),
"The tab should not be marked as changed for invalid input"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_CancelKeepsState() {
const tab = await pinTab("https://example.com/1");
const originalUrl = tab._zenPinnedInitialState.entry.url;
const restorePrompt = mockPrompt(null); // user cancels the dialog
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
originalUrl,
"The pinned URL should be unchanged when the dialog is cancelled"
);
} finally {
restorePrompt();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_FixesSchemeTypo() {
const tab = await pinTab("https://example.com/1");
const restorePrompt = mockPrompt("htps://example.org/typo");
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
tab._zenPinnedInitialState.entry.url,
"https://example.org/typo",
"A mistyped scheme (htps://) should be auto-fixed to https://"
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_AddsMissingScheme() {
const tab = await pinTab("https://example.com/1");
const restorePrompt = mockPrompt("example.org/no-scheme");
const favicons = mockFavicons("data:image/png;base64,iVBORw0KGgo=");
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
const stored = tab._zenPinnedInitialState.entry.url;
ok(
/^https?:\/\//.test(stored),
`A scheme should be prepended when omitted (got "${stored}")`
);
ok(
stored.endsWith("example.org/no-scheme"),
`Host and path should be preserved (got "${stored}")`
);
} finally {
restorePrompt();
favicons.restore();
await BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_EditPinnedUrl_PrefillsWithStoredUrl() {
const tab = await pinTab("https://example.com/1");
// The stored pinned URL differs from the live browser URL (e.g. it was pinned
// as http but the server redirected the tab to https).
tab._zenPinnedInitialState = {
entry: { url: "http://example.com/pinned" },
image: "",
};
let prefilled;
const originalPrompt = Services.prompt;
Services.prompt = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptService]),
prompt(win, title, label, result) {
prefilled = result.value;
return false; // cancel, we only care about the prefilled value
},
};
try {
await gZenPinnedTabManager.editPinnedUrl(tab);
Assert.equal(
prefilled,
"http://example.com/pinned",
"The edit dialog should prefill with the stored pinned URL, not the live browser URL"
);
} finally {
Services.prompt = originalPrompt;
await BrowserTestUtils.removeTab(tab);
}
});

View File

@@ -5,8 +5,8 @@
"binaryName": "zen", "binaryName": "zen",
"version": { "version": {
"product": "firefox", "product": "firefox",
"version": "152.0", "version": "152.0.2",
"candidate": "152.0", "candidate": "152.0.2",
"candidateBuild": 1 "candidateBuild": 1
}, },
"buildOptions": { "buildOptions": {
@@ -20,7 +20,7 @@
"brandShortName": "Zen", "brandShortName": "Zen",
"brandFullName": "Zen Browser", "brandFullName": "Zen Browser",
"release": { "release": {
"displayVersion": "1.21.2b", "displayVersion": "1.21.4b",
"github": { "github": {
"repo": "zen-browser/desktop" "repo": "zen-browser/desktop"
}, },