mirror of
https://github.com/zen-browser/desktop.git
synced 2026-06-23 19:29:39 +00:00
gh-12153: edit pinned-page url directly via submenu (gh-14329)
This commit is contained in:
@@ -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!
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<command id="cmd_zenToggleTabsOnRight" />
|
||||
|
||||
<command id="cmd_zenReplacePinnedUrlWithCurrent" />
|
||||
<command id="cmd_zenEditPinnedUrl" />
|
||||
<command id="cmd_contextZenAddToEssentials" />
|
||||
<command id="cmd_contextZenRemoveFromEssentials" />
|
||||
|
||||
|
||||
@@ -78,6 +78,9 @@ document.addEventListener(
|
||||
case "cmd_zenReplacePinnedUrlWithCurrent":
|
||||
gZenPinnedTabManager.replacePinnedUrlWithCurrent();
|
||||
break;
|
||||
case "cmd_zenEditPinnedUrl":
|
||||
gZenPinnedTabManager.editPinnedUrl();
|
||||
break;
|
||||
case "cmd_contextZenAddToEssentials":
|
||||
gZenPinnedTabManager.addToEssentials();
|
||||
break;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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(`
|
||||
<menuseparator id="context_zen-pinned-tab-separator" hidden="true"/>
|
||||
<menuitem id="context_zen-replace-pinned-url-with-current"
|
||||
data-lazy-l10n-id="tab-context-zen-replace-pinned-url-with-current"
|
||||
data-l10n-args="{"isEssential":""}"
|
||||
hidden="true"
|
||||
command="cmd_zenReplacePinnedUrlWithCurrent"/>
|
||||
<menu id="context_zen-edit-pinned-page"
|
||||
data-lazy-l10n-id="tab-context-zen-edit-pinned-page"
|
||||
data-l10n-args="{"isEssential":""}"
|
||||
hidden="true">
|
||||
<menupopup>
|
||||
<menuitem id="context_zen-replace-pinned-url-with-current"
|
||||
data-lazy-l10n-id="tab-context-zen-replace-pinned-url-with-current"
|
||||
data-l10n-args="{"isEssential":""}"
|
||||
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"
|
||||
data-lazy-l10n-id="tab-context-zen-reset-pinned-tab"
|
||||
data-l10n-args="{"isEssential":""}"
|
||||
@@ -619,15 +688,24 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
const zenResetPinnedTab = document.getElementById(
|
||||
"context_zen-reset-pinned-tab"
|
||||
);
|
||||
const zenEditPinnedPage = document.getElementById(
|
||||
"context_zen-edit-pinned-page"
|
||||
);
|
||||
const zenReplacePinnedUrl = document.getElementById(
|
||||
"context_zen-replace-pinned-url-with-current"
|
||||
);
|
||||
[zenResetPinnedTab, zenReplacePinnedUrl].forEach(element => {
|
||||
[zenResetPinnedTab, zenEditPinnedPage].forEach(element => {
|
||||
if (element) {
|
||||
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;
|
||||
document.l10n
|
||||
.formatValue("tab-context-zen-add-essential-badge", {
|
||||
@@ -857,7 +935,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// Remove # and ? from the URL
|
||||
// Remove # from the URL
|
||||
const pinUrl = tab._zenPinnedInitialState.entry.url.split("#")[0];
|
||||
const currentUrl = location.split("#")[0];
|
||||
// Add an indicator that the pin has been changed
|
||||
@@ -897,10 +975,14 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
} else {
|
||||
tab.setAttribute("zen-pinned-changed", "true");
|
||||
}
|
||||
tab.style.setProperty(
|
||||
"--zen-original-tab-icon",
|
||||
`url(${tab._zenPinnedInitialState.image})`
|
||||
);
|
||||
if (tab._zenPinnedInitialState.image) {
|
||||
tab.style.setProperty(
|
||||
"--zen-original-tab-icon",
|
||||
`url(${tab._zenPinnedInitialState.image})`
|
||||
);
|
||||
} else {
|
||||
tab.style.removeProperty("--zen-original-tab-icon");
|
||||
}
|
||||
}
|
||||
|
||||
removeTabContainersDragoverClass(hideIndicator = true) {
|
||||
|
||||
@@ -13,6 +13,8 @@ prefs = ["zen.workspaces.separate-essentials=false"]
|
||||
|
||||
["browser_pinned_created.js"]
|
||||
|
||||
["browser_pinned_edit_url.js"]
|
||||
|
||||
["browser_pinned_nounload_reset.js"]
|
||||
|
||||
["browser_pinned_reset_button.js"]
|
||||
|
||||
384
src/zen/tests/pinned/browser_pinned_edit_url.js
Normal file
384
src/zen/tests/pinned/browser_pinned_edit_url.js
Normal 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);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user