Bug 1890689 Don't pretend to pre-buffer with DynamicResampler r=pehrsons
[gecko.git] / browser / modules / URILoadingHelper.sys.mjs
blob413a28af3689fc89a197a46d4d24942c6ea76cdd
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
6 import { BrowserUtils } from "resource://gre/modules/BrowserUtils.sys.mjs";
7 import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
9 const lazy = {};
11 ChromeUtils.defineESModuleGetters(lazy, {
12   AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
13   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
14 });
16 ChromeUtils.defineLazyGetter(lazy, "ReferrerInfo", () =>
17   Components.Constructor(
18     "@mozilla.org/referrer-info;1",
19     "nsIReferrerInfo",
20     "init"
21   )
24 function saveLink(window, url, params) {
25   if ("isContentWindowPrivate" in params) {
26     window.saveURL(
27       url,
28       null,
29       null,
30       null,
31       true,
32       true,
33       params.referrerInfo,
34       null,
35       null,
36       params.isContentWindowPrivate,
37       params.originPrincipal
38     );
39   } else {
40     if (!params.initiatingDoc) {
41       console.error(
42         "openUILink/openLinkIn was called with " +
43           "where == 'save' but without initiatingDoc.  See bug 814264."
44       );
45       return;
46     }
47     window.saveURL(
48       url,
49       null,
50       null,
51       null,
52       true,
53       true,
54       params.referrerInfo,
55       null,
56       params.initiatingDoc
57     );
58   }
61 function openInWindow(url, params, sourceWindow) {
62   let {
63     referrerInfo,
64     forceNonPrivate,
65     triggeringRemoteType,
66     forceAllowDataURI,
67     globalHistoryOptions,
68     allowThirdPartyFixup,
69     userContextId,
70     postData,
71     originPrincipal,
72     originStoragePrincipal,
73     triggeringPrincipal,
74     csp,
75     resolveOnContentBrowserCreated,
76   } = params;
77   let features = "chrome,dialog=no,all";
78   if (params.private) {
79     features += ",private";
80     // To prevent regular browsing data from leaking to private browsing sites,
81     // strip the referrer when opening a new private window. (See Bug: 1409226)
82     referrerInfo = new lazy.ReferrerInfo(
83       referrerInfo.referrerPolicy,
84       false,
85       referrerInfo.originalReferrer
86     );
87   } else if (forceNonPrivate) {
88     features += ",non-private";
89   }
91   // This propagates to window.arguments.
92   var sa = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
94   var wuri = Cc["@mozilla.org/supports-string;1"].createInstance(
95     Ci.nsISupportsString
96   );
97   wuri.data = url;
99   let extraOptions = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
100     Ci.nsIWritablePropertyBag2
101   );
102   if (triggeringRemoteType) {
103     extraOptions.setPropertyAsACString(
104       "triggeringRemoteType",
105       triggeringRemoteType
106     );
107   }
108   if (params.hasValidUserGestureActivation !== undefined) {
109     extraOptions.setPropertyAsBool(
110       "hasValidUserGestureActivation",
111       params.hasValidUserGestureActivation
112     );
113   }
114   if (forceAllowDataURI) {
115     extraOptions.setPropertyAsBool("forceAllowDataURI", true);
116   }
117   if (params.fromExternal !== undefined) {
118     extraOptions.setPropertyAsBool("fromExternal", params.fromExternal);
119   }
120   if (globalHistoryOptions?.triggeringSponsoredURL) {
121     extraOptions.setPropertyAsACString(
122       "triggeringSponsoredURL",
123       globalHistoryOptions.triggeringSponsoredURL
124     );
125     if (globalHistoryOptions.triggeringSponsoredURLVisitTimeMS) {
126       extraOptions.setPropertyAsUint64(
127         "triggeringSponsoredURLVisitTimeMS",
128         globalHistoryOptions.triggeringSponsoredURLVisitTimeMS
129       );
130     }
131   }
132   if (params.wasSchemelessInput !== undefined) {
133     extraOptions.setPropertyAsBool(
134       "wasSchemelessInput",
135       params.wasSchemelessInput
136     );
137   }
139   var allowThirdPartyFixupSupports = Cc[
140     "@mozilla.org/supports-PRBool;1"
141   ].createInstance(Ci.nsISupportsPRBool);
142   allowThirdPartyFixupSupports.data = allowThirdPartyFixup;
144   var userContextIdSupports = Cc[
145     "@mozilla.org/supports-PRUint32;1"
146   ].createInstance(Ci.nsISupportsPRUint32);
147   userContextIdSupports.data = userContextId;
149   sa.appendElement(wuri);
150   sa.appendElement(extraOptions);
151   sa.appendElement(referrerInfo);
152   sa.appendElement(postData);
153   sa.appendElement(allowThirdPartyFixupSupports);
154   sa.appendElement(userContextIdSupports);
155   sa.appendElement(originPrincipal);
156   sa.appendElement(originStoragePrincipal);
157   sa.appendElement(triggeringPrincipal);
158   sa.appendElement(null); // allowInheritPrincipal
159   sa.appendElement(csp);
161   let win;
163   // Returns a promise that will be resolved when the new window's startup is finished.
164   function waitForWindowStartup() {
165     return new Promise(resolve => {
166       const delayedStartupObserver = aSubject => {
167         if (aSubject == win) {
168           Services.obs.removeObserver(
169             delayedStartupObserver,
170             "browser-delayed-startup-finished"
171           );
172           resolve();
173         }
174       };
175       Services.obs.addObserver(
176         delayedStartupObserver,
177         "browser-delayed-startup-finished"
178       );
179     });
180   }
182   if (params.frameID != undefined && sourceWindow) {
183     // Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
184     // event if it contains the expected frameID params.
185     // (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
186     // opening a new window using the keyboard shortcut).
187     const sourceTabBrowser = sourceWindow.gBrowser.selectedBrowser;
188     waitForWindowStartup().then(() => {
189       Services.obs.notifyObservers(
190         {
191           wrappedJSObject: {
192             url,
193             createdTabBrowser: win.gBrowser.selectedBrowser,
194             sourceTabBrowser,
195             sourceFrameID: params.frameID,
196           },
197         },
198         "webNavigation-createdNavigationTarget"
199       );
200     });
201   }
203   if (resolveOnContentBrowserCreated) {
204     waitForWindowStartup().then(() =>
205       resolveOnContentBrowserCreated(win.gBrowser.selectedBrowser)
206     );
207   }
209   win = Services.ww.openWindow(
210     sourceWindow,
211     AppConstants.BROWSER_CHROME_URL,
212     null,
213     features,
214     sa
215   );
218 function openInCurrentTab(targetBrowser, url, uriObj, params) {
219   let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
221   if (params.allowThirdPartyFixup) {
222     flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
223     flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
224   }
225   // LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL isn't supported for javascript URIs,
226   // i.e. it causes them not to load at all. Callers should strip
227   // "javascript:" from pasted strings to prevent blank tabs
228   if (!params.allowInheritPrincipal) {
229     flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
230   }
232   if (params.allowPopups) {
233     flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_POPUPS;
234   }
235   if (params.indicateErrorPageLoad) {
236     flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ERROR_LOAD_CHANGES_RV;
237   }
238   if (params.forceAllowDataURI) {
239     flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
240   }
241   if (params.fromExternal) {
242     flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
243   }
245   let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler;
246   if (
247     params.forceAboutBlankViewerInCurrent &&
248     (!uriObj ||
249       Services.io.getDynamicProtocolFlags(uriObj) &
250         URI_INHERITS_SECURITY_CONTEXT)
251   ) {
252     // Unless we know for sure we're not inheriting principals,
253     // force the about:blank viewer to have the right principal:
254     targetBrowser.createAboutBlankDocumentViewer(
255       params.originPrincipal,
256       params.originStoragePrincipal
257     );
258   }
260   let {
261     triggeringPrincipal,
262     csp,
263     referrerInfo,
264     postData,
265     userContextId,
266     hasValidUserGestureActivation,
267     globalHistoryOptions,
268     triggeringRemoteType,
269     wasSchemelessInput,
270   } = params;
272   targetBrowser.fixupAndLoadURIString(url, {
273     triggeringPrincipal,
274     csp,
275     flags,
276     referrerInfo,
277     postData,
278     userContextId,
279     hasValidUserGestureActivation,
280     globalHistoryOptions,
281     triggeringRemoteType,
282     wasSchemelessInput,
283   });
284   params.resolveOnContentBrowserCreated?.(targetBrowser);
287 function updatePrincipals(window, params) {
288   let { userContextId } = params;
289   // Teach the principal about the right OA to use, e.g. in case when
290   // opening a link in a new private window, or in a new container tab.
291   // Please note we do not have to do that for SystemPrincipals and we
292   // can not do it for NullPrincipals since NullPrincipals are only
293   // identical if they actually are the same object (See Bug: 1346759)
294   function useOAForPrincipal(principal) {
295     if (principal && principal.isContentPrincipal) {
296       let privateBrowsingId =
297         params.private ||
298         (window && PrivateBrowsingUtils.isWindowPrivate(window));
299       let attrs = {
300         userContextId,
301         privateBrowsingId,
302         firstPartyDomain: principal.originAttributes.firstPartyDomain,
303       };
304       return Services.scriptSecurityManager.principalWithOA(principal, attrs);
305     }
306     return principal;
307   }
308   params.originPrincipal = useOAForPrincipal(params.originPrincipal);
309   params.originStoragePrincipal = useOAForPrincipal(
310     params.originStoragePrincipal
311   );
312   params.triggeringPrincipal = useOAForPrincipal(params.triggeringPrincipal);
315 export const URILoadingHelper = {
316   /* openLinkIn opens a URL in a place specified by the parameter |where|.
317    *
318    * The params object is the same as for `openLinkIn` and documented below.
319    *
320    * @param {String}  where
321    *   |where| can be:
322    *    "current"     current tab            (if there aren't any browser windows, then in a new window instead)
323    *    "tab"         new tab                (if there aren't any browser windows, then in a new window instead)
324    *    "tabshifted"  same as "tab" but in background if default is to select new tabs, and vice versa
325    *    "window"      new window
326    *    "save"        save to disk (with no filename hint!)
327    *
328    * @param {Object}  params
329    *
330    * Options relating to what tab/window to use and how to open it:
331    *
332    * @param {boolean} params.private
333    *                  Load the URL in a private window.
334    * @param {boolean} params.forceNonPrivate
335    *                  Force the load to happen in non-private windows.
336    * @param {boolean} params.relatedToCurrent
337    *                  Whether new tabs should go immediately next to the current tab.
338    * @param {Element} params.targetBrowser
339    *                  The browser to use for the load. Only used if where == "current".
340    * @param {boolean} params.inBackground
341    *                  If explicitly true or false, whether to switch to the tab immediately.
342    *                  If null, will switch to the tab if `forceForeground` was true. If
343    *                  neither is passed, will defer to the user preference browser.tabs.loadInBackground.
344    * @param {boolean} params.forceForeground
345    *                  Ignore the user preference and load in the foreground.
346    * @param {boolean} params.allowPinnedTabHostChange
347    *                  Allow even a pinned tab to change hosts.
348    * @param {boolean} params.allowPopups
349    *                  whether the link is allowed to open in a popup window (ie one with no browser
350    *                  chrome)
351    * @param {boolean} params.skipTabAnimation
352    *                  Skip the tab opening animation.
353    * @param {Element} params.openerBrowser
354    *                  The browser that started the load.
355    * @param {boolean} params.avoidBrowserFocus
356    *                  Don't focus the browser element immediately after starting
357    *                  the load. Used by the URL bar to avoid leaking user input
358    *                  into web content, see bug 1641287.
359    *
360    * Options relating to the load itself:
361    *
362    * @param {boolean} params.allowThirdPartyFixup
363    *                  Allow transforming the 'url' into a search query.
364    * @param {nsIInputStream} params.postData
365    *                  Data to post as part of the request.
366    * @param {nsIReferrerInfo} params.referrerInfo
367    *                  Referrer info for the request.
368    * @param {boolean} params.indicateErrorPageLoad
369    *                  Whether docshell should throw an exception (i.e. return non-NS_OK)
370    *                  if the load fails.
371    * @param {string}  params.charset
372    *                  Character set to use for the load. Only honoured for tabs.
373    *                  Legacy argument - do not use.
374    * @param {string}  params.wasSchemelessInput
375    *                  Whether the search/URL term was without an explicit scheme.
376    *
377    * Options relating to security, whether the load is allowed to happen,
378    * and what cookie container to use for the load:
379    *
380    * @param {boolean} params.forceAllowDataURI
381    *                  Force allow a data URI to load as a toplevel load.
382    * @param {number}  params.userContextId
383    *                  The userContextId (container identifier) to use for the load.
384    * @param {boolean} params.allowInheritPrincipal
385    *                  Allow the load to inherit the triggering principal.
386    * @param {boolean} params.forceAboutBlankViewerInCurrent
387    *                  Force load an about:blank page first. Only used if
388    *                  allowInheritPrincipal is passed or no URL was provided.
389    * @param {nsIPrincipal} params.triggeringPrincipal
390    *                  Triggering principal to pass to docshell for the load.
391    * @param {nsIPrincipal} params.originPrincipal
392    *                  Origin principal to pass to docshell for the load.
393    * @param {nsIPrincipal} params.originStoragePrincipal
394    *                  Storage principal to pass to docshell for the load.
395    * @param {string}  params.triggeringRemoteType
396    *                  The remoteType triggering this load.
397    * @param {nsIContentSecurityPolicy} params.csp
398    *                  The CSP that should apply to the load.
399    * @param {boolean} params.hasValidUserGestureActivation
400    *                  Indicates if a valid user gesture caused this load. This
401    *                  informs e.g. popup blocker decisions.
402    * @param {boolean} params.fromExternal
403    *                  Indicates the load was started outside of the browser,
404    *                  e.g. passed on the commandline or through OS mechanisms.
405    *
406    * Options used to track the load elsewhere
407    *
408    * @param {function} params.resolveOnNewTabCreated
409    *                   This callback will be called when a new tab is created.
410    * @param {function} params.resolveOnContentBrowserCreated
411    *                   This callback will be called with the content browser once it's created.
412    * @param {Object}   params.globalHistoryOptions
413    *                   Used by places to keep track of search related metadata for loads.
414    * @param {Number}   params.frameID
415    *                   Used by webextensions for their loads.
416    *
417    * Options used for where="save" only:
418    *
419    * @param {boolean}  params.isContentWindowPrivate
420    *                   Save content as coming from a private window.
421    * @param {Document} params.initiatingDoc
422    *                   Used to determine where to prompt for a filename.
423    */
424   openLinkIn(window, url, where, params) {
425     if (!where || !url) {
426       return;
427     }
429     let {
430       allowThirdPartyFixup,
431       postData,
432       charset,
433       relatedToCurrent,
434       allowInheritPrincipal,
435       forceAllowDataURI,
436       forceNonPrivate,
437       skipTabAnimation,
438       allowPinnedTabHostChange,
439       userContextId,
440       triggeringPrincipal,
441       originPrincipal,
442       originStoragePrincipal,
443       triggeringRemoteType,
444       csp,
445       resolveOnNewTabCreated,
446       resolveOnContentBrowserCreated,
447       globalHistoryOptions,
448     } = params;
450     // We want to overwrite some things for convenience when passing it to other
451     // methods. To avoid impacting callers, copy the params.
452     params = Object.assign({}, params);
454     if (!params.referrerInfo) {
455       params.referrerInfo = new lazy.ReferrerInfo(
456         Ci.nsIReferrerInfo.EMPTY,
457         true,
458         null
459       );
460     }
462     if (!triggeringPrincipal) {
463       throw new Error("Must load with a triggering Principal");
464     }
466     if (where == "save") {
467       saveLink(window, url, params);
468       return;
469     }
471     // Establish which window we'll load the link in.
472     let w;
473     if (where == "current" && params.targetBrowser) {
474       w = params.targetBrowser.ownerGlobal;
475     } else {
476       w = this.getTargetWindow(window, { forceNonPrivate });
477     }
478     // We don't want to open tabs in popups, so try to find a non-popup window in
479     // that case.
480     if ((where == "tab" || where == "tabshifted") && w && !w.toolbar.visible) {
481       w = this.getTargetWindow(window, {
482         skipPopups: true,
483         forceNonPrivate,
484       });
485       relatedToCurrent = false;
486     }
488     updatePrincipals(w, params);
490     if (!w || where == "window") {
491       openInWindow(url, params, w || window);
492       return;
493     }
495     // We're now committed to loading the link in an existing browser window.
497     // Raise the target window before loading the URI, since loading it may
498     // result in a new frontmost window (e.g. "javascript:window.open('');").
499     w.focus();
501     let targetBrowser;
502     let loadInBackground;
503     let uriObj;
505     if (where == "current") {
506       targetBrowser = params.targetBrowser || w.gBrowser.selectedBrowser;
507       loadInBackground = false;
508       try {
509         uriObj = Services.io.newURI(url);
510       } catch (e) {}
512       // In certain tabs, we restrict what if anything may replace the loaded
513       // page. If a load request bounces off for the currently selected tab,
514       // we'll open a new tab instead.
515       let tab = w.gBrowser.getTabForBrowser(targetBrowser);
516       if (tab == w.FirefoxViewHandler.tab) {
517         where = "tab";
518         targetBrowser = null;
519       } else if (
520         !allowPinnedTabHostChange &&
521         tab.pinned &&
522         url != "about:crashcontent"
523       ) {
524         try {
525           // nsIURI.host can throw for non-nsStandardURL nsIURIs.
526           if (
527             !uriObj ||
528             (!uriObj.schemeIs("javascript") &&
529               targetBrowser.currentURI.host != uriObj.host)
530           ) {
531             where = "tab";
532             targetBrowser = null;
533           }
534         } catch (err) {
535           where = "tab";
536           targetBrowser = null;
537         }
538       }
539     } else {
540       // `where` is "tab" or "tabshifted", so we'll load the link in a new tab.
541       loadInBackground = params.inBackground;
542       if (loadInBackground == null) {
543         loadInBackground = params.forceForeground
544           ? false
545           : Services.prefs.getBoolPref("browser.tabs.loadInBackground");
546       }
547     }
549     let focusUrlBar = false;
551     switch (where) {
552       case "current":
553         openInCurrentTab(targetBrowser, url, uriObj, params);
555         // Don't focus the content area if focus is in the address bar and we're
556         // loading the New Tab page.
557         focusUrlBar =
558           w.document.activeElement == w.gURLBar.inputField &&
559           w.isBlankPageURL(url);
560         break;
561       case "tabshifted":
562         loadInBackground = !loadInBackground;
563       // fall through
564       case "tab":
565         focusUrlBar =
566           !loadInBackground &&
567           w.isBlankPageURL(url) &&
568           !lazy.AboutNewTab.willNotifyUser;
570         let tabUsedForLoad = w.gBrowser.addTab(url, {
571           referrerInfo: params.referrerInfo,
572           charset,
573           postData,
574           inBackground: loadInBackground,
575           allowThirdPartyFixup,
576           relatedToCurrent,
577           skipAnimation: skipTabAnimation,
578           userContextId,
579           originPrincipal,
580           originStoragePrincipal,
581           triggeringPrincipal,
582           allowInheritPrincipal,
583           triggeringRemoteType,
584           csp,
585           forceAllowDataURI,
586           focusUrlBar,
587           openerBrowser: params.openerBrowser,
588           fromExternal: params.fromExternal,
589           globalHistoryOptions,
590           wasSchemelessInput: params.wasSchemelessInput,
591         });
592         targetBrowser = tabUsedForLoad.linkedBrowser;
594         resolveOnNewTabCreated?.(targetBrowser);
595         resolveOnContentBrowserCreated?.(targetBrowser);
597         if (params.frameID != undefined && w) {
598           // Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
599           // event if it contains the expected frameID params.
600           // (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
601           // opening a new tab using the keyboard shortcut).
602           Services.obs.notifyObservers(
603             {
604               wrappedJSObject: {
605                 url,
606                 createdTabBrowser: targetBrowser,
607                 sourceTabBrowser: w.gBrowser.selectedBrowser,
608                 sourceFrameID: params.frameID,
609               },
610             },
611             "webNavigation-createdNavigationTarget"
612           );
613         }
614         break;
615     }
617     if (
618       !params.avoidBrowserFocus &&
619       !focusUrlBar &&
620       targetBrowser == w.gBrowser.selectedBrowser
621     ) {
622       // Focus the content, but only if the browser used for the load is selected.
623       targetBrowser.focus();
624     }
625   },
627   /**
628    * Finds a browser window suitable for opening a link matching the
629    * requirements given in the `params` argument. If the current window matches
630    * the requirements then it is returned otherwise the top-most window that
631    * matches will be returned.
632    *
633    * @param {Window} window - The current window.
634    * @param {Object} params - Parameters for selecting the window.
635    * @param {boolean} params.skipPopups - Require a non-popup window.
636    * @param {boolean} params.forceNonPrivate - Require a non-private window.
637    * @returns {Window | null} A matching browser window or null if none matched.
638    */
639   getTargetWindow(window, { skipPopups, forceNonPrivate } = {}) {
640     let { top } = window;
641     // If this is called in a browser window, use that window regardless of
642     // whether it's the frontmost window, since commands can be executed in
643     // background windows (bug 626148).
644     if (
645       top.document.documentElement.getAttribute("windowtype") ==
646         "navigator:browser" &&
647       (!skipPopups || top.toolbar.visible) &&
648       (!forceNonPrivate || !PrivateBrowsingUtils.isWindowPrivate(top))
649     ) {
650       return top;
651     }
653     return lazy.BrowserWindowTracker.getTopWindow({
654       private: !forceNonPrivate && PrivateBrowsingUtils.isWindowPrivate(window),
655       allowPopups: !skipPopups,
656     });
657   },
659   /**
660    * openUILink handles clicks on UI elements that cause URLs to load.
661    *
662    * @param {string} url
663    * @param {Event | Object} event Event or JSON object representing an Event
664    * @param {Boolean | Object} aIgnoreButton
665    *                           Boolean or object with the same properties as
666    *                           accepted by openLinkIn, plus "ignoreButton"
667    *                           and "ignoreAlt".
668    * @param {Boolean} aIgnoreAlt
669    * @param {Boolean} aAllowThirdPartyFixup
670    * @param {Object} aPostData
671    * @param {Object} aReferrerInfo
672    */
673   openUILink(
674     window,
675     url,
676     event,
677     aIgnoreButton,
678     aIgnoreAlt,
679     aAllowThirdPartyFixup,
680     aPostData,
681     aReferrerInfo
682   ) {
683     event = BrowserUtils.getRootEvent(event);
684     let params;
686     if (aIgnoreButton && typeof aIgnoreButton == "object") {
687       params = aIgnoreButton;
689       // don't forward "ignoreButton" and "ignoreAlt" to openLinkIn
690       aIgnoreButton = params.ignoreButton;
691       aIgnoreAlt = params.ignoreAlt;
692       delete params.ignoreButton;
693       delete params.ignoreAlt;
694     } else {
695       params = {
696         allowThirdPartyFixup: aAllowThirdPartyFixup,
697         postData: aPostData,
698         referrerInfo: aReferrerInfo,
699         initiatingDoc: event ? event.target.ownerDocument : null,
700       };
701     }
703     if (!params.triggeringPrincipal) {
704       throw new Error(
705         "Required argument triggeringPrincipal missing within openUILink"
706       );
707     }
709     let where = BrowserUtils.whereToOpenLink(event, aIgnoreButton, aIgnoreAlt);
710     params.forceForeground ??= true;
711     this.openLinkIn(window, url, where, params);
712   },
714   /* openTrustedLinkIn will attempt to open the given URI using the SystemPrincipal
715    * as the trigeringPrincipal, unless a more specific Principal is provided.
716    *
717    * Otherwise, parameters are the same as openLinkIn, but we will set `forceForeground`
718    * to true.
719    */
720   openTrustedLinkIn(window, url, where, params = {}) {
721     if (!params.triggeringPrincipal) {
722       params.triggeringPrincipal =
723         Services.scriptSecurityManager.getSystemPrincipal();
724     }
726     params.forceForeground ??= true;
727     this.openLinkIn(window, url, where, params);
728   },
730   /* openWebLinkIn will attempt to open the given URI using the NullPrincipal
731    * as the triggeringPrincipal, unless a more specific Principal is provided.
732    *
733    * Otherwise, parameters are the same as openLinkIn, but we will set `forceForeground`
734    * to true.
735    */
736   openWebLinkIn(window, url, where, params = {}) {
737     if (!params.triggeringPrincipal) {
738       params.triggeringPrincipal =
739         Services.scriptSecurityManager.createNullPrincipal({});
740     }
741     if (params.triggeringPrincipal.isSystemPrincipal) {
742       throw new Error(
743         "System principal should never be passed into openWebLinkIn()"
744       );
745     }
746     params.forceForeground ??= true;
747     this.openLinkIn(window, url, where, params);
748   },
750   /**
751    * Given a URI, guess which container to use to open it. This is used for external
752    * openers as a quality of life improvement (e.g. to open a document into the container
753    * where you are logged in to the service that hosts it).
754    * matches will be returned.
755    * For now this can only use currently-open tabs, until history is tagged with the
756    * container id (https://bugzilla.mozilla.org/show_bug.cgi?id=1283320).
757    *
758    * @param {nsIURI} aURI - The URI being opened.
759    * @returns {number | null} The guessed userContextId, or null if none.
760    */
761   guessUserContextId(aURI) {
762     let host;
763     try {
764       host = aURI.host;
765     } catch (e) {}
766     if (!host) {
767       return null;
768     }
769     const containerScores = new Map();
770     let guessedUserContextId = null;
771     let maxCount = 0;
772     for (let win of lazy.BrowserWindowTracker.orderedWindows) {
773       for (let tab of win.gBrowser.visibleTabs) {
774         let { userContextId } = tab;
775         let currentURIHost = null;
776         try {
777           currentURIHost = tab.linkedBrowser.currentURI.host;
778         } catch (e) {}
780         if (currentURIHost == host) {
781           let count = (containerScores.get(userContextId) ?? 0) + 1;
782           containerScores.set(userContextId, count);
783           if (count > maxCount) {
784             guessedUserContextId = userContextId;
785             maxCount = count;
786           }
787         }
788       }
789     }
791     return guessedUserContextId;
792   },