feat: add TabFeatures implemented with overlays

(cherry picked from commit 09d6df4188732749da7648ec58bf22c1dd0c7949)
This commit is contained in:
adamp01
2022-07-18 15:35:15 +01:00
committed by Alex Kontos
parent 60b5092c93
commit 915e226a14
11 changed files with 458 additions and 0 deletions

View File

@@ -238,3 +238,13 @@ pref("userContent.page.proton", true); // Need proton_color
// ** Useful Options *********************************************************** // ** Useful Options ***********************************************************
// Integrated calculator at urlbar // Integrated calculator at urlbar
pref("browser.urlbar.suggest.calculator", true); pref("browser.urlbar.suggest.calculator", true);
// Extensibles prefs
pref("browser.tabs.duplicateTab", true);
pref("browser.tabs.copyurl", true);
pref("browser.tabs.copyallurls", false);
pref("browser.tabs.copyurl.activetab", false);
pref("browser.tabs.unloadTab", false);
pref("browser.restart_menu.showpanelmenubtn", true);
pref("browser.restart_menu.purgecache", false);
pref("browser.restart_menu.requireconfirm", true);

View File

@@ -15,6 +15,7 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetters(this, { XPCOMUtils.defineLazyModuleGetters(this, {
ChromeManifest: "resource:///modules/ChromeManifest.jsm", ChromeManifest: "resource:///modules/ChromeManifest.jsm",
Overlays: "resource:///modules/Overlays.jsm", Overlays: "resource:///modules/Overlays.jsm",
TabFeatures: "resource:///modules/TabFeatures.jsm",
}); });
XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]); XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
@@ -60,6 +61,8 @@ const WaterfoxGlue = {
if (subject.URL.includes("browser.xhtml")) { if (subject.URL.includes("browser.xhtml")) {
const window = subject.defaultView; const window = subject.defaultView;
Overlays.load(this.startupManifest, window); Overlays.load(this.startupManifest, window);
TabFeatures.init(window);
} }
break; break;
} }

View File

@@ -0,0 +1 @@
overlay chrome://browser/content/browser.xhtml chrome://browser/content/overlays/tabfeatures.xhtml

View File

@@ -4,6 +4,10 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # 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/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
"tabfeatures",
]
EXTRA_JS_MODULES += [ EXTRA_JS_MODULES += [
"WaterfoxGlue.jsm", "WaterfoxGlue.jsm",
] ]

View File

@@ -0,0 +1,167 @@
/* 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/. */
/* global */
const EXPORTED_SYMBOLS = ["TabFeatures"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const TabFeatures = {
PREF_ACTIVETAB: "browser.tabs.copyurl.activetab",
PREF_REQUIRECONFIRM: "browser.restart_menu.requireconfirm",
PREF_PURGECACHE: "browser.restart_menu.purgecache",
get browserBundle() {
return Services.strings.createBundle(
"chrome://extensibles/locale/extensibles.properties"
);
},
get brandBundle() {
return Services.strings.createBundle(
"chrome://branding/locale/brand.properties"
);
},
init(window) {
window.TabFeatures = this;
this.initListeners(window);
},
initListeners(aWindow) {
aWindow.document
.getElementById("tabContextMenu")
?.addEventListener("popupshowing", this.tabContext);
if (AppConstants.platform == "macosx") {
aWindow.document
.getElementById("file-menu")
?.addEventListener("popupshowing", this.tabContext);
} else {
aWindow.document
.getElementById("appMenu-popup")
?.addEventListener("popupshowing", this.tabContext);
}
},
tabContext(aEvent) {
let win = aEvent.view;
if (!win) {
win = Services.wm.getMostRecentWindow("navigator:browser");
}
let { document } = win;
let elements = document.getElementsByClassName("tabFeature");
for (let i = 0; i < elements.length; i++) {
let el = elements[i];
let pref = el.getAttribute("preference");
if (pref) {
let 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;
}
},
// 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
let 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
let blocklist = /^about:.*/i;
let urlArr = [];
let tabCount = aWindow.gBrowser.browsers.length;
Array(tabCount)
.fill()
.map((_, i) => {
let 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 }
let l10n = new Localization([
"branding/brand.ftl",
"browser/extensibles.ftl",
]);
let [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) {
Cu.reportError(
"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
);
},
};

View File

@@ -0,0 +1,43 @@
<!-- 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/. -->
#filter substitution
<?xml version="1.0"?>
<overlay id="tabfeature-overlay" xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<popupset id="mainPopupSet">
<menupopup id="tabContextMenu">
<menuitem id="context_duplicateTab" data-lazy-l10n-id="duplicate-tab"
oncommand="duplicateTabIn(TabContextMenu.contextTab, 'tab');" class="tabFeature"
preference="browser.tabs.duplicateTab" />
<menuitem id="context_copyTabUrl" class="tabFeature"
oncommand="TabFeatures.copyTabUrl(TabContextMenu.contextTab.linkedBrowser.currentURI.spec, window);"
data-lazy-l10n-id="copy-url" preference="browser.tabs.copyurl" insertafter="context_duplicateTab" />
<menuitem id="context_copyAllTabUrls" class="tabFeature" oncommand="TabFeatures.copyAllTabUrls(window);"
data-lazy-l10n-id="copy-all-urls" preference="browser.tabs.copyallurls"
insertafter="context_copyTabUrl" />
<menuitem id="context_unloadTab" class="tabFeature"
oncommand="gBrowser.discardBrowser(TabContextMenu.contextTab);" data-lazy-l10n-id="unload-tab"
preference="browser.tabs.unloadTab" insertafter="context_copyAllTabUrls" />
</menupopup>
</popupset>
#ifdef XP_MACOSX
<menubar id="main-menubar">
<menuitem id="app_restartBrowser" class="tabFeature" oncommand="TabFeatures.restartBrowser();"
data-l10n-id="appmenuitem-restart-browser" preference="browser.restart_menu.showpanelmenubtn" hidden="true"
insertafter="goOfflineMenuitem" />
</menubar>
#else
<html:template id="appMenu-viewCache">
<panelview id="appMenu-protonMainView">
<vbox id="appMenu-subview-body" class="panel-subview-body">
<toolbarbutton id="appMenu-restart-button" class="subviewbutton subviewbutton-iconic tabFeature"
oncommand="TabFeatures.restartBrowser();" data-l10n-id="appmenuitem-restart-browser"
preference="browser.restart_menu.showpanelmenubtn" hidden="true" />
</vbox>
</panelview>
</html:template>
#endif
</overlay>

View File

@@ -0,0 +1,3 @@
browser.jar:
% content browser %content/browser/ contentaccessible=yes
* content/browser/overlays/tabfeatures.xhtml (content/tabfeatures.xhtml)

View File

@@ -0,0 +1,13 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXTRA_JS_MODULES += [
"TabFeatures.jsm",
]
BROWSER_CHROME_MANIFESTS += ["test/browser/browser.ini"]
JAR_MANIFESTS += ["jar.mn"]

View File

@@ -0,0 +1,7 @@
[DEFAULT]
tags = waterfox_tabfeatures
firefox-appdir = browser
support-files =
head.js
[browser_tabfeatures.js]

View File

@@ -0,0 +1,114 @@
"use strict";
add_task(async function testCopyTabUrls() {
// Make sure elements are present
let copyTabUrl = document.getElementById("context_copyTabUrl");
let copyAllTabUrls = document.getElementById("context_copyAllTabUrls");
ok(copyTabUrl, "Copy tab URL is included");
ok(copyAllTabUrls, "Copy all tab URLs is included");
// Make sure that defaults are set correctly
await openAndCloseTabContextMenu(gBrowser.selectedTab);
is(copyTabUrl.hidden, false, "Copy tab URL visible by default");
is(copyAllTabUrls.hidden, true, "Copy all tab URLs hidden by default");
// Make sure changing prefs causes elements to be shown/hidden
Services.prefs.setBoolPref(COPY_URL_PREF, false);
Services.prefs.setBoolPref(COPY_ALL_URLS_PREF, false);
await openAndCloseTabContextMenu(gBrowser.selectedTab);
is(copyTabUrl.hidden, true, "Copy tab URL hidden");
is(copyAllTabUrls.hidden, true, "Copy all tab URLs hidden");
Services.prefs.setBoolPref(COPY_URL_PREF, true);
Services.prefs.setBoolPref(COPY_ALL_URLS_PREF, true);
await openAndCloseTabContextMenu(gBrowser.selectedTab);
is(copyTabUrl.hidden, false, "Copy tab URL visible");
is(copyAllTabUrls.hidden, false, "Copy all tab URLs visible");
Services.prefs.clearUserPref(COPY_URL_PREF);
Services.prefs.clearUserPref(COPY_ALL_URLS_PREF);
});
add_task(async function testHideDuplicateTab() {
// Setting duplicateTab pref to false should hide element in all windows
let duplicateTab = document.getElementById("context_duplicateTab");
Services.prefs.setBoolPref(DUPLICATE_TAB_PREF, false);
await openAndCloseTabContextMenu(gBrowser.selectedTab);
is(duplicateTab.hidden, true, "Duplicate tab hidden");
// Should fall back to default value of true, i.e. element showing
Services.prefs.clearUserPref(DUPLICATE_TAB_PREF);
// Ensure showing
await openAndCloseTabContextMenu(gBrowser.selectedTab);
is(duplicateTab.hidden, false, "Duplicate tab showing");
});
add_task(async function testRestartItem() {
// Make sure element is present
let restartBrowserMenu = document.getElementById("app_restartBrowser");
// Need to use PanelMultiView to get PanelUI elements
let restartBrowserApp = PanelMultiView.getViewNode(
document,
"appMenu-restart-button"
);
if (OS == "macosx") {
ok(restartBrowserMenu, "Restart browser menu bar item is included");
is(restartBrowserApp, null, "Restart browser appMenu item not included");
await openAndCloseFileMenu();
is(
restartBrowserMenu.hidden,
false,
"Restart browser menu bar item is visible"
);
} else {
is(
restartBrowserMenu,
null,
"Restart browser menu bar item is not included"
);
ok(restartBrowserApp, "Restart browser appMenu item included");
}
// Make sure element is hidden
Services.prefs.setBoolPref(RESTART_PREF, false);
if (OS == "macosx") {
await openAndCloseFileMenu();
is(
restartBrowserMenu.hidden,
true,
"Restart browser menu bar item is hidden"
);
}
Services.prefs.clearUserPref(RESTART_PREF);
});
add_task(async function testCopyUrlFunctionality() {
let copyTabUrl = document.getElementById("context_copyTabUrl");
let copyAllTabUrls = document.getElementById("context_copyAllTabUrls");
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URI1);
let browser = tab.linkedBrowser;
// Test copy tab url copies URL
await openTabContextMenu(tab);
EventUtils.synthesizeMouseAtCenter(copyTabUrl, {});
let tabURI = await pasteFromClipboard(browser);
is(tabURI, URI1);
// Test copy all tab urls
Services.prefs.setBoolPref(COPY_ALL_URLS_PREF, true);
const tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, URI2);
await openTabContextMenu(tab);
EventUtils.synthesizeMouseAtCenter(copyAllTabUrls, {});
let tabURIs = await pasteFromClipboard(browser);
is(tabURIs, URI1 + "\n" + URI2);
// Test copy active tab pref
Services.prefs.setBoolPref(COPY_ACTIVE_URL_PREF, true);
await openTabContextMenu(tab);
EventUtils.synthesizeMouseAtCenter(copyTabUrl, {});
let activeURI = await pasteFromClipboard(browser);
// URI2 should be active, so we copy from tab1 to verify
is(activeURI, URI2);
// Then we verify that URI1 is copied when active pref is false
Services.prefs.setBoolPref(COPY_ACTIVE_URL_PREF, false);
await openTabContextMenu(tab);
EventUtils.synthesizeMouseAtCenter(copyTabUrl, {});
activeURI = await pasteFromClipboard(browser);
is(activeURI, URI1);
// Cleanup
Services.prefs.clearUserPref(COPY_ALL_URLS_PREF);
Services.prefs.clearUserPref(COPY_ACTIVE_URL_PREF);
BrowserTestUtils.removeTab(tab);
BrowserTestUtils.removeTab(tab2);
});

View File

@@ -0,0 +1,93 @@
"use strict";
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
var { synthesizeDrop, synthesizeMouseAtCenter } = EventUtils;
const COPY_URL_PREF = "browser.tabs.copyurl";
const COPY_ALL_URLS_PREF = "browser.tabs.copyallurls";
const COPY_ACTIVE_URL_PREF = "browser.tabs.copyurl.activetab";
const DUPLICATE_TAB_PREF = "browser.tabs.duplicateTab";
const RESTART_PREF = "browser.restart_menu.showpanelmenubtn";
const URI1 = "https://test1.example.com/";
const URI2 = "https://example.com/";
let OS = AppConstants.platform;
/**
* Helper for opening the toolbar context menu.
*/
async function openTabContextMenu(tab) {
info("Opening tab context menu");
let contextMenu = document.getElementById("tabContextMenu");
let openTabContextMenuPromise = BrowserTestUtils.waitForPopupEvent(
contextMenu,
"shown"
);
EventUtils.synthesizeMouseAtCenter(tab, { type: "contextmenu" });
await openTabContextMenuPromise;
return contextMenu;
}
async function openAndCloseTabContextMenu(tab) {
await openTabContextMenu(tab);
info("Opened tab context menu");
await EventUtils.synthesizeKey("VK_ESCAPE", {});
info("Closed tab context menu");
}
/**
* Helper for opening the file menu.
*/
async function openFileMenu() {
info("Opening file menu");
let fileMenu = document.getElementById("file-menu");
let openFileMenuPromise = BrowserTestUtils.waitForPopupEvent(
fileMenu,
"shown"
);
EventUtils.synthesizeMouseAtCenter(fileMenu, {});
await openFileMenuPromise;
return fileMenu;
}
async function openAndCloseFileMenu() {
await openFileMenu();
await EventUtils.synthesizeKey("VK_ESCAPE", {});
info("Closed file menu");
}
/**
* Helper for opening toolbar context menu.
*/
async function openToolbarContextMenu(contextMenu, target) {
let popupshown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
EventUtils.synthesizeMouseAtCenter(target, { type: "contextmenu" });
await popupshown;
}
/**
* Helper to paste from clipboard
*/
async function pasteFromClipboard(browser) {
return SpecialPowers.spawn(browser, [], () => {
let { document } = content;
document.body.contentEditable = true;
document.body.focus();
let pastePromise = new Promise(resolve => {
document.addEventListener(
"paste",
e => {
resolve(e.clipboardData.getData("text/plain"));
},
{ once: true }
);
});
document.execCommand("paste");
return pastePromise;
});
}