diff --git a/locales/en-US/browser/browser/zen-general.ftl b/locales/en-US/browser/browser/zen-general.ftl
index d1229b04f..c1a4efa7c 100644
--- a/locales/en-US/browser/browser/zen-general.ftl
+++ b/locales/en-US/browser/browser/zen-general.ftl
@@ -19,13 +19,19 @@ tab-context-zen-add-essential-badge = { $num } / { $max }
tab-context-zen-remove-essential =
.label = Remove from Essentials
.accesskey = R
-tab-context-zen-replace-pinned-url-with-current =
+tab-context-zen-edit-pinned-page =
.label =
{ $isEssential ->
- [true] Replace Essential URL with Current
- *[false] Replace Pinned URL with Current
+ [true] Edit Essential URL
+ *[false] Edit Pinned URL
}
+ .accesskey = P
+tab-context-zen-replace-pinned-url-with-current =
+ .label = Replace with Current URL
.accesskey = C
+tab-context-zen-edit-pinned-url =
+ .label = Edit...
+ .accesskey = E
tab-context-zen-edit-title =
.label = Change Label...
tab-context-zen-edit-icon =
@@ -55,6 +61,10 @@ zen-general-confirm =
.label = Confirm
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-background-tab-opened-toast = New background tab opened!
zen-workspace-renamed-toast = Workspace has been successfully renamed!
diff --git a/src/browser/base/content/zen-commands.inc.xhtml b/src/browser/base/content/zen-commands.inc.xhtml
index 2122c56f0..3019feaca 100644
--- a/src/browser/base/content/zen-commands.inc.xhtml
+++ b/src/browser/base/content/zen-commands.inc.xhtml
@@ -35,6 +35,7 @@
+
diff --git a/src/zen/common/zen-sets.js b/src/zen/common/zen-sets.js
index 289dbe9eb..e9fecea05 100644
--- a/src/zen/common/zen-sets.js
+++ b/src/zen/common/zen-sets.js
@@ -78,6 +78,9 @@ document.addEventListener(
case "cmd_zenReplacePinnedUrlWithCurrent":
gZenPinnedTabManager.replacePinnedUrlWithCurrent();
break;
+ case "cmd_zenEditPinnedUrl":
+ gZenPinnedTabManager.editPinnedUrl();
+ break;
case "cmd_contextZenAddToEssentials":
gZenPinnedTabManager.addToEssentials();
break;
diff --git a/src/zen/sessionstore/ZenWindowSync.sys.mjs b/src/zen/sessionstore/ZenWindowSync.sys.mjs
index 0d4ad78ec..f4f993923 100644
--- a/src/zen/sessionstore/ZenWindowSync.sys.mjs
+++ b/src/zen/sessionstore/ZenWindowSync.sys.mjs
@@ -1232,19 +1232,38 @@ class nsZenWindowSync {
activeIndex = Math.min(activeIndex, entries.length - 1);
activeIndex = Math.max(activeIndex, 0);
let entryToUse = (entries[activeIndex] || entries[0]) ?? null;
- const initialState = {
- entry: {
- url: entryToUse?.url,
- title: entryToUse?.title,
- },
- image,
- };
- this.#runOnAllWindows(null, win => {
- const targetTab = this.getItemFromWindow(win, aTab.id);
- if (targetTab) {
- targetTab._zenPinnedInitialState = initialState;
- }
- });
+ this.#setPinnedInitialState(
+ aTab,
+ { url: entryToUse?.url, title: entryToUse?.title },
+ 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 => {
+ const targetTab = this.getItemFromWindow(win, aTab.id);
+ if (targetTab) {
+ targetTab._zenPinnedInitialState = initialState;
+ }
});
}
diff --git a/src/zen/tabs/ZenPinnedTabManager.mjs b/src/zen/tabs/ZenPinnedTabManager.mjs
index b2adb121f..4a441f8b2 100644
--- a/src/zen/tabs/ZenPinnedTabManager.mjs
+++ b/src/zen/tabs/ZenPinnedTabManager.mjs
@@ -246,6 +246,66 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
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() {
let cmdClose = document.getElementById("cmd_close");
@@ -545,11 +605,20 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
const elements = window.MozXULElement.parseXULToFragment(`
-
+