1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /* eslint-env mozilla/browser-window */
11 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
12 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
14 var BrowserCommands = {
16 const where = BrowserUtils.whereToOpenLink(aEvent, false, true);
18 if (where == "current") {
23 duplicateTabIn(gBrowser.selectedTab, where, -1);
28 const where = BrowserUtils.whereToOpenLink(aEvent, false, true);
30 if (where == "current") {
35 duplicateTabIn(gBrowser.selectedTab, where, 1);
40 switch (Services.prefs.getIntPref("browser.backspace_action")) {
45 goDoCommand("cmd_scrollPageUp");
50 handleShiftBackspace() {
51 switch (Services.prefs.getIntPref("browser.backspace_action")) {
56 goDoCommand("cmd_scrollPageDown");
61 gotoHistoryIndex(aEvent) {
62 aEvent = BrowserUtils.getRootEvent(aEvent);
64 const index = aEvent.target.getAttribute("index");
69 const where = BrowserUtils.whereToOpenLink(aEvent);
71 if (where == "current") {
72 // Normal click. Go there in the current tab and update session history.
75 gBrowser.gotoIndex(index);
81 // Modified click. Go there in a new tab/window.
83 const historyindex = aEvent.target.getAttribute("historyindex");
84 duplicateTabIn(gBrowser.selectedTab, where, Number(historyindex));
88 reloadOrDuplicate(aEvent) {
89 aEvent = BrowserUtils.getRootEvent(aEvent);
90 const accelKeyPressed =
91 AppConstants.platform == "macosx" ? aEvent.metaKey : aEvent.ctrlKey;
92 const backgroundTabModifier = aEvent.button == 1 || accelKeyPressed;
94 if (aEvent.shiftKey && !backgroundTabModifier) {
95 this.reloadSkipCache();
99 const where = BrowserUtils.whereToOpenLink(aEvent, false, true);
100 if (where == "current") {
103 duplicateTabIn(gBrowser.selectedTab, where);
108 if (gBrowser.currentURI.schemeIs("view-source")) {
109 // Bug 1167797: For view source, we always skip the cache
110 this.reloadSkipCache();
113 this.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
117 // Bypass proxy and cache.
118 this.reloadWithFlags(kSkipCacheFlags);
121 reloadWithFlags(reloadFlags) {
122 const unchangedRemoteness = [];
124 for (const tab of gBrowser.selectedTabs) {
125 const browser = tab.linkedBrowser;
126 const url = browser.currentURI;
127 const urlSpec = url.spec;
128 // We need to cache the content principal here because the browser will be
129 // reconstructed when the remoteness changes and the content prinicpal will
130 // be cleared after reconstruction.
131 const principal = tab.linkedBrowser.contentPrincipal;
132 if (gBrowser.updateBrowserRemotenessByURL(browser, urlSpec)) {
133 // If the remoteness has changed, the new browser doesn't have any
134 // information of what was loaded before, so we need to load the previous
136 if (tab.linkedPanel) {
137 loadBrowserURI(browser, url, principal);
139 // Shift to fully loaded browser and make
140 // sure load handler is instantiated.
141 tab.addEventListener(
143 () => loadBrowserURI(browser, url, principal),
146 gBrowser._insertBrowser(tab);
149 unchangedRemoteness.push(tab);
153 if (!unchangedRemoteness.length) {
157 // Reset temporary permissions on the remaining tabs to reload.
158 // This is done here because we only want to reset
159 // permissions on user reload.
160 for (const tab of unchangedRemoteness) {
161 SitePermissions.clearTemporaryBlockPermissions(tab.linkedBrowser);
162 // Also reset DOS mitigations for the basic auth prompt on reload.
163 delete tab.linkedBrowser.authPromptAbuseCounter;
165 gIdentityHandler.hidePopup();
166 gPermissionPanel.hidePopup();
168 if (document.hasValidTransientUserGestureActivation) {
169 reloadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_USER_ACTIVATION;
172 for (const tab of unchangedRemoteness) {
173 reloadBrowser(tab, reloadFlags);
176 function reloadBrowser(tab) {
177 if (tab.linkedPanel) {
178 const { browsingContext } = tab.linkedBrowser;
179 const { sessionHistory } = browsingContext;
180 if (sessionHistory) {
181 sessionHistory.reload(reloadFlags);
183 browsingContext.reload(reloadFlags);
186 // Shift to fully loaded browser and make
187 // sure load handler is instantiated.
188 tab.addEventListener(
190 () => tab.linkedBrowser.browsingContext.reload(reloadFlags),
195 gBrowser._insertBrowser(tab);
199 function loadBrowserURI(browser, url, principal) {
200 browser.loadURI(url, {
202 triggeringPrincipal: principal,
208 gBrowser.webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
212 if (aEvent?.button == 2) {
213 // right-click: do nothing
217 const homePage = HomePage.get(window);
218 let where = BrowserUtils.whereToOpenLink(aEvent, false, true);
220 // Don't load the home page in pinned or hidden tabs (e.g. Firefox View).
222 where == "current" &&
223 (gBrowser?.selectedTab.pinned || gBrowser?.selectedTab.hidden)
228 // openTrustedLinkIn in utilityOverlay.js doesn't handle loading multiple pages
232 // If we're going to load an initial page in the current tab as the
233 // home page, we set initialPageLoadedFromURLBar so that the URL
234 // bar is cleared properly (even during a remoteness flip).
235 if (isInitialPage(homePage)) {
236 gBrowser.selectedBrowser.initialPageLoadedFromUserAction = homePage;
240 Services.scriptSecurityManager.getSystemPrincipal(),
243 if (isBlankPageURL(homePage)) {
246 gBrowser.selectedBrowser.focus();
248 notifyObservers = true;
249 aEvent?.preventDefault();
253 const urls = homePage.split("|");
254 const loadInBackground = Services.prefs.getBoolPref(
255 "browser.tabs.loadBookmarksInBackground",
258 // The homepage observer event should only be triggered when the homepage opens
259 // in the foreground. This is mostly to support the homepage changed by extension
260 // doorhanger which doesn't currently support background pages. This may change in
262 notifyObservers = !loadInBackground;
263 gBrowser.loadTabs(urls, {
264 inBackground: loadInBackground,
266 Services.scriptSecurityManager.getSystemPrincipal(),
269 if (!loadInBackground) {
270 if (isBlankPageURL(homePage)) {
273 gBrowser.selectedBrowser.focus();
276 aEvent?.preventDefault();
280 // OpenBrowserWindow will trigger the observer event, so no need to do so here.
281 notifyObservers = false;
283 aEvent?.preventDefault();
287 if (notifyObservers) {
288 // A notification for when a user has triggered their homepage. This is used
289 // to display a doorhanger explaining that an extension has modified the
290 // homepage, if necessary. Observers are only notified if the homepage
291 // becomes the active page.
292 Services.obs.notifyObservers(null, "browser-open-homepage-start");
296 openTab({ event, url } = {}) {
297 let werePassedURL = !!url;
298 url ??= BROWSER_NEW_TAB_URL;
299 let searchClipboard =
300 gMiddleClickNewTabUsesPasteboard && event?.button == 1;
302 let relatedToCurrent = false;
306 where = BrowserUtils.whereToOpenLink(event, false, true);
311 // When accel-click or middle-click are used, open the new tab as
312 // related to the current tab.
313 relatedToCurrent = true;
321 // A notification intended to be useful for modular peformance tracking
322 // starting as close as is reasonably possible to the time when the user
323 // expressed the intent to open a new tab. Since there are a lot of
324 // entry points, this won't catch every single tab created, but most
325 // initiated by the user should go through here.
327 // Note 1: This notification gets notified with a promise that resolves
328 // with the linked browser when the tab gets created
329 // Note 2: This is also used to notify a user that an extension has changed
331 Services.obs.notifyObservers(
333 wrappedJSObject: new Promise(resolve => {
336 resolveOnNewTabCreated: resolve,
338 if (!werePassedURL && searchClipboard) {
339 let clipboard = readFromClipboard();
341 UrlbarUtils.stripUnsafeProtocolOnPaste(clipboard).trim();
344 options.allowThirdPartyFixup = true;
347 openTrustedLinkIn(url, where, options);
350 "browser-open-newtab-start"
355 // Get filepicker component.
357 const nsIFilePicker = Ci.nsIFilePicker;
358 const fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
359 const fpCallback = function fpCallback_done(aResult) {
360 if (aResult == nsIFilePicker.returnOK) {
363 gLastOpenDirectory.path = fp.file.parent.QueryInterface(
368 openTrustedLinkIn(fp.fileURL.spec, "current");
373 window.browsingContext,
374 gNavigatorBundle.getString("openFile"),
375 nsIFilePicker.modeOpen
378 nsIFilePicker.filterAll |
379 nsIFilePicker.filterText |
380 nsIFilePicker.filterImages |
381 nsIFilePicker.filterXML |
382 nsIFilePicker.filterHTML |
383 nsIFilePicker.filterPDF
385 fp.displayDirectory = gLastOpenDirectory.path;
390 closeTabOrWindow(event) {
391 // If we're not a browser window, just close the window.
392 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
397 // In a multi-select context, close all selected tabs
398 if (gBrowser.multiSelectedTabsCount) {
399 gBrowser.removeMultiSelectedTabs();
403 // Keyboard shortcuts that would close a tab that is pinned select the first
404 // unpinned tab instead.
407 (event.ctrlKey || event.metaKey || event.altKey) &&
408 gBrowser.selectedTab.pinned
410 if (gBrowser.visibleTabs.length > gBrowser.pinnedTabCount) {
411 gBrowser.tabContainer.selectedIndex = gBrowser.pinnedTabCount;
416 // If the current tab is the last one, this will close the window.
417 gBrowser.removeCurrentTab({ animate: true });
420 tryToCloseWindow(event) {
421 if (WindowIsClosing(event)) {
423 } // WindowIsClosing does all the necessary checks
427 * Open the View Source dialog.
430 * An object with the following properties:
433 * A string URL for the page we'd like to view the source of.
434 * browser (optional):
435 * The browser containing the document that we would like to view the
436 * source of. This is required if outerWindowID is passed.
437 * outerWindowID (optional):
438 * The outerWindowID of the content window containing the document that
439 * we want to view the source of. You only need to provide this if you
440 * want to attempt to retrieve the document source from the network
442 * lineNumber (optional):
443 * The line number to focus on once the source is loaded.
445 async viewSourceOfDocument(args) {
446 // Check if external view source is enabled. If so, try it. If it fails,
447 // fallback to internal view source.
448 if (Services.prefs.getBoolPref("view_source.editor.external")) {
450 await top.gViewSourceUtils.openInExternalEditor(args);
455 let tabBrowser = gBrowser;
456 let preferredRemoteType;
457 let initialBrowsingContextGroupId;
459 preferredRemoteType = args.browser.remoteType;
460 initialBrowsingContextGroupId = args.browser.browsingContext.group.id;
464 "viewSourceOfDocument should be passed the " +
465 "subject browser if called from a window without " +
469 // Some internal URLs (such as specific chrome: and about: URLs that are
470 // not yet remote ready) cannot be loaded in a remote browser. View
471 // source in tab expects the new view source browser's remoteness to match
472 // that of the original URL, so disable remoteness if necessary for this
474 const oa = E10SUtils.predictOriginAttributes({ window });
475 preferredRemoteType = E10SUtils.getRemoteTypeForURI(
477 gMultiProcessBrowser,
479 E10SUtils.DEFAULT_REMOTE_TYPE,
485 // In the case of popups, we need to find a non-popup browser window.
486 if (!tabBrowser || !window.toolbar.visible) {
487 // This returns only non-popup browser windows by default.
488 const browserWindow = BrowserWindowTracker.getTopWindow();
489 tabBrowser = browserWindow.gBrowser;
492 const inNewWindow = !Services.prefs.getBoolPref("view_source.tab");
494 // `viewSourceInBrowser` will load the source content from the page
495 // descriptor for the tab (when possible) or fallback to the network if
496 // that fails. Either way, the view source module will manage the tab's
497 // location, so use "about:blank" here to avoid unnecessary redundant
499 const tab = tabBrowser.addTab("about:blank", {
500 relatedToCurrent: true,
501 inBackground: inNewWindow,
502 skipAnimation: inNewWindow,
504 initialBrowsingContextGroupId,
505 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
508 args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
509 top.gViewSourceUtils.viewSourceInBrowser(args);
512 tabBrowser.hideTab(tab);
513 tabBrowser.replaceTabWithWindow(tab);
518 * Opens the View Source dialog for the source loaded in the root
519 * top-level document of the browser. This is really just a
520 * convenience wrapper around viewSourceOfDocument.
523 * The browser that we want to load the source of.
525 viewSource(browser) {
526 this.viewSourceOfDocument({
528 outerWindowID: browser.outerWindowID,
529 URL: browser.currentURI.spec,
534 * @param documentURL URL of the document to view, or null for this window's document
535 * @param initialTab name of the initial tab to display, or null for the first tab
536 * @param imageElement image to load in the Media Tab of the Page Info window; can be null/omitted
537 * @param browsingContext the browsingContext of the frame that we want to view information about; can be null/omitted
538 * @param browser the browser containing the document we're interested in inspecting; can be null/omitted
540 pageInfo(documentURL, initialTab, imageElement, browsingContext, browser) {
541 const args = { initialTab, imageElement, browsingContext, browser };
544 documentURL || window.gBrowser.selectedBrowser.currentURI.spec;
546 const isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
548 // Check for windows matching the url
549 for (const currentWindow of Services.wm.getEnumerator(
552 if (currentWindow.closed) {
556 currentWindow.document.documentElement.getAttribute("relatedUrl") ==
558 PrivateBrowsingUtils.isWindowPrivate(currentWindow) == isPrivate
560 currentWindow.focus();
561 currentWindow.resetPageInfo(args);
562 return currentWindow;
566 // We didn't find a matching window, so open a new one.
567 let options = "chrome,toolbar,dialog=no,resizable";
569 // Ensure the window groups correctly in the Windows taskbar
571 options += ",private";
574 "chrome://browser/content/pageinfo/pageInfo.xhtml",
582 window.fullScreen = !window.fullScreen || BrowserHandler.kiosk;
586 if (PrivateBrowsingUtils.isWindowPrivate(window)) {
587 openTrustedLinkIn("about:downloads", "tab");
589 PlacesCommandHook.showPlacesOrganizer("Downloads");
593 forceEncodingDetection() {
594 gBrowser.selectedBrowser.forceEncodingDetection();
595 BrowserCommands.reloadWithFlags(
596 Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE