browser.js (158221B)
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/. */ 5 6 var { XPCOMUtils } = ChromeUtils.importESModule( 7 "resource://gre/modules/XPCOMUtils.sys.mjs" 8 ); 9 var { AppConstants } = ChromeUtils.importESModule( 10 "resource://gre/modules/AppConstants.sys.mjs" 11 ); 12 13 // lazy module getters 14 15 ChromeUtils.defineESModuleGetters(this, { 16 AIWindow: 17 "moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs", 18 AMTelemetry: "resource://gre/modules/AddonManager.sys.mjs", 19 AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs", 20 AboutReaderParent: "resource:///actors/AboutReaderParent.sys.mjs", 21 ActionsProviderContextualSearch: 22 "moz-src:///browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs", 23 AddonManager: "resource://gre/modules/AddonManager.sys.mjs", 24 BrowserTelemetryUtils: "resource://gre/modules/BrowserTelemetryUtils.sys.mjs", 25 BrowserUIUtils: "resource:///modules/BrowserUIUtils.sys.mjs", 26 BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs", 27 BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", 28 CFRPageActions: "resource:///modules/asrouter/CFRPageActions.sys.mjs", 29 Color: "resource://gre/modules/Color.sys.mjs", 30 ContentAnalysis: 31 "moz-src:///browser/components/contentanalysis/content/ContentAnalysis.sys.mjs", 32 ContextualIdentityService: 33 "resource://gre/modules/ContextualIdentityService.sys.mjs", 34 CustomizableUI: 35 "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs", 36 DevToolsSocketStatus: 37 "resource://devtools/shared/security/DevToolsSocketStatus.sys.mjs", 38 DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs", 39 DownloadsCommon: 40 "moz-src:///browser/components/downloads/DownloadsCommon.sys.mjs", 41 E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs", 42 ExtensionsUI: "resource:///modules/ExtensionsUI.sys.mjs", 43 HomePage: "resource:///modules/HomePage.sys.mjs", 44 LightweightThemeConsumer: 45 "resource://gre/modules/LightweightThemeConsumer.sys.mjs", 46 LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs", 47 LoginManagerParent: "resource://gre/modules/LoginManagerParent.sys.mjs", 48 MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs", 49 NetUtil: "resource://gre/modules/NetUtil.sys.mjs", 50 NewTabPagePreloading: 51 "moz-src:///browser/components/tabbrowser/NewTabPagePreloading.sys.mjs", 52 NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs", 53 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", 54 nsContextMenu: "chrome://browser/content/nsContextMenu.sys.mjs", 55 OnionLocationParent: "resource:///modules/OnionLocationParent.sys.mjs", 56 OpenInTabsUtils: 57 "moz-src:///browser/components/tabbrowser/OpenInTabsUtils.sys.mjs", 58 OpenSearchManager: 59 "moz-src:///browser/components/search/OpenSearchManager.sys.mjs", 60 PageActions: "resource:///modules/PageActions.sys.mjs", 61 PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs", 62 PanelMultiView: 63 "moz-src:///browser/components/customizableui/PanelMultiView.sys.mjs", 64 PanelView: 65 "moz-src:///browser/components/customizableui/PanelMultiView.sys.mjs", 66 PictureInPicture: "resource://gre/modules/PictureInPicture.sys.mjs", 67 PlacesTransactions: "resource://gre/modules/PlacesTransactions.sys.mjs", 68 PlacesUIUtils: "moz-src:///browser/components/places/PlacesUIUtils.sys.mjs", 69 PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", 70 PopupAndRedirectBlockerObserver: 71 "resource:///modules/PopupAndRedirectBlockerObserver.sys.mjs", 72 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", 73 PrivateBrowsingUI: "moz-src:///browser/modules/PrivateBrowsingUI.sys.mjs", 74 ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.sys.mjs", 75 ProfilesDatastoreService: 76 "moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs", 77 PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs", 78 ReaderMode: "moz-src:///toolkit/components/reader/ReaderMode.sys.mjs", 79 ResetPBMPanel: 80 "moz-src:///browser/components/privatebrowsing/ResetPBMPanel.sys.mjs", 81 SafeBrowsing: "resource://gre/modules/SafeBrowsing.sys.mjs", 82 Sanitizer: "resource:///modules/Sanitizer.sys.mjs", 83 ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs", 84 SearchUIUtils: "moz-src:///browser/components/search/SearchUIUtils.sys.mjs", 85 SelectableProfileService: 86 "resource:///modules/profiles/SelectableProfileService.sys.mjs", 87 SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs", 88 SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", 89 SessionWindowUI: "resource:///modules/sessionstore/SessionWindowUI.sys.mjs", 90 SharingUtils: "resource:///modules/SharingUtils.sys.mjs", 91 ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs", 92 SiteDataManager: "resource:///modules/SiteDataManager.sys.mjs", 93 SitePermissions: "resource:///modules/SitePermissions.sys.mjs", 94 SubDialog: "resource://gre/modules/SubDialog.sys.mjs", 95 SubDialogManager: "resource://gre/modules/SubDialog.sys.mjs", 96 TabCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs", 97 TabsSetupFlowManager: 98 "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs", 99 TaskbarTabsChrome: 100 "resource:///modules/taskbartabs/TaskbarTabsChrome.sys.mjs", 101 TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs", 102 ToolbarContextMenu: 103 "moz-src:///browser/components/customizableui/ToolbarContextMenu.sys.mjs", 104 ToolbarDropHandler: 105 "moz-src:///browser/components/customizableui/ToolbarDropHandler.sys.mjs", 106 ToolbarIconColor: "moz-src:///browser/themes/ToolbarIconColor.sys.mjs", 107 TorConnect: "resource://gre/modules/TorConnect.sys.mjs", 108 TorConnectStage: "resource://gre/modules/TorConnect.sys.mjs", 109 TorConnectTopics: "resource://gre/modules/TorConnect.sys.mjs", 110 TorConnectParent: "resource://gre/actors/TorConnectParent.sys.mjs", 111 TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs", 112 TorUIUtils: "resource:///modules/TorUIUtils.sys.mjs", 113 TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs", 114 UITour: "moz-src:///browser/components/uitour/UITour.sys.mjs", 115 UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs", 116 URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs", 117 UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs", 118 UrlbarProviderSearchTips: 119 "moz-src:///browser/components/urlbar/UrlbarProviderSearchTips.sys.mjs", 120 UrlbarTokenizer: 121 "moz-src:///browser/components/urlbar/UrlbarTokenizer.sys.mjs", 122 UrlbarUtils: "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs", 123 Weave: "resource://services-sync/main.sys.mjs", 124 WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs", 125 webrtcUI: "resource:///modules/webrtcUI.sys.mjs", 126 WebsiteFilter: "resource:///modules/policies/WebsiteFilter.sys.mjs", 127 ZoomUI: "resource:///modules/ZoomUI.sys.mjs", 128 }); 129 130 ChromeUtils.defineLazyGetter(this, "fxAccounts", () => { 131 return ChromeUtils.importESModule( 132 "resource://gre/modules/FxAccounts.sys.mjs" 133 ).getFxAccountsSingleton(); 134 }); 135 136 XPCOMUtils.defineLazyScriptGetter( 137 this, 138 ["BrowserCommands", "kSkipCacheFlags"], 139 "chrome://browser/content/browser-commands.js" 140 ); 141 142 XPCOMUtils.defineLazyScriptGetter( 143 this, 144 "PlacesTreeView", 145 "chrome://browser/content/places/treeView.js" 146 ); 147 XPCOMUtils.defineLazyScriptGetter( 148 this, 149 ["PlacesInsertionPoint", "PlacesController", "PlacesControllerDragHelper"], 150 "chrome://browser/content/places/controller.js" 151 ); 152 XPCOMUtils.defineLazyScriptGetter( 153 this, 154 "PrintUtils", 155 "chrome://global/content/printUtils.js" 156 ); 157 XPCOMUtils.defineLazyScriptGetter( 158 this, 159 "ZoomManager", 160 "chrome://global/content/viewZoomOverlay.js" 161 ); 162 XPCOMUtils.defineLazyScriptGetter( 163 this, 164 "FullZoom", 165 "chrome://browser/content/tabbrowser/browser-fullZoom.js" 166 ); 167 XPCOMUtils.defineLazyScriptGetter( 168 this, 169 "PanelUI", 170 "chrome://browser/content/customizableui/panelUI.js" 171 ); 172 XPCOMUtils.defineLazyScriptGetter( 173 this, 174 "gViewSourceUtils", 175 "chrome://global/content/viewSourceUtils.js" 176 ); 177 XPCOMUtils.defineLazyScriptGetter( 178 this, 179 "gTabsPanel", 180 "chrome://browser/content/tabbrowser/browser-allTabsMenu.js" 181 ); 182 XPCOMUtils.defineLazyScriptGetter( 183 this, 184 [ 185 "BrowserAddonUI", 186 "gExtensionsNotifications", 187 "gUnifiedExtensions", 188 "gXPInstallObserver", 189 ], 190 "chrome://browser/content/browser-addons.js" 191 ); 192 XPCOMUtils.defineLazyScriptGetter( 193 this, 194 "ctrlTab", 195 "chrome://browser/content/tabbrowser/browser-ctrlTab.js" 196 ); 197 XPCOMUtils.defineLazyScriptGetter( 198 this, 199 ["CustomizationHandler", "AutoHideMenubar"], 200 "chrome://browser/content/browser-customization.js" 201 ); 202 XPCOMUtils.defineLazyScriptGetter( 203 this, 204 ["PointerLock", "FullScreen"], 205 "chrome://browser/content/browser-fullScreenAndPointerLock.js" 206 ); 207 XPCOMUtils.defineLazyScriptGetter( 208 this, 209 "gIdentityHandler", 210 "chrome://browser/content/browser-siteIdentity.js" 211 ); 212 XPCOMUtils.defineLazyScriptGetter( 213 this, 214 "gPermissionPanel", 215 "chrome://browser/content/browser-sitePermissionPanel.js" 216 ); 217 XPCOMUtils.defineLazyScriptGetter( 218 this, 219 "SelectTranslationsPanel", 220 "chrome://browser/content/translations/selectTranslationsPanel.js" 221 ); 222 XPCOMUtils.defineLazyScriptGetter( 223 this, 224 "FullPageTranslationsPanel", 225 "chrome://browser/content/translations/fullPageTranslationsPanel.js" 226 ); 227 XPCOMUtils.defineLazyScriptGetter( 228 this, 229 "gProtectionsHandler", 230 "chrome://browser/content/browser-siteProtections.js" 231 ); 232 XPCOMUtils.defineLazyScriptGetter( 233 this, 234 "gTrustPanelHandler", 235 "chrome://browser/content/browser-trustPanel.js" 236 ); 237 XPCOMUtils.defineLazyScriptGetter( 238 this, 239 ["gGestureSupport", "gHistorySwipeAnimation"], 240 "chrome://browser/content/browser-gestureSupport.js" 241 ); 242 XPCOMUtils.defineLazyScriptGetter( 243 this, 244 "gSafeBrowsing", 245 "chrome://browser/content/browser-safebrowsing.js" 246 ); 247 XPCOMUtils.defineLazyScriptGetter( 248 this, 249 "gSync", 250 "chrome://browser/content/browser-sync.js" 251 ); 252 XPCOMUtils.defineLazyScriptGetter( 253 this, 254 "gBrowserThumbnails", 255 "chrome://browser/content/browser-thumbnails.js" 256 ); 257 XPCOMUtils.defineLazyScriptGetter( 258 this, 259 [ 260 "DownloadsPanel", 261 "DownloadsOverlayLoader", 262 "DownloadsView", 263 "DownloadsViewUI", 264 "DownloadsViewController", 265 "DownloadsSummary", 266 "DownloadsFooter", 267 "DownloadsBlockedSubview", 268 ], 269 "chrome://browser/content/downloads/downloads.js" 270 ); 271 XPCOMUtils.defineLazyScriptGetter( 272 this, 273 ["DownloadsButton", "DownloadsIndicatorView"], 274 "chrome://browser/content/downloads/indicator.js" 275 ); 276 XPCOMUtils.defineLazyScriptGetter( 277 this, 278 ["SecurityLevelButton"], 279 "chrome://browser/content/securitylevel/securityLevel.js" 280 ); 281 XPCOMUtils.defineLazyScriptGetter( 282 this, 283 ["NewIdentityButton"], 284 "chrome://browser/content/newidentity.js" 285 ); 286 XPCOMUtils.defineLazyScriptGetter( 287 this, 288 ["OnionAuthPrompt"], 289 "chrome://browser/content/onionservices/authPrompt.js" 290 ); 291 XPCOMUtils.defineLazyScriptGetter( 292 this, 293 "gEditItemOverlay", 294 "chrome://browser/content/places/editBookmark.js" 295 ); 296 XPCOMUtils.defineLazyScriptGetter( 297 this, 298 "gGfxUtils", 299 "chrome://browser/content/browser-graphics-utils.js" 300 ); 301 XPCOMUtils.defineLazyScriptGetter( 302 this, 303 "ToolbarKeyboardNavigator", 304 "chrome://browser/content/browser-toolbarKeyNav.js" 305 ); 306 XPCOMUtils.defineLazyScriptGetter( 307 this, 308 "A11yUtils", 309 "chrome://browser/content/browser-a11yUtils.js" 310 ); 311 XPCOMUtils.defineLazyScriptGetter( 312 this, 313 "gSharedTabWarning", 314 "chrome://browser/content/browser-webrtc.js" 315 ); 316 XPCOMUtils.defineLazyScriptGetter( 317 this, 318 "gPageStyleMenu", 319 "chrome://browser/content/browser-pagestyle.js" 320 ); 321 XPCOMUtils.defineLazyScriptGetter( 322 this, 323 "gProfiles", 324 "chrome://browser/content/browser-profiles.js" 325 ); 326 XPCOMUtils.defineLazyScriptGetter( 327 this, 328 ["gTorConnectUrlbarButton"], 329 "chrome://global/content/torconnect/torConnectUrlbarButton.js" 330 ); 331 XPCOMUtils.defineLazyScriptGetter( 332 this, 333 ["gTorConnectTitlebarStatus"], 334 "chrome://global/content/torconnect/torConnectTitlebarStatus.js" 335 ); 336 XPCOMUtils.defineLazyScriptGetter( 337 this, 338 ["gTorCircuitPanel"], 339 "chrome://browser/content/torCircuitPanel.js" 340 ); 341 342 // lazy service getters 343 344 XPCOMUtils.defineLazyServiceGetters(this, { 345 ContentPrefService2: [ 346 "@mozilla.org/content-pref/service;1", 347 Ci.nsIContentPrefService2, 348 ], 349 classifierService: [ 350 "@mozilla.org/url-classifier/dbservice;1", 351 Ci.nsIURIClassifier, 352 ], 353 Favicons: ["@mozilla.org/browser/favicon-service;1", Ci.nsIFaviconService], 354 WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", Ci.nsIWindowsUIUtils], 355 BrowserHandler: ["@mozilla.org/browser/clh;1", Ci.nsIBrowserHandler], 356 }); 357 358 if (AppConstants.ENABLE_WEBDRIVER) { 359 XPCOMUtils.defineLazyServiceGetter( 360 this, 361 "Marionette", 362 "@mozilla.org/remote/marionette;1", 363 Ci.nsIMarionette 364 ); 365 366 XPCOMUtils.defineLazyServiceGetter( 367 this, 368 "RemoteAgent", 369 "@mozilla.org/remote/agent;1", 370 Ci.nsIRemoteAgent 371 ); 372 } else { 373 this.Marionette = { running: false }; 374 this.RemoteAgent = { running: false }; 375 } 376 377 ChromeUtils.defineLazyGetter(this, "RTL_UI", () => { 378 return Services.locale.isAppLocaleRTL; 379 }); 380 function gLocaleChangeObserver() { 381 delete window.RTL_UI; 382 window.RTL_UI = Services.locale.isAppLocaleRTL; 383 } 384 385 ChromeUtils.defineLazyGetter(this, "gBrandBundle", () => { 386 return Services.strings.createBundle( 387 "chrome://branding/locale/brand.properties" 388 ); 389 }); 390 391 ChromeUtils.defineLazyGetter(this, "gBrowserBundle", () => { 392 return Services.strings.createBundle( 393 "chrome://browser/locale/browser.properties" 394 ); 395 }); 396 397 ChromeUtils.defineLazyGetter(this, "gCustomizeMode", () => { 398 let { CustomizeMode } = ChromeUtils.importESModule( 399 "moz-src:///browser/components/customizableui/CustomizeMode.sys.mjs" 400 ); 401 return new CustomizeMode(window); 402 }); 403 404 ChromeUtils.defineLazyGetter(this, "gNavToolbox", () => { 405 return document.getElementById("navigator-toolbox"); 406 }); 407 408 ChromeUtils.defineLazyGetter(this, "gURLBar", () => { 409 let urlbar = document.getElementById("urlbar"); 410 411 let beforeFocusOrSelect = event => { 412 // In customize mode, the url bar is disabled. If a new tab is opened or the 413 // user switches to a different tab, this function gets called before we've 414 // finished leaving customize mode, and the url bar will still be disabled. 415 // We can't focus it when it's disabled, so we need to re-run ourselves when 416 // we've finished leaving customize mode. 417 if ( 418 CustomizationHandler.isCustomizing() || 419 CustomizationHandler.isExitingCustomizeMode 420 ) { 421 gNavToolbox.addEventListener( 422 "aftercustomization", 423 () => { 424 if (event.type == "beforeselect") { 425 gURLBar.select(); 426 } else { 427 gURLBar.focus(); 428 } 429 }, 430 { 431 once: true, 432 } 433 ); 434 event.preventDefault(); 435 return; 436 } 437 438 if (window.fullScreen) { 439 FullScreen.showNavToolbox(); 440 } 441 }; 442 urlbar.addEventListener("beforefocus", beforeFocusOrSelect); 443 urlbar.addEventListener("beforeselect", beforeFocusOrSelect); 444 445 return urlbar; 446 }); 447 448 // High priority notification bars shown at the top of the window. 449 ChromeUtils.defineLazyGetter(this, "gNotificationBox", () => { 450 let securityDelayMS = Services.prefs.getIntPref( 451 "security.notification_enable_delay" 452 ); 453 454 return new MozElements.NotificationBox(element => { 455 element.classList.add("global-notificationbox"); 456 element.setAttribute("notificationside", "top"); 457 element.setAttribute("prepend-notifications", true); 458 // We want this before the tab notifications. 459 document.getElementById("notifications-toolbar").prepend(element); 460 }, securityDelayMS); 461 }); 462 463 ChromeUtils.defineLazyGetter(this, "InlineSpellCheckerUI", () => { 464 let { InlineSpellChecker } = ChromeUtils.importESModule( 465 "resource://gre/modules/InlineSpellChecker.sys.mjs" 466 ); 467 return new InlineSpellChecker(); 468 }); 469 470 ChromeUtils.defineLazyGetter(this, "PopupNotifications", () => { 471 // eslint-disable-next-line no-shadow 472 let { PopupNotifications } = ChromeUtils.importESModule( 473 "resource://gre/modules/PopupNotifications.sys.mjs" 474 ); 475 try { 476 // Hide all PopupNotifications while the the address bar has focus, 477 // including the virtual focus in the results popup, and the URL is being 478 // edited or the page proxy state is invalid while async tab switching. 479 let shouldSuppress = () => { 480 // "Blank" pages, like about:welcome, have a pageproxystate of "invalid", but 481 // popups like CFRs should not automatically be suppressed when the address 482 // bar has focus on these pages as it disrupts user navigation using FN+F6. 483 // See `UrlbarInput.setURI()` where pageproxystate is set to "invalid" for 484 // all pages that the "isBlankPageURL" method returns true for. 485 const urlBarEdited = isBlankPageURL(gBrowser.currentURI.spec) 486 ? gURLBar.hasAttribute("usertyping") 487 : gURLBar.getAttribute("pageproxystate") != "valid"; 488 return ( 489 (urlBarEdited && gURLBar.focused) || 490 (gURLBar.getAttribute("pageproxystate") != "valid" && 491 gBrowser.selectedBrowser._awaitingSetURI) || 492 shouldSuppressPopupNotifications() 493 ); 494 }; 495 496 // Before a Popup is shown, check that its anchor is visible. 497 // If the anchor is not visible, use one of the fallbacks. 498 // If no fallbacks are visible, return null. 499 const getVisibleAnchorElement = anchorElement => { 500 // If the anchor element is present in the Urlbar, 501 // ensure that both the anchor and page URL are visible. 502 gURLBar.maybeHandleRevertFromPopup(anchorElement); 503 anchorElement?.dispatchEvent( 504 new CustomEvent("PopupNotificationsBeforeAnchor", { bubbles: true }) 505 ); 506 if (anchorElement?.checkVisibility()) { 507 return anchorElement; 508 } 509 let fallback = [ 510 document.getElementById("trust-icon-container"), 511 gURLBar.querySelector(".searchmode-switcher-icon"), 512 document.getElementById("identity-icon"), 513 document.getElementById("remote-control-icon"), 514 ]; 515 return fallback.find(element => element?.checkVisibility()) ?? null; 516 }; 517 518 return new PopupNotifications( 519 gBrowser, 520 document.getElementById("notification-popup"), 521 document.getElementById("notification-popup-box"), 522 { shouldSuppress, getVisibleAnchorElement } 523 ); 524 } catch (ex) { 525 console.error(ex); 526 return null; 527 } 528 }); 529 530 ChromeUtils.defineLazyGetter(this, "MacUserActivityUpdater", () => { 531 if (AppConstants.platform != "macosx") { 532 return null; 533 } 534 535 return Cc["@mozilla.org/widget/macuseractivityupdater;1"].getService( 536 Ci.nsIMacUserActivityUpdater 537 ); 538 }); 539 540 ChromeUtils.defineLazyGetter(this, "Win7Features", () => { 541 if (AppConstants.platform != "win") { 542 return null; 543 } 544 545 const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1"; 546 if ( 547 WINTASKBAR_CONTRACTID in Cc && 548 Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available 549 ) { 550 let { AeroPeek } = ChromeUtils.importESModule( 551 "resource:///modules/WindowsPreviewPerTab.sys.mjs" 552 ); 553 return { 554 onOpenWindow() { 555 AeroPeek.onOpenWindow(window); 556 this.handledOpening = true; 557 }, 558 onCloseWindow() { 559 if (this.handledOpening) { 560 AeroPeek.onCloseWindow(window); 561 } 562 }, 563 handledOpening: false, 564 }; 565 } 566 return null; 567 }); 568 569 ChromeUtils.defineLazyGetter(this, "gRestoreLastSessionObserver", () => { 570 let { RestoreLastSessionObserver } = ChromeUtils.importESModule( 571 "resource:///modules/sessionstore/SessionWindowUI.sys.mjs" 572 ); 573 return new RestoreLastSessionObserver(window); 574 }); 575 576 XPCOMUtils.defineLazyPreferenceGetter( 577 this, 578 "gToolbarKeyNavEnabled", 579 "browser.toolbars.keyboard_navigation", 580 false, 581 (aPref, aOldVal, aNewVal) => { 582 if (window.closed) { 583 return; 584 } 585 if (aNewVal) { 586 ToolbarKeyboardNavigator.init(); 587 } else { 588 ToolbarKeyboardNavigator.uninit(); 589 } 590 } 591 ); 592 593 XPCOMUtils.defineLazyPreferenceGetter( 594 this, 595 "gBookmarksToolbarVisibility", 596 "browser.toolbars.bookmarks.visibility", 597 "newtab" 598 ); 599 600 XPCOMUtils.defineLazyPreferenceGetter( 601 this, 602 "gFxaToolbarEnabled", 603 "identity.fxaccounts.toolbar.enabled", 604 false, 605 (aPref, aOldVal, aNewVal) => { 606 updateFxaToolbarMenu(aNewVal); 607 } 608 ); 609 610 XPCOMUtils.defineLazyPreferenceGetter( 611 this, 612 "gFxaToolbarAccessed", 613 "identity.fxaccounts.toolbar.accessed", 614 false, 615 () => { 616 updateFxaToolbarMenu(gFxaToolbarEnabled); 617 } 618 ); 619 620 XPCOMUtils.defineLazyPreferenceGetter( 621 this, 622 "gAddonAbuseReportEnabled", 623 "extensions.abuseReport.enabled", 624 false 625 ); 626 627 XPCOMUtils.defineLazyPreferenceGetter( 628 this, 629 "gMiddleClickNewTabUsesPasteboard", 630 "browser.tabs.searchclipboardfor.middleclick", 631 true 632 ); 633 634 XPCOMUtils.defineLazyPreferenceGetter( 635 this, 636 "gPrintEnabled", 637 "print.enabled", 638 false, 639 (aPref, aOldVal, aNewVal) => { 640 updatePrintCommands(aNewVal); 641 } 642 ); 643 644 XPCOMUtils.defineLazyPreferenceGetter( 645 this, 646 "gTranslationsEnabled", 647 "browser.translations.enable", 648 false 649 ); 650 651 XPCOMUtils.defineLazyPreferenceGetter( 652 this, 653 "gUseFeltPrivacyUI", 654 "browser.privatebrowsing.felt-privacy-v1", 655 false 656 ); 657 658 customElements.setElementCreationCallback("screenshots-buttons", () => { 659 Services.scriptloader.loadSubScript( 660 "chrome://browser/content/screenshots/screenshots-buttons.js", 661 window 662 ); 663 }); 664 665 customElements.setElementCreationCallback("menu-message", () => { 666 ChromeUtils.importESModule( 667 "chrome://browser/content/asrouter/components/menu-message.mjs", 668 { global: "current" } 669 ); 670 }); 671 672 customElements.setElementCreationCallback("webrtc-preview", () => { 673 ChromeUtils.importESModule( 674 "chrome://browser/content/webrtc/webrtc-preview.mjs", 675 { global: "current" } 676 ); 677 }); 678 679 var gBrowser; 680 var gContextMenu = null; // nsContextMenu instance 681 var gMultiProcessBrowser = window.docShell.QueryInterface( 682 Ci.nsILoadContext 683 ).useRemoteTabs; 684 var gFissionBrowser = window.docShell.QueryInterface( 685 Ci.nsILoadContext 686 ).useRemoteSubframes; 687 688 var gBrowserAllowScriptsToCloseInitialTabs = false; 689 690 if (AppConstants.platform != "macosx") { 691 var gEditUIVisible = true; 692 } 693 694 Object.defineProperty(this, "gReduceMotion", { 695 enumerable: true, 696 get() { 697 return typeof gReduceMotionOverride == "boolean" 698 ? gReduceMotionOverride 699 : gReduceMotionSetting; 700 }, 701 }); 702 // Reduce motion during startup. The setting will be reset later. 703 let gReduceMotionSetting = true; 704 // This is for tests to set. 705 var gReduceMotionOverride; 706 707 // Smart getter for the findbar. If you don't wish to force the creation of 708 // the findbar, check gFindBarInitialized first. 709 710 Object.defineProperty(this, "gFindBar", { 711 enumerable: true, 712 get() { 713 return gBrowser.getCachedFindBar(); 714 }, 715 }); 716 717 Object.defineProperty(this, "gFindBarInitialized", { 718 enumerable: true, 719 get() { 720 return gBrowser.isFindBarInitialized(); 721 }, 722 }); 723 724 Object.defineProperty(this, "gFindBarPromise", { 725 enumerable: true, 726 get() { 727 return gBrowser.getFindBar(); 728 }, 729 }); 730 731 function shouldSuppressPopupNotifications() { 732 // We have to hide notifications explicitly when the window is 733 // minimized because of the effects of the "noautohide" attribute on Linux. 734 // This can be removed once bug 545265 and bug 1320361 are fixed. 735 // Hide popup notifications when system tab prompts are shown so they 736 // don't cover up the prompt. 737 return ( 738 window.windowState == window.STATE_MINIMIZED || 739 gBrowser?.selectedBrowser.hasAttribute("tabDialogShowing") || 740 gDialogBox?.isOpen 741 ); 742 } 743 744 async function gLazyFindCommand(cmd, ...args) { 745 let fb = await gFindBarPromise; 746 // We could be closed by now, or the tab with XBL binding could have gone away: 747 if (fb && fb[cmd]) { 748 fb[cmd].apply(fb, args); 749 } 750 } 751 752 var gPageIcons = { 753 "about:tor": "chrome://branding/content/icon32.png", 754 "about:home": "chrome://branding/content/icon32.png", 755 "about:newtab": "chrome://branding/content/icon32.png", 756 "about:opentabs": "chrome://branding/content/icon32.png", 757 "about:welcome": "chrome://branding/content/icon32.png", 758 "about:privatebrowsing": "chrome://browser/skin/privatebrowsing/favicon.svg", 759 }; 760 761 var gInitialPages = [ 762 "about:tor", 763 "about:torconnect", 764 "about:blank", 765 "about:home", 766 "about:firefoxview", 767 "about:newtab", 768 "about:opentabs", 769 "about:privatebrowsing", 770 "about:sessionrestore", 771 "about:welcome", 772 "about:welcomeback", 773 "chrome://browser/content/blanktab.html", 774 ]; 775 776 function isInitialPage(url) { 777 if (!(url instanceof Ci.nsIURI)) { 778 try { 779 url = Services.io.newURI(url); 780 } catch (ex) { 781 return false; 782 } 783 } 784 785 let nonQuery = url.prePath + url.filePath; 786 return gInitialPages.includes(nonQuery) || nonQuery == BROWSER_NEW_TAB_URL; 787 } 788 789 function browserWindows() { 790 return Services.wm.getEnumerator("navigator:browser"); 791 } 792 793 function updateBookmarkToolbarVisibility() { 794 BookmarkingUI.updateEmptyToolbarMessage(); 795 setToolbarVisibility( 796 BookmarkingUI.toolbar, 797 gBookmarksToolbarVisibility, 798 false, 799 false 800 ); 801 } 802 803 // This is a stringbundle-like interface to gBrowserBundle, formerly a getter for 804 // the "bundle_browser" element. 805 var gNavigatorBundle = { 806 getString(key) { 807 return gBrowserBundle.GetStringFromName(key); 808 }, 809 getFormattedString(key, array) { 810 return gBrowserBundle.formatStringFromName(key, array); 811 }, 812 }; 813 814 function updateFxaToolbarMenu(enable, isInitialUpdate = false) { 815 // We only show the Firefox Account toolbar menu if the feature is enabled and 816 // if sync is enabled. 817 const syncEnabled = Services.prefs.getBoolPref( 818 "identity.fxaccounts.enabled", 819 false 820 ); 821 822 const mainWindowEl = document.documentElement; 823 const fxaPanelEl = PanelMultiView.getViewNode(document, "PanelUI-fxa"); 824 const taskbarTab = mainWindowEl.hasAttribute("taskbartab"); 825 826 // To minimize the toolbar button flickering or appearing/disappearing during startup, 827 // we use this pref to anticipate the likely FxA status. 828 const statusGuess = !!Services.prefs.getStringPref( 829 "identity.fxaccounts.account.device.name", 830 "" 831 ); 832 mainWindowEl.setAttribute( 833 "fxastatus", 834 statusGuess ? "signed_in" : "not_configured" 835 ); 836 837 fxaPanelEl.addEventListener("ViewShowing", gSync.updateSendToDeviceTitle); 838 839 if (enable && syncEnabled && !taskbarTab) { 840 mainWindowEl.setAttribute("fxatoolbarmenu", "visible"); 841 842 // We have to manually update the sync state UI when toggling the FxA toolbar 843 // because it could show an invalid icon if the user is logged in and no sync 844 // event was performed yet. 845 if (!isInitialUpdate) { 846 gSync.maybeUpdateUIState(); 847 } 848 } else { 849 mainWindowEl.removeAttribute("fxatoolbarmenu"); 850 } 851 } 852 853 function UpdateBackForwardCommands(aWebNavigation) { 854 var backCommand = document.getElementById("Browser:Back"); 855 var forwardCommand = document.getElementById("Browser:Forward"); 856 857 // Avoid setting attributes on commands if the value hasn't changed! 858 // Remember, guys, setting attributes on elements is expensive! They 859 // get inherited into anonymous content, broadcast to other widgets, etc.! 860 // Don't do it if the value hasn't changed! - dwh 861 862 var backDisabled = backCommand.hasAttribute("disabled"); 863 var forwardDisabled = forwardCommand.hasAttribute("disabled"); 864 if (backDisabled == aWebNavigation.canGoBack) { 865 if (backDisabled) { 866 backCommand.removeAttribute("disabled"); 867 } else { 868 backCommand.setAttribute("disabled", true); 869 } 870 } 871 872 if (forwardDisabled == aWebNavigation.canGoForward) { 873 if (forwardDisabled) { 874 forwardCommand.removeAttribute("disabled"); 875 } else { 876 forwardCommand.setAttribute("disabled", true); 877 } 878 } 879 } 880 881 function updatePrintCommands(enabled) { 882 var printCommand = document.getElementById("cmd_print"); 883 var printPreviewCommand = document.getElementById("cmd_printPreviewToggle"); 884 885 if (enabled) { 886 printCommand.removeAttribute("disabled"); 887 printPreviewCommand.removeAttribute("disabled"); 888 } else { 889 printCommand.setAttribute("disabled", "true"); 890 printPreviewCommand.setAttribute("disabled", "true"); 891 } 892 } 893 894 /** 895 * Click-and-Hold implementation for the Back and Forward buttons 896 * XXXmano: should this live in toolbarbutton.js? 897 */ 898 function SetClickAndHoldHandlers() { 899 // Bug 414797: Clone the back/forward buttons' context menu into both buttons. 900 let popup = document.getElementById("backForwardMenu").cloneNode(true); 901 popup.removeAttribute("id"); 902 // Prevent the back/forward buttons' context attributes from being inherited. 903 popup.setAttribute("context", ""); 904 905 function backForwardMenuCommand(event) { 906 BrowserCommands.gotoHistoryIndex(event); 907 // event.stopPropagation is here for the cloned version 908 // to prevent already-handled clicks on menu items from 909 // propagating to the back or forward button. 910 event.stopPropagation(); 911 } 912 913 let backButton = document.getElementById("back-button"); 914 backButton.setAttribute("type", "menu"); 915 popup.addEventListener("command", backForwardMenuCommand); 916 popup.addEventListener("popupshowing", FillHistoryMenu); 917 backButton.prepend(popup); 918 gClickAndHoldListenersOnElement.add(backButton); 919 920 let forwardButton = document.getElementById("forward-button"); 921 popup = popup.cloneNode(true); 922 forwardButton.setAttribute("type", "menu"); 923 popup.addEventListener("command", backForwardMenuCommand); 924 popup.addEventListener("popupshowing", FillHistoryMenu); 925 forwardButton.prepend(popup); 926 gClickAndHoldListenersOnElement.add(forwardButton); 927 } 928 929 const gClickAndHoldListenersOnElement = { 930 _timers: new Map(), 931 932 _mousedownHandler(aEvent) { 933 if ( 934 aEvent.button != 0 || 935 aEvent.currentTarget.open || 936 aEvent.currentTarget.disabled 937 ) { 938 return; 939 } 940 941 // Prevent the menupopup from opening immediately 942 aEvent.currentTarget.menupopup.hidden = true; 943 944 aEvent.currentTarget.addEventListener("mouseout", this); 945 aEvent.currentTarget.addEventListener("mouseup", this); 946 this._timers.set( 947 aEvent.currentTarget, 948 setTimeout(b => this._openMenu(b), 500, aEvent.currentTarget) 949 ); 950 }, 951 952 _clickHandler(aEvent) { 953 if ( 954 aEvent.button == 0 && 955 aEvent.target == aEvent.currentTarget && 956 !aEvent.currentTarget.open && 957 !aEvent.currentTarget.disabled && 958 // When menupopup is not hidden and we receive 959 // a click event, it means the mousedown occurred 960 // on aEvent.currentTarget and mouseup occurred on 961 // aEvent.currentTarget.menupopup, we don't 962 // need to handle the click event as menupopup 963 // handled mouseup event already. 964 aEvent.currentTarget.menupopup.hidden 965 ) { 966 let cmdEvent = document.createEvent("xulcommandevent"); 967 cmdEvent.initCommandEvent( 968 "command", 969 true, 970 true, 971 window, 972 0, 973 aEvent.ctrlKey, 974 aEvent.altKey, 975 aEvent.shiftKey, 976 aEvent.metaKey, 977 0, 978 null, 979 aEvent.inputSource 980 ); 981 aEvent.currentTarget.dispatchEvent(cmdEvent); 982 983 // This is here to cancel the XUL default event 984 // dom.click() triggers a command even if there is a click handler 985 // however this can now be prevented with preventDefault(). 986 aEvent.preventDefault(); 987 } 988 }, 989 990 _openMenu(aButton) { 991 this._cancelHold(aButton); 992 aButton.firstElementChild.hidden = false; 993 aButton.open = true; 994 }, 995 996 _mouseoutHandler(aEvent) { 997 let buttonRect = aEvent.currentTarget.getBoundingClientRect(); 998 if ( 999 aEvent.clientX >= buttonRect.left && 1000 aEvent.clientX <= buttonRect.right && 1001 aEvent.clientY >= buttonRect.bottom 1002 ) { 1003 this._openMenu(aEvent.currentTarget); 1004 } else { 1005 this._cancelHold(aEvent.currentTarget); 1006 } 1007 }, 1008 1009 _mouseupHandler(aEvent) { 1010 this._cancelHold(aEvent.currentTarget); 1011 }, 1012 1013 _cancelHold(aButton) { 1014 clearTimeout(this._timers.get(aButton)); 1015 aButton.removeEventListener("mouseout", this); 1016 aButton.removeEventListener("mouseup", this); 1017 }, 1018 1019 _keypressHandler(aEvent) { 1020 if (aEvent.key == " " || aEvent.key == "Enter") { 1021 aEvent.preventDefault(); 1022 // Normally, command events get fired for keyboard activation. However, 1023 // we've set type="menu", so that doesn't happen. Handle this the same 1024 // way we handle clicks. 1025 aEvent.target.click(); 1026 } 1027 }, 1028 1029 handleEvent(e) { 1030 switch (e.type) { 1031 case "mouseout": 1032 this._mouseoutHandler(e); 1033 break; 1034 case "mousedown": 1035 this._mousedownHandler(e); 1036 break; 1037 case "click": 1038 this._clickHandler(e); 1039 break; 1040 case "mouseup": 1041 this._mouseupHandler(e); 1042 break; 1043 case "keypress": 1044 // Note that we might not be the only ones dealing with keypresses. 1045 // See bug 1921772 for more context. 1046 if (!e.defaultPrevented) { 1047 this._keypressHandler(e); 1048 } 1049 break; 1050 } 1051 }, 1052 1053 remove(aButton) { 1054 aButton.removeEventListener("mousedown", this, true); 1055 aButton.removeEventListener("click", this, true); 1056 aButton.removeEventListener("keypress", this, true); 1057 }, 1058 1059 add(aElm) { 1060 this._timers.delete(aElm); 1061 1062 aElm.addEventListener("mousedown", this, true); 1063 aElm.addEventListener("click", this, true); 1064 aElm.addEventListener("keypress", this, true); 1065 }, 1066 }; 1067 1068 const gSessionHistoryObserver = { 1069 observe(subject, topic) { 1070 if (topic != "browser:purge-session-history") { 1071 return; 1072 } 1073 1074 var backCommand = document.getElementById("Browser:Back"); 1075 backCommand.setAttribute("disabled", "true"); 1076 var fwdCommand = document.getElementById("Browser:Forward"); 1077 fwdCommand.setAttribute("disabled", "true"); 1078 1079 // Clear undo history of the URL bar 1080 gURLBar.editor.clearUndoRedo(); 1081 }, 1082 }; 1083 1084 const gStoragePressureObserver = { 1085 _lastNotificationTime: -1, 1086 1087 async observe(subject, topic) { 1088 if (topic != "QuotaManager::StoragePressure") { 1089 return; 1090 } 1091 1092 const NOTIFICATION_VALUE = "storage-pressure-notification"; 1093 if (gNotificationBox.getNotificationWithValue(NOTIFICATION_VALUE)) { 1094 // Do not display the 2nd notification when there is already one 1095 return; 1096 } 1097 1098 // Don't display notification twice within the given interval. 1099 // This is because 1100 // - not to annoy user 1101 // - give user some time to clean space. 1102 // Even user sees notification and starts acting, it still takes some time. 1103 const MIN_NOTIFICATION_INTERVAL_MS = Services.prefs.getIntPref( 1104 "browser.storageManager.pressureNotification.minIntervalMS" 1105 ); 1106 let duration = Date.now() - this._lastNotificationTime; 1107 if (duration <= MIN_NOTIFICATION_INTERVAL_MS) { 1108 return; 1109 } 1110 this._lastNotificationTime = Date.now(); 1111 1112 MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl"); 1113 1114 const BYTES_IN_GIGABYTE = 1073741824; 1115 const USAGE_THRESHOLD_BYTES = 1116 BYTES_IN_GIGABYTE * 1117 Services.prefs.getIntPref( 1118 "browser.storageManager.pressureNotification.usageThresholdGB" 1119 ); 1120 let messageFragment = document.createDocumentFragment(); 1121 let message = document.createElement("span"); 1122 1123 let buttons = [{ supportPage: "storage-permissions" }]; 1124 let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data; 1125 if (usage < USAGE_THRESHOLD_BYTES) { 1126 // The firefox-used space < 5GB, then warn user to free some disk space. 1127 // This is because this usage is small and not the main cause for space issue. 1128 // In order to avoid the bad and wrong impression among users that 1129 // firefox eats disk space a lot, indicate users to clean up other disk space. 1130 document.l10n.setAttributes(message, "space-alert-under-5gb-message2"); 1131 } else { 1132 // The firefox-used space >= 5GB, then guide users to about:preferences 1133 // to clear some data stored on firefox by websites. 1134 document.l10n.setAttributes(message, "space-alert-over-5gb-message2"); 1135 buttons.push({ 1136 "l10n-id": "space-alert-over-5gb-settings-button", 1137 callback() { 1138 // The advanced subpanes are only supported in the old organization, which will 1139 // be removed by bug 1349689. 1140 openPreferences("privacy-sitedata"); 1141 }, 1142 }); 1143 } 1144 messageFragment.appendChild(message); 1145 1146 await gNotificationBox.appendNotification( 1147 NOTIFICATION_VALUE, 1148 { 1149 label: messageFragment, 1150 priority: gNotificationBox.PRIORITY_WARNING_HIGH, 1151 }, 1152 buttons 1153 ); 1154 1155 // This seems to be necessary to get the buttons to display correctly 1156 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1504216 1157 document.l10n.translateFragment(gNotificationBox.currentNotification); 1158 }, 1159 }; 1160 1161 var gKeywordURIFixup = { 1162 check(browser, { fixedURI, keywordProviderName, preferredURI }) { 1163 // We get called irrespective of whether we did a keyword search, or 1164 // whether the original input would be vaguely interpretable as a URL, 1165 // so figure that out first. 1166 if ( 1167 !keywordProviderName || 1168 !fixedURI || 1169 !fixedURI.host || 1170 UrlbarPrefs.get("browser.fixup.dns_first_for_single_words") || 1171 UrlbarPrefs.get("dnsResolveSingleWordsAfterSearch") == 0 1172 ) { 1173 return; 1174 } 1175 1176 let contentPrincipal = browser.contentPrincipal; 1177 1178 // At this point we're still only just about to load this URI. 1179 // When the async DNS lookup comes back, we may be in any of these states: 1180 // 1) still on the previous URI, waiting for the preferredURI (keyword 1181 // search) to respond; 1182 // 2) at the keyword search URI (preferredURI) 1183 // 3) at some other page because the user stopped navigation. 1184 // We keep track of the currentURI to detect case (1) in the DNS lookup 1185 // callback. 1186 let previousURI = browser.currentURI; 1187 1188 // now swap for a weak ref so we don't hang on to browser needlessly 1189 // even if the DNS query takes forever 1190 let weakBrowser = Cu.getWeakReference(browser); 1191 browser = null; 1192 1193 // Additionally, we need the host of the parsed url 1194 let hostName = fixedURI.displayHost; 1195 // and the ascii-only host for the pref: 1196 let asciiHost = fixedURI.asciiHost; 1197 1198 let onLookupCompleteListener = { 1199 async onLookupComplete(request, record, status) { 1200 let browserRef = weakBrowser.get(); 1201 if (!Components.isSuccessCode(status) || !browserRef) { 1202 return; 1203 } 1204 1205 let currentURI = browserRef.currentURI; 1206 // If we're in case (3) (see above), don't show an info bar. 1207 if ( 1208 !currentURI.equals(previousURI) && 1209 !currentURI.equals(preferredURI) 1210 ) { 1211 return; 1212 } 1213 1214 // show infobar offering to visit the host 1215 let notificationBox = gBrowser.getNotificationBox(browserRef); 1216 if (notificationBox.getNotificationWithValue("keyword-uri-fixup")) { 1217 return; 1218 } 1219 1220 let displayHostName = "http://" + hostName + "/"; 1221 let message = gNavigatorBundle.getFormattedString( 1222 "keywordURIFixup.message", 1223 [displayHostName] 1224 ); 1225 let yesMessage = gNavigatorBundle.getFormattedString( 1226 "keywordURIFixup.goTo", 1227 [displayHostName] 1228 ); 1229 1230 let buttons = [ 1231 { 1232 label: yesMessage, 1233 accessKey: gNavigatorBundle.getString( 1234 "keywordURIFixup.goTo.accesskey" 1235 ), 1236 callback() { 1237 // Do not set this preference while in private browsing. 1238 if (!PrivateBrowsingUtils.isWindowPrivate(window)) { 1239 let prefHost = asciiHost; 1240 // Normalize out a single trailing dot - NB: not using endsWith/lastIndexOf 1241 // because we need to be sure this last dot is the *only* dot, too. 1242 // More generally, this is used for the pref and should stay in sync with 1243 // the code in URIFixup::KeywordURIFixup . 1244 if (prefHost.indexOf(".") == prefHost.length - 1) { 1245 prefHost = prefHost.slice(0, -1); 1246 } 1247 let pref = "browser.fixup.domainwhitelist." + prefHost; 1248 Services.prefs.setBoolPref(pref, true); 1249 } 1250 openTrustedLinkIn(fixedURI.spec, "current"); 1251 }, 1252 }, 1253 ]; 1254 let notification = await notificationBox.appendNotification( 1255 "keyword-uri-fixup", 1256 { 1257 label: message, 1258 priority: notificationBox.PRIORITY_INFO_HIGH, 1259 }, 1260 buttons 1261 ); 1262 notification.persistence = 1; 1263 }, 1264 }; 1265 1266 try { 1267 Services.uriFixup.checkHost( 1268 fixedURI, 1269 onLookupCompleteListener, 1270 contentPrincipal.originAttributes 1271 ); 1272 } catch (ex) { 1273 // Ignore errors. 1274 } 1275 }, 1276 1277 observe(fixupInfo) { 1278 fixupInfo.QueryInterface(Ci.nsIURIFixupInfo); 1279 1280 let browser = fixupInfo.consumer?.top?.embedderElement; 1281 if (!browser || browser.ownerGlobal != window) { 1282 return; 1283 } 1284 1285 this.check(browser, fixupInfo); 1286 }, 1287 }; 1288 1289 function HandleAppCommandEvent(evt) { 1290 switch (evt.command) { 1291 case "Back": 1292 BrowserCommands.back(); 1293 break; 1294 case "Forward": 1295 BrowserCommands.forward(); 1296 break; 1297 case "Reload": 1298 BrowserCommands.reloadSkipCache(); 1299 break; 1300 case "Stop": 1301 if (XULBrowserWindow.stopCommand.hasAttribute("disabled")) { 1302 BrowserCommands.stop(); 1303 } 1304 break; 1305 case "Search": 1306 SearchUIUtils.webSearch(window); 1307 break; 1308 case "Bookmarks": 1309 SidebarController.toggle("viewBookmarksSidebar"); 1310 break; 1311 case "Home": 1312 BrowserCommands.home(); 1313 break; 1314 case "New": 1315 BrowserCommands.openTab(); 1316 break; 1317 case "Close": 1318 BrowserCommands.closeTabOrWindow(); 1319 break; 1320 case "Find": 1321 gLazyFindCommand("onFindCommand"); 1322 break; 1323 case "Help": 1324 openHelpLink("firefox-help"); 1325 break; 1326 case "Open": 1327 BrowserCommands.openFileWindow(); 1328 break; 1329 case "Print": 1330 PrintUtils.startPrintWindow(gBrowser.selectedBrowser.browsingContext); 1331 break; 1332 case "Save": 1333 saveBrowser(gBrowser.selectedBrowser); 1334 break; 1335 case "SendMail": 1336 MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser); 1337 break; 1338 default: 1339 return; 1340 } 1341 evt.stopPropagation(); 1342 evt.preventDefault(); 1343 } 1344 1345 function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal, aPolicyContainer) { 1346 // we're not a browser window, pass the URI string to a new browser window 1347 if (window.location.href != AppConstants.BROWSER_CHROME_URL) { 1348 window.openDialog( 1349 AppConstants.BROWSER_CHROME_URL, 1350 "_blank", 1351 "all,dialog=no", 1352 aURIString 1353 ); 1354 return; 1355 } 1356 1357 // This function throws for certain malformed URIs, so use exception handling 1358 // so that we don't disrupt startup 1359 try { 1360 gBrowser.loadTabs(aURIString.split("|"), { 1361 inBackground: false, 1362 replace: true, 1363 triggeringPrincipal: aTriggeringPrincipal, 1364 policyContainer: aPolicyContainer, 1365 }); 1366 } catch (e) {} 1367 } 1368 1369 function openLocation(event) { 1370 if (window.location.href == AppConstants.BROWSER_CHROME_URL) { 1371 gURLBar.select(); 1372 gURLBar.view.autoOpen({ event }); 1373 return; 1374 } 1375 1376 // If there's an open browser window, redirect the command there. 1377 let win = URILoadingHelper.getTargetWindow(window); 1378 if (win) { 1379 win.focus(); 1380 win.openLocation(); 1381 return; 1382 } 1383 1384 // There are no open browser windows; open a new one. 1385 window.openDialog( 1386 AppConstants.BROWSER_CHROME_URL, 1387 "_blank", 1388 "chrome,all,dialog=no", 1389 BROWSER_NEW_TAB_URL 1390 ); 1391 } 1392 1393 var gLastOpenDirectory = { 1394 _lastDir: null, 1395 get path() { 1396 if (!this._lastDir || !this._lastDir.exists()) { 1397 try { 1398 this._lastDir = Services.prefs.getComplexValue( 1399 "browser.open.lastDir", 1400 Ci.nsIFile 1401 ); 1402 if (!this._lastDir.exists()) { 1403 this._lastDir = null; 1404 } 1405 } catch (e) {} 1406 } 1407 return this._lastDir; 1408 }, 1409 set path(val) { 1410 try { 1411 if (!val || !val.isDirectory()) { 1412 return; 1413 } 1414 } catch (e) { 1415 return; 1416 } 1417 this._lastDir = val.clone(); 1418 1419 // Don't save the last open directory pref inside the Private Browsing mode 1420 if (!PrivateBrowsingUtils.isWindowPrivate(window)) { 1421 Services.prefs.setComplexValue( 1422 "browser.open.lastDir", 1423 Ci.nsIFile, 1424 this._lastDir 1425 ); 1426 } 1427 }, 1428 reset() { 1429 this._lastDir = null; 1430 }, 1431 }; 1432 1433 function readFromClipboard() { 1434 var url; 1435 1436 try { 1437 // Create transferable that will transfer the text. 1438 var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( 1439 Ci.nsITransferable 1440 ); 1441 trans.init(window.docShell.QueryInterface(Ci.nsILoadContext)); 1442 1443 trans.addDataFlavor("text/plain"); 1444 1445 // If available, use selection clipboard, otherwise global one 1446 let clipboard = Services.clipboard; 1447 if (clipboard.isClipboardTypeSupported(clipboard.kSelectionClipboard)) { 1448 clipboard.getData(trans, clipboard.kSelectionClipboard); 1449 } else { 1450 clipboard.getData(trans, clipboard.kGlobalClipboard); 1451 } 1452 1453 var data = {}; 1454 trans.getTransferData("text/plain", data); 1455 1456 if (data) { 1457 data = data.value.QueryInterface(Ci.nsISupportsString); 1458 url = data.data; 1459 } 1460 } catch (ex) {} 1461 1462 return url; 1463 } 1464 1465 function UpdateUrlbarSearchSplitterState() { 1466 var splitter = document.getElementById("urlbar-search-splitter"); 1467 var urlbar = document.getElementById("urlbar-container"); 1468 var searchbar = document.getElementById("search-container"); 1469 1470 if (document.documentElement.hasAttribute("customizing")) { 1471 if (splitter) { 1472 splitter.remove(); 1473 } 1474 return; 1475 } 1476 1477 // If the splitter is already in the right place, we don't need to do anything: 1478 if ( 1479 splitter && 1480 ((splitter.nextElementSibling == searchbar && 1481 splitter.previousElementSibling == urlbar) || 1482 (splitter.nextElementSibling == urlbar && 1483 splitter.previousElementSibling == searchbar)) 1484 ) { 1485 return; 1486 } 1487 1488 let ibefore = null; 1489 let resizebefore = "none"; 1490 let resizeafter = "none"; 1491 if (urlbar && searchbar) { 1492 if (urlbar.nextElementSibling == searchbar) { 1493 resizeafter = "sibling"; 1494 ibefore = searchbar; 1495 } else if (searchbar.nextElementSibling == urlbar) { 1496 resizebefore = "sibling"; 1497 ibefore = urlbar; 1498 } 1499 } 1500 1501 if (ibefore) { 1502 if (!splitter) { 1503 splitter = document.createXULElement("splitter"); 1504 splitter.id = "urlbar-search-splitter"; 1505 splitter.setAttribute("resizebefore", resizebefore); 1506 splitter.setAttribute("resizeafter", resizeafter); 1507 splitter.setAttribute("skipintoolbarset", "true"); 1508 splitter.setAttribute("overflows", "false"); 1509 splitter.className = "chromeclass-toolbar-additional"; 1510 } 1511 urlbar.parentNode.insertBefore(splitter, ibefore); 1512 } else if (splitter) { 1513 splitter.remove(); 1514 } 1515 } 1516 1517 function UpdatePopupNotificationsVisibility() { 1518 // Only need to update PopupNotifications if it has already been initialized 1519 // for this window (i.e. its getter no longer exists). 1520 if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) { 1521 // Notify PopupNotifications that the visible anchors may have changed. This 1522 // also checks the suppression state according to the "shouldSuppress" 1523 // function defined earlier in this file. 1524 PopupNotifications.anchorVisibilityChange(); 1525 } 1526 1527 // This is similar to the above, but for notifications attached to the 1528 // hamburger menu icon (such as update notifications and add-on install 1529 // notifications.) 1530 PanelUI?.updateNotifications(); 1531 } 1532 1533 function PageProxyClickHandler(aEvent) { 1534 if (aEvent.button == 1 && Services.prefs.getBoolPref("middlemouse.paste")) { 1535 middleMousePaste(aEvent); 1536 } 1537 } 1538 1539 function CreateContainerTabMenu(event) { 1540 // Do not open context menus within menus. 1541 // Note that triggerNode is null if we're opened by long press. 1542 if (event.target.triggerNode?.closest("menupopup")) { 1543 event.preventDefault(); 1544 return; 1545 } 1546 createUserContextMenu(event, { 1547 useAccessKeys: false, 1548 showDefaultTab: true, 1549 }); 1550 } 1551 1552 function FillHistoryMenu(event) { 1553 let parent = event.target; 1554 1555 // Lazily add the hover listeners on first showing and never remove them 1556 if (!parent.hasStatusListener) { 1557 // Show history item's uri in the status bar when hovering, and clear on exit 1558 parent.addEventListener("DOMMenuItemActive", function (aEvent) { 1559 // Only the current page should have the checked attribute, so skip it 1560 if (!aEvent.target.hasAttribute("checked")) { 1561 XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri")); 1562 } 1563 }); 1564 parent.addEventListener("DOMMenuItemInactive", function () { 1565 XULBrowserWindow.setOverLink(""); 1566 }); 1567 1568 parent.hasStatusListener = true; 1569 } 1570 1571 // Remove old entries if any 1572 let children = parent.children; 1573 for (var i = children.length - 1; i >= 0; --i) { 1574 if (children[i].hasAttribute("index")) { 1575 parent.removeChild(children[i]); 1576 } 1577 } 1578 1579 const MAX_HISTORY_MENU_ITEMS = 15; 1580 1581 const tooltipBack = gNavigatorBundle.getString("tabHistory.goBack"); 1582 const tooltipCurrent = gNavigatorBundle.getString("tabHistory.reloadCurrent"); 1583 const tooltipForward = gNavigatorBundle.getString("tabHistory.goForward"); 1584 1585 function updateSessionHistory(sessionHistory, initial, ssInParent) { 1586 let count = ssInParent 1587 ? sessionHistory.count 1588 : sessionHistory.entries.length; 1589 1590 if (!initial) { 1591 if (count <= 1) { 1592 // if there is only one entry now, close the popup. 1593 parent.hidePopup(); 1594 return; 1595 } else if (parent.id != "backForwardMenu" && !parent.parentNode.open) { 1596 // if the popup wasn't open before, but now needs to be, reopen the menu. 1597 // It should trigger FillHistoryMenu again. This might happen with the 1598 // delay from click-and-hold menus but skip this for the context menu 1599 // (backForwardMenu) rather than figuring out how the menu should be 1600 // positioned and opened as it is an extreme edgecase. 1601 parent.parentNode.open = true; 1602 return; 1603 } 1604 } 1605 1606 let index = sessionHistory.index; 1607 let half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2); 1608 let start = Math.max(index - half_length, 0); 1609 let end = Math.min( 1610 start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1, 1611 count 1612 ); 1613 if (end == count) { 1614 start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0); 1615 } 1616 1617 let existingIndex = 0; 1618 1619 for (let j = end - 1; j >= start; j--) { 1620 let entry = ssInParent 1621 ? sessionHistory.getEntryAtIndex(j) 1622 : sessionHistory.entries[j]; 1623 // Explicitly check for "false" to stay backwards-compatible with session histories 1624 // from before the hasUserInteraction was implemented. 1625 if ( 1626 BrowserUtils.navigationRequireUserInteraction && 1627 entry.hasUserInteraction === false && 1628 // Always list the current and last navigation points. 1629 j != end - 1 && 1630 j != index 1631 ) { 1632 continue; 1633 } 1634 let uri = ssInParent ? entry.URI.spec : entry.url; 1635 1636 let item = 1637 existingIndex < children.length 1638 ? children[existingIndex] 1639 : document.createXULElement("menuitem"); 1640 1641 item.setAttribute("uri", uri); 1642 item.setAttribute("label", entry.title || uri); 1643 item.setAttribute("index", j); 1644 1645 // Cache this so that BrowserCommands.gotoHistoryIndex doesn't need the 1646 // original index 1647 item.setAttribute("historyindex", j - index); 1648 1649 if (j != index) { 1650 // Use --menuitem-icon rather than the image attribute in order to 1651 // allow CSS to override this. 1652 item.style.setProperty( 1653 "--menuitem-icon", 1654 `url(page-icon:${CSS.escape(uri)})` 1655 ); 1656 } 1657 1658 if (j < index) { 1659 item.className = 1660 "unified-nav-back menuitem-iconic menuitem-with-favicon"; 1661 item.setAttribute("tooltiptext", tooltipBack); 1662 } else if (j == index) { 1663 item.setAttribute("type", "radio"); 1664 item.setAttribute("checked", "true"); 1665 item.className = "unified-nav-current"; 1666 item.setAttribute("tooltiptext", tooltipCurrent); 1667 } else { 1668 item.className = 1669 "unified-nav-forward menuitem-iconic menuitem-with-favicon"; 1670 item.setAttribute("tooltiptext", tooltipForward); 1671 } 1672 1673 if (!item.parentNode) { 1674 parent.appendChild(item); 1675 } 1676 1677 existingIndex++; 1678 } 1679 1680 if (!initial) { 1681 let existingLength = children.length; 1682 while (existingIndex < existingLength) { 1683 parent.removeChild(parent.lastElementChild); 1684 existingIndex++; 1685 } 1686 } 1687 } 1688 1689 // If session history in parent is available, use it. Otherwise, get the session history 1690 // from session store. 1691 let sessionHistory = gBrowser.selectedBrowser.browsingContext.sessionHistory; 1692 if (sessionHistory?.count) { 1693 // Don't show the context menu if there is only one item. 1694 if (sessionHistory.count <= 1) { 1695 event.preventDefault(); 1696 return; 1697 } 1698 1699 updateSessionHistory(sessionHistory, true, true); 1700 } else { 1701 sessionHistory = SessionStore.getSessionHistory( 1702 gBrowser.selectedTab, 1703 updateSessionHistory 1704 ); 1705 updateSessionHistory(sessionHistory, true, false); 1706 } 1707 } 1708 1709 function toOpenWindowByType(inType, uri, features) { 1710 var topWindow = Services.wm.getMostRecentWindow(inType); 1711 1712 if (topWindow) { 1713 topWindow.focus(); 1714 } else if (features) { 1715 window.open(uri, "_blank", features); 1716 } else { 1717 window.open( 1718 uri, 1719 "_blank", 1720 "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar" 1721 ); 1722 } 1723 } 1724 /** 1725 * Open a new browser window. See `BrowserWindowTracker.openWindow` for 1726 * options. 1727 * 1728 * @return a reference to the new window. 1729 */ 1730 function OpenBrowserWindow(options) { 1731 let timerId = Glean.browserTimings.newWindow.start(); 1732 options ??= {}; 1733 options.openerWindow ??= window; 1734 1735 let win = BrowserWindowTracker.openWindow(options); 1736 1737 win.addEventListener( 1738 "MozAfterPaint", 1739 () => { 1740 Glean.browserTimings.newWindow.stopAndAccumulate(timerId); 1741 }, 1742 { once: true } 1743 ); 1744 1745 return win; 1746 } 1747 1748 /** 1749 * Update the global flag that tracks whether or not any edit UI (the Edit menu, 1750 * edit-related items in the context menu, and edit-related toolbar buttons 1751 * is visible, then update the edit commands' enabled state accordingly. We use 1752 * this flag to skip updating the edit commands on focus or selection changes 1753 * when no UI is visible to improve performance (including pageload performance, 1754 * since focus changes when you load a new page). 1755 * 1756 * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands' 1757 * enabled state so the UI will reflect it appropriately. 1758 * 1759 * If the UI isn't visible, we enable all edit commands so keyboard shortcuts 1760 * still work and just lazily disable them as needed when the user presses a 1761 * shortcut. 1762 * 1763 * This doesn't work on Mac, since Mac menus flash when users press their 1764 * keyboard shortcuts, so edit UI is essentially always visible on the Mac, 1765 * and we need to always update the edit commands. Thus on Mac this function 1766 * is a no op. 1767 */ 1768 function updateEditUIVisibility() { 1769 if (AppConstants.platform == "macosx") { 1770 return; 1771 } 1772 1773 let editMenuPopupState = document.getElementById("menu_EditPopup").state; 1774 let contextMenuPopupState = document.getElementById( 1775 "contentAreaContextMenu" 1776 ).state; 1777 let placesContextMenuPopupState = 1778 document.getElementById("placesContext").state; 1779 1780 let oldVisible = gEditUIVisible; 1781 1782 // The UI is visible if the Edit menu is opening or open, if the context menu 1783 // is open, or if the toolbar has been customized to include the Cut, Copy, 1784 // or Paste toolbar buttons. 1785 gEditUIVisible = 1786 editMenuPopupState == "showing" || 1787 editMenuPopupState == "open" || 1788 contextMenuPopupState == "showing" || 1789 contextMenuPopupState == "open" || 1790 placesContextMenuPopupState == "showing" || 1791 placesContextMenuPopupState == "open"; 1792 const kOpenPopupStates = ["showing", "open"]; 1793 if (!gEditUIVisible) { 1794 // Now check the edit-controls toolbar buttons. 1795 let placement = CustomizableUI.getPlacementOfWidget("edit-controls"); 1796 let areaType = placement ? CustomizableUI.getAreaType(placement.area) : ""; 1797 if (areaType == CustomizableUI.TYPE_PANEL) { 1798 let customizablePanel = PanelUI.overflowPanel; 1799 gEditUIVisible = kOpenPopupStates.includes(customizablePanel.state); 1800 } else if ( 1801 areaType == CustomizableUI.TYPE_TOOLBAR && 1802 window.toolbar.visible 1803 ) { 1804 // The edit controls are on a toolbar, so they are visible, 1805 // unless they're in a panel that isn't visible... 1806 if (placement.area == "nav-bar") { 1807 let editControls = document.getElementById("edit-controls"); 1808 gEditUIVisible = 1809 !editControls.hasAttribute("overflowedItem") || 1810 kOpenPopupStates.includes( 1811 document.getElementById("widget-overflow").state 1812 ); 1813 } else { 1814 gEditUIVisible = true; 1815 } 1816 } 1817 } 1818 1819 // Now check the main menu panel 1820 if (!gEditUIVisible) { 1821 gEditUIVisible = kOpenPopupStates.includes(PanelUI.panel.state); 1822 } 1823 1824 // No need to update commands if the edit UI visibility has not changed. 1825 if (gEditUIVisible == oldVisible) { 1826 return; 1827 } 1828 1829 // If UI is visible, update the edit commands' enabled state to reflect 1830 // whether or not they are actually enabled for the current focus/selection. 1831 if (gEditUIVisible) { 1832 goUpdateGlobalEditMenuItems(); 1833 } else { 1834 // Otherwise, enable all commands, so that keyboard shortcuts still work, 1835 // then lazily determine their actual enabled state when the user presses 1836 // a keyboard shortcut. 1837 goSetCommandEnabled("cmd_undo", true); 1838 goSetCommandEnabled("cmd_redo", true); 1839 goSetCommandEnabled("cmd_cut", true); 1840 goSetCommandEnabled("cmd_copy", true); 1841 goSetCommandEnabled("cmd_paste", true); 1842 goSetCommandEnabled("cmd_selectAll", true); 1843 goSetCommandEnabled("cmd_delete", true); 1844 goSetCommandEnabled("cmd_switchTextDirection", true); 1845 } 1846 } 1847 1848 let gFileMenu = { 1849 /** 1850 * Updates User Context Menu Item UI visibility depending on 1851 * privacy.userContext.enabled pref state. 1852 */ 1853 updateUserContextUIVisibility() { 1854 let menu = document.getElementById("menu_newUserContext"); 1855 menu.hidden = !Services.prefs.getBoolPref( 1856 "privacy.userContext.enabled", 1857 false 1858 ); 1859 // Visibility of File menu item shouldn't change frequently. 1860 if (PrivateBrowsingUtils.isWindowPrivate(window)) { 1861 menu.setAttribute("disabled", "true"); 1862 } 1863 }, 1864 1865 /** 1866 * Updates the enabled state of the "Import From Another Browser" command 1867 * depending on the DisableProfileImport policy. 1868 */ 1869 updateImportCommandEnabledState() { 1870 if (!Services.policies.isAllowed("profileImport")) { 1871 document 1872 .getElementById("cmd_file_importFromAnotherBrowser") 1873 .setAttribute("disabled", "true"); 1874 } 1875 }, 1876 1877 /** 1878 * Updates the "Close tab" command to reflect the number of selected tabs, 1879 * when applicable. 1880 */ 1881 updateTabCloseCountState() { 1882 document.l10n.setAttributes( 1883 document.getElementById("menu_close"), 1884 "menu-file-close-tab", 1885 { tabCount: gBrowser.selectedTabs.length } 1886 ); 1887 }, 1888 1889 onPopupShowing(event) { 1890 // We don't care about submenus: 1891 if (event.target.id != "menu_FilePopup") { 1892 return; 1893 } 1894 this.updateUserContextUIVisibility(); 1895 this.updateImportCommandEnabledState(); 1896 this.updateTabCloseCountState(); 1897 if (AppConstants.platform == "macosx") { 1898 SharingUtils.updateShareURLMenuItem( 1899 gBrowser.selectedBrowser, 1900 document.getElementById("menu_savePage") 1901 ); 1902 } 1903 PrintUtils.updatePrintSetupMenuHiddenState(); 1904 1905 const aiWindowMenu = event.target.querySelector("#menu_newAIWindow"); 1906 const classicWindowMenu = event.target.querySelector( 1907 "#menu_newClassicWindow" 1908 ); 1909 1910 aiWindowMenu.hidden = 1911 !AIWindow.isAIWindowEnabled() || AIWindow.isAIWindowActive(window); 1912 classicWindowMenu.hidden = 1913 !AIWindow.isAIWindowEnabled() || !AIWindow.isAIWindowActive(window); 1914 }, 1915 }; 1916 1917 /** 1918 * Opens a new tab with the userContextId specified as an attribute of 1919 * sourceEvent. This attribute is propagated to the top level originAttributes 1920 * living on the tab's docShell. 1921 * 1922 * @param event 1923 * A click event on a userContext File Menu option 1924 */ 1925 function openNewUserContextTab(event) { 1926 openTrustedLinkIn(BROWSER_NEW_TAB_URL, "tab", { 1927 userContextId: parseInt(event.target.getAttribute("data-usercontextid")), 1928 }); 1929 } 1930 1931 var XULBrowserWindow = { 1932 // Stored Status, Link and Loading values 1933 status: "", 1934 defaultStatus: "", 1935 overLink: "", 1936 startTime: 0, 1937 isBusy: false, 1938 busyUI: false, 1939 1940 QueryInterface: ChromeUtils.generateQI([ 1941 "nsIWebProgressListener", 1942 "nsIWebProgressListener2", 1943 "nsISupportsWeakReference", 1944 "nsIXULBrowserWindow", 1945 ]), 1946 1947 get stopCommand() { 1948 delete this.stopCommand; 1949 return (this.stopCommand = document.getElementById("Browser:Stop")); 1950 }, 1951 get reloadCommand() { 1952 delete this.reloadCommand; 1953 return (this.reloadCommand = document.getElementById("Browser:Reload")); 1954 }, 1955 get _elementsForTextBasedTypes() { 1956 delete this._elementsForTextBasedTypes; 1957 return (this._elementsForTextBasedTypes = [ 1958 document.getElementById("pageStyleMenu"), 1959 document.getElementById("context-viewpartialsource-selection"), 1960 document.getElementById("context-print-selection"), 1961 ]); 1962 }, 1963 get _elementsForFind() { 1964 delete this._elementsForFind; 1965 return (this._elementsForFind = [ 1966 document.getElementById("cmd_find"), 1967 document.getElementById("cmd_findAgain"), 1968 document.getElementById("cmd_findPrevious"), 1969 ]); 1970 }, 1971 get _elementsForViewSource() { 1972 delete this._elementsForViewSource; 1973 return (this._elementsForViewSource = [ 1974 document.getElementById("context-viewsource"), 1975 document.getElementById("View:PageSource"), 1976 ]); 1977 }, 1978 get _menuItemForRepairTextEncoding() { 1979 delete this._menuItemForRepairTextEncoding; 1980 return (this._menuItemForRepairTextEncoding = document.getElementById( 1981 "repair-text-encoding" 1982 )); 1983 }, 1984 get _menuItemForTranslations() { 1985 delete this._menuItemForTranslations; 1986 return (this._menuItemForTranslations = 1987 document.getElementById("cmd_translate")); 1988 }, 1989 1990 setDefaultStatus(status) { 1991 this.defaultStatus = status; 1992 StatusPanel.update(); 1993 }, 1994 1995 /** 1996 * Tells the UI what link we are currently over. 1997 * 1998 * @param {string} url 1999 * The URL of the link. 2000 * @param {object} [options] 2001 * This is an extension of nsIXULBrowserWindow for JS callers, will be 2002 * passed on to LinkTargetDisplay. 2003 */ 2004 setOverLink(url, options = undefined) { 2005 window.dispatchEvent( 2006 new CustomEvent("OverLink", { 2007 detail: { url }, 2008 }) 2009 ); 2010 2011 if (url) { 2012 url = Services.textToSubURI.unEscapeURIForUI(url); 2013 2014 /** 2015 * Encode bidirectional formatting characters. 2016 * 2017 * @see https://url.spec.whatwg.org/#url-rendering-i18n 2018 * @see https://www.unicode.org/reports/tr9/#Directional_Formatting_Characters 2019 */ 2020 url = url.replace( 2021 /[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, 2022 encodeURIComponent 2023 ); 2024 2025 if (UrlbarPrefs.get("trimURLs")) { 2026 url = BrowserUIUtils.trimURL(url); 2027 } 2028 } 2029 2030 this.overLink = url; 2031 LinkTargetDisplay.update(options); 2032 }, 2033 2034 onEnterDOMFullscreen() { 2035 // Clear the status panel. 2036 this.status = ""; 2037 this.setDefaultStatus(""); 2038 this.setOverLink("", { hideStatusPanelImmediately: true }); 2039 }, 2040 2041 showTooltip(xDevPix, yDevPix, tooltip, direction, _browser) { 2042 if ( 2043 Cc["@mozilla.org/widget/dragservice;1"] 2044 .getService(Ci.nsIDragService) 2045 .getCurrentSession() 2046 ) { 2047 return; 2048 } 2049 2050 if (!document.hasFocus()) { 2051 return; 2052 } 2053 2054 let elt = document.getElementById("remoteBrowserTooltip"); 2055 elt.label = tooltip; 2056 elt.style.direction = direction; 2057 elt.openPopupAtScreen( 2058 xDevPix / window.devicePixelRatio, 2059 yDevPix / window.devicePixelRatio, 2060 false, 2061 null 2062 ); 2063 }, 2064 2065 hideTooltip() { 2066 let elt = document.getElementById("remoteBrowserTooltip"); 2067 elt.hidePopup(); 2068 }, 2069 2070 getTabCount() { 2071 return gBrowser.tabs.length; 2072 }, 2073 2074 onProgressChange() { 2075 // Do nothing. 2076 }, 2077 2078 onProgressChange64( 2079 aWebProgress, 2080 aRequest, 2081 aCurSelfProgress, 2082 aMaxSelfProgress, 2083 aCurTotalProgress, 2084 aMaxTotalProgress 2085 ) { 2086 return this.onProgressChange( 2087 aWebProgress, 2088 aRequest, 2089 aCurSelfProgress, 2090 aMaxSelfProgress, 2091 aCurTotalProgress, 2092 aMaxTotalProgress 2093 ); 2094 }, 2095 2096 // This function fires only for the currently selected tab. 2097 onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { 2098 const nsIWebProgressListener = Ci.nsIWebProgressListener; 2099 2100 let browser = gBrowser.selectedBrowser; 2101 gProtectionsHandler.onStateChange(aWebProgress, aStateFlags); 2102 2103 if ( 2104 aStateFlags & nsIWebProgressListener.STATE_START && 2105 aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK 2106 ) { 2107 if (aRequest && aWebProgress.isTopLevel) { 2108 OpenSearchManager.clearEngines(browser); 2109 } 2110 2111 this.isBusy = true; 2112 2113 if ( 2114 !(aStateFlags & nsIWebProgressListener.STATE_RESTORING) && 2115 aWebProgress.isTopLevel 2116 ) { 2117 this.busyUI = true; 2118 2119 if (this.spinCursorWhileBusy) { 2120 window.setCursor("progress"); 2121 } 2122 2123 // XXX: This needs to be based on window activity... 2124 this.stopCommand.removeAttribute("disabled"); 2125 CombinedStopReload.switchToStop(aRequest, aWebProgress); 2126 } 2127 } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) { 2128 // This (thanks to the filter) is a network stop or the last 2129 // request stop outside of loading the document, stop throbbers 2130 // and progress bars and such 2131 if (aRequest) { 2132 let msg = ""; 2133 let location; 2134 let canViewSource = true; 2135 // Get the URI either from a channel or a pseudo-object 2136 if (aRequest instanceof Ci.nsIChannel || "URI" in aRequest) { 2137 location = aRequest.URI; 2138 2139 // For keyword URIs clear the user typed value since they will be changed into real URIs 2140 if (location.scheme == "keyword" && aWebProgress.isTopLevel) { 2141 gBrowser.userTypedValue = null; 2142 } 2143 2144 canViewSource = location.scheme != "view-source"; 2145 2146 if (location.spec != "about:blank") { 2147 switch (aStatus) { 2148 case Cr.NS_ERROR_NET_TIMEOUT: 2149 msg = gNavigatorBundle.getString("nv_timeout"); 2150 break; 2151 } 2152 } 2153 } 2154 2155 this.status = ""; 2156 this.setDefaultStatus(msg); 2157 2158 // Disable View Source menu entries for images, enable otherwise 2159 let isText = 2160 browser.documentContentType && 2161 BrowserUtils.mimeTypeIsTextBased(browser.documentContentType); 2162 for (let element of this._elementsForViewSource) { 2163 if (canViewSource && isText) { 2164 element.removeAttribute("disabled"); 2165 } else { 2166 element.setAttribute("disabled", "true"); 2167 } 2168 } 2169 2170 this._updateElementsForContentType(); 2171 2172 // Update Override Text Encoding state. 2173 // Can't cache the button, because the presence of the element in the DOM 2174 // may change over time. 2175 let button = document.getElementById("characterencoding-button"); 2176 if (browser.mayEnableCharacterEncodingMenu) { 2177 this._menuItemForRepairTextEncoding.removeAttribute("disabled"); 2178 button?.removeAttribute("disabled"); 2179 } else { 2180 this._menuItemForRepairTextEncoding.setAttribute("disabled", "true"); 2181 button?.setAttribute("disabled", "true"); 2182 } 2183 } 2184 2185 this.isBusy = false; 2186 2187 if (this.busyUI && aWebProgress.isTopLevel) { 2188 this.busyUI = false; 2189 2190 if (this.spinCursorWhileBusy) { 2191 window.setCursor("auto"); 2192 } 2193 2194 this.stopCommand.setAttribute("disabled", "true"); 2195 CombinedStopReload.switchToReload(aRequest, aWebProgress); 2196 } 2197 } 2198 }, 2199 2200 /** 2201 * An nsIWebProgressListener method called by tabbrowser. The `aIsSimulated` 2202 * parameter is extra and not declared in nsIWebProgressListener, however; see 2203 * below. 2204 * 2205 * @param {nsIWebProgress} aWebProgress 2206 * The nsIWebProgress instance that fired the notification. 2207 * @param {nsIRequest} aRequest 2208 * The associated nsIRequest. This may be null in some cases. 2209 * @param {nsIURI} aLocationURI 2210 * The URI of the location that is being loaded. 2211 * @param {integer} aFlags 2212 * Flags that indicate the reason the location changed. See the 2213 * nsIWebProgressListener.LOCATION_CHANGE_* values. 2214 * @param {boolean} aIsSimulated 2215 * True when this is called by tabbrowser due to switching tabs and 2216 * undefined otherwise. This parameter is not declared in 2217 * nsIWebProgressListener.onLocationChange; see bug 1478348. 2218 */ 2219 onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags, aIsSimulated) { 2220 var location = aLocationURI ? aLocationURI.spec : ""; 2221 2222 UpdateBackForwardCommands(gBrowser.webNavigation); 2223 2224 Services.obs.notifyObservers( 2225 aWebProgress, 2226 "touchbar-location-change", 2227 location 2228 ); 2229 2230 // For most changes we only need to update the browser UI if the primary 2231 // content area was navigated or the selected tab was changed. We don't need 2232 // to do anything else if there was a subframe navigation. 2233 2234 if (!aWebProgress.isTopLevel) { 2235 return; 2236 } 2237 2238 this.setOverLink("", { hideStatusPanelImmediately: true }); 2239 2240 let isSameDocument = 2241 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT; 2242 if ( 2243 (location == "about:blank" && 2244 BrowserUIUtils.checkEmptyPageOrigin(gBrowser.selectedBrowser)) || 2245 location == "" 2246 ) { 2247 // Second condition is for new tabs, otherwise 2248 // reload function is enabled until tab is refreshed. 2249 this.reloadCommand.setAttribute("disabled", "true"); 2250 } else { 2251 this.reloadCommand.removeAttribute("disabled"); 2252 } 2253 2254 let isSessionRestore = !!( 2255 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE 2256 ); 2257 2258 // Don't update URL for document PiP window as it shows its opener url 2259 if (!window.browsingContext.isDocumentPiP) { 2260 // We want to update the popup visibility if we received this notification 2261 // via simulated locationchange events such as switching between tabs, however 2262 // if this is a document navigation then PopupNotifications will be updated 2263 // via TabsProgressListener.onLocationChange and we do not want it called twice 2264 gURLBar.setURI({ 2265 uri: aLocationURI, 2266 dueToTabSwitch: aIsSimulated, 2267 dueToSessionRestore: isSessionRestore, 2268 isSameDocument, 2269 }); 2270 } 2271 2272 BookmarkingUI.onLocationChange(); 2273 // If we've actually changed document, update the toolbar visibility. 2274 if (!isSameDocument) { 2275 updateBookmarkToolbarVisibility(); 2276 } 2277 2278 let closeOpenPanels = selector => { 2279 for (let panel of document.querySelectorAll(selector)) { 2280 if (panel.state != "closed") { 2281 panel.hidePopup(); 2282 } 2283 } 2284 }; 2285 2286 // If the location is changed due to switching tabs, 2287 // ensure we close any open tabspecific popups. 2288 if (aIsSimulated) { 2289 closeOpenPanels(":is(panel, menupopup)[tabspecific='true']"); 2290 } 2291 2292 // Ensure we close any remaining open locationspecific panels 2293 if (!isSameDocument) { 2294 closeOpenPanels(":is(panel, menupopup)[locationspecific='true']"); 2295 } 2296 2297 gPermissionPanel.onLocationChange(); 2298 2299 gProtectionsHandler.onLocationChange(); 2300 2301 BrowserPageActions.onLocationChange(); 2302 2303 UrlbarProviderSearchTips.onLocationChange( 2304 window, 2305 aLocationURI, 2306 aWebProgress, 2307 aFlags 2308 ); 2309 2310 if (aLocationURI.scheme.startsWith("http")) { 2311 ActionsProviderContextualSearch.onLocationChange( 2312 window, 2313 aLocationURI, 2314 aWebProgress, 2315 aFlags 2316 ); 2317 } 2318 2319 this._updateElementsForContentType(); 2320 2321 this._updateMacUserActivity(window, aLocationURI, aWebProgress); 2322 2323 // Unconditionally disable the Text Encoding button during load to 2324 // keep the UI calm when navigating from one modern page to another and 2325 // the toolbar button is visible. 2326 // Can't cache the button, because the presence of the element in the DOM 2327 // may change over time. 2328 let button = document.getElementById("characterencoding-button"); 2329 this._menuItemForRepairTextEncoding.setAttribute("disabled", "true"); 2330 button?.setAttribute("disabled", "true"); 2331 2332 // Try not to instantiate gCustomizeMode as much as possible, 2333 // so don't use CustomizeMode.sys.mjs to check for URI or customizing. 2334 if ( 2335 location == "about:blank" && 2336 gBrowser.selectedTab.hasAttribute("customizemode") 2337 ) { 2338 gCustomizeMode.enter(); 2339 } else if ( 2340 CustomizationHandler.isEnteringCustomizeMode || 2341 CustomizationHandler.isCustomizing() 2342 ) { 2343 gCustomizeMode.exit(); 2344 } 2345 2346 CFRPageActions.updatePageActions(gBrowser.selectedBrowser); 2347 2348 AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser); 2349 OnionLocationParent.updateOnionLocationBadge(gBrowser.selectedBrowser); 2350 TranslationsParent.onLocationChange(gBrowser.selectedBrowser); 2351 2352 PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser); 2353 2354 if (!gMultiProcessBrowser) { 2355 // Bug 1108553 - Cannot rotate images with e10s 2356 gGestureSupport.restoreRotationState(); 2357 } 2358 2359 // See bug 358202, when tabs are switched during a drag operation, 2360 // timers don't fire on windows (bug 203573) 2361 if (aRequest) { 2362 setTimeout(function () { 2363 XULBrowserWindow.asyncUpdateUI(); 2364 }, 0); 2365 } else { 2366 this.asyncUpdateUI(); 2367 } 2368 2369 if (AppConstants.MOZ_CRASHREPORTER && aLocationURI) { 2370 let uri = aLocationURI; 2371 try { 2372 // If the current URI contains a username/password, remove it. 2373 uri = aLocationURI.mutate().setUserPass("").finalize(); 2374 } catch (ex) { 2375 /* Ignore failures on about: URIs. */ 2376 } 2377 2378 try { 2379 Services.appinfo.annotateCrashReport("URL", uri.spec); 2380 } catch (ex) { 2381 // Don't make noise when the crash reporter is built but not enabled. 2382 if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) { 2383 throw ex; 2384 } 2385 } 2386 } 2387 }, 2388 2389 _updateElementsForContentType() { 2390 let browser = gBrowser.selectedBrowser; 2391 2392 let isText = 2393 browser.documentContentType && 2394 BrowserUtils.mimeTypeIsTextBased(browser.documentContentType); 2395 for (let element of this._elementsForTextBasedTypes) { 2396 if (isText) { 2397 element.removeAttribute("disabled"); 2398 } else { 2399 element.setAttribute("disabled", "true"); 2400 } 2401 } 2402 2403 // Always enable find commands in PDF documents, otherwise do it only for 2404 // text documents whose location is not in the blacklist. 2405 let enableFind = 2406 browser.contentPrincipal?.spec == "resource://pdf.js/web/viewer.html" || 2407 (isText && BrowserUtils.canFindInPage(gBrowser.currentURI.spec)); 2408 for (let element of this._elementsForFind) { 2409 if (enableFind) { 2410 element.removeAttribute("disabled"); 2411 } else { 2412 element.setAttribute("disabled", "true"); 2413 } 2414 } 2415 2416 if (TranslationsParent.isFullPageTranslationsRestrictedForPage(gBrowser)) { 2417 this._menuItemForTranslations.setAttribute("disabled", "true"); 2418 } else { 2419 this._menuItemForTranslations.removeAttribute("disabled"); 2420 } 2421 if (gTranslationsEnabled) { 2422 if (TranslationsParent.getIsTranslationsEngineSupported()) { 2423 this._menuItemForTranslations.removeAttribute("hidden"); 2424 } else { 2425 this._menuItemForTranslations.setAttribute("hidden", "true"); 2426 } 2427 } else { 2428 this._menuItemForTranslations.setAttribute("hidden", "true"); 2429 } 2430 }, 2431 2432 /** 2433 * Updates macOS platform code with the current URI and page title. 2434 * From there, we update the current NSUserActivity, enabling Handoff to other 2435 * Apple devices. 2436 * 2437 * @param {Window} window 2438 * The window in which the navigation occurred. 2439 * @param {nsIURI} uri 2440 * The URI pointing to the current page. 2441 * @param {nsIWebProgress} webProgress 2442 * The nsIWebProgress instance that fired a onLocationChange notification. 2443 */ 2444 _updateMacUserActivity(win, uri, webProgress) { 2445 if (!webProgress.isTopLevel || AppConstants.platform != "macosx") { 2446 return; 2447 } 2448 2449 let url = uri.spec; 2450 if (PrivateBrowsingUtils.isWindowPrivate(win)) { 2451 // Passing an empty string to MacUserActivityUpdater will invalidate the 2452 // current user activity. 2453 url = ""; 2454 } 2455 let baseWin = win.docShell.treeOwner.QueryInterface(Ci.nsIBaseWindow); 2456 MacUserActivityUpdater.updateLocation( 2457 url, 2458 win.gBrowser.contentTitle, 2459 baseWin 2460 ); 2461 }, 2462 2463 /** 2464 * Potentially gets a URI for a MozBrowser to be shown to the user in the 2465 * identity panel. For browsers whose content does not have a principal, 2466 * this tries the precursor. If this is null, we should not override the 2467 * browser's currentURI. 2468 * 2469 * @param {MozBrowser} browser 2470 * The browser that we need a URI to show the user in the 2471 * identity panel. 2472 * @return nsIURI of the principal for the browser's content if 2473 * the browser's currentURI should not be used, null otherwise. 2474 */ 2475 _securityURIOverride(browser) { 2476 let uri = browser.currentURI; 2477 if (!uri) { 2478 return null; 2479 } 2480 2481 // If the browser's currentURI is sufficiently good that we 2482 // do not require an override, bail out here. 2483 // browser.currentURI should be used. 2484 let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler; 2485 if ( 2486 !(doGetProtocolFlags(uri) & URI_INHERITS_SECURITY_CONTEXT) && 2487 !(uri.scheme == "about" && uri.filePath == "srcdoc") && 2488 !(uri.scheme == "about" && uri.filePath == "blank") 2489 ) { 2490 return null; 2491 } 2492 2493 let principal = browser.contentPrincipal; 2494 2495 if (principal.isNullPrincipal) { 2496 principal = principal.precursorPrincipal; 2497 } 2498 2499 if (!principal) { 2500 return null; 2501 } 2502 2503 // Can't get the original URI for a PDF viewer principal yet. 2504 if (principal.originNoSuffix == "resource://pdf.js") { 2505 return null; 2506 } 2507 2508 return principal.URI; 2509 }, 2510 2511 asyncUpdateUI() { 2512 OpenSearchManager.updateOpenSearchBadge(window); 2513 }, 2514 2515 onStatusChange(aWebProgress, aRequest, aStatus, aMessage) { 2516 this.status = aMessage; 2517 StatusPanel.update(); 2518 }, 2519 2520 // Properties used to cache security state used to update the UI 2521 _event: null, 2522 _lastLocationForEvent: null, 2523 2524 // This is called in multiple ways: 2525 // 1. Due to the nsIWebProgressListener.onContentBlockingEvent notification. 2526 // 2. Called by tabbrowser.xml when updating the current browser. 2527 // 3. Called directly during this object's initializations. 2528 // 4. Due to the nsIWebProgressListener.onLocationChange notification. 2529 // aRequest will be null always in case 2 and 3, and sometimes in case 1 (for 2530 // instance, there won't be a request when STATE_BLOCKED_TRACKING_CONTENT or 2531 // other blocking events are observed). 2532 onContentBlockingEvent(aWebProgress, aRequest, aEvent, aIsSimulated) { 2533 // Don't need to do anything if the data we use to update the UI hasn't 2534 // changed 2535 let uri = gBrowser.currentURI; 2536 let spec = uri.spec; 2537 if (this._event == aEvent && this._lastLocationForEvent == spec) { 2538 return; 2539 } 2540 this._lastLocationForEvent = spec; 2541 2542 if ( 2543 typeof aIsSimulated != "boolean" && 2544 typeof aIsSimulated != "undefined" 2545 ) { 2546 throw new Error( 2547 "onContentBlockingEvent: aIsSimulated receieved an unexpected type" 2548 ); 2549 } 2550 2551 gProtectionsHandler.onContentBlockingEvent( 2552 aEvent, 2553 aWebProgress, 2554 aIsSimulated, 2555 this._event // previous content blocking event 2556 ); 2557 2558 gTrustPanelHandler.onContentBlockingEvent( 2559 aEvent, 2560 aWebProgress, 2561 aIsSimulated, 2562 this._event // previous content blocking event 2563 ); 2564 2565 // We need the state of the previous content blocking event, so update 2566 // event after onContentBlockingEvent is called. 2567 this._event = aEvent; 2568 }, 2569 2570 // This is called in multiple ways: 2571 // 1. Due to the nsIWebProgressListener.onSecurityChange notification. 2572 // 2. Called by tabbrowser.xml when updating the current browser. 2573 // 3. Called directly during this object's initializations. 2574 // aRequest will be null always in case 2 and 3, and sometimes in case 1. 2575 onSecurityChange(aWebProgress, aRequest, aState, _aIsSimulated) { 2576 // Make sure the "https" part of the URL is striked out or not, 2577 // depending on the current mixed active content blocking state. 2578 gURLBar.formatValue(); 2579 2580 // Update the identity panel, making sure we use the precursorPrincipal's 2581 // URI where appropriate, for example about:blank windows. 2582 let uri = gBrowser.currentURI; 2583 let uriOverride = this._securityURIOverride(gBrowser.selectedBrowser); 2584 if (uriOverride) { 2585 uri = uriOverride; 2586 aState |= Ci.nsIWebProgressListener.STATE_IDENTITY_ASSOCIATED; 2587 } 2588 2589 if (window.browsingContext.isDocumentPiP) { 2590 gURLBar.setURI({ 2591 uri, 2592 isSameDocument: true, 2593 }); 2594 } 2595 2596 try { 2597 uri = Services.io.createExposableURI(uri); 2598 } catch (e) {} 2599 gIdentityHandler.updateIdentity(aState, uri); 2600 gTrustPanelHandler.updateIdentity(aState, uri); 2601 }, 2602 2603 // simulate all change notifications after switching tabs 2604 onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser( 2605 aStateFlags, 2606 aStatus, 2607 aMessage, 2608 _aTotalProgress 2609 ) { 2610 if (FullZoom.updateBackgroundTabs) { 2611 FullZoom.onLocationChange(gBrowser.currentURI, true); 2612 } 2613 2614 CombinedStopReload.onTabSwitch(); 2615 2616 // Docshell should normally take care of hiding the tooltip, but we need to do it 2617 // ourselves for tabswitches. 2618 this.hideTooltip(); 2619 2620 // Also hide tooltips for content loaded in the parent process: 2621 document.getElementById("aHTMLTooltip").hidePopup(); 2622 2623 var nsIWebProgressListener = Ci.nsIWebProgressListener; 2624 var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP; 2625 // use a pseudo-object instead of a (potentially nonexistent) channel for getting 2626 // a correct error message - and make sure that the UI is always either in 2627 // loading (STATE_START) or done (STATE_STOP) mode 2628 this.onStateChange( 2629 gBrowser.webProgress, 2630 { URI: gBrowser.currentURI }, 2631 loadingDone 2632 ? nsIWebProgressListener.STATE_STOP 2633 : nsIWebProgressListener.STATE_START, 2634 aStatus 2635 ); 2636 // status message and progress value are undefined if we're done with loading 2637 if (loadingDone) { 2638 return; 2639 } 2640 this.onStatusChange(gBrowser.webProgress, null, 0, aMessage); 2641 }, 2642 }; 2643 2644 XPCOMUtils.defineLazyPreferenceGetter( 2645 XULBrowserWindow, 2646 "spinCursorWhileBusy", 2647 "browser.spin_cursor_while_busy" 2648 ); 2649 2650 var LinkTargetDisplay = { 2651 get DELAY_SHOW() { 2652 delete this.DELAY_SHOW; 2653 return (this.DELAY_SHOW = Services.prefs.getIntPref( 2654 "browser.overlink-delay" 2655 )); 2656 }, 2657 2658 DELAY_HIDE: 250, 2659 _timer: 0, 2660 2661 get _contextMenu() { 2662 delete this._contextMenu; 2663 return (this._contextMenu = document.getElementById( 2664 "contentAreaContextMenu" 2665 )); 2666 }, 2667 2668 update({ hideStatusPanelImmediately = false } = {}) { 2669 if ( 2670 this._contextMenu.state == "open" || 2671 this._contextMenu.state == "showing" 2672 ) { 2673 this._contextMenu.addEventListener("popuphidden", () => this.update(), { 2674 once: true, 2675 }); 2676 return; 2677 } 2678 2679 clearTimeout(this._timer); 2680 window.removeEventListener("mousemove", this, true); 2681 2682 if (!XULBrowserWindow.overLink) { 2683 if (hideStatusPanelImmediately) { 2684 this._hide(); 2685 } else { 2686 this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE); 2687 } 2688 return; 2689 } 2690 2691 if (StatusPanel.isVisible) { 2692 StatusPanel.update(); 2693 } else { 2694 // Let the display appear when the mouse doesn't move within the delay 2695 this._showDelayed(); 2696 window.addEventListener("mousemove", this, true); 2697 } 2698 }, 2699 2700 handleEvent(event) { 2701 switch (event.type) { 2702 case "mousemove": 2703 // Restart the delay since the mouse was moved 2704 clearTimeout(this._timer); 2705 this._showDelayed(); 2706 break; 2707 } 2708 }, 2709 2710 _showDelayed() { 2711 this._timer = setTimeout( 2712 function (self) { 2713 StatusPanel.update(); 2714 window.removeEventListener("mousemove", self, true); 2715 }, 2716 this.DELAY_SHOW, 2717 this 2718 ); 2719 }, 2720 2721 _hide() { 2722 clearTimeout(this._timer); 2723 2724 StatusPanel.update(); 2725 }, 2726 }; 2727 2728 var CombinedStopReload = { 2729 // Try to initialize. Returns whether initialization was successful, which 2730 // may mean we had already initialized. 2731 ensureInitialized() { 2732 if (this._initialized) { 2733 return true; 2734 } 2735 if (this._destroyed) { 2736 return false; 2737 } 2738 2739 let reload = document.getElementById("reload-button"); 2740 let stop = document.getElementById("stop-button"); 2741 // It's possible the stop/reload buttons have been moved to the palette. 2742 // They may be reinserted later, so we will retry initialization if/when 2743 // we get notified of document loads. 2744 if (!stop || !reload) { 2745 return false; 2746 } 2747 2748 this._initialized = true; 2749 if (!XULBrowserWindow.stopCommand.hasAttribute("disabled")) { 2750 reload.setAttribute("displaystop", "true"); 2751 } 2752 stop.addEventListener("click", this); 2753 2754 // Removing attributes based on the observed command doesn't happen if the button 2755 // is in the palette when the command's attribute is removed (cf. bug 309953) 2756 for (let button of [stop, reload]) { 2757 if (button.hasAttribute("disabled")) { 2758 let command = document.getElementById(button.getAttribute("command")); 2759 if (!command.hasAttribute("disabled")) { 2760 button.removeAttribute("disabled"); 2761 } 2762 } 2763 } 2764 2765 this.reload = reload; 2766 this.stop = stop; 2767 this.stopReloadContainer = this.reload.parentNode; 2768 this.timeWhenSwitchedToStop = 0; 2769 2770 this.stopReloadContainer.addEventListener("animationend", this); 2771 this.stopReloadContainer.addEventListener("animationcancel", this); 2772 2773 return true; 2774 }, 2775 2776 uninit() { 2777 this._destroyed = true; 2778 2779 if (!this._initialized) { 2780 return; 2781 } 2782 2783 this._cancelTransition(); 2784 this.stop.removeEventListener("click", this); 2785 this.stopReloadContainer.removeEventListener("animationend", this); 2786 this.stopReloadContainer.removeEventListener("animationcancel", this); 2787 this.stopReloadContainer = null; 2788 this.reload = null; 2789 this.stop = null; 2790 }, 2791 2792 handleEvent(event) { 2793 switch (event.type) { 2794 case "click": 2795 if (event.button == 0 && !this.stop.disabled) { 2796 this._stopClicked = true; 2797 } 2798 break; 2799 case "animationcancel": 2800 case "animationend": { 2801 if ( 2802 event.target.classList.contains("toolbarbutton-animatable-image") && 2803 (event.animationName == "reload-to-stop" || 2804 event.animationName == "stop-to-reload") 2805 ) { 2806 this.stopReloadContainer.removeAttribute("animate"); 2807 } 2808 } 2809 } 2810 }, 2811 2812 onTabSwitch() { 2813 // Reset the time in the event of a tabswitch since the stored time 2814 // would have been associated with the previous tab, so the animation will 2815 // still run if the page has been loading until long after the tab switch. 2816 this.timeWhenSwitchedToStop = window.performance.now(); 2817 }, 2818 2819 switchToStop(aRequest, aWebProgress) { 2820 if ( 2821 !this.ensureInitialized() || 2822 !this._shouldSwitch(aRequest, aWebProgress) 2823 ) { 2824 return; 2825 } 2826 2827 // Store the time that we switched to the stop button only if a request 2828 // is active. Requests are null if the switch is related to a tabswitch. 2829 // This is used to determine if we should show the stop->reload animation. 2830 if (aRequest instanceof Ci.nsIRequest) { 2831 this.timeWhenSwitchedToStop = window.performance.now(); 2832 } 2833 2834 let shouldAnimate = 2835 aRequest instanceof Ci.nsIRequest && 2836 aWebProgress.isTopLevel && 2837 aWebProgress.isLoadingDocument && 2838 !gBrowser.tabAnimationsInProgress && 2839 !gReduceMotion && 2840 this.stopReloadContainer.closest("#nav-bar-customization-target"); 2841 2842 this._cancelTransition(); 2843 if (shouldAnimate) { 2844 this.stopReloadContainer.setAttribute("animate", "true"); 2845 } else { 2846 this.stopReloadContainer.removeAttribute("animate"); 2847 } 2848 this.reload.setAttribute("displaystop", "true"); 2849 }, 2850 2851 switchToReload(aRequest, aWebProgress) { 2852 if (!this.ensureInitialized() || !this.reload.hasAttribute("displaystop")) { 2853 return; 2854 } 2855 2856 let shouldAnimate = 2857 aRequest instanceof Ci.nsIRequest && 2858 aWebProgress.isTopLevel && 2859 !aWebProgress.isLoadingDocument && 2860 !gBrowser.tabAnimationsInProgress && 2861 !gReduceMotion && 2862 this._loadTimeExceedsMinimumForAnimation() && 2863 this.stopReloadContainer.closest("#nav-bar-customization-target"); 2864 2865 if (shouldAnimate) { 2866 this.stopReloadContainer.setAttribute("animate", "true"); 2867 } else { 2868 this.stopReloadContainer.removeAttribute("animate"); 2869 } 2870 2871 this.reload.removeAttribute("displaystop"); 2872 2873 if (!shouldAnimate || this._stopClicked) { 2874 this._stopClicked = false; 2875 this._cancelTransition(); 2876 this.reload.disabled = 2877 XULBrowserWindow.reloadCommand.hasAttribute("disabled"); 2878 return; 2879 } 2880 2881 if (this._timer) { 2882 return; 2883 } 2884 2885 // Temporarily disable the reload button to prevent the user from 2886 // accidentally reloading the page when intending to click the stop button 2887 this.reload.disabled = true; 2888 this._timer = setTimeout( 2889 function (self) { 2890 self._timer = 0; 2891 self.reload.disabled = 2892 XULBrowserWindow.reloadCommand.hasAttribute("disabled"); 2893 }, 2894 650, 2895 this 2896 ); 2897 }, 2898 2899 _loadTimeExceedsMinimumForAnimation() { 2900 // If the time between switching to the stop button then switching to 2901 // the reload button exceeds 150ms, then we will show the animation. 2902 // If we don't know when we switched to stop (switchToStop is called 2903 // after init but before switchToReload), then we will prevent the 2904 // animation from occuring. 2905 return ( 2906 this.timeWhenSwitchedToStop && 2907 window.performance.now() - this.timeWhenSwitchedToStop > 150 2908 ); 2909 }, 2910 2911 _shouldSwitch(aRequest, aWebProgress) { 2912 if ( 2913 aRequest && 2914 aRequest.originalURI && 2915 (aRequest.originalURI.schemeIs("chrome") || 2916 (aRequest.originalURI.schemeIs("about") && 2917 aWebProgress.isTopLevel && 2918 !aRequest.originalURI.spec.startsWith("about:reader"))) 2919 ) { 2920 return false; 2921 } 2922 2923 return true; 2924 }, 2925 2926 _cancelTransition() { 2927 if (this._timer) { 2928 clearTimeout(this._timer); 2929 this._timer = 0; 2930 } 2931 }, 2932 }; 2933 2934 var TabsProgressListener = { 2935 onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { 2936 // Clear OnionLocation UI 2937 if ( 2938 aStateFlags & Ci.nsIWebProgressListener.STATE_START && 2939 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && 2940 aRequest && 2941 aWebProgress.isTopLevel 2942 ) { 2943 OnionLocationParent.onStateChange(aBrowser); 2944 } 2945 2946 // Collect telemetry data about tab load times. 2947 if ( 2948 aWebProgress.isTopLevel && 2949 (!aRequest.originalURI || aRequest.originalURI.scheme != "about") 2950 ) { 2951 let metricName = "pageLoad"; 2952 2953 if (aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD) { 2954 // loadType is constructed by shifting loadFlags, this is why we need to 2955 // do the same shifting here. 2956 // https://searchfox.org/mozilla-central/rev/11cfa0462a6b5d8c5e2111b8cfddcf78098f0141/docshell/base/nsDocShellLoadTypes.h#22 2957 if (aWebProgress.loadType & (kSkipCacheFlags << 16)) { 2958 metricName = "pageReloadSkipCache"; 2959 } else if (aWebProgress.loadType == Ci.nsIDocShell.LOAD_CMD_RELOAD) { 2960 metricName = "pageReloadNormal"; 2961 } else { 2962 metricName = ""; 2963 } 2964 } 2965 2966 const timerIdField = `_${metricName}TimerId`; 2967 if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) { 2968 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) { 2969 if (metricName) { 2970 if (aBrowser[timerIdField]) { 2971 // Oops, we're seeing another start without having noticed the previous stop. 2972 Glean.browserTimings[metricName].cancel(aBrowser[timerIdField]); 2973 } 2974 aBrowser[timerIdField] = Glean.browserTimings[metricName].start(); 2975 } 2976 Glean.browserEngagement.totalTopVisits.true.add(); 2977 } else if ( 2978 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && 2979 /* we won't see STATE_START events for pre-rendered tabs */ 2980 metricName && 2981 aBrowser[timerIdField] 2982 ) { 2983 Glean.browserTimings[metricName].stopAndAccumulate( 2984 aBrowser[timerIdField] 2985 ); 2986 aBrowser[timerIdField] = null; 2987 BrowserTelemetryUtils.recordSiteOriginTelemetry(browserWindows()); 2988 } 2989 } else if ( 2990 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && 2991 /* we won't see STATE_START events for pre-rendered tabs */ 2992 aStatus == Cr.NS_BINDING_ABORTED && 2993 metricName && 2994 aBrowser[timerIdField] 2995 ) { 2996 Glean.browserTimings[metricName].cancel(aBrowser[timerIdField]); 2997 aBrowser[timerIdField] = null; 2998 } 2999 } 3000 }, 3001 3002 onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) { 3003 // Filter out location changes in sub documents. 3004 if (!aWebProgress.isTopLevel) { 3005 return; 3006 } 3007 3008 // Filter out location changes caused by anchor navigation 3009 // or history.push/pop/replaceState. 3010 if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) { 3011 // Reader mode cares about history.pushState and friends. 3012 // FIXME: The content process should manage this directly (bug 1445351). 3013 aBrowser.sendMessageToActor( 3014 "Reader:PushState", 3015 { 3016 isArticle: aBrowser.isArticle, 3017 }, 3018 "AboutReader" 3019 ); 3020 return; 3021 } 3022 3023 // Only need to call locationChange if the PopupNotifications object 3024 // for this window has already been initialized (i.e. its getter no 3025 // longer exists) 3026 if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) { 3027 PopupNotifications.locationChange(aBrowser); 3028 } 3029 3030 let tab = gBrowser.getTabForBrowser(aBrowser); 3031 if (tab && tab._sharingState) { 3032 gBrowser.resetBrowserSharing(aBrowser); 3033 } 3034 3035 gBrowser.readNotificationBox(aBrowser)?.removeTransientNotifications(); 3036 3037 // Notify the mailto notification creation code _after_ clearing transient 3038 // notifications, so its notification does not immediately get removed. 3039 Services.obs.notifyObservers(aBrowser, "mailto::onLocationChange", aFlags); 3040 3041 FullZoom.onLocationChange(aLocationURI, false, aBrowser); 3042 CaptivePortalWatcher.onLocationChange(aBrowser); 3043 }, 3044 3045 onLinkIconAvailable(browser, dataURI, iconURI) { 3046 if (!iconURI) { 3047 return; 3048 } 3049 if (browser == gBrowser.selectedBrowser) { 3050 // If the "Add Search Engine" page action is in the urlbar, its image 3051 // needs to be set to the new icon, so call updateOpenSearchBadge. 3052 OpenSearchManager.updateOpenSearchBadge(window); 3053 } 3054 }, 3055 }; 3056 3057 function showFullScreenViewContextMenuItems(popup) { 3058 for (let node of popup.querySelectorAll('[contexttype="fullscreen"]')) { 3059 node.hidden = !window.fullScreen; 3060 } 3061 let autoHide = popup.querySelector(".fullscreen-context-autohide"); 3062 if (autoHide) { 3063 FullScreen.updateAutohideMenuitem(autoHide); 3064 } 3065 } 3066 3067 function onViewToolbarCommand(aEvent) { 3068 let node = aEvent.originalTarget; 3069 let menuId; 3070 let toolbarId; 3071 let isVisible; 3072 if (node.dataset.bookmarksToolbarVisibility) { 3073 isVisible = node.dataset.visibilityEnum; 3074 toolbarId = "PersonalToolbar"; 3075 menuId = node.parentNode.parentNode.parentNode.id; 3076 Services.prefs.setCharPref( 3077 "browser.toolbars.bookmarks.visibility", 3078 isVisible 3079 ); 3080 } else { 3081 menuId = node.parentNode.id; 3082 toolbarId = node.getAttribute("toolbarId"); 3083 isVisible = node.hasAttribute("checked"); 3084 } 3085 CustomizableUI.setToolbarVisibility(toolbarId, isVisible); 3086 BrowserUsageTelemetry.recordToolbarVisibility(toolbarId, isVisible, menuId); 3087 } 3088 3089 function setToolbarVisibility( 3090 toolbar, 3091 isVisible, 3092 persist = true, 3093 animated = true 3094 ) { 3095 let hidingAttribute; 3096 if (toolbar.getAttribute("type") == "menubar") { 3097 hidingAttribute = "autohide"; 3098 if (AppConstants.platform == "linux") { 3099 Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", !isVisible); 3100 } 3101 } else { 3102 hidingAttribute = "collapsed"; 3103 } 3104 3105 if (toolbar == BookmarkingUI.toolbar) { 3106 // For the bookmarks toolbar, we need to persist state before toggling 3107 // the visibility in this window, because the state can be different 3108 // (newtab vs never or always) even when that won't change visibility 3109 // in this window. 3110 if (persist) { 3111 let prefValue; 3112 if (typeof isVisible == "string") { 3113 prefValue = isVisible; 3114 } else { 3115 prefValue = isVisible ? "always" : "never"; 3116 } 3117 Services.prefs.setCharPref( 3118 "browser.toolbars.bookmarks.visibility", 3119 prefValue 3120 ); 3121 } 3122 3123 switch (isVisible) { 3124 case true: 3125 case "always": 3126 isVisible = true; 3127 break; 3128 case false: 3129 case "never": 3130 isVisible = false; 3131 break; 3132 case "newtab": 3133 default: { 3134 let currentURI; 3135 if (!gBrowserInit.domContentLoaded) { 3136 let uriToLoad = gBrowserInit.uriToLoadPromise; 3137 if (uriToLoad) { 3138 if (Array.isArray(uriToLoad)) { 3139 // We only care about the first tab being loaded 3140 uriToLoad = uriToLoad[0]; 3141 } 3142 currentURI = URL.parse(uriToLoad)?.URI; 3143 if (!currentURI) { 3144 currentURI = gBrowser?.currentURI; 3145 } 3146 } 3147 } else { 3148 currentURI = gBrowser.currentURI; 3149 } 3150 isVisible = BookmarkingUI.isOnNewTabPage(currentURI); 3151 break; 3152 } 3153 } 3154 } 3155 3156 if (toolbar.hasAttribute(hidingAttribute) != isVisible) { 3157 // If this call will not result in a visibility change, return early 3158 // since dispatching toolbarvisibilitychange will cause views to get rebuilt. 3159 return; 3160 } 3161 3162 toolbar.classList.toggle("instant", !animated); 3163 toolbar.toggleAttribute(hidingAttribute, !isVisible); 3164 // For the bookmarks toolbar, we will have saved state above. For other 3165 // toolbars, we need to do it after setting the attribute, or we might 3166 // save the wrong state. 3167 if (persist && toolbar.id != "PersonalToolbar") { 3168 Services.xulStore.persist(toolbar, hidingAttribute); 3169 } 3170 3171 let eventParams = { 3172 detail: { 3173 visible: isVisible, 3174 }, 3175 bubbles: true, 3176 }; 3177 let event = new CustomEvent("toolbarvisibilitychange", eventParams); 3178 toolbar.dispatchEvent(event); 3179 } 3180 3181 function updateToggleControlLabel(control) { 3182 if (!control.hasAttribute("label-checked")) { 3183 return; 3184 } 3185 3186 if (!control.hasAttribute("label-unchecked")) { 3187 control.setAttribute("label-unchecked", control.getAttribute("label")); 3188 } 3189 let prefix = control.hasAttribute("checked") ? "" : "un"; 3190 control.setAttribute("label", control.getAttribute(`label-${prefix}checked`)); 3191 } 3192 3193 // Propagates Win10's tablet mode into the browser CSS. (Win11's tablet mode is 3194 // more like non-tablet mode and has no need for this.) 3195 const Win10TabletModeUpdater = { 3196 init() { 3197 if (AppConstants.platform == "win") { 3198 this.update(WindowsUIUtils.inWin10TabletMode); 3199 Services.obs.addObserver(this, "tablet-mode-change"); 3200 } 3201 }, 3202 3203 uninit() { 3204 if (AppConstants.platform == "win") { 3205 Services.obs.removeObserver(this, "tablet-mode-change"); 3206 } 3207 }, 3208 3209 observe(subject, topic, data) { 3210 this.update(data == "win10-tablet-mode"); 3211 }, 3212 3213 update(isInTabletMode) { 3214 if (isInTabletMode) { 3215 document.documentElement.setAttribute("win10-tablet-mode", "true"); 3216 } else { 3217 document.documentElement.removeAttribute("win10-tablet-mode"); 3218 } 3219 }, 3220 }; 3221 3222 function displaySecurityInfo() { 3223 BrowserCommands.pageInfo(null, "securityTab"); 3224 } 3225 3226 // Updates the UI density (for touch and compact mode) based on the uidensity pref. 3227 var gUIDensity = { 3228 MODE_NORMAL: 0, 3229 MODE_COMPACT: 1, 3230 MODE_TOUCH: 2, 3231 uiDensityPref: "browser.uidensity", 3232 autoTouchModePref: "browser.touchmode.auto", 3233 knownPrefs: new Set(["browser.uidensity", "browser.touchmode.auto"]), 3234 3235 init() { 3236 this.update(); 3237 Services.obs.addObserver(this, "tablet-mode-change"); 3238 Services.prefs.addObserver(this.uiDensityPref, this); 3239 Services.prefs.addObserver(this.autoTouchModePref, this); 3240 }, 3241 3242 uninit() { 3243 Services.obs.removeObserver(this, "tablet-mode-change"); 3244 Services.prefs.removeObserver(this.uiDensityPref, this); 3245 Services.prefs.removeObserver(this.autoTouchModePref, this); 3246 }, 3247 3248 observe(aSubject, aTopic, aPrefName) { 3249 const ok = (() => { 3250 if (aTopic == "tablet-mode-change") { 3251 return true; 3252 } 3253 if (aTopic == "nsPref:changed" && this.knownPrefs.has(aPrefName)) { 3254 return true; 3255 } 3256 return false; 3257 })(); 3258 if (!ok) { 3259 return; 3260 } 3261 3262 this.update(); 3263 }, 3264 3265 getCurrentDensity() { 3266 // Automatically override the uidensity to touch in Windows tablet mode 3267 // (either Win10 or Win11). 3268 if (AppConstants.platform == "win") { 3269 const inTablet = 3270 WindowsUIUtils.inWin10TabletMode || WindowsUIUtils.inWin11TabletMode; 3271 if (inTablet && Services.prefs.getBoolPref(this.autoTouchModePref)) { 3272 return { mode: this.MODE_TOUCH, overridden: true }; 3273 } 3274 } 3275 return { 3276 mode: Services.prefs.getIntPref(this.uiDensityPref), 3277 overridden: false, 3278 }; 3279 }, 3280 3281 update(mode) { 3282 if (mode == null) { 3283 mode = this.getCurrentDensity().mode; 3284 } 3285 3286 let docs = [document.documentElement]; 3287 let shouldUpdateSidebar = 3288 SidebarController.initialized && SidebarController.isOpen; 3289 if (shouldUpdateSidebar) { 3290 docs.push(SidebarController.browser.contentDocument.documentElement); 3291 } 3292 for (let doc of docs) { 3293 switch (mode) { 3294 case this.MODE_COMPACT: 3295 doc.setAttribute("uidensity", "compact"); 3296 break; 3297 case this.MODE_TOUCH: 3298 doc.setAttribute("uidensity", "touch"); 3299 break; 3300 default: 3301 doc.removeAttribute("uidensity"); 3302 break; 3303 } 3304 } 3305 if (shouldUpdateSidebar) { 3306 let tree = SidebarController.browser.contentDocument.querySelector( 3307 ".sidebar-placesTree" 3308 ); 3309 if (tree) { 3310 // Tree items don't update their styles without changing some property on the 3311 // parent tree element, like background-color or border. See bug 1407399. 3312 tree.style.border = "1px"; 3313 tree.style.border = ""; 3314 } 3315 } 3316 3317 gBrowser.tabContainer.uiDensityChanged(); 3318 gURLBar.uiDensityChanged(); 3319 }, 3320 }; 3321 3322 const DynamicShortcutTooltip = { 3323 nodeToTooltipMap: { 3324 "bookmarks-menu-button": "bookmarksMenuButton.tooltip", 3325 "context-reload": "reloadButton.tooltip", 3326 "context-stop": "stopButton.tooltip", 3327 "downloads-button": "downloads.tooltip", 3328 "fullscreen-button": "fullscreenButton.tooltip", 3329 "appMenu-fullscreen-button2": "fullscreenButton.tooltip", 3330 "new-window-button": "newWindowButton.tooltip", 3331 "new-tab-button": "newTabButton.tooltip", 3332 "tabs-newtab-button": "newTabButton.tooltip", 3333 "reload-button": "reloadButton.tooltip", 3334 "stop-button": "stopButton.tooltip", 3335 "urlbar-zoom-button": "urlbar-zoom-button.tooltip", 3336 "appMenu-zoomEnlarge-button2": "zoomEnlarge-button.tooltip", 3337 "appMenu-zoomReset-button2": "zoomReset-button.tooltip", 3338 "appMenu-zoomReduce-button2": "zoomReduce-button.tooltip", 3339 "reader-mode-button": "reader-mode-button.tooltip", 3340 "reader-mode-button-icon": "reader-mode-button.tooltip", 3341 "vertical-tabs-newtab-button": "newTabButton.tooltip", 3342 }, 3343 3344 nodeToShortcutMap: { 3345 "bookmarks-menu-button": "manBookmarkKb", 3346 "context-reload": "key_reload", 3347 "context-stop": "key_stop", 3348 "downloads-button": "key_openDownloads", 3349 "fullscreen-button": "key_enterFullScreen", 3350 "appMenu-fullscreen-button2": "key_enterFullScreen", 3351 "new-window-button": "key_newNavigator", 3352 "new-tab-button": "key_newNavigatorTab", 3353 "tabs-newtab-button": "key_newNavigatorTab", 3354 "reload-button": "key_reload", 3355 "stop-button": "key_stop", 3356 "urlbar-zoom-button": "key_fullZoomReset", 3357 "appMenu-zoomEnlarge-button2": "key_fullZoomEnlarge", 3358 "appMenu-zoomReset-button2": "key_fullZoomReset", 3359 "appMenu-zoomReduce-button2": "key_fullZoomReduce", 3360 "reader-mode-button": "key_toggleReaderMode", 3361 "reader-mode-button-icon": "key_toggleReaderMode", 3362 "vertical-tabs-newtab-button": "key_newNavigatorTab", 3363 }, 3364 3365 getText(nodeId) { 3366 if (!this.cache.has(nodeId) && nodeId in this.nodeToTooltipMap) { 3367 let strId = this.nodeToTooltipMap[nodeId]; 3368 let args = []; 3369 let shouldCache = true; 3370 if (nodeId in this.nodeToShortcutMap) { 3371 let shortcutId = this.nodeToShortcutMap[nodeId]; 3372 let shortcut = document.getElementById(shortcutId); 3373 if (shortcut) { 3374 let prettyShortcut = ShortcutUtils.prettifyShortcut(shortcut); 3375 args.push(prettyShortcut); 3376 if (!prettyShortcut) { 3377 shouldCache = false; 3378 } 3379 } 3380 } 3381 let string = gNavigatorBundle.getFormattedString(strId, args); 3382 if (shouldCache) { 3383 this.cache.set(nodeId, string); 3384 } 3385 return string; 3386 } 3387 return this.cache.get(nodeId); 3388 }, 3389 3390 updateText(aTooltip) { 3391 let nodeId = aTooltip.triggerNode.id; 3392 aTooltip.setAttribute("label", this.getText(nodeId)); 3393 }, 3394 3395 cache: new Map(), 3396 }; 3397 3398 /* 3399 * - [ Dependencies ] --------------------------------------------------------- 3400 * utilityOverlay.js: 3401 * - gatherTextUnder 3402 */ 3403 3404 /** 3405 * Extracts linkNode and href for the current click target. 3406 * 3407 * Note: linkNode will be null if the click wasn't on an anchor 3408 * element (or XLink). 3409 * 3410 * @param event 3411 * The click event. 3412 * @return [href, linkNode]. 3413 */ 3414 function hrefAndLinkNodeForClickEvent(event) { 3415 function isHTMLLink(aNode) { 3416 // Be consistent with what nsContextMenu.js does. 3417 return ( 3418 (HTMLAnchorElement.isInstance(aNode) && aNode.href) || 3419 (HTMLAreaElement.isInstance(aNode) && aNode.href) || 3420 HTMLLinkElement.isInstance(aNode) 3421 ); 3422 } 3423 3424 let node = event.composedTarget; 3425 while (node && !isHTMLLink(node)) { 3426 node = node.flattenedTreeParentNode; 3427 } 3428 3429 if (node) { 3430 return [node.href, node]; 3431 } 3432 3433 // If there is no linkNode, try simple XLink. 3434 let href, baseURI; 3435 node = event.composedTarget; 3436 while (node && !href) { 3437 if ( 3438 node.nodeType == Node.ELEMENT_NODE && 3439 (node.localName == "a" || 3440 node.namespaceURI == "http://www.w3.org/1998/Math/MathML") 3441 ) { 3442 href = 3443 node.getAttribute("href") || 3444 node.getAttributeNS("http://www.w3.org/1999/xlink", "href"); 3445 3446 if (href) { 3447 baseURI = node.baseURI; 3448 break; 3449 } 3450 } 3451 node = node.flattenedTreeParentNode; 3452 } 3453 3454 // In case of XLink, we don't return the node we got href from since 3455 // callers expect <a>-like elements. 3456 return [href ? makeURLAbsolute(baseURI, href) : null, null]; 3457 } 3458 3459 /** 3460 * Called whenever the user clicks in the content area. 3461 * 3462 * Note: the default event is prevented if the click is handled. 3463 * 3464 * @param event 3465 * The click event. 3466 * @param isPanelClick 3467 * Whether the event comes from an extension panel. 3468 */ 3469 function contentAreaClick(event, isPanelClick) { 3470 if (!event.isTrusted || event.defaultPrevented || event.button != 0) { 3471 return; 3472 } 3473 3474 let [href, linkNode] = hrefAndLinkNodeForClickEvent(event); 3475 if (!href) { 3476 // Not a link, handle middle mouse navigation. 3477 if ( 3478 event.button == 1 && 3479 Services.prefs.getBoolPref("middlemouse.contentLoadURL") && 3480 !Services.prefs.getBoolPref("general.autoScroll") 3481 ) { 3482 middleMousePaste(event); 3483 event.preventDefault(); 3484 } 3485 return; 3486 } 3487 3488 // This code only applies if we have a linkNode (i.e. clicks on real anchor 3489 // elements, as opposed to XLink). 3490 if ( 3491 linkNode && 3492 event.button == 0 && 3493 !event.ctrlKey && 3494 !event.shiftKey && 3495 !event.altKey && 3496 !event.metaKey 3497 ) { 3498 // An extension panel's links should target the main content area. Do this 3499 // if no modifier keys are down and if there's no target or the target 3500 // equals _main (the IE convention) or _content (the Mozilla convention). 3501 let target = linkNode.target; 3502 let mainTarget = !target || target == "_content" || target == "_main"; 3503 if (isPanelClick && mainTarget) { 3504 // javascript and data links should be executed in the current browser. 3505 if ( 3506 linkNode.getAttribute("onclick") || 3507 href.startsWith("javascript:") || 3508 href.startsWith("data:") 3509 ) { 3510 return; 3511 } 3512 3513 try { 3514 urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal); 3515 } catch (ex) { 3516 // Prevent loading unsecure destinations. 3517 event.preventDefault(); 3518 return; 3519 } 3520 3521 openLinkIn(href, "current", { 3522 allowThirdPartyFixup: false, 3523 }); 3524 event.preventDefault(); 3525 return; 3526 } 3527 } 3528 3529 handleLinkClick(event, href, linkNode); 3530 3531 // Mark the page as a user followed link. This is done so that history can 3532 // distinguish automatic embed visits from user activated ones. For example 3533 // pages loaded in frames are embed visits and lost with the session, while 3534 // visits across frames should be preserved. 3535 try { 3536 if (!PrivateBrowsingUtils.isWindowPrivate(window)) { 3537 PlacesUIUtils.markPageAsFollowedLink(href); 3538 } 3539 } catch (ex) { 3540 /* Skip invalid URIs. */ 3541 } 3542 } 3543 3544 /** 3545 * Handles clicks on links. 3546 * 3547 * @return true if the click event was handled, false otherwise. 3548 */ 3549 function handleLinkClick(event, href, linkNode) { 3550 if (event.button == 2) { 3551 // right click 3552 return false; 3553 } 3554 3555 var where = BrowserUtils.whereToOpenLink(event); 3556 if (where == "current") { 3557 return false; 3558 } 3559 3560 var doc = event.target.ownerDocument; 3561 let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance( 3562 Ci.nsIReferrerInfo 3563 ); 3564 if (linkNode) { 3565 referrerInfo.initWithElement(linkNode); 3566 } else { 3567 referrerInfo.initWithDocument(doc); 3568 } 3569 3570 if (where == "save") { 3571 saveURL( 3572 href, 3573 null, 3574 linkNode ? gatherTextUnder(linkNode) : "", 3575 null, 3576 true, 3577 true, 3578 referrerInfo, 3579 doc.cookieJarSettings, 3580 doc 3581 ); 3582 event.preventDefault(); 3583 return true; 3584 } 3585 3586 let frameID = WebNavigationFrames.getFrameId(doc.defaultView); 3587 3588 urlSecurityCheck(href, doc.nodePrincipal); 3589 let params = { 3590 charset: doc.characterSet, 3591 referrerInfo, 3592 originPrincipal: doc.nodePrincipal, 3593 originStoragePrincipal: doc.effectiveStoragePrincipal, 3594 triggeringPrincipal: doc.nodePrincipal, 3595 policyContainer: doc.policyContainer, 3596 frameID, 3597 }; 3598 3599 // The new tab/window must use the same userContextId 3600 if (doc.nodePrincipal.originAttributes.userContextId) { 3601 params.userContextId = doc.nodePrincipal.originAttributes.userContextId; 3602 } 3603 3604 openLinkIn(href, where, params); 3605 event.preventDefault(); 3606 return true; 3607 } 3608 3609 /** 3610 * Handles paste on middle mouse clicks. 3611 * 3612 * @param event {Event | Object} Event or JSON object. 3613 */ 3614 function middleMousePaste(event) { 3615 let clipboard = readFromClipboard(); 3616 if (!clipboard) { 3617 return; 3618 } 3619 3620 // Strip embedded newlines and surrounding whitespace, to match the URL 3621 // bar's behavior (stripsurroundingwhitespace) 3622 clipboard = clipboard.replace(/\s*\n\s*/g, ""); 3623 3624 clipboard = UrlbarUtils.stripUnsafeProtocolOnPaste(clipboard); 3625 3626 // if it's not the current tab, we don't need to do anything because the 3627 // browser doesn't exist. 3628 let where = BrowserUtils.whereToOpenLink(event, true, false); 3629 let lastLocationChange; 3630 if (where == "current") { 3631 lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; 3632 } 3633 3634 UrlbarUtils.getShortcutOrURIAndPostData(clipboard).then(data => { 3635 try { 3636 makeURI(data.url); 3637 } catch (ex) { 3638 // Not a valid URI. 3639 return; 3640 } 3641 3642 try { 3643 UrlbarUtils.addToUrlbarHistory(data.url, window); 3644 } catch (ex) { 3645 // Things may go wrong when adding url to session history, 3646 // but don't let that interfere with the loading of the url. 3647 console.error(ex); 3648 } 3649 3650 if ( 3651 where != "current" || 3652 lastLocationChange == gBrowser.selectedBrowser.lastLocationChange 3653 ) { 3654 openUILink(data.url, event, { 3655 ignoreButton: true, 3656 allowInheritPrincipal: data.mayInheritPrincipal, 3657 triggeringPrincipal: gBrowser.selectedBrowser.contentPrincipal, 3658 policyContainer: gBrowser.selectedBrowser.policyContainer, 3659 }); 3660 } 3661 }); 3662 3663 if (Event.isInstance(event)) { 3664 event.stopPropagation(); 3665 } 3666 } 3667 3668 // handleDroppedLink has the following 2 overloads: 3669 // handleDroppedLink(event, url, name, triggeringPrincipal) 3670 // handleDroppedLink(event, links, triggeringPrincipal) 3671 function handleDroppedLink( 3672 event, 3673 urlOrLinks, 3674 nameOrTriggeringPrincipal, 3675 triggeringPrincipal 3676 ) { 3677 let links; 3678 if (Array.isArray(urlOrLinks)) { 3679 links = urlOrLinks; 3680 triggeringPrincipal = nameOrTriggeringPrincipal; 3681 } else { 3682 links = [{ url: urlOrLinks, nameOrTriggeringPrincipal, type: "" }]; 3683 } 3684 3685 let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; 3686 3687 let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid"); 3688 3689 // event is null if links are dropped in content process. 3690 // inBackground should be false, as it's loading into current browser. 3691 let inBackground = false; 3692 if (event) { 3693 inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); 3694 if (event.shiftKey) { 3695 inBackground = !inBackground; 3696 } 3697 } 3698 3699 (async function () { 3700 if ( 3701 links.length >= 3702 Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn") 3703 ) { 3704 // Sync dialog cannot be used inside drop event handler. 3705 let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs( 3706 links.length, 3707 window 3708 ); 3709 if (!answer) { 3710 return; 3711 } 3712 } 3713 3714 let urls = []; 3715 let postDatas = []; 3716 for (let link of links) { 3717 let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url); 3718 urls.push(data.url); 3719 postDatas.push(data.postData); 3720 } 3721 if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) { 3722 gBrowser.loadTabs(urls, { 3723 inBackground, 3724 replace: true, 3725 allowThirdPartyFixup: false, 3726 postDatas, 3727 userContextId, 3728 triggeringPrincipal, 3729 }); 3730 } 3731 })(); 3732 3733 // If links are dropped in content process, event.preventDefault() should be 3734 // called in content process. 3735 if (event) { 3736 // Keep the event from being handled by the dragDrop listeners 3737 // built-in to gecko if they happen to be above us. 3738 event.preventDefault(); 3739 } 3740 } 3741 3742 // Note that this is also called from non-browser windows on OSX, which do 3743 // share menu items but not much else. See nonbrowser-mac.js. 3744 var BrowserOffline = { 3745 _inited: false, 3746 3747 // BrowserOffline Public Methods 3748 init() { 3749 if (!this._uiElement) { 3750 this._uiElement = document.getElementById("cmd_toggleOfflineStatus"); 3751 } 3752 3753 Services.obs.addObserver(this, "network:offline-status-changed"); 3754 3755 this._updateOfflineUI(Services.io.offline); 3756 3757 this._inited = true; 3758 }, 3759 3760 uninit() { 3761 if (this._inited) { 3762 Services.obs.removeObserver(this, "network:offline-status-changed"); 3763 } 3764 }, 3765 3766 toggleOfflineStatus() { 3767 var ioService = Services.io; 3768 3769 if (!ioService.offline && !this._canGoOffline()) { 3770 this._updateOfflineUI(false); 3771 return; 3772 } 3773 3774 ioService.offline = !ioService.offline; 3775 }, 3776 3777 // nsIObserver 3778 observe(aSubject, aTopic) { 3779 if (aTopic != "network:offline-status-changed") { 3780 return; 3781 } 3782 3783 // This notification is also received because of a loss in connectivity, 3784 // which we ignore by updating the UI to the current value of io.offline 3785 this._updateOfflineUI(Services.io.offline); 3786 }, 3787 3788 // BrowserOffline Implementation Methods 3789 _canGoOffline() { 3790 try { 3791 var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance( 3792 Ci.nsISupportsPRBool 3793 ); 3794 Services.obs.notifyObservers(cancelGoOffline, "offline-requested"); 3795 3796 // Something aborted the quit process. 3797 if (cancelGoOffline.data) { 3798 return false; 3799 } 3800 } catch (ex) {} 3801 3802 return true; 3803 }, 3804 3805 _uiElement: null, 3806 _updateOfflineUI(aOffline) { 3807 var offlineLocked = Services.prefs.prefIsLocked("network.online"); 3808 this._uiElement.toggleAttribute("disabled", !!offlineLocked); 3809 this._uiElement.toggleAttribute("checked", aOffline); 3810 }, 3811 }; 3812 3813 function CanCloseWindow() { 3814 // Avoid redundant calls to canClose from showing multiple 3815 // PermitUnload dialogs. 3816 if (Services.startup.shuttingDown || window.skipNextCanClose) { 3817 return true; 3818 } 3819 3820 for (let browser of gBrowser.browsers) { 3821 // Don't instantiate lazy browsers. 3822 if (!browser.isConnected) { 3823 continue; 3824 } 3825 3826 let { permitUnload } = browser.permitUnload(); 3827 if (!permitUnload) { 3828 return false; 3829 } 3830 } 3831 return true; 3832 } 3833 3834 function WindowIsClosing(event) { 3835 let source; 3836 if (event) { 3837 let target = event.sourceEvent?.target; 3838 if (target?.id?.startsWith("menu_")) { 3839 source = "menuitem"; 3840 } else if (target?.nodeName == "toolbarbutton") { 3841 source = "close-button"; 3842 } else { 3843 let key = AppConstants.platform == "macosx" ? "metaKey" : "ctrlKey"; 3844 source = event[key] ? "shortcut" : "OS"; 3845 } 3846 } 3847 if (!closeWindow(false, warnAboutClosingWindow, source)) { 3848 return false; 3849 } 3850 3851 // In theory we should exit here and the Window's internal Close 3852 // method should trigger canClose on BrowserDOMWindow. However, by 3853 // that point it's too late to be able to show a prompt for 3854 // PermitUnload. So we do it here, when we still can. 3855 if (CanCloseWindow()) { 3856 // This flag ensures that the later canClose call does nothing. 3857 // It's only needed to make tests pass, since they detect the 3858 // prompt even when it's not actually shown. 3859 window.skipNextCanClose = true; 3860 return true; 3861 } 3862 3863 return false; 3864 } 3865 3866 /** 3867 * Checks if this is the last full *browser* window around. If it is, this will 3868 * be communicated like quitting. Otherwise, we warn about closing multiple tabs. 3869 * 3870 * @returns true if closing can proceed, false if it got cancelled. 3871 */ 3872 function warnAboutClosingWindow() { 3873 // Popups aren't considered full browser windows; we also ignore private windows. 3874 let isPBWindow = 3875 PrivateBrowsingUtils.isWindowPrivate(window) && 3876 !PrivateBrowsingUtils.permanentPrivateBrowsing; 3877 3878 if (!isPBWindow && !toolbar.visible) { 3879 return gBrowser.warnAboutClosingTabs( 3880 gBrowser.openTabs.length, 3881 gBrowser.closingTabsEnum.ALL 3882 ); 3883 } 3884 3885 // Figure out if there's at least one other browser window around. 3886 let otherPBWindowExists = false; 3887 let otherWindowExists = false; 3888 for (let win of browserWindows()) { 3889 if (!win.closed && win != window) { 3890 otherWindowExists = true; 3891 if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win)) { 3892 otherPBWindowExists = true; 3893 } 3894 // If the current window is not in private browsing mode we don't need to 3895 // look for other pb windows, we can leave the loop when finding the 3896 // first non-popup window. If however the current window is in private 3897 // browsing mode then we need at least one other pb and one non-popup 3898 // window to break out early. 3899 if (!isPBWindow || otherPBWindowExists) { 3900 break; 3901 } 3902 } 3903 } 3904 3905 if (isPBWindow && !otherPBWindowExists) { 3906 let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance( 3907 Ci.nsISupportsPRBool 3908 ); 3909 exitingCanceled.data = false; 3910 Services.obs.notifyObservers(exitingCanceled, "last-pb-context-exiting"); 3911 if (exitingCanceled.data) { 3912 return false; 3913 } 3914 } 3915 3916 if (otherWindowExists) { 3917 return ( 3918 isPBWindow || 3919 gBrowser.warnAboutClosingTabs( 3920 gBrowser.openTabs.length, 3921 gBrowser.closingTabsEnum.ALL 3922 ) 3923 ); 3924 } 3925 3926 let os = Services.obs; 3927 3928 let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance( 3929 Ci.nsISupportsPRBool 3930 ); 3931 os.notifyObservers(closingCanceled, "browser-lastwindow-close-requested"); 3932 if (closingCanceled.data) { 3933 return false; 3934 } 3935 3936 os.notifyObservers(null, "browser-lastwindow-close-granted"); 3937 3938 // OS X doesn't quit the application when the last window is closed, but keeps 3939 // the session alive. Hence don't prompt users to save tabs, but warn about 3940 // closing multiple tabs. 3941 return ( 3942 AppConstants.platform != "macosx" || 3943 isPBWindow || 3944 gBrowser.warnAboutClosingTabs( 3945 gBrowser.openTabs.length, 3946 gBrowser.closingTabsEnum.ALL 3947 ) 3948 ); 3949 } 3950 3951 var MailIntegration = { 3952 sendLinkForBrowser(aBrowser) { 3953 this.sendMessage( 3954 gURLBar.makeURIReadable(aBrowser.currentURI).displaySpec, 3955 aBrowser.contentTitle 3956 ); 3957 }, 3958 3959 sendMessage(aBody, aSubject) { 3960 // generate a mailto url based on the url and the url's title 3961 var mailtoUrl = "mailto:"; 3962 if (aBody) { 3963 mailtoUrl += "?body=" + encodeURIComponent(aBody); 3964 mailtoUrl += "&subject=" + encodeURIComponent(aSubject); 3965 } 3966 3967 var uri = makeURI(mailtoUrl); 3968 3969 // now pass this uri to the operating system 3970 this._launchExternalUrl(uri); 3971 }, 3972 3973 // a generic method which can be used to pass arbitrary urls to the operating 3974 // system. 3975 // aURL --> a nsIURI which represents the url to launch 3976 _launchExternalUrl(aURL) { 3977 var extProtocolSvc = Cc[ 3978 "@mozilla.org/uriloader/external-protocol-service;1" 3979 ].getService(Ci.nsIExternalProtocolService); 3980 if (extProtocolSvc) { 3981 extProtocolSvc.loadURI( 3982 aURL, 3983 Services.scriptSecurityManager.getSystemPrincipal() 3984 ); 3985 } 3986 }, 3987 }; 3988 3989 /** 3990 * When the browser is being controlled from out-of-process, 3991 * e.g. when Marionette or the remote debugging protocol is used, 3992 * we add a visual hint to the browser UI to indicate to the user 3993 * that the browser session is under remote control. 3994 * 3995 * This is called when the content browser initialises (from gBrowserInit.onLoad()) 3996 * and when the "remote-listening" system notification fires. 3997 */ 3998 const gRemoteControl = { 3999 observe() { 4000 gRemoteControl.updateVisualCue(); 4001 }, 4002 4003 updateVisualCue() { 4004 // Disable updating the remote control cue for performance tests, 4005 // because these could fail due to an early initialization of Marionette. 4006 const disableRemoteControlCue = Services.prefs.getBoolPref( 4007 "browser.chrome.disableRemoteControlCueForTests", 4008 false 4009 ); 4010 if (disableRemoteControlCue && Cu.isInAutomation) { 4011 return; 4012 } 4013 4014 const mainWindow = document.documentElement; 4015 const remoteControlComponent = this.getRemoteControlComponent(); 4016 if (remoteControlComponent) { 4017 mainWindow.setAttribute("remotecontrol", "true"); 4018 const remoteControlIcon = document.getElementById("remote-control-icon"); 4019 document.l10n.setAttributes( 4020 remoteControlIcon, 4021 "urlbar-remote-control-notification-anchor2", 4022 { component: remoteControlComponent } 4023 ); 4024 } else { 4025 mainWindow.removeAttribute("remotecontrol"); 4026 } 4027 }, 4028 4029 getRemoteControlComponent() { 4030 // For DevTools sockets, only show the remote control cue if the socket is 4031 // not coming from a regular Browser Toolbox debugging session. 4032 if ( 4033 DevToolsSocketStatus.hasSocketOpened({ 4034 excludeBrowserToolboxSockets: true, 4035 }) 4036 ) { 4037 return "DevTools"; 4038 } 4039 4040 if (Marionette.running) { 4041 return "Marionette"; 4042 } 4043 4044 if (RemoteAgent.running) { 4045 return "RemoteAgent"; 4046 } 4047 4048 return null; 4049 }, 4050 }; 4051 4052 /** 4053 * Switch to a tab that has a given URI, and focuses its browser window. 4054 * If a matching tab is in this window, it will be switched to. Otherwise, other 4055 * windows will be searched. 4056 * 4057 * @param aURI 4058 * URI to search for 4059 * @param aOpenNew 4060 * True to open a new tab and switch to it, if no existing tab is found. 4061 * If no suitable window is found, a new one will be opened. 4062 * @param aOpenParams 4063 * If switching to this URI results in us opening a tab, aOpenParams 4064 * will be the parameter object that gets passed to openTrustedLinkIn. Please 4065 * see the documentation for openTrustedLinkIn to see what parameters can be 4066 * passed via this object. 4067 * This object also allows: 4068 * - 'ignoreFragment' property to be set to true to exclude fragment-portion 4069 * matching when comparing URIs. 4070 * If set to "whenComparing", the fragment will be unmodified. 4071 * If set to "whenComparingAndReplace", the fragment will be replaced. 4072 * - 'ignoreQueryString' boolean property to be set to true to exclude query string 4073 * matching when comparing URIs. 4074 * - 'replaceQueryString' boolean property to be set to true to exclude query string 4075 * matching when comparing URIs and overwrite the initial query string with 4076 * the one from the new URI. 4077 * - 'adoptIntoActiveWindow' boolean property to be set to true to adopt the tab 4078 * into the current window. 4079 * @param aUserContextId 4080 * If not null, will switch to the first found tab having the provided 4081 * userContextId. 4082 * @return True if an existing tab was found, false otherwise 4083 */ 4084 function switchToTabHavingURI( 4085 aURI, 4086 aOpenNew, 4087 aOpenParams = {}, 4088 aUserContextId = null 4089 ) { 4090 return URILoadingHelper.switchToTabHavingURI( 4091 window, 4092 aURI, 4093 aOpenNew, 4094 aOpenParams, 4095 aUserContextId 4096 ); 4097 } 4098 4099 // Prompt user to restart the browser in safe mode 4100 function safeModeRestart() { 4101 if (Services.appinfo.inSafeMode) { 4102 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance( 4103 Ci.nsISupportsPRBool 4104 ); 4105 Services.obs.notifyObservers( 4106 cancelQuit, 4107 "quit-application-requested", 4108 "restart" 4109 ); 4110 4111 if (cancelQuit.data) { 4112 return; 4113 } 4114 4115 Services.startup.quit( 4116 Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit 4117 ); 4118 return; 4119 } 4120 4121 Services.obs.notifyObservers(window, "restart-in-safe-mode"); 4122 } 4123 4124 /* duplicateTabIn duplicates tab in a place specified by the parameter |where|. 4125 * 4126 * |where| can be: 4127 * "tab" new tab 4128 * "tabshifted" same as "tab" but in background if default is to select new 4129 * tabs, and vice versa 4130 * "window" new window 4131 * 4132 * delta is the offset to the history entry that you want to load. 4133 */ 4134 function duplicateTabIn(aTab, where, delta) { 4135 switch (where) { 4136 case "window": { 4137 let otherWin = OpenBrowserWindow({ 4138 private: PrivateBrowsingUtils.isBrowserPrivate(aTab.linkedBrowser), 4139 }); 4140 let delayedStartupFinished = (subject, topic) => { 4141 if ( 4142 topic == "browser-delayed-startup-finished" && 4143 subject == otherWin 4144 ) { 4145 Services.obs.removeObserver(delayedStartupFinished, topic); 4146 let otherGBrowser = otherWin.gBrowser; 4147 let otherTab = otherGBrowser.selectedTab; 4148 SessionStore.duplicateTab(otherWin, aTab, delta); 4149 otherGBrowser.removeTab(otherTab, { animate: false }); 4150 } 4151 }; 4152 4153 Services.obs.addObserver( 4154 delayedStartupFinished, 4155 "browser-delayed-startup-finished" 4156 ); 4157 break; 4158 } 4159 case "tabshifted": 4160 SessionStore.duplicateTab(window, aTab, delta); 4161 // A background tab has been opened, nothing else to do here. 4162 break; 4163 case "tab": 4164 SessionStore.duplicateTab(window, aTab, delta, true, { 4165 inBackground: false, 4166 }); 4167 break; 4168 } 4169 if (aTab.group) { 4170 Glean.tabgroup.tabInteractions.duplicate.add(); 4171 } 4172 } 4173 4174 var MousePosTracker = { 4175 _listeners: new Set(), 4176 _x: 0, 4177 _y: 0, 4178 4179 /** 4180 * Registers a listener. 4181 * 4182 * @param listener (object) 4183 * A listener is expected to expose the following properties: 4184 * 4185 * getMouseTargetRect (function) 4186 * Returns the rect that the MousePosTracker needs to alert 4187 * the listener about if the mouse happens to be within it. 4188 * 4189 * onMouseEnter (function, optional) 4190 * The function to be called if the mouse enters the rect 4191 * returned by getMouseTargetRect. MousePosTracker always 4192 * runs this inside of a requestAnimationFrame, since it 4193 * assumes that the notification is used to update the DOM. 4194 * 4195 * onMouseLeave (function, optional) 4196 * The function to be called if the mouse exits the rect 4197 * returned by getMouseTargetRect. MousePosTracker always 4198 * runs this inside of a requestAnimationFrame, since it 4199 * assumes that the notification is used to update the DOM. 4200 */ 4201 addListener(listener) { 4202 if (this._listeners.has(listener)) { 4203 return; 4204 } 4205 4206 listener._hover = false; 4207 this._listeners.add(listener); 4208 4209 this._callListener(listener); 4210 }, 4211 4212 removeListener(listener) { 4213 this._listeners.delete(listener); 4214 }, 4215 4216 handleEvent(event) { 4217 if (event.type === "mouseout" && event.currentTarget !== window) { 4218 return; 4219 } 4220 4221 this._x = event.screenX - window.mozInnerScreenX; 4222 this._y = event.screenY - window.mozInnerScreenY; 4223 4224 this._listeners.forEach(listener => { 4225 try { 4226 this._callListener(listener); 4227 } catch (e) { 4228 console.error(e); 4229 } 4230 }); 4231 }, 4232 4233 _callListener(listener) { 4234 let rect = listener.getMouseTargetRect(); 4235 let hover = 4236 this._x >= rect.left && 4237 this._x <= rect.right && 4238 this._y >= rect.top && 4239 this._y <= rect.bottom; 4240 4241 if (hover == listener._hover) { 4242 return; 4243 } 4244 4245 listener._hover = hover; 4246 4247 if (hover) { 4248 if (listener.onMouseEnter) { 4249 listener.onMouseEnter(); 4250 } 4251 } else if (listener.onMouseLeave) { 4252 listener.onMouseLeave(); 4253 } 4254 }, 4255 }; 4256 4257 var PanicButtonNotifier = { 4258 init() { 4259 this._initialized = true; 4260 if (window.PanicButtonNotifierShouldNotify) { 4261 delete window.PanicButtonNotifierShouldNotify; 4262 this.notify(); 4263 } 4264 }, 4265 createPanelIfNeeded() { 4266 // Lazy load the panic-button-success-notification panel the first time we need to display it. 4267 if (!document.getElementById("panic-button-success-notification")) { 4268 let template = document.getElementById("panicButtonNotificationTemplate"); 4269 template.replaceWith(template.content); 4270 } 4271 }, 4272 notify() { 4273 if (!this._initialized) { 4274 window.PanicButtonNotifierShouldNotify = true; 4275 return; 4276 } 4277 // Display notification panel here... 4278 try { 4279 this.createPanelIfNeeded(); 4280 let popup = document.getElementById("panic-button-success-notification"); 4281 popup.hidden = false; 4282 4283 // To close the popup in 3 seconds after the popup is shown but left uninteracted. 4284 let closePopup = () => { 4285 popup.hidePopup(); 4286 }; 4287 popup.addEventListener("popupshown", function () { 4288 PanicButtonNotifier.timer = setTimeout(closePopup, 3000); 4289 }); 4290 4291 let closeButton = document.getElementById( 4292 "panic-button-success-closebutton" 4293 ); 4294 closeButton.addEventListener("command", closePopup); 4295 4296 // To prevent the popup from closing when user tries to interact with the 4297 // popup using mouse or keyboard. 4298 let onUserInteractsWithPopup = () => { 4299 clearTimeout(PanicButtonNotifier.timer); 4300 }; 4301 popup.addEventListener("mouseover", onUserInteractsWithPopup); 4302 window.addEventListener("keydown", onUserInteractsWithPopup); 4303 4304 let removeListeners = () => { 4305 popup.removeEventListener("mouseover", onUserInteractsWithPopup); 4306 window.removeEventListener("keydown", onUserInteractsWithPopup); 4307 popup.removeEventListener("popuphidden", removeListeners); 4308 closeButton.removeEventListener("command", closePopup); 4309 clearTimeout(PanicButtonNotifier.timer); 4310 }; 4311 popup.addEventListener("popuphidden", removeListeners); 4312 4313 let widget = CustomizableUI.getWidget("panic-button").forWindow(window); 4314 let anchor = widget.anchor.icon; 4315 popup.openPopup(anchor, popup.getAttribute("position")); 4316 } catch (ex) { 4317 console.error(ex); 4318 } 4319 }, 4320 }; 4321 4322 /** 4323 * The TabDialogBox supports opening window dialogs as SubDialogs on the tab and content 4324 * level. Both tab and content dialogs have their own separate managers. 4325 * Dialogs will be queued FIFO and cover the web content. 4326 * Dialogs are closed when the user reloads or leaves the page. 4327 * While a dialog is open PopupNotifications, such as permission prompts, are 4328 * suppressed. 4329 */ 4330 class TabDialogBox { 4331 static _containerFor(browser) { 4332 return browser.closest( 4333 ".browserSidebarContainer, .webextension-popup-stack, .sidebar-browser-stack" 4334 ); 4335 } 4336 4337 constructor(browser) { 4338 this._weakBrowserRef = Cu.getWeakReference(browser); 4339 4340 // Create parent element for tab dialogs 4341 let template = document.getElementById("dialogStackTemplate"); 4342 let dialogStack = template.content.cloneNode(true).firstElementChild; 4343 dialogStack.classList.add("tab-prompt-dialog"); 4344 4345 TabDialogBox._containerFor(browser).appendChild(dialogStack); 4346 4347 // Initially the stack only contains the template 4348 let dialogTemplate = dialogStack.firstElementChild; 4349 4350 // Create dialog manager for prompts at the tab level. 4351 this._tabDialogManager = new SubDialogManager({ 4352 dialogStack, 4353 dialogTemplate, 4354 orderType: SubDialogManager.ORDER_QUEUE, 4355 allowDuplicateDialogs: true, 4356 dialogOptions: { 4357 consumeOutsideClicks: false, 4358 }, 4359 }); 4360 } 4361 4362 /** 4363 * Open a dialog on tab or content level. 4364 * 4365 * @param {string} aURL - URL of the dialog to load in the tab box. 4366 * @param {object} [aOptions] 4367 * @param {string} [aOptions.features] - Comma separated list of window 4368 * features. 4369 * @param {boolean} [aOptions.allowDuplicateDialogs] - Whether to allow 4370 * showing multiple dialogs with aURL at the same time. If false calls for 4371 * duplicate dialogs will be dropped. 4372 * @param {string} [aOptions.sizeTo] - Pass "available" to stretch dialog to 4373 * roughly content size. Any max-width or max-height style values on the document root 4374 * will also be applied to the dialog box. 4375 * @param {boolean} [aOptions.keepOpenSameOriginNav] - By default dialogs are 4376 * aborted on any navigation. 4377 * Set to true to keep the dialog open for same origin navigation. 4378 * @param {number} [aOptions.modalType] - The modal type to create the dialog for. 4379 * By default, we show the dialog for tab prompts. 4380 * @param {boolean} [aOptions.hideContent] - When true, we are about to show a prompt that is requesting the 4381 * users credentials for a toplevel load of a resource from a base domain different from the base domain of the currently loaded page. 4382 * To avoid auth prompt spoofing (see bug 791594) we hide the current sites content 4383 * (among other protection mechanisms, that are not handled here, see the bug for reference). 4384 * @param {nsIWebProgress} [aOptions.webProgress] - If passed, use to detect when a site is being 4385 * navigated to in order to close the dialog. By default, this.browser.webProgress is used. 4386 * @returns {object} [result] Returns an object { closedPromise, dialog }. 4387 * @returns {Promise} [result.closedPromise] Resolves once the dialog has been closed. 4388 * @returns {SubDialog} [result.dialog] A reference to the opened SubDialog. 4389 */ 4390 open( 4391 aURL, 4392 { 4393 features = null, 4394 allowDuplicateDialogs = true, 4395 sizeTo, 4396 keepOpenSameOriginNav, 4397 modalType = null, 4398 allowFocusCheckbox = false, 4399 hideContent = false, 4400 webProgress = undefined, 4401 } = {}, 4402 ...aParams 4403 ) { 4404 let resolveClosed; 4405 let closedPromise = new Promise(resolve => (resolveClosed = resolve)); 4406 // Get the dialog manager to open the prompt with. 4407 let dialogManager = 4408 modalType === Ci.nsIPrompt.MODAL_TYPE_CONTENT 4409 ? this.getContentDialogManager() 4410 : this._tabDialogManager; 4411 4412 let hasDialogs = () => 4413 this._tabDialogManager.hasDialogs || 4414 this._contentDialogManager?.hasDialogs; 4415 4416 if (!hasDialogs()) { 4417 this._onFirstDialogOpen(webProgress ?? this.browser.webProgress); 4418 } 4419 4420 let closingCallback = event => { 4421 if (!hasDialogs()) { 4422 this._onLastDialogClose(webProgress ?? this.browser.webProgress); 4423 } 4424 4425 if (allowFocusCheckbox && !event.detail?.abort) { 4426 this.maybeSetAllowTabSwitchPermission(event.target); 4427 } 4428 }; 4429 4430 if (modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT) { 4431 sizeTo = "limitheight"; 4432 } 4433 4434 // Open dialog and resolve once it has been closed 4435 let dialog = dialogManager.open( 4436 aURL, 4437 { 4438 features, 4439 allowDuplicateDialogs, 4440 sizeTo, 4441 closingCallback, 4442 closedCallback: resolveClosed, 4443 hideContent, 4444 }, 4445 ...aParams 4446 ); 4447 4448 // Marking the dialog externally, instead of passing it as an option. 4449 // The SubDialog(Manager) does not care about navigation. 4450 // dialog can be null here if allowDuplicateDialogs = false. 4451 if (dialog) { 4452 dialog._keepOpenSameOriginNav = keepOpenSameOriginNav; 4453 } 4454 return { closedPromise, dialog }; 4455 } 4456 4457 _onFirstDialogOpen(webProgress) { 4458 // Hide PopupNotifications to prevent them from covering up dialogs. 4459 this.browser.setAttribute("tabDialogShowing", true); 4460 UpdatePopupNotificationsVisibility(); 4461 4462 // Register listeners 4463 this._lastPrincipal = this.browser.contentPrincipal; 4464 webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION); 4465 4466 this.tab?.addEventListener("TabClose", this); 4467 } 4468 4469 _onLastDialogClose(webProgress) { 4470 // Show PopupNotifications again. 4471 this.browser.removeAttribute("tabDialogShowing"); 4472 UpdatePopupNotificationsVisibility(); 4473 4474 // Clean up listeners 4475 webProgress.removeProgressListener(this); 4476 this._lastPrincipal = null; 4477 4478 this.tab?.removeEventListener("TabClose", this); 4479 } 4480 4481 _buildContentPromptDialog() { 4482 let template = document.getElementById("dialogStackTemplate"); 4483 let contentDialogStack = template.content.cloneNode(true).firstElementChild; 4484 contentDialogStack.classList.add("content-prompt-dialog"); 4485 4486 // Create a dialog manager for content prompts. 4487 let browserContainer = TabDialogBox._containerFor(this.browser); 4488 let tabPromptDialog = browserContainer.querySelector(".tab-prompt-dialog"); 4489 browserContainer.insertBefore(contentDialogStack, tabPromptDialog); 4490 4491 let contentDialogTemplate = contentDialogStack.firstElementChild; 4492 this._contentDialogManager = new SubDialogManager({ 4493 dialogStack: contentDialogStack, 4494 dialogTemplate: contentDialogTemplate, 4495 orderType: SubDialogManager.ORDER_QUEUE, 4496 allowDuplicateDialogs: true, 4497 dialogOptions: { 4498 consumeOutsideClicks: false, 4499 }, 4500 }); 4501 } 4502 4503 handleEvent(event) { 4504 if (event.type !== "TabClose") { 4505 return; 4506 } 4507 this.abortAllDialogs(); 4508 } 4509 4510 abortAllDialogs() { 4511 this._tabDialogManager.abortDialogs(); 4512 this._contentDialogManager?.abortDialogs(); 4513 } 4514 4515 focus() { 4516 // Prioritize focusing the dialog manager for tab prompts 4517 if (this._tabDialogManager._dialogs.length) { 4518 this._tabDialogManager.focusTopDialog(); 4519 return; 4520 } 4521 this._contentDialogManager?.focusTopDialog(); 4522 } 4523 4524 /** 4525 * If the user navigates away or refreshes the page, close all dialogs for 4526 * the current browser. 4527 */ 4528 onLocationChange(aWebProgress, aRequest, aLocation, aFlags) { 4529 if ( 4530 !aWebProgress.isTopLevel || 4531 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT 4532 ) { 4533 return; 4534 } 4535 4536 // Dialogs can be exempt from closing on same origin location change. 4537 let filterFn; 4538 4539 // Test for same origin location change 4540 if ( 4541 this._lastPrincipal?.isSameOrigin( 4542 aLocation, 4543 this.browser.browsingContext.usePrivateBrowsing 4544 ) 4545 ) { 4546 filterFn = dialog => !dialog._keepOpenSameOriginNav; 4547 } 4548 4549 this._lastPrincipal = this.browser.contentPrincipal; 4550 4551 this._tabDialogManager.abortDialogs(filterFn); 4552 this._contentDialogManager?.abortDialogs(filterFn); 4553 } 4554 4555 get tab() { 4556 return gBrowser.getTabForBrowser(this.browser); 4557 } 4558 4559 get browser() { 4560 let browser = this._weakBrowserRef.get(); 4561 if (!browser) { 4562 throw new Error("Stale dialog box! The associated browser is gone."); 4563 } 4564 return browser; 4565 } 4566 4567 getTabDialogManager() { 4568 return this._tabDialogManager; 4569 } 4570 4571 getContentDialogManager() { 4572 if (!this._contentDialogManager) { 4573 this._buildContentPromptDialog(); 4574 } 4575 return this._contentDialogManager; 4576 } 4577 4578 onNextPromptShowAllowFocusCheckboxFor(principal) { 4579 this._allowTabFocusByPromptPrincipal = principal; 4580 } 4581 4582 /** 4583 * Sets the "focus-tab-by-prompt" permission for the dialog. 4584 */ 4585 maybeSetAllowTabSwitchPermission(dialog) { 4586 let checkbox = dialog.querySelector("checkbox"); 4587 4588 if (checkbox.checked) { 4589 Services.perms.addFromPrincipal( 4590 this._allowTabFocusByPromptPrincipal, 4591 "focus-tab-by-prompt", 4592 Services.perms.ALLOW_ACTION 4593 ); 4594 } 4595 4596 // Don't show the "allow tab switch checkbox" for subsequent prompts. 4597 this._allowTabFocusByPromptPrincipal = null; 4598 } 4599 } 4600 4601 TabDialogBox.prototype.QueryInterface = ChromeUtils.generateQI([ 4602 "nsIWebProgressListener", 4603 "nsISupportsWeakReference", 4604 ]); 4605 4606 // Handle window-modal prompts that we want to display with the same style as 4607 // tab-modal prompts. 4608 var gDialogBox = { 4609 _dialog: null, 4610 _nextOpenJumpsQueue: false, 4611 _queued: [], 4612 4613 // Used to wait for a `close` event from the HTML 4614 // dialog. The event is fired asynchronously, which means 4615 // that if we open another dialog immediately after the 4616 // previous one, we might be confused into thinking a 4617 // `close` event for the old dialog is for the new one. 4618 // As they have the same event target, we have no way of 4619 // distinguishing them. So we wait for the `close` event 4620 // to have happened before allowing another dialog to open. 4621 _didCloseHTMLDialog: null, 4622 // Whether we managed to open the dialog we tried to open. 4623 // Used to avoid waiting for the above callback in case 4624 // of an error opening the dialog. 4625 _didOpenHTMLDialog: false, 4626 4627 get dialog() { 4628 return this._dialog; 4629 }, 4630 4631 get isOpen() { 4632 return !!this._dialog; 4633 }, 4634 4635 replaceDialogIfOpen() { 4636 this._dialog?.close(); 4637 this._nextOpenJumpsQueue = true; 4638 }, 4639 4640 async open(uri, args) { 4641 // If we need to queue, some callers indicate they should go first. 4642 const queueMethod = this._nextOpenJumpsQueue ? "unshift" : "push"; 4643 this._nextOpenJumpsQueue = false; 4644 4645 // If we already have a dialog opened and are trying to open another, 4646 // queue the next one to be opened later. 4647 if (this.isOpen) { 4648 return new Promise((resolve, reject) => { 4649 this._queued[queueMethod]({ resolve, reject, uri, args }); 4650 }); 4651 } 4652 4653 // We're not open. If we're in a modal state though, we can't 4654 // show the dialog effectively. To avoid hanging by deadlock, 4655 // just return immediately for sync prompts: 4656 if (window.windowUtils.isInModalState() && !args.getProperty("async")) { 4657 throw Components.Exception( 4658 "Prompt could not be shown.", 4659 Cr.NS_ERROR_NOT_AVAILABLE 4660 ); 4661 } 4662 4663 // Indicate if we should wait for the dialog to close. 4664 this._didOpenHTMLDialog = false; 4665 let haveClosedPromise = new Promise(resolve => { 4666 this._didCloseHTMLDialog = resolve; 4667 }); 4668 4669 // Bring the window to the front in case we're minimized or occluded: 4670 window.focus(); 4671 4672 try { 4673 // Prevent URL bar from showing on top of modal 4674 gURLBar.incrementBreakoutBlockerCount(); 4675 } catch (ex) { 4676 console.error(ex); 4677 } 4678 4679 try { 4680 await this._open(uri, args); 4681 } catch (ex) { 4682 console.error(ex); 4683 } finally { 4684 let dialog = document.getElementById("window-modal-dialog"); 4685 if (dialog.open) { 4686 dialog.close(); 4687 } 4688 // If the dialog was opened successfully, then we can wait for it 4689 // to close before trying to open any others. 4690 if (this._didOpenHTMLDialog) { 4691 await haveClosedPromise; 4692 } 4693 dialog.style.visibility = "hidden"; 4694 dialog.style.height = "0"; 4695 dialog.style.width = "0"; 4696 document.documentElement.removeAttribute("window-modal-open"); 4697 dialog.removeEventListener("dialogopen", this); 4698 dialog.removeEventListener("close", this); 4699 this._updateMenuAndCommandState(true /* to enable */); 4700 this._dialog = null; 4701 UpdatePopupNotificationsVisibility(); 4702 // Restores URL bar breakout if needed 4703 gURLBar.decrementBreakoutBlockerCount(); 4704 } 4705 if (this._queued.length) { 4706 setTimeout(() => this._openNextDialog(), 0); 4707 } 4708 return args; 4709 }, 4710 4711 _openNextDialog() { 4712 if (!this.isOpen) { 4713 let { resolve, reject, uri, args } = this._queued.shift(); 4714 this.open(uri, args).then(resolve, reject); 4715 } 4716 }, 4717 4718 handleEvent(event) { 4719 switch (event.type) { 4720 case "dialogopen": 4721 this._dialog.focus(true); 4722 break; 4723 case "close": 4724 this._didCloseHTMLDialog(); 4725 this._dialog.close(); 4726 break; 4727 } 4728 }, 4729 4730 _open(uri, args) { 4731 // Get this offset before we touch style below, as touching style seems 4732 // to reset the cached layout bounds. 4733 let offset = window.windowUtils.getBoundsWithoutFlushing( 4734 gBrowser.selectedBrowser 4735 ).top; 4736 let parentElement = document.getElementById("window-modal-dialog"); 4737 parentElement.style.setProperty("--chrome-offset", offset + "px"); 4738 parentElement.style.removeProperty("visibility"); 4739 parentElement.style.removeProperty("width"); 4740 parentElement.style.removeProperty("height"); 4741 document.documentElement.setAttribute("window-modal-open", true); 4742 // Call this first so the contents show up and get layout, which is 4743 // required for SubDialog to work. 4744 parentElement.showModal(); 4745 this._didOpenHTMLDialog = true; 4746 4747 // Disable menus and shortcuts. 4748 this._updateMenuAndCommandState(false /* to disable */); 4749 4750 // Now actually set up the dialog contents: 4751 let template = document.getElementById("window-modal-dialog-template") 4752 .content.firstElementChild; 4753 parentElement.addEventListener("dialogopen", this); 4754 parentElement.addEventListener("close", this); 4755 this._dialog = new SubDialog({ 4756 template, 4757 parentElement, 4758 id: "window-modal-dialog-subdialog", 4759 options: { 4760 consumeOutsideClicks: false, 4761 }, 4762 }); 4763 let closedPromise = new Promise(resolve => { 4764 this._closedCallback = function () { 4765 PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed"); 4766 resolve(); 4767 }; 4768 }); 4769 this._dialog.open( 4770 uri, 4771 { 4772 features: "resizable=no", 4773 modalType: Ci.nsIPrompt.MODAL_TYPE_INTERNAL_WINDOW, 4774 closedCallback: () => { 4775 this._closedCallback(); 4776 }, 4777 }, 4778 args 4779 ); 4780 UpdatePopupNotificationsVisibility(); 4781 return closedPromise; 4782 }, 4783 4784 _nonUpdatableElements: new Set([ 4785 // Make an exception for debugging tools, for developer ease of use. 4786 "key_browserConsole", 4787 "key_browserToolbox", 4788 4789 // Don't touch the editing keys/commands which we might want inside the dialog. 4790 "key_undo", 4791 "key_redo", 4792 4793 "key_cut", 4794 "key_copy", 4795 "key_paste", 4796 "key_delete", 4797 "key_selectAll", 4798 ]), 4799 4800 _updateMenuAndCommandState(shouldBeEnabled) { 4801 let editorCommands = document.getElementById("editMenuCommands"); 4802 // For the following items, set or clear disabled state: 4803 // - toplevel menubar items (will affect inner items on macOS) 4804 // - command elements 4805 // - key elements not connected to command elements. 4806 for (let element of document.querySelectorAll( 4807 "menubar > menu, command, key:not([command])" 4808 )) { 4809 if ( 4810 editorCommands?.contains(element) || 4811 (element.id && this._nonUpdatableElements.has(element.id)) 4812 ) { 4813 continue; 4814 } 4815 if (element.nodeName == "key" && element.command) { 4816 continue; 4817 } 4818 if (!shouldBeEnabled) { 4819 if (!element.hasAttribute("disabled")) { 4820 element.setAttribute("disabled", true); 4821 } else { 4822 element.setAttribute("wasdisabled", true); 4823 } 4824 } else if (element.getAttribute("wasdisabled") != "true") { 4825 element.removeAttribute("disabled"); 4826 } else { 4827 element.removeAttribute("wasdisabled"); 4828 } 4829 } 4830 }, 4831 }; 4832 4833 // browser.js loads in the library window, too, but we can only show prompts 4834 // in the main browser window: 4835 if (window.location.href != AppConstants.BROWSER_CHROME_URL) { 4836 gDialogBox = null; 4837 } 4838 4839 var ConfirmationHint = { 4840 _timerID: null, 4841 4842 /** 4843 * Shows a transient, non-interactive confirmation hint anchored to an 4844 * element, usually used in response to a user action to reaffirm that it was 4845 * successful and potentially provide extra context. Examples for such hints: 4846 * - "Saved to bookmarks" after bookmarking a page 4847 * - "Sent!" after sending a tab to another device 4848 * - "Queued (offline)" when attempting to send a tab to another device 4849 * while offline 4850 * 4851 * @param anchor (DOM node, required) 4852 * The anchor for the panel. 4853 * @param messageId (string, required) 4854 * For getting the message string from confirmationHints.ftl 4855 * @param options (object, optional) 4856 * An object with the following optional properties: 4857 * - event (DOM event): The event that triggered the feedback 4858 * - descriptionId (string): message ID of the description text 4859 * - position (string): position of the panel relative to the anchor. 4860 * - l10nArgs (object): l10n arguments for the messageId. 4861 */ 4862 show(anchor, messageId, options = {}) { 4863 this._reset(); 4864 4865 MozXULElement.insertFTLIfNeeded("toolkit/branding/brandings.ftl"); 4866 MozXULElement.insertFTLIfNeeded("browser/confirmationHints.ftl"); 4867 document.l10n.setAttributes(this._message, messageId, options.l10nArgs); 4868 if (options.descriptionId) { 4869 document.l10n.setAttributes(this._description, options.descriptionId); 4870 this._description.hidden = false; 4871 this._panel.classList.add("with-description"); 4872 } else { 4873 this._description.hidden = true; 4874 this._panel.classList.remove("with-description"); 4875 } 4876 4877 this._panel.setAttribute("data-message-id", messageId); 4878 4879 // The timeout value used here allows the panel to stay open for 4880 // 3s after the text transition (duration=120ms) has finished. 4881 // If there is a description, we show for 6s after the text transition. 4882 const DURATION = options.showDescription ? 6000 : 3000; 4883 this._panel.addEventListener( 4884 "popupshown", 4885 () => { 4886 this._animationBox.setAttribute("animate", "true"); 4887 this._timerID = setTimeout(() => { 4888 this._panel.hidePopup(true); 4889 }, DURATION + 120); 4890 }, 4891 { once: true } 4892 ); 4893 4894 this._panel.addEventListener( 4895 "popuphidden", 4896 () => { 4897 // reset the timerId in case our timeout wasn't the cause of the popup being hidden 4898 this._reset(); 4899 }, 4900 { once: true } 4901 ); 4902 4903 this._panel.openPopup(anchor, { 4904 position: options.position ?? "bottomleft topleft", 4905 triggerEvent: options.event, 4906 }); 4907 }, 4908 4909 _reset() { 4910 if (this._timerID) { 4911 clearTimeout(this._timerID); 4912 this._timerID = null; 4913 } 4914 if (this.__panel) { 4915 this._animationBox.removeAttribute("animate"); 4916 this._panel.removeAttribute("data-message-id"); 4917 } 4918 }, 4919 4920 get _panel() { 4921 this._ensurePanel(); 4922 return this.__panel; 4923 }, 4924 4925 get _animationBox() { 4926 this._ensurePanel(); 4927 delete this._animationBox; 4928 return (this._animationBox = document.getElementById( 4929 "confirmation-hint-checkmark-animation-container" 4930 )); 4931 }, 4932 4933 get _message() { 4934 this._ensurePanel(); 4935 delete this._message; 4936 return (this._message = document.getElementById( 4937 "confirmation-hint-message" 4938 )); 4939 }, 4940 4941 get _description() { 4942 this._ensurePanel(); 4943 delete this._description; 4944 return (this._description = document.getElementById( 4945 "confirmation-hint-description" 4946 )); 4947 }, 4948 4949 _ensurePanel() { 4950 if (!this.__panel) { 4951 let wrapper = document.getElementById("confirmation-hint-wrapper"); 4952 wrapper.replaceWith(wrapper.content); 4953 this.__panel = document.getElementById("confirmation-hint"); 4954 } 4955 }, 4956 }; 4957 4958 var FirefoxViewHandler = { 4959 tab: null, 4960 BUTTON_ID: "firefox-view-button", 4961 get button() { 4962 return document.getElementById(this.BUTTON_ID); 4963 }, 4964 init() { 4965 CustomizableUI.addListener(this); 4966 4967 ChromeUtils.defineESModuleGetters(this, { 4968 SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs", 4969 }); 4970 }, 4971 uninit() { 4972 CustomizableUI.removeListener(this); 4973 }, 4974 onWidgetRemoved(aWidgetId) { 4975 if (aWidgetId == this.BUTTON_ID && this.tab) { 4976 gBrowser.removeTab(this.tab); 4977 } 4978 }, 4979 onWidgetAdded(aWidgetId) { 4980 if (aWidgetId === this.BUTTON_ID) { 4981 this.button.removeAttribute("open"); 4982 } 4983 }, 4984 openTab(section) { 4985 if (AppConstants.BASE_BROWSER_VERSION) { 4986 // about:firefoxview is disabled. tor-browser#42037. 4987 return; 4988 } 4989 4990 if (!CustomizableUI.getPlacementOfWidget(this.BUTTON_ID)) { 4991 CustomizableUI.addWidgetToArea( 4992 this.BUTTON_ID, 4993 CustomizableUI.AREA_TABSTRIP, 4994 CustomizableUI.getPlacementOfWidget("tabbrowser-tabs").position 4995 ); 4996 } 4997 let viewURL = "about:firefoxview"; 4998 if (section) { 4999 viewURL = `${viewURL}#${section}`; 5000 } 5001 // Need to account for navigation to Firefox View pages 5002 if ( 5003 this.tab && 5004 this.tab.linkedBrowser.currentURI.spec.split("#")[0] != viewURL 5005 ) { 5006 gBrowser.removeTab(this.tab); 5007 this.tab = null; 5008 } 5009 if (!this.tab) { 5010 this.tab = gBrowser.addTrustedTab(viewURL); 5011 this.tab.addEventListener("TabClose", this, { once: true }); 5012 gBrowser.tabContainer.addEventListener("TabSelect", this); 5013 window.addEventListener("activate", this); 5014 gBrowser.hideTab(this.tab); 5015 this.button.setAttribute("aria-controls", this.tab.linkedPanel); 5016 } 5017 // we put this here to avoid a race condition that would occur 5018 // if this was called in response to "TabSelect" 5019 this._closeDeviceConnectedTab(); 5020 gBrowser.selectedTab = this.tab; 5021 }, 5022 openToolbarMouseEvent(event, section) { 5023 if (event?.type == "mousedown" && event?.button != 0) { 5024 return; 5025 } 5026 this.openTab(section); 5027 }, 5028 handleEvent(e) { 5029 switch (e.type) { 5030 case "TabSelect": { 5031 const selected = e.target == this.tab; 5032 this.button?.toggleAttribute("open", selected); 5033 this.button?.setAttribute("aria-pressed", selected); 5034 this._recordViewIfTabSelected(); 5035 this._onTabForegrounded(); 5036 // If Fx View is opened, add temporary style to make first available tab focusable 5037 // When Fx View is closed, remove temporary -moz-user-focus style from first available tab 5038 gBrowser.visibleTabs[0].style.MozUserFocus = 5039 e.target == this.tab ? "normal" : ""; 5040 break; 5041 } 5042 case "TabClose": 5043 this.tab = null; 5044 gBrowser.tabContainer.removeEventListener("TabSelect", this); 5045 this.button?.removeAttribute("aria-controls"); 5046 break; 5047 case "activate": 5048 this._onTabForegrounded(); 5049 break; 5050 } 5051 }, 5052 _closeDeviceConnectedTab() { 5053 if (!TabsSetupFlowManager.didFxaTabOpen) { 5054 return; 5055 } 5056 // close the tab left behind after a user pairs a device and 5057 // is redirected back to the Firefox View tab 5058 const fxaRoot = Services.prefs.getCharPref( 5059 "identity.fxaccounts.remote.root" 5060 ); 5061 const fxDeviceConnectedTab = gBrowser.tabs.find(tab => 5062 tab.linkedBrowser.currentURI.displaySpec.startsWith( 5063 `${fxaRoot}pair/auth/complete` 5064 ) 5065 ); 5066 5067 if (!fxDeviceConnectedTab) { 5068 return; 5069 } 5070 5071 if (gBrowser.tabs.length <= 2) { 5072 // if its the only tab besides the Firefox View tab, 5073 // open a new tab first so the browser doesn't close 5074 gBrowser.addTrustedTab("about:newtab"); 5075 } 5076 gBrowser.removeTab(fxDeviceConnectedTab); 5077 TabsSetupFlowManager.didFxaTabOpen = false; 5078 }, 5079 _onTabForegrounded() { 5080 if (this.tab?.selected) { 5081 this.SyncedTabs.syncTabs(); 5082 } 5083 }, 5084 _recordViewIfTabSelected() { 5085 if (this.tab?.selected) { 5086 const PREF_NAME = "browser.firefox-view.view-count"; 5087 const MAX_VIEW_COUNT = 10; 5088 let viewCount = Services.prefs.getIntPref(PREF_NAME, 0); 5089 5090 // Record telemetry 5091 Glean.firefoxviewNext.tabSelectedToolbarbutton.record(); 5092 5093 if (viewCount < MAX_VIEW_COUNT) { 5094 Services.prefs.setIntPref(PREF_NAME, viewCount + 1); 5095 } 5096 } 5097 }, 5098 };