BrowserDOMWindow.sys.mjs (15822B)
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 import { BrowserWindowTracker } from "resource:///modules/BrowserWindowTracker.sys.mjs"; 7 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; 8 import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs"; 9 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 10 11 let lazy = {}; 12 13 ChromeUtils.defineESModuleGetters(lazy, { 14 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", 15 TaskbarTabsUtils: "resource:///modules/taskbartabs/TaskbarTabsUtils.sys.mjs", 16 URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs", 17 }); 18 19 XPCOMUtils.defineLazyPreferenceGetter( 20 lazy, 21 "loadDivertedInBackground", 22 "browser.tabs.loadDivertedInBackground" 23 ); 24 25 ChromeUtils.defineLazyGetter(lazy, "ReferrerInfo", () => 26 Components.Constructor( 27 "@mozilla.org/referrer-info;1", 28 "nsIReferrerInfo", 29 "init" 30 ) 31 ); 32 33 /** 34 * This class is instantiated once for each browser window, and the instance 35 * is exposed as a `browserDOMWindow` property on that window. 36 * 37 * It implements the nsIBrowserDOMWindow interface, which is used by C++ as 38 * well as toolkit code to have an application-agnostic interface to do things 39 * like opening new tabs and windows. Fenix (Firefox on Android) has its own 40 * implementation of the same interface. 41 * 42 * @implements {nsIBrowserDOMWindow} 43 */ 44 export class BrowserDOMWindow { 45 /** 46 * @type {Window} 47 */ 48 win = null; 49 50 /** 51 * @param {Window} win 52 */ 53 constructor(win) { 54 this.win = win; 55 } 56 57 /** 58 * @param {Window} win 59 */ 60 static setupInWindow(win) { 61 win.browserDOMWindow = new BrowserDOMWindow(win); 62 } 63 64 /** 65 * @param {Window} win 66 */ 67 static teardownInWindow(win) { 68 win.browserDOMWindow = null; 69 } 70 71 /** 72 * @param {nsIURI} aURI 73 * @param {nsIReferrerInfo} aReferrerInfo 74 * @param {boolean} aIsPrivate 75 * @param {boolean} aIsExternal 76 * @param {boolean} [aForceNotRemote=false] 77 * @param {number} [aUserContextId=0] 78 * @param {nsIOpenWindowInfo} [aOpenWindowInfo=null] 79 * @param {Element} [aOpenerBrowser=null] 80 * @param {nsIPrincipal} [aTriggeringPrincipal=null] 81 * @param {string} [aName=""] 82 * @param {nsIPolicyContainer} [aPolicyContainer=null] 83 * @param {boolean} [skipLoad=false] 84 * @param {i16} [aWhere=undefined] 85 * @returns {nsIBrowser|null} 86 */ 87 #openURIInNewTab( 88 aURI, 89 aReferrerInfo, 90 aIsPrivate, 91 aIsExternal, 92 aForceNotRemote = false, 93 aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID, 94 aOpenWindowInfo = null, 95 aOpenerBrowser = null, 96 aTriggeringPrincipal = null, 97 aName = "", 98 aPolicyContainer = null, 99 aSkipLoad = false, 100 aWhere = undefined 101 ) { 102 let win, needToFocusWin; 103 104 // try the current window. if we're in a popup or a taskbar tab, fall 105 // back on the most recent browser window 106 if ( 107 this.win.toolbar.visible && 108 !lazy.TaskbarTabsUtils.isTaskbarTabWindow(this.win) 109 ) { 110 win = this.win; 111 } else { 112 win = BrowserWindowTracker.getTopWindow({ private: aIsPrivate }); 113 needToFocusWin = true; 114 } 115 116 if (!win) { 117 // we couldn't find a suitable window, a new one needs to be opened. 118 return null; 119 } 120 121 if (aIsExternal && (!aURI || aURI.spec == "about:blank")) { 122 win.BrowserCommands.openTab(); // this also focuses the location bar 123 win.focus(); 124 return win.gBrowser.selectedBrowser; 125 } 126 127 // OPEN_NEWTAB_BACKGROUND and OPEN_NEWTAB_FOREGROUND are used by 128 // `window.open` with modifiers. 129 // The last case is OPEN_NEWTAB, which is used by: 130 // * a link with `target="_blank"`, without modifiers 131 // * `window.open` without features, without modifiers 132 let loadInBackground; 133 if (aWhere === Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND) { 134 loadInBackground = true; 135 } else if (aWhere === Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_FOREGROUND) { 136 loadInBackground = false; 137 } else { 138 loadInBackground = lazy.loadDivertedInBackground; 139 } 140 141 const uriString = aURI ? aURI.spec : "about:blank"; 142 const tabOptions = { 143 triggeringPrincipal: aTriggeringPrincipal, 144 referrerInfo: aReferrerInfo, 145 userContextId: aUserContextId, 146 fromExternal: aIsExternal, 147 inBackground: loadInBackground, 148 forceNotRemote: aForceNotRemote, 149 openWindowInfo: aOpenWindowInfo, 150 openerBrowser: aOpenerBrowser, 151 name: aName, 152 policyContainer: aPolicyContainer, 153 skipLoad: aSkipLoad, 154 }; 155 156 let tab; 157 if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT) { 158 tab = win.gBrowser.addAdjacentTab( 159 win.gBrowser.selectedTab, 160 uriString, 161 tabOptions 162 ); 163 } else { 164 tab = win.gBrowser.addTab(uriString, tabOptions); 165 } 166 167 let browser = win.gBrowser.getBrowserForTab(tab); 168 169 if (needToFocusWin || (!loadInBackground && aIsExternal)) { 170 win.focus(); 171 } 172 173 return browser; 174 } 175 176 /** 177 * @type {nsIBrowserDOMWindow["createContentWindow"]} 178 */ 179 createContentWindow( 180 aURI, 181 aOpenWindowInfo, 182 aWhere, 183 aFlags, 184 aTriggeringPrincipal, 185 aPolicyContainer 186 ) { 187 return this.#getContentWindowOrOpenURI( 188 null, 189 aOpenWindowInfo, 190 aWhere, 191 aFlags, 192 aTriggeringPrincipal, 193 aPolicyContainer, 194 true 195 ); 196 } 197 198 /** 199 * @type {nsIBrowserDOMWindow["openURI"]} 200 */ 201 openURI( 202 aURI, 203 aOpenWindowInfo, 204 aWhere, 205 aFlags, 206 aTriggeringPrincipal, 207 aPolicyContainer 208 ) { 209 if (!aURI) { 210 console.error("openURI should only be called with a valid URI"); 211 throw Components.Exception("", Cr.NS_ERROR_FAILURE); 212 } 213 return this.#getContentWindowOrOpenURI( 214 aURI, 215 aOpenWindowInfo, 216 aWhere, 217 aFlags, 218 aTriggeringPrincipal, 219 aPolicyContainer, 220 false 221 ); 222 } 223 224 /** 225 * @param {nsIURI} aURI 226 * @param {nsIOpenWindowInfo} aOpenWindowInfo 227 * @param {i16} aWhere 228 * @param {i32} aFlags 229 * @param {nsIPrincipal} aTriggeringPrincipal 230 * @param {nsIPolicyContainer} aPolicyContainer 231 * @param {boolean} aSkipLoad 232 * @returns {BrowsingContext} 233 */ 234 #getContentWindowOrOpenURI( 235 aURI, 236 aOpenWindowInfo, 237 aWhere, 238 aFlags, 239 aTriggeringPrincipal, 240 aPolicyContainer, 241 aSkipLoad 242 ) { 243 var browsingContext = null; 244 var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); 245 var guessUserContextIdEnabled = 246 isExternal && 247 !Services.prefs.getBoolPref( 248 "browser.link.force_default_user_context_id_for_external_opens", 249 false 250 ); 251 var openingUserContextId = 252 (guessUserContextIdEnabled && 253 lazy.URILoadingHelper.guessUserContextId(aURI)) || 254 Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID; 255 256 if (aOpenWindowInfo && isExternal) { 257 console.error( 258 "BrowserDOMWindow.openURI did not expect aOpenWindowInfo to be " + 259 "passed if the context is OPEN_EXTERNAL." 260 ); 261 throw Components.Exception("", Cr.NS_ERROR_FAILURE); 262 } 263 264 if (isExternal && aURI && aURI.schemeIs("chrome")) { 265 dump("use --chrome command-line option to load external chrome urls\n"); 266 return null; 267 } 268 269 if (isExternal) { 270 lazy.NimbusFeatures.externalLinkHandling.recordExposureEvent({ 271 once: true, 272 }); 273 } 274 275 if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) { 276 /** @type {number} proxy for `browser.link.open_newwindow.override.external` */ 277 const externalLinkOpeningBehavior = 278 lazy.NimbusFeatures.externalLinkHandling.getVariable("openBehavior"); 279 if (isExternal && externalLinkOpeningBehavior != -1) { 280 aWhere = externalLinkOpeningBehavior; 281 } else { 282 aWhere = Services.prefs.getIntPref("browser.link.open_newwindow"); 283 } 284 } 285 286 let referrerInfo; 287 if (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_REFERRER) { 288 referrerInfo = new lazy.ReferrerInfo( 289 Ci.nsIReferrerInfo.EMPTY, 290 false, 291 null 292 ); 293 } else if ( 294 aOpenWindowInfo && 295 aOpenWindowInfo.parent && 296 aOpenWindowInfo.parent.window 297 ) { 298 referrerInfo = new lazy.ReferrerInfo( 299 aOpenWindowInfo.parent.window.document.referrerInfo.referrerPolicy, 300 true, 301 Services.io.newURI(aOpenWindowInfo.parent.window.location.href) 302 ); 303 } else { 304 referrerInfo = new lazy.ReferrerInfo( 305 Ci.nsIReferrerInfo.EMPTY, 306 true, 307 null 308 ); 309 } 310 311 let isPrivate = aOpenWindowInfo 312 ? aOpenWindowInfo.originAttributes.privateBrowsingId != 0 313 : PrivateBrowsingUtils.isWindowPrivate(this.win); 314 315 switch (aWhere) { 316 case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW: { 317 // FIXME: Bug 408379. So how come this doesn't send the 318 // referrer like the other loads do? 319 var url = aURI && aURI.spec; 320 let features = "all,dialog=no"; 321 if (isPrivate) { 322 features += ",private"; 323 } 324 // Pass all params to openDialog to ensure that "url" isn't passed through 325 // loadOneOrMoreURIs, which splits based on "|" 326 try { 327 let extraOptions = Cc[ 328 "@mozilla.org/hash-property-bag;1" 329 ].createInstance(Ci.nsIWritablePropertyBag2); 330 extraOptions.setPropertyAsBool("fromExternal", isExternal); 331 332 this.win.openDialog( 333 AppConstants.BROWSER_CHROME_URL, 334 "_blank", 335 features, 336 // window.arguments 337 url, 338 extraOptions, 339 null, 340 null, 341 null, 342 null, 343 null, 344 null, 345 aTriggeringPrincipal, 346 null, 347 aPolicyContainer, 348 aOpenWindowInfo 349 ); 350 // At this point, the new browser window is just starting to load, and 351 // hasn't created the content <browser> that we should return. 352 // If the caller of this function is originating in C++, they can pass a 353 // callback in nsOpenWindowInfo and it will be invoked when the browsing 354 // context for a newly opened window is ready. 355 browsingContext = null; 356 } catch (ex) { 357 console.error(ex); 358 } 359 break; 360 } 361 case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB: 362 case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND: 363 case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_FOREGROUND: 364 case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT: { 365 // If we have an opener, that means that the caller is expecting access 366 // to the nsIDOMWindow of the opened tab right away. For e10s windows, 367 // this means forcing the newly opened browser to be non-remote so that 368 // we can hand back the nsIDOMWindow. DocumentLoadListener will do the 369 // job of shuttling off the newly opened browser to run in the right 370 // process once it starts loading a URI. 371 let forceNotRemote = aOpenWindowInfo && !aOpenWindowInfo.isRemote; 372 let userContextId = aOpenWindowInfo 373 ? aOpenWindowInfo.originAttributes.userContextId 374 : openingUserContextId; 375 let browser = this.#openURIInNewTab( 376 aURI, 377 referrerInfo, 378 isPrivate, 379 isExternal, 380 forceNotRemote, 381 userContextId, 382 aOpenWindowInfo, 383 aOpenWindowInfo?.parent?.top.embedderElement, 384 aTriggeringPrincipal, 385 "", 386 aPolicyContainer, 387 aSkipLoad, 388 aWhere 389 ); 390 if (browser) { 391 browsingContext = browser.browsingContext; 392 } 393 break; 394 } 395 case Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER: { 396 let browser = 397 this.win.PrintUtils.handleStaticCloneCreatedForPrint(aOpenWindowInfo); 398 if (browser) { 399 browsingContext = browser.browsingContext; 400 } 401 break; 402 } 403 default: 404 // OPEN_CURRENTWINDOW or an illegal value 405 browsingContext = this.win.gBrowser.selectedBrowser.browsingContext; 406 if (aURI) { 407 let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; 408 if (isExternal) { 409 loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL; 410 } else if (!aTriggeringPrincipal.isSystemPrincipal) { 411 // XXX this code must be reviewed and changed when bug 1616353 412 // lands. 413 loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIRST_LOAD; 414 } 415 // This should ideally be able to call loadURI with the actual URI. 416 // However, that would bypass some styles of fixup (notably Windows 417 // paths passed as "URI"s), so this needs some further thought. It 418 // should be addressed in bug 1815509. 419 this.win.gBrowser.fixupAndLoadURIString(aURI.spec, { 420 triggeringPrincipal: aTriggeringPrincipal, 421 policyContainer: aPolicyContainer, 422 loadFlags, 423 referrerInfo, 424 }); 425 } 426 if (!lazy.loadDivertedInBackground) { 427 this.win.focus(); 428 } 429 } 430 return browsingContext; 431 } 432 433 /** 434 * @type {nsIBrowserDOMWindow["createContentWindowInFrame"]} 435 */ 436 createContentWindowInFrame(aURI, aParams, aWhere, aFlags, aName) { 437 // Passing a null-URI to only create the content window, 438 // and pass true for aSkipLoad to prevent loading of 439 // about:blank 440 return this.#getContentWindowOrOpenURIInFrame( 441 null, 442 aParams, 443 aWhere, 444 aFlags, 445 aName, 446 true 447 ); 448 } 449 450 /** 451 * @type {nsIBrowserDOMWindow["openURIInFrame"]} 452 */ 453 openURIInFrame(aURI, aParams, aWhere, aFlags, aName) { 454 return this.#getContentWindowOrOpenURIInFrame( 455 aURI, 456 aParams, 457 aWhere, 458 aFlags, 459 aName, 460 false 461 ); 462 } 463 464 /** 465 * @param {nsIURI} aURI 466 * @param {nsIOpenURIInFrameParams} aParams 467 * @param {i16} aWhere 468 * @param {i32} aFlags 469 * @param {string} aName 470 * @param {boolean} aSkipLoad 471 * @returns {Element} 472 */ 473 #getContentWindowOrOpenURIInFrame( 474 aURI, 475 aParams, 476 aWhere, 477 aFlags, 478 aName, 479 aSkipLoad 480 ) { 481 if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER) { 482 return this.win.PrintUtils.handleStaticCloneCreatedForPrint( 483 aParams.openWindowInfo 484 ); 485 } 486 487 if ( 488 aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB && 489 aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND && 490 aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_FOREGROUND 491 ) { 492 dump("Error: openURIInFrame can only open in new tabs or print"); 493 return null; 494 } 495 496 var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); 497 498 var userContextId = 499 aParams.openerOriginAttributes && 500 "userContextId" in aParams.openerOriginAttributes 501 ? aParams.openerOriginAttributes.userContextId 502 : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID; 503 504 return this.#openURIInNewTab( 505 aURI, 506 aParams.referrerInfo, 507 aParams.isPrivate, 508 isExternal, 509 false, 510 userContextId, 511 aParams.openWindowInfo, 512 aParams.openerBrowser, 513 aParams.triggeringPrincipal, 514 aName, 515 aParams.policyContainer, 516 aSkipLoad, 517 aWhere 518 ); 519 } 520 521 /** 522 * @type {nsIBrowserDOMWindow["canClose"]} 523 */ 524 canClose() { 525 return this.win.CanCloseWindow(); 526 } 527 528 /** 529 * @type {nsIBrowserDOMWindow["tabCount"]} 530 */ 531 get tabCount() { 532 return this.win.gBrowser.tabs.length; 533 } 534 } 535 536 BrowserDOMWindow.prototype.QueryInterface = ChromeUtils.generateQI([ 537 "nsIBrowserDOMWindow", 538 ]);