323 lines
10 KiB
JavaScript
323 lines
10 KiB
JavaScript
/* 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/. */
|
|
|
|
const { AppConstants } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/AppConstants.sys.mjs"
|
|
);
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
|
|
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
|
});
|
|
|
|
export const TabFeatures = {
|
|
NEW_TAB_CONFIG_PATH: "browser.newtab.url",
|
|
newTabURL: null,
|
|
prefListener: null,
|
|
PREF_ACTIVETAB: "browser.tabs.copyurl.activetab",
|
|
PREF_REQUIRECONFIRM: "browser.restart_menu.requireconfirm",
|
|
PREF_PURGECACHE: "browser.restart_menu.purgecache",
|
|
|
|
init(aWindow) {
|
|
// Wait for XUL elements to be available before initializing listeners.
|
|
// 'context_copyTabUrl' is an element from our associated XUL, which we will now wait for.
|
|
if (!aWindow.document.getElementById("context_copyTabUrl")) {
|
|
lazy.setTimeout(() => {
|
|
this.init(aWindow);
|
|
}, 50); // Retry after 50ms
|
|
return;
|
|
}
|
|
|
|
aWindow.TabFeatures = this;
|
|
this.initListeners(aWindow);
|
|
this.initNewTabConfig(); // Does not require aWindow
|
|
this.initNewTabFocus(aWindow);
|
|
},
|
|
|
|
destroy() {
|
|
this.destroyNewTabConfig();
|
|
},
|
|
|
|
initListeners(aWindow) {
|
|
const doc = aWindow.document;
|
|
|
|
doc
|
|
.getElementById("tabContextMenu")
|
|
?.addEventListener("popupshowing", this.tabContext.bind(this));
|
|
if (AppConstants.platform === "macosx") {
|
|
doc
|
|
.getElementById("file-menu")
|
|
?.addEventListener("popupshowing", this.tabContext.bind(this));
|
|
} else {
|
|
doc
|
|
.getElementById("appMenu-popup")
|
|
?.addEventListener("popupshowing", this.tabContext.bind(this));
|
|
}
|
|
|
|
const copyTabUrlElement = doc.getElementById("context_copyTabUrl");
|
|
if (copyTabUrlElement) {
|
|
copyTabUrlElement.addEventListener("command", (_event) => {
|
|
if (aWindow.TabContextMenu?.contextTab?.linkedBrowser) {
|
|
try {
|
|
this.copyTabUrl(
|
|
aWindow.TabContextMenu.contextTab.linkedBrowser.currentURI.spec,
|
|
aWindow
|
|
);
|
|
} catch (e) {
|
|
console.error(
|
|
"TabFeatures: Error inside copyTabUrl listener execution:",
|
|
e
|
|
);
|
|
}
|
|
} else {
|
|
// console.warn("TabFeatures: copyTabUrl not called, context or linkedBrowser not available.");
|
|
}
|
|
});
|
|
} else {
|
|
console.error(
|
|
"TabFeatures: FAILED to find element 'context_copyTabUrl'. Listener NOT attached."
|
|
);
|
|
}
|
|
|
|
doc
|
|
.getElementById("context_copyAllTabUrls")
|
|
?.addEventListener("command", (_event) => {
|
|
this.copyAllTabUrls(aWindow);
|
|
});
|
|
|
|
doc
|
|
.getElementById("context_unloadTab")
|
|
?.addEventListener("command", (_event) => {
|
|
if (
|
|
aWindow.gBrowser &&
|
|
aWindow.TabContextMenu &&
|
|
aWindow.TabContextMenu.contextTab
|
|
) {
|
|
// Prevent unloading if it's the last tab or the only non-pinned tab in the window
|
|
if (
|
|
aWindow.gBrowser.tabs.length > 1 &&
|
|
(Array.from(aWindow.gBrowser.tabs).filter((t) => !t.pinned).length >
|
|
1 ||
|
|
!aWindow.TabContextMenu.contextTab.pinned)
|
|
) {
|
|
aWindow.gBrowser.discardBrowser(aWindow.TabContextMenu.contextTab);
|
|
} else {
|
|
// console.log("TabFeatures: discardBrowser not called, conditions not met (e.g., last tab).");
|
|
}
|
|
} else {
|
|
// console.warn("TabFeatures: discardBrowser not called, context not available.");
|
|
}
|
|
});
|
|
|
|
const restartMac = doc.getElementById("app_restartBrowser");
|
|
if (restartMac) {
|
|
restartMac.addEventListener("command", (_event) => {
|
|
this.restartBrowser();
|
|
});
|
|
}
|
|
|
|
const restartOther = doc.getElementById("appMenu-restart-button");
|
|
if (restartOther && restartOther.getAttribute("data-tabfeatures-handler-attached") !== "true") {
|
|
restartOther.addEventListener("command", (_event) => {
|
|
this.restartBrowser();
|
|
});
|
|
restartOther.setAttribute("data-tabfeatures-handler-attached", "true");
|
|
}
|
|
},
|
|
|
|
initNewTabConfig() {
|
|
// Fetch pref if it exists
|
|
this.newTabURL = Services.prefs.getStringPref(this.NEW_TAB_CONFIG_PATH, "");
|
|
|
|
// Only proceed if a value is actually set
|
|
if (this.newTabURL) {
|
|
try {
|
|
lazy.AboutNewTab.newTabURL = this.newTabURL;
|
|
this.prefListener = Services.prefs.addObserver(
|
|
this.NEW_TAB_CONFIG_PATH,
|
|
(_subject, _topic, _data) => {
|
|
const newURL = Services.prefs.getStringPref(
|
|
this.NEW_TAB_CONFIG_PATH,
|
|
""
|
|
);
|
|
if (newURL) {
|
|
lazy.AboutNewTab.newTabURL = newURL;
|
|
} else {
|
|
// If the pref is cleared, revert to default behavior
|
|
lazy.AboutNewTab.resetNewTabURL();
|
|
}
|
|
}
|
|
);
|
|
} catch (e) {
|
|
console.error("Error initializing new tab config:", e);
|
|
}
|
|
}
|
|
},
|
|
|
|
initNewTabFocus(window) {
|
|
window.gBrowser.tabContainer.addEventListener("TabOpen", (event) => {
|
|
const tab = event.target;
|
|
const browser = window.gBrowser.getBrowserForTab(tab);
|
|
|
|
browser.addEventListener(
|
|
"load",
|
|
function onLoad() {
|
|
browser.removeEventListener("load", onLoad);
|
|
window.setTimeout(() => {
|
|
browser.contentWindow.focus();
|
|
}, 0);
|
|
},
|
|
{ once: true }
|
|
);
|
|
});
|
|
},
|
|
|
|
destroyNewTabConfig() {
|
|
if (this.prefListener) {
|
|
Services.prefs.removeObserver(
|
|
this.NEW_TAB_CONFIG_PATH,
|
|
this.prefListener
|
|
);
|
|
this.prefListener = null;
|
|
}
|
|
},
|
|
|
|
tabContext(aEvent) {
|
|
let win = aEvent.view;
|
|
if (!win) {
|
|
win = Services.wm.getMostRecentWindow("navigator:browser");
|
|
}
|
|
const { document } = win;
|
|
const elements = document.getElementsByClassName("tabFeature");
|
|
for (let i = 0; i < elements.length; i++) {
|
|
const el = elements[i];
|
|
const pref = el.getAttribute("preference");
|
|
if (pref) {
|
|
const visible = Services.prefs.getBoolPref(pref);
|
|
el.hidden = !visible;
|
|
}
|
|
}
|
|
// Can't unload selected tab, so don't show menu item in that case
|
|
if (win.TabContextMenu.contextTab === win.gBrowser.selectedTab) {
|
|
const el = document.getElementById("context_unloadTab");
|
|
el.hidden = true;
|
|
}
|
|
|
|
// Ensure restart button in App Menu (Windows/Linux) has its handler after template instantiation
|
|
const restartBtn = document.getElementById("appMenu-restart-button");
|
|
if (restartBtn) {
|
|
// Toggle iconic styling to match icon prefs so alignment matches Exit when icons are disabled
|
|
const iconsDisabled = Services.prefs.getBoolPref("userChrome.icon.disabled", false);
|
|
const iconsInPanel = Services.prefs.getBoolPref("userChrome.icon.panel", false);
|
|
const shouldIconic = iconsInPanel && !iconsDisabled;
|
|
restartBtn.classList.toggle("subviewbutton-iconic", shouldIconic);
|
|
|
|
if (restartBtn.getAttribute("data-tabfeatures-handler-attached") !== "true") {
|
|
restartBtn.addEventListener("command", (_event) => {
|
|
this.restartBrowser();
|
|
});
|
|
restartBtn.setAttribute("data-tabfeatures-handler-attached", "true");
|
|
}
|
|
}
|
|
},
|
|
|
|
// Copies current tab url to clipboard
|
|
copyTabUrl(aUri, aWindow) {
|
|
const gClipboardHelper = Cc[
|
|
"@mozilla.org/widget/clipboardhelper;1"
|
|
].getService(Ci.nsIClipboardHelper);
|
|
try {
|
|
Services.prefs.getBoolPref(this.PREF_ACTIVETAB)
|
|
? gClipboardHelper.copyString(aWindow.gBrowser.currentURI.spec)
|
|
: gClipboardHelper.copyString(aUri);
|
|
} catch (e) {
|
|
throw new Error(
|
|
`We're sorry but something has gone wrong with 'CopyTabUrl' ${e}`
|
|
);
|
|
}
|
|
},
|
|
|
|
// Copies all tab urls to clipboard
|
|
copyAllTabUrls(aWindow) {
|
|
const gClipboardHelper = Cc[
|
|
"@mozilla.org/widget/clipboardhelper;1"
|
|
].getService(Ci.nsIClipboardHelper);
|
|
//Get all urls
|
|
const urlArr = this._getAllUrls(aWindow);
|
|
try {
|
|
// Enumerate all urls in to a list.
|
|
let urlList = urlArr.join("\n");
|
|
// Send list to clipboard.
|
|
gClipboardHelper.copyString(urlList.trim());
|
|
// Clear url list after clipboard event
|
|
urlList = "";
|
|
} catch (e) {
|
|
throw new Error(
|
|
`We're sorry but something has gone wrong with 'copyAllTabUrls' ${e}`
|
|
);
|
|
}
|
|
},
|
|
|
|
// Get all the tab urls into an array.
|
|
_getAllUrls(aWindow) {
|
|
// We don't want to copy about uri's
|
|
const blocklist = /^about:.*/i;
|
|
const urlArr = [];
|
|
const tabCount = aWindow.gBrowser.browsers.length;
|
|
Array(tabCount)
|
|
.fill()
|
|
.map((_, i) => {
|
|
const spec = aWindow.gBrowser.getBrowserAtIndex(i).currentURI.spec;
|
|
if (!blocklist.test(spec)) {
|
|
urlArr.push(spec);
|
|
}
|
|
});
|
|
return urlArr;
|
|
},
|
|
|
|
async restartBrowser() {
|
|
try {
|
|
if (Services.prefs.getBoolPref(this.PREF_REQUIRECONFIRM)) {
|
|
// Need brand in here to be able to expand { -brand-short-name }
|
|
const l10n = new Localization([
|
|
"branding/brand.ftl",
|
|
"browser/waterfox.ftl",
|
|
]);
|
|
const [title, question] = (
|
|
await l10n.formatMessages([
|
|
{ id: "restart-prompt-title" },
|
|
{ id: "restart-prompt-question" },
|
|
])
|
|
).map(({ value }) => value);
|
|
|
|
if (Services.prompt.confirm(null, title, question)) {
|
|
// only restart if confirmation given
|
|
this._attemptRestart();
|
|
}
|
|
} else {
|
|
this._attemptRestart();
|
|
}
|
|
} catch (e) {
|
|
console.error(
|
|
"We're sorry but something has gone wrong with 'restartBrowser' ",
|
|
e
|
|
);
|
|
}
|
|
},
|
|
|
|
_attemptRestart() {
|
|
// Purge cache if required
|
|
if (Services.prefs.getBoolPref(this.PREF_PURGECACHE)) {
|
|
Services.appinfo.invalidateCachesOnRestart();
|
|
}
|
|
|
|
// Initiate the restart
|
|
Services.startup.quit(
|
|
Services.startup.eRestart | Services.startup.eAttemptQuit
|
|
);
|
|
},
|
|
};
|