summaryrefslogtreecommitdiffstats
path: root/browser/base/content/browser-commands.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/browser-commands.js')
-rw-r--r--browser/base/content/browser-commands.js576
1 files changed, 576 insertions, 0 deletions
diff --git a/browser/base/content/browser-commands.js b/browser/base/content/browser-commands.js
new file mode 100644
index 0000000000..40eb4d5baa
--- /dev/null
+++ b/browser/base/content/browser-commands.js
@@ -0,0 +1,576 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * 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/. */
+
+/* eslint-env mozilla/browser-window */
+
+"use strict";
+
+var kSkipCacheFlags =
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
+
+var BrowserCommands = {
+ back(aEvent) {
+ const where = BrowserUtils.whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ gBrowser.goBack();
+ } catch (ex) {}
+ } else {
+ duplicateTabIn(gBrowser.selectedTab, where, -1);
+ }
+ },
+
+ forward(aEvent) {
+ const where = BrowserUtils.whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ gBrowser.goForward();
+ } catch (ex) {}
+ } else {
+ duplicateTabIn(gBrowser.selectedTab, where, 1);
+ }
+ },
+
+ handleBackspace() {
+ switch (Services.prefs.getIntPref("browser.backspace_action")) {
+ case 0:
+ this.back();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageUp");
+ break;
+ }
+ },
+
+ handleShiftBackspace() {
+ switch (Services.prefs.getIntPref("browser.backspace_action")) {
+ case 0:
+ this.forward();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageDown");
+ break;
+ }
+ },
+
+ gotoHistoryIndex(aEvent) {
+ aEvent = BrowserUtils.getRootEvent(aEvent);
+
+ const index = aEvent.target.getAttribute("index");
+ if (!index) {
+ return false;
+ }
+
+ const where = BrowserUtils.whereToOpenLink(aEvent);
+
+ if (where == "current") {
+ // Normal click. Go there in the current tab and update session history.
+
+ try {
+ gBrowser.gotoIndex(index);
+ } catch (ex) {
+ return false;
+ }
+ return true;
+ }
+ // Modified click. Go there in a new tab/window.
+
+ const historyindex = aEvent.target.getAttribute("historyindex");
+ duplicateTabIn(gBrowser.selectedTab, where, Number(historyindex));
+ return true;
+ },
+
+ reloadOrDuplicate(aEvent) {
+ aEvent = BrowserUtils.getRootEvent(aEvent);
+ const accelKeyPressed =
+ AppConstants.platform == "macosx" ? aEvent.metaKey : aEvent.ctrlKey;
+ const backgroundTabModifier = aEvent.button == 1 || accelKeyPressed;
+
+ if (aEvent.shiftKey && !backgroundTabModifier) {
+ this.reloadSkipCache();
+ return;
+ }
+
+ const where = BrowserUtils.whereToOpenLink(aEvent, false, true);
+ if (where == "current") {
+ this.reload();
+ } else {
+ duplicateTabIn(gBrowser.selectedTab, where);
+ }
+ },
+
+ reload() {
+ if (gBrowser.currentURI.schemeIs("view-source")) {
+ // Bug 1167797: For view source, we always skip the cache
+ this.reloadSkipCache();
+ return;
+ }
+ this.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
+ },
+
+ reloadSkipCache() {
+ // Bypass proxy and cache.
+ this.reloadWithFlags(kSkipCacheFlags);
+ },
+
+ reloadWithFlags(reloadFlags) {
+ const unchangedRemoteness = [];
+
+ for (const tab of gBrowser.selectedTabs) {
+ const browser = tab.linkedBrowser;
+ const url = browser.currentURI;
+ const urlSpec = url.spec;
+ // We need to cache the content principal here because the browser will be
+ // reconstructed when the remoteness changes and the content prinicpal will
+ // be cleared after reconstruction.
+ const principal = tab.linkedBrowser.contentPrincipal;
+ if (gBrowser.updateBrowserRemotenessByURL(browser, urlSpec)) {
+ // If the remoteness has changed, the new browser doesn't have any
+ // information of what was loaded before, so we need to load the previous
+ // URL again.
+ if (tab.linkedPanel) {
+ loadBrowserURI(browser, url, principal);
+ } else {
+ // Shift to fully loaded browser and make
+ // sure load handler is instantiated.
+ tab.addEventListener(
+ "SSTabRestoring",
+ () => loadBrowserURI(browser, url, principal),
+ { once: true }
+ );
+ gBrowser._insertBrowser(tab);
+ }
+ } else {
+ unchangedRemoteness.push(tab);
+ }
+ }
+
+ if (!unchangedRemoteness.length) {
+ return;
+ }
+
+ // Reset temporary permissions on the remaining tabs to reload.
+ // This is done here because we only want to reset
+ // permissions on user reload.
+ for (const tab of unchangedRemoteness) {
+ SitePermissions.clearTemporaryBlockPermissions(tab.linkedBrowser);
+ // Also reset DOS mitigations for the basic auth prompt on reload.
+ delete tab.linkedBrowser.authPromptAbuseCounter;
+ }
+ gIdentityHandler.hidePopup();
+ gPermissionPanel.hidePopup();
+
+ const handlingUserInput = document.hasValidTransientUserGestureActivation;
+
+ for (const tab of unchangedRemoteness) {
+ if (tab.linkedPanel) {
+ sendReloadMessage(tab);
+ } else {
+ // Shift to fully loaded browser and make
+ // sure load handler is instantiated.
+ tab.addEventListener("SSTabRestoring", () => sendReloadMessage(tab), {
+ once: true,
+ });
+ gBrowser._insertBrowser(tab);
+ }
+ }
+
+ function loadBrowserURI(browser, url, principal) {
+ browser.loadURI(url, {
+ flags: reloadFlags,
+ triggeringPrincipal: principal,
+ });
+ }
+
+ function sendReloadMessage(tab) {
+ tab.linkedBrowser.sendMessageToActor(
+ "Browser:Reload",
+ { flags: reloadFlags, handlingUserInput },
+ "BrowserTab"
+ );
+ }
+ },
+
+ stop() {
+ gBrowser.webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
+ },
+
+ home(aEvent) {
+ if (aEvent?.button == 2) {
+ // right-click: do nothing
+ return;
+ }
+
+ const homePage = HomePage.get(window);
+ let where = BrowserUtils.whereToOpenLink(aEvent, false, true);
+
+ // Don't load the home page in pinned or hidden tabs (e.g. Firefox View).
+ if (
+ where == "current" &&
+ (gBrowser?.selectedTab.pinned || gBrowser?.selectedTab.hidden)
+ ) {
+ where = "tab";
+ }
+
+ // openTrustedLinkIn in utilityOverlay.js doesn't handle loading multiple pages
+ let notifyObservers;
+ switch (where) {
+ case "current":
+ // If we're going to load an initial page in the current tab as the
+ // home page, we set initialPageLoadedFromURLBar so that the URL
+ // bar is cleared properly (even during a remoteness flip).
+ if (isInitialPage(homePage)) {
+ gBrowser.selectedBrowser.initialPageLoadedFromUserAction = homePage;
+ }
+ loadOneOrMoreURIs(
+ homePage,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null
+ );
+ if (isBlankPageURL(homePage)) {
+ gURLBar.select();
+ } else {
+ gBrowser.selectedBrowser.focus();
+ }
+ notifyObservers = true;
+ aEvent?.preventDefault();
+ break;
+ case "tabshifted":
+ case "tab": {
+ const urls = homePage.split("|");
+ const loadInBackground = Services.prefs.getBoolPref(
+ "browser.tabs.loadBookmarksInBackground",
+ false
+ );
+ // The homepage observer event should only be triggered when the homepage opens
+ // in the foreground. This is mostly to support the homepage changed by extension
+ // doorhanger which doesn't currently support background pages. This may change in
+ // bug 1438396.
+ notifyObservers = !loadInBackground;
+ gBrowser.loadTabs(urls, {
+ inBackground: loadInBackground,
+ triggeringPrincipal:
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ csp: null,
+ });
+ if (!loadInBackground) {
+ if (isBlankPageURL(homePage)) {
+ gURLBar.select();
+ } else {
+ gBrowser.selectedBrowser.focus();
+ }
+ }
+ aEvent?.preventDefault();
+ break;
+ }
+ case "window":
+ // OpenBrowserWindow will trigger the observer event, so no need to do so here.
+ notifyObservers = false;
+ OpenBrowserWindow();
+ aEvent?.preventDefault();
+ break;
+ }
+
+ if (notifyObservers) {
+ // A notification for when a user has triggered their homepage. This is used
+ // to display a doorhanger explaining that an extension has modified the
+ // homepage, if necessary. Observers are only notified if the homepage
+ // becomes the active page.
+ Services.obs.notifyObservers(null, "browser-open-homepage-start");
+ }
+ },
+
+ openTab({ event, url } = {}) {
+ let werePassedURL = !!url;
+ url ??= BROWSER_NEW_TAB_URL;
+ let searchClipboard =
+ gMiddleClickNewTabUsesPasteboard && event?.button == 1;
+
+ let relatedToCurrent = false;
+ let where = "tab";
+
+ if (event) {
+ where = whereToOpenLink(event, false, true);
+
+ switch (where) {
+ case "tab":
+ case "tabshifted":
+ // When accel-click or middle-click are used, open the new tab as
+ // related to the current tab.
+ relatedToCurrent = true;
+ break;
+ case "current":
+ where = "tab";
+ break;
+ }
+ }
+
+ // A notification intended to be useful for modular peformance tracking
+ // starting as close as is reasonably possible to the time when the user
+ // expressed the intent to open a new tab. Since there are a lot of
+ // entry points, this won't catch every single tab created, but most
+ // initiated by the user should go through here.
+ //
+ // Note 1: This notification gets notified with a promise that resolves
+ // with the linked browser when the tab gets created
+ // Note 2: This is also used to notify a user that an extension has changed
+ // the New Tab page.
+ Services.obs.notifyObservers(
+ {
+ wrappedJSObject: new Promise(resolve => {
+ let options = {
+ relatedToCurrent,
+ resolveOnNewTabCreated: resolve,
+ };
+ if (!werePassedURL && searchClipboard) {
+ let clipboard = readFromClipboard();
+ clipboard =
+ UrlbarUtils.stripUnsafeProtocolOnPaste(clipboard).trim();
+ if (clipboard) {
+ url = clipboard;
+ options.allowThirdPartyFixup = true;
+ }
+ }
+ openTrustedLinkIn(url, where, options);
+ }),
+ },
+ "browser-open-newtab-start"
+ );
+ },
+
+ openFileWindow() {
+ // Get filepicker component.
+ try {
+ const nsIFilePicker = Ci.nsIFilePicker;
+ const fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ const fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK) {
+ try {
+ if (fp.file) {
+ gLastOpenDirectory.path = fp.file.parent.QueryInterface(
+ Ci.nsIFile
+ );
+ }
+ } catch (ex) {}
+ openTrustedLinkIn(fp.fileURL.spec, "current");
+ }
+ };
+
+ fp.init(
+ window.browsingContext,
+ gNavigatorBundle.getString("openFile"),
+ nsIFilePicker.modeOpen
+ );
+ fp.appendFilters(
+ nsIFilePicker.filterAll |
+ nsIFilePicker.filterText |
+ nsIFilePicker.filterImages |
+ nsIFilePicker.filterXML |
+ nsIFilePicker.filterHTML |
+ nsIFilePicker.filterPDF
+ );
+ fp.displayDirectory = gLastOpenDirectory.path;
+ fp.open(fpCallback);
+ } catch (ex) {}
+ },
+
+ closeTabOrWindow(event) {
+ // If we're not a browser window, just close the window.
+ if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
+ closeWindow(true);
+ return;
+ }
+
+ // In a multi-select context, close all selected tabs
+ if (gBrowser.multiSelectedTabsCount) {
+ gBrowser.removeMultiSelectedTabs();
+ return;
+ }
+
+ // Keyboard shortcuts that would close a tab that is pinned select the first
+ // unpinned tab instead.
+ if (
+ event &&
+ (event.ctrlKey || event.metaKey || event.altKey) &&
+ gBrowser.selectedTab.pinned
+ ) {
+ if (gBrowser.visibleTabs.length > gBrowser._numPinnedTabs) {
+ gBrowser.tabContainer.selectedIndex = gBrowser._numPinnedTabs;
+ }
+ return;
+ }
+
+ // If the current tab is the last one, this will close the window.
+ gBrowser.removeCurrentTab({ animate: true });
+ },
+
+ tryToCloseWindow(event) {
+ if (WindowIsClosing(event)) {
+ window.close();
+ } // WindowIsClosing does all the necessary checks
+ },
+
+ /**
+ * Open the View Source dialog.
+ *
+ * @param args
+ * An object with the following properties:
+ *
+ * URL (required):
+ * A string URL for the page we'd like to view the source of.
+ * browser (optional):
+ * The browser containing the document that we would like to view the
+ * source of. This is required if outerWindowID is passed.
+ * outerWindowID (optional):
+ * The outerWindowID of the content window containing the document that
+ * we want to view the source of. You only need to provide this if you
+ * want to attempt to retrieve the document source from the network
+ * cache.
+ * lineNumber (optional):
+ * The line number to focus on once the source is loaded.
+ */
+ async viewSourceOfDocument(args) {
+ // Check if external view source is enabled. If so, try it. If it fails,
+ // fallback to internal view source.
+ if (Services.prefs.getBoolPref("view_source.editor.external")) {
+ try {
+ await top.gViewSourceUtils.openInExternalEditor(args);
+ return;
+ } catch (data) {}
+ }
+
+ let tabBrowser = gBrowser;
+ let preferredRemoteType;
+ let initialBrowsingContextGroupId;
+ if (args.browser) {
+ preferredRemoteType = args.browser.remoteType;
+ initialBrowsingContextGroupId = args.browser.browsingContext.group.id;
+ } else {
+ if (!tabBrowser) {
+ throw new Error(
+ "viewSourceOfDocument should be passed the " +
+ "subject browser if called from a window without " +
+ "gBrowser defined."
+ );
+ }
+ // Some internal URLs (such as specific chrome: and about: URLs that are
+ // not yet remote ready) cannot be loaded in a remote browser. View
+ // source in tab expects the new view source browser's remoteness to match
+ // that of the original URL, so disable remoteness if necessary for this
+ // URL.
+ const oa = E10SUtils.predictOriginAttributes({ window });
+ preferredRemoteType = E10SUtils.getRemoteTypeForURI(
+ args.URL,
+ gMultiProcessBrowser,
+ gFissionBrowser,
+ E10SUtils.DEFAULT_REMOTE_TYPE,
+ null,
+ oa
+ );
+ }
+
+ // In the case of popups, we need to find a non-popup browser window.
+ if (!tabBrowser || !window.toolbar.visible) {
+ // This returns only non-popup browser windows by default.
+ const browserWindow = BrowserWindowTracker.getTopWindow();
+ tabBrowser = browserWindow.gBrowser;
+ }
+
+ const inNewWindow = !Services.prefs.getBoolPref("view_source.tab");
+
+ // `viewSourceInBrowser` will load the source content from the page
+ // descriptor for the tab (when possible) or fallback to the network if
+ // that fails. Either way, the view source module will manage the tab's
+ // location, so use "about:blank" here to avoid unnecessary redundant
+ // requests.
+ const tab = tabBrowser.addTab("about:blank", {
+ relatedToCurrent: true,
+ inBackground: inNewWindow,
+ skipAnimation: inNewWindow,
+ preferredRemoteType,
+ initialBrowsingContextGroupId,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ skipLoad: true,
+ });
+ args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
+ top.gViewSourceUtils.viewSourceInBrowser(args);
+
+ if (inNewWindow) {
+ tabBrowser.hideTab(tab);
+ tabBrowser.replaceTabWithWindow(tab);
+ }
+ },
+
+ /**
+ * Opens the View Source dialog for the source loaded in the root
+ * top-level document of the browser. This is really just a
+ * convenience wrapper around viewSourceOfDocument.
+ *
+ * @param browser
+ * The browser that we want to load the source of.
+ */
+ viewSource(browser) {
+ this.viewSourceOfDocument({
+ browser,
+ outerWindowID: browser.outerWindowID,
+ URL: browser.currentURI.spec,
+ });
+ },
+
+ /**
+ * @param documentURL URL of the document to view, or null for this window's document
+ * @param initialTab name of the initial tab to display, or null for the first tab
+ * @param imageElement image to load in the Media Tab of the Page Info window; can be null/omitted
+ * @param browsingContext the browsingContext of the frame that we want to view information about; can be null/omitted
+ * @param browser the browser containing the document we're interested in inspecting; can be null/omitted
+ */
+ pageInfo(documentURL, initialTab, imageElement, browsingContext, browser) {
+ const args = { initialTab, imageElement, browsingContext, browser };
+
+ documentURL =
+ documentURL || window.gBrowser.selectedBrowser.currentURI.spec;
+
+ const isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+
+ // Check for windows matching the url
+ for (const currentWindow of Services.wm.getEnumerator(
+ "Browser:page-info"
+ )) {
+ if (currentWindow.closed) {
+ continue;
+ }
+ if (
+ currentWindow.document.documentElement.getAttribute("relatedUrl") ==
+ documentURL &&
+ PrivateBrowsingUtils.isWindowPrivate(currentWindow) == isPrivate
+ ) {
+ currentWindow.focus();
+ currentWindow.resetPageInfo(args);
+ return currentWindow;
+ }
+ }
+
+ // We didn't find a matching window, so open a new one.
+ let options = "chrome,toolbar,dialog=no,resizable";
+
+ // Ensure the window groups correctly in the Windows taskbar
+ if (isPrivate) {
+ options += ",private";
+ }
+ return openDialog(
+ "chrome://browser/content/pageinfo/pageInfo.xhtml",
+ "",
+ options,
+ args
+ );
+ },
+
+ fullScreen() {
+ window.fullScreen = !window.fullScreen || BrowserHandler.kiosk;
+ },
+};