forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			591 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			591 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- 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 = BrowserUtils.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;
 | |
|   },
 | |
| 
 | |
|   downloadsUI() {
 | |
|     if (PrivateBrowsingUtils.isWindowPrivate(window)) {
 | |
|       openTrustedLinkIn("about:downloads", "tab");
 | |
|     } else {
 | |
|       PlacesCommandHook.showPlacesOrganizer("Downloads");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   forceEncodingDetection() {
 | |
|     gBrowser.selectedBrowser.forceEncodingDetection();
 | |
|     BrowserCommands.reloadWithFlags(
 | |
|       Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE
 | |
|     );
 | |
|   },
 | |
| };
 | 
