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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
9 XPCOMUtils.defineLazyPreferenceGetter(
11 "useSeparateFileUriProcess",
12 "browser.tabs.remote.separateFileUriProcess",
15 XPCOMUtils.defineLazyPreferenceGetter(
17 "useSeparatePrivilegedAboutContentProcess",
18 "browser.tabs.remote.separatePrivilegedContentProcess",
21 XPCOMUtils.defineLazyPreferenceGetter(
23 "separatePrivilegedMozillaWebContentProcess",
24 "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess",
27 XPCOMUtils.defineLazyPreferenceGetter(
29 "separatedMozillaDomains",
30 "browser.tabs.remote.separatedMozillaDomains",
36 XPCOMUtils.defineLazyPreferenceGetter(
38 "useCrossOriginOpenerPolicy",
39 "browser.tabs.remote.useCrossOriginOpenerPolicy",
42 XPCOMUtils.defineLazyServiceGetter(
44 "serializationHelper",
45 "@mozilla.org/network/serialization-helper;1",
46 "nsISerializationHelper"
48 XPCOMUtils.defineLazyServiceGetter(
51 "@mozilla.org/uriloader/external-protocol-service;1",
52 "nsIExternalProtocolService"
55 function getOriginalReaderModeURI(aURI) {
57 let searchParams = new URLSearchParams(aURI.query);
58 if (searchParams.has("url")) {
59 return Services.io.newURI(searchParams.get("url"));
65 const NOT_REMOTE = null;
67 // These must match the similar ones in RemoteTypes.h, ProcInfo.h, ChromeUtils.webidl and ChromeUtils.cpp
68 const WEB_REMOTE_TYPE = "web";
69 const FISSION_WEB_REMOTE_TYPE = "webIsolated";
70 const WEB_REMOTE_COOP_COEP_TYPE_PREFIX = "webCOOP+COEP=";
71 const FILE_REMOTE_TYPE = "file";
72 const EXTENSION_REMOTE_TYPE = "extension";
73 const PRIVILEGEDABOUT_REMOTE_TYPE = "privilegedabout";
74 const PRIVILEGEDMOZILLA_REMOTE_TYPE = "privilegedmozilla";
75 const SERVICEWORKER_REMOTE_TYPE = "webServiceWorker";
77 // This must start with the WEB_REMOTE_TYPE above.
78 const DEFAULT_REMOTE_TYPE = WEB_REMOTE_TYPE;
80 // This list is duplicated between Navigator.cpp and here because navigator
81 // is not accessible in this context. Please update both if the list changes.
82 const kSafeSchemes = [
109 const STANDARD_SAFE_PROTOCOLS = kSafeSchemes;
111 // Note that even if the scheme fits the criteria for a web-handled scheme
112 // (ie it is compatible with the checks registerProtocolHandler uses), it may
113 // not be web-handled - it could still be handled via the OS by another app.
114 function hasPotentiallyWebHandledScheme({ scheme }) {
115 // Note that `scheme` comes from a URI object so is already lowercase.
116 if (kSafeSchemes.includes(scheme)) {
119 if (!scheme.startsWith("web+") || scheme.length < 5) {
122 // Check the rest of the scheme only consists of ascii a-z chars
123 return /^[a-z]+$/.test(scheme.substr("web+".length));
126 function validatedWebRemoteType(
127 aPreferredRemoteType,
132 aOriginAttributes = {}
134 // To load into the Privileged Mozilla Content Process you must be https,
135 // and be an exact match or a subdomain of an allowlisted domain.
136 // This code is duplicated in ProcessIolation.cpp, please update both.
138 lazy.separatePrivilegedMozillaWebContentProcess &&
139 aTargetUri.asciiHost &&
140 aTargetUri.scheme == "https" &&
141 lazy.separatedMozillaDomains.some(function (val) {
143 aTargetUri.asciiHost == val || aTargetUri.asciiHost.endsWith("." + val)
147 return PRIVILEGEDMOZILLA_REMOTE_TYPE;
150 // If we're in the parent and we were passed a web-handled scheme,
151 // transform it now to avoid trying to load it in the wrong process.
152 if (aRemoteSubframes && hasPotentiallyWebHandledScheme(aTargetUri)) {
154 Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT &&
155 Services.appinfo.remoteType.startsWith(FISSION_WEB_REMOTE_TYPE + "=")
157 // If we're in a child process, assume we're OK to load this non-web
158 // URL for now. We'll either load it externally or re-evaluate once
159 // we know the "real" URL to which we'll redirect.
160 return Services.appinfo.remoteType;
163 // This doesn't work (throws) in the child - see
164 // https://bugzilla.mozilla.org/show_bug.cgi?id=1589082
165 // Even if it did, it'd cause sync IPC
166 // ( https://bugzilla.mozilla.org/show_bug.cgi?id=1589085 ), and this code
167 // can get called several times per page load so that seems like something
168 // we'd want to avoid.
169 let handlerInfo = lazy.extProtService.getProtocolHandlerInfo(
173 if (!handlerInfo.alwaysAskBeforeHandling) {
174 let app = handlerInfo.preferredApplicationHandler;
175 app.QueryInterface(Ci.nsIWebHandlerApp);
176 // If we get here, the default handler is a web app.
177 // Target to the origin of that web app:
178 let uriStr = app.uriTemplate.replace(/%s/, aTargetUri.spec);
179 aTargetUri = Services.io.newURI(uriStr);
182 // It's not strange for this to throw, we just ignore it and fall through.
186 // If the domain is allow listed to allow it to use file:// URIs, then we have
187 // to run it in a file content process, in case it uses file:// sub-resources.
188 const sm = Services.scriptSecurityManager;
189 if (sm.inFileURIAllowlist(aTargetUri)) {
190 return FILE_REMOTE_TYPE;
193 // If we're within a fission window, extract site information from the URI in
194 // question, and use it to generate an isolated origin.
195 if (aRemoteSubframes) {
196 let originAttributes = {};
197 // Only use specific properties of OriginAttributes in our remoteType
198 let { userContextId, privateBrowsingId, geckoViewSessionContextId } =
203 geckoViewSessionContextId,
206 // Get a principal to use for isolation.
208 if (aResultPrincipal) {
209 targetPrincipal = sm.principalWithOA(aResultPrincipal, originAttributes);
211 targetPrincipal = sm.createContentPrincipal(aTargetUri, originAttributes);
214 // If this is a special webCOOP+COEP= remote type that matches the
215 // principal's siteOrigin, we don't want to override it with webIsolated=
216 // as it's already isolated.
218 aPreferredRemoteType &&
219 aPreferredRemoteType.startsWith(
220 `${WEB_REMOTE_COOP_COEP_TYPE_PREFIX}${targetPrincipal.siteOrigin}`
223 return aPreferredRemoteType;
226 return `${FISSION_WEB_REMOTE_TYPE}=${targetPrincipal.siteOrigin}`;
227 // else fall through and probably return WEB_REMOTE_TYPE
230 if (!aPreferredRemoteType) {
231 return WEB_REMOTE_TYPE;
234 if (aPreferredRemoteType.startsWith(WEB_REMOTE_TYPE)) {
235 return aPreferredRemoteType;
238 return WEB_REMOTE_TYPE;
241 export var E10SUtils = {
245 WEB_REMOTE_COOP_COEP_TYPE_PREFIX,
247 EXTENSION_REMOTE_TYPE,
248 PRIVILEGEDABOUT_REMOTE_TYPE,
249 PRIVILEGEDMOZILLA_REMOTE_TYPE,
250 FISSION_WEB_REMOTE_TYPE,
251 SERVICEWORKER_REMOTE_TYPE,
252 STANDARD_SAFE_PROTOCOLS,
255 * @param aURI The URI of the about page
256 * @return The instance of the nsIAboutModule related to this uri
258 getAboutModule(aURL) {
259 // Needs to match NS_GetAboutModuleName
260 let moduleName = aURL.pathQueryRef.replace(/[#?].*/, "").toLowerCase();
261 let contract = "@mozilla.org/network/protocol/about;1?what=" + moduleName;
263 return Cc[contract].getService(Ci.nsIAboutModule);
265 // Either the about module isn't defined or it is broken. In either case
271 useCrossOriginOpenerPolicy() {
272 return lazy.useCrossOriginOpenerPolicy;
276 _uriStr: function uriStr(aUri) {
277 return aUri ? aUri.spec : "undefined";
280 log: function log() {
282 this._log = console.createInstance({
283 prefix: "ProcessSwitch",
284 maxLogLevel: "Error", // Change to "Debug" the process switching code
287 this._log.debug("Setup logger");
294 * Serialize csp data.
296 * @param {nsIContentSecurity} csp. The csp to serialize.
297 * @return {String} The base64 encoded csp data.
300 let serializedCSP = null;
304 serializedCSP = lazy.serializationHelper.serializeToString(csp);
307 this.log().error(`Failed to serialize csp '${csp}' ${e}`);
309 return serializedCSP;
313 * Deserialize a base64 encoded csp (serialized with
314 * Utils::serializeCSP).
316 * @param {String} csp_b64 A base64 encoded serialized csp.
317 * @return {nsIContentSecurityPolicy} A deserialized csp.
319 deserializeCSP(csp_b64) {
325 let csp = lazy.serializationHelper.deserializeObject(csp_b64);
326 csp.QueryInterface(Ci.nsIContentSecurityPolicy);
329 this.log().error(`Failed to deserialize csp_b64 '${csp_b64}' ${e}`);
334 canLoadURIInRemoteType(
337 aRemoteType = DEFAULT_REMOTE_TYPE,
338 aOriginAttributes = {}
340 // aRemoteType cannot be undefined, as that would cause it to default to
341 // `DEFAULT_REMOTE_TYPE`. This means any falsy remote types are
342 // intentionally `NOT_REMOTE`.
346 this.getRemoteTypeForURI(
361 aPreferredRemoteType = DEFAULT_REMOTE_TYPE,
363 aOriginAttributes = {}
365 if (!aMultiProcess) {
369 // loadURI in browser.js treats null as about:blank
371 aURL = "about:blank";
376 uri = Services.uriFixup.getFixupURIInfo(aURL).preferredURI;
378 // If we have an invalid URI, it's still possible that it might get
379 // fixed-up into a valid URI later on. However, we don't want to return
380 // aPreferredRemoteType here, in case the URI gets fixed-up into
381 // something that wouldn't normally run in that process.
382 return DEFAULT_REMOTE_TYPE;
385 return this.getRemoteTypeForURIObject(uri, {
386 multiProcess: aMultiProcess,
387 remoteSubFrames: aRemoteSubframes,
388 preferredRemoteType: aPreferredRemoteType,
389 currentURI: aCurrentUri,
390 originAttributes: aOriginAttributes,
394 getRemoteTypeForURIObject(aURI, options) {
396 multiProcess = Services.appinfo.browserTabsRemoteAutostart,
397 remoteSubFrames = Services.appinfo.fissionAutostart,
398 preferredRemoteType = DEFAULT_REMOTE_TYPE,
400 resultPrincipal = null,
401 originAttributes = {},
407 switch (aURI.scheme) {
409 // javascript URIs can load in any, they apply to the current document.
410 return preferredRemoteType;
414 // We need data: and blob: URIs to load in any remote process, because
415 // they need to be able to load in whatever is the current process
416 // unless it is non-remote. In that case we don't want to load them in
417 // the parent process, so we load them in the default remote process,
418 // which is sandboxed and limits any risk.
419 return preferredRemoteType == NOT_REMOTE
420 ? DEFAULT_REMOTE_TYPE
421 : preferredRemoteType;
424 return lazy.useSeparateFileUriProcess
426 : DEFAULT_REMOTE_TYPE;
429 let module = this.getAboutModule(aURI);
430 // If the module doesn't exist then an error page will be loading, that
431 // should be ok to load in any process
433 return preferredRemoteType;
436 let flags = module.getURIFlags(aURI);
437 if (flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_EXTENSION_PROCESS) {
438 return WebExtensionPolicy.useRemoteWebExtensions
439 ? EXTENSION_REMOTE_TYPE
443 if (flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD) {
445 flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS &&
446 (lazy.useSeparatePrivilegedAboutContentProcess ||
447 aURI.filePath == "logins" ||
448 // Force about:welcome and about:home into the privileged content process to
449 // workaround code coverage test failures which result from the
450 // workaround in bug 161269. Once that bug is fixed for real,
451 // the about:welcome and about:home case below can be removed.
452 aURI.filePath == "welcome" ||
453 aURI.filePath == "home")
455 return PRIVILEGEDABOUT_REMOTE_TYPE;
458 // When loading about:reader, try to display the document in the same
459 // web remote type as the document it's loading.
460 if (aURI.filePath == "reader") {
461 let readerModeURI = getOriginalReaderModeURI(aURI);
463 let innerRemoteType = this.getRemoteTypeForURIObject(
469 innerRemoteType.startsWith(WEB_REMOTE_TYPE)
471 return innerRemoteType;
476 return DEFAULT_REMOTE_TYPE;
479 // If the about page can load in parent or child, it should be safe to
480 // load in any remote type.
481 if (flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD) {
482 return preferredRemoteType;
488 let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(
489 Ci.nsIXULChromeRegistry
491 if (chromeReg.mustLoadURLRemotely(aURI)) {
492 return DEFAULT_REMOTE_TYPE;
496 chromeReg.canLoadURLRemotely(aURI) &&
497 preferredRemoteType != NOT_REMOTE
499 return DEFAULT_REMOTE_TYPE;
504 case "moz-extension":
505 // Extension iframes should load in the same process
506 // as their outer frame, but that's handled elsewhere.
507 return WebExtensionPolicy.useRemoteWebExtensions
508 ? EXTENSION_REMOTE_TYPE
516 // Protocols used by Thunderbird to display email messages.
520 // WebExtensions may set up protocol handlers for protocol names
521 // beginning with ext+. These may redirect to http(s) pages or to
522 // moz-extension pages. We can't actually tell here where one of
523 // these pages will end up loading but Talos tests use protocol
524 // handlers that redirect to extension pages that rely on this
525 // behavior so a pageloader frame script is injected correctly.
526 // Protocols that redirect to http(s) will just flip back to a
527 // regular content process after the redirect.
528 if (aURI.scheme.startsWith("ext+")) {
529 return WebExtensionPolicy.useRemoteWebExtensions
530 ? EXTENSION_REMOTE_TYPE
534 // For any other nested URIs, we use the innerURI to determine the
535 // remote type. In theory we should use the innermost URI, but some URIs
536 // have fake inner URIs (e.g. about URIs with inner moz-safe-about) and
537 // if such URIs are wrapped in other nested schemes like view-source:,
538 // we don't want to "skip" past "about:" by going straight to the
539 // innermost URI. Any URIs like this will need to be handled in the
540 // cases above, so we don't still end up using the fake inner URI here.
541 if (aURI instanceof Ci.nsINestedURI) {
542 let innerURI = aURI.QueryInterface(Ci.nsINestedURI).innerURI;
543 return this.getRemoteTypeForURIObject(innerURI, options);
546 var log = this.log();
547 log.debug("validatedWebRemoteType()");
548 log.debug(` aPreferredRemoteType: ${preferredRemoteType}`);
549 log.debug(` aTargetUri: ${this._uriStr(aURI)}`);
550 log.debug(` aCurrentUri: ${this._uriStr(currentURI)}`);
551 var remoteType = validatedWebRemoteType(
559 log.debug(` validatedWebRemoteType() returning: ${remoteType}`);
564 makeInputStream(data) {
565 if (typeof data == "string") {
566 let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
567 Ci.nsISupportsCString
570 return stream; // XPConnect will QI this to nsIInputStream for us.
573 let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
574 Ci.nsISupportsCString
576 stream.data = data.content;
580 "@mozilla.org/network/mime-input-stream;1"
581 ].createInstance(Ci.nsIMIMEInputStream);
583 mimeStream.setData(stream);
584 for (let [name, value] of data.headers) {
585 mimeStream.addHeader(name, value);
590 return stream; // XPConnect will QI this to nsIInputStream for us.
594 * Serialize principal data.
596 * @param {nsIPrincipal} principal The principal to serialize.
597 * @return {String} The serialized principal data.
599 serializePrincipal(principal) {
600 let serializedPrincipal = null;
604 serializedPrincipal =
605 Services.scriptSecurityManager.principalToJSON(principal);
608 this.log().error(`Failed to serialize principal '${principal}' ${e}`);
611 return serializedPrincipal;
615 * Deserialize a principal (serialized with serializePrincipal).
617 * @param {String} serializedPincipal A serialized principal.
618 * @return {nsIPrincipal} A deserialized principal.
620 deserializePrincipal(serializedPincipal, fallbackPrincipalCallback = null) {
621 if (!serializedPincipal) {
622 if (!fallbackPrincipalCallback) {
624 "No principal passed to deserializePrincipal and no fallbackPrincipalCallback"
629 return fallbackPrincipalCallback();
634 // The current JSON representation of principal is not stored as base64. We start by checking
635 // if the serialized data starts with '{' to determine if we're using the new JSON representation.
636 // If it doesn't we try the two legacy formats, old JSON and nsISerializable.
637 if (serializedPincipal.startsWith("{")) {
639 Services.scriptSecurityManager.JSONToPrincipal(serializedPincipal);
641 // Both the legacy and legacy JSON representation of principals are stored as base64
642 // The legacy JSON kind are the only ones that will start with "{" when decoded.
643 // We check here for the legacy JSON serialized, if it doesn't start with that continue using nsISerializable.
644 // JSONToPrincipal accepts a *non* base64 encoded string and returns a principal or a null.
645 let tmpa = atob(serializedPincipal);
646 if (tmpa.startsWith("{")) {
647 principal = Services.scriptSecurityManager.JSONToPrincipal(tmpa);
650 lazy.serializationHelper.deserializeObject(serializedPincipal);
653 principal.QueryInterface(Ci.nsIPrincipal);
657 `Failed to deserialize serializedPincipal '${serializedPincipal}' ${e}`
660 if (!fallbackPrincipalCallback) {
662 "No principal passed to deserializePrincipal and no fallbackPrincipalCallback"
666 return fallbackPrincipalCallback();
670 * Serialize cookieJarSettings.
672 * @param {nsICookieJarSettings} cookieJarSettings The cookieJarSettings to
674 * @return {String} The base64 encoded cookieJarSettings data.
676 serializeCookieJarSettings(cookieJarSettings) {
677 let serialized = null;
678 if (cookieJarSettings) {
681 lazy.serializationHelper.serializeToString(cookieJarSettings);
684 `Failed to serialize cookieJarSettings '${cookieJarSettings}' ${e}`
692 * Deserialize a base64 encoded cookieJarSettings
694 * @param {String} cookieJarSettings_b64 A base64 encoded serialized cookieJarSettings.
695 * @return {nsICookieJarSettings} A deserialized cookieJarSettings.
697 deserializeCookieJarSettings(cookieJarSettings_b64) {
698 let deserialized = null;
699 if (cookieJarSettings_b64) {
701 deserialized = lazy.serializationHelper.deserializeObject(
702 cookieJarSettings_b64
704 deserialized.QueryInterface(Ci.nsICookieJarSettings);
707 `Failed to deserialize cookieJarSettings_b64 '${cookieJarSettings_b64}' ${e}`
714 wrapHandlingUserInput(aWindow, aIsHandling, aCallback) {
715 var handlingUserInput;
717 handlingUserInput = aWindow.windowUtils.setHandlingUserInput(aIsHandling);
720 handlingUserInput.destruct();
725 * Serialize referrerInfo.
727 * @param {nsIReferrerInfo} The referrerInfo to serialize.
728 * @return {String} The base64 encoded referrerInfo.
730 serializeReferrerInfo(referrerInfo) {
731 let serialized = null;
734 serialized = lazy.serializationHelper.serializeToString(referrerInfo);
737 `Failed to serialize referrerInfo '${referrerInfo}' ${e}`
744 * Deserialize a base64 encoded referrerInfo
746 * @param {String} referrerInfo_b64 A base64 encoded serialized referrerInfo.
747 * @return {nsIReferrerInfo} A deserialized referrerInfo.
749 deserializeReferrerInfo(referrerInfo_b64) {
750 let deserialized = null;
751 if (referrerInfo_b64) {
754 lazy.serializationHelper.deserializeObject(referrerInfo_b64);
755 deserialized.QueryInterface(Ci.nsIReferrerInfo);
758 `Failed to deserialize referrerInfo_b64 '${referrerInfo_b64}' ${e}`
766 * Returns the pids for a remote browser and its remote subframes.
768 getBrowserPids(aBrowser, aRemoteSubframes) {
769 if (!aBrowser.isRemoteBrowser || !aBrowser.frameLoader) {
772 let tabPid = aBrowser.frameLoader.remoteTab.osPid;
773 let pids = new Set();
774 if (aRemoteSubframes) {
775 let stack = [aBrowser.browsingContext];
776 while (stack.length) {
777 let bc = stack.pop();
778 stack.push(...bc.children);
779 if (bc.currentWindowGlobal) {
780 let pid = bc.currentWindowGlobal.osPid;
787 return [tabPid, ...pids];
791 * The suffix after a `=` in a remoteType is dynamic, and used to control the
792 * process pool to use. The C++ version of this method is mozilla::dom::RemoteTypePrefix().
794 remoteTypePrefix(aRemoteType) {
795 return aRemoteType.split("=")[0];
799 * There are various types of remote types that are for web content processes, but
800 * they all start with "web". The C++ version of this method is
801 * mozilla::dom::IsWebRemoteType().
803 isWebRemoteType(aRemoteType) {
804 return aRemoteType.startsWith(WEB_REMOTE_TYPE);
808 * Assemble or predict originAttributes from available arguments.
810 predictOriginAttributes({
814 geckoViewSessionContextId,
818 if (browser.browsingContext) {
819 return browser.browsingContext.originAttributes;
822 window = browser.contentDocument?.defaultView;
824 if (!userContextId) {
825 userContextId = browser.getAttribute("usercontextid") || 0;
827 if (!geckoViewSessionContextId) {
828 geckoViewSessionContextId =
829 browser.getAttribute("geckoViewSessionContextId") || "";
833 if (window && !privateBrowsingId) {
834 privateBrowsingId = window.browsingContext.usePrivateBrowsing ? 1 : 0;
836 return { privateBrowsingId, userContextId, geckoViewSessionContextId };
840 ChromeUtils.defineLazyGetter(
842 "SERIALIZED_SYSTEMPRINCIPAL",
845 E10SUtils.serializePrincipal(
846 Services.scriptSecurityManager.getSystemPrincipal()