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.
137 lazy.separatePrivilegedMozillaWebContentProcess &&
138 aTargetUri.asciiHost &&
139 aTargetUri.scheme == "https" &&
140 lazy.separatedMozillaDomains.some(function (val) {
142 aTargetUri.asciiHost == val || aTargetUri.asciiHost.endsWith("." + val)
146 return PRIVILEGEDMOZILLA_REMOTE_TYPE;
149 // If we're in the parent and we were passed a web-handled scheme,
150 // transform it now to avoid trying to load it in the wrong process.
151 if (aRemoteSubframes && hasPotentiallyWebHandledScheme(aTargetUri)) {
153 Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT &&
154 Services.appinfo.remoteType.startsWith(FISSION_WEB_REMOTE_TYPE + "=")
156 // If we're in a child process, assume we're OK to load this non-web
157 // URL for now. We'll either load it externally or re-evaluate once
158 // we know the "real" URL to which we'll redirect.
159 return Services.appinfo.remoteType;
162 // This doesn't work (throws) in the child - see
163 // https://bugzilla.mozilla.org/show_bug.cgi?id=1589082
164 // Even if it did, it'd cause sync IPC
165 // ( https://bugzilla.mozilla.org/show_bug.cgi?id=1589085 ), and this code
166 // can get called several times per page load so that seems like something
167 // we'd want to avoid.
168 let handlerInfo = lazy.extProtService.getProtocolHandlerInfo(
172 if (!handlerInfo.alwaysAskBeforeHandling) {
173 let app = handlerInfo.preferredApplicationHandler;
174 app.QueryInterface(Ci.nsIWebHandlerApp);
175 // If we get here, the default handler is a web app.
176 // Target to the origin of that web app:
177 let uriStr = app.uriTemplate.replace(/%s/, aTargetUri.spec);
178 aTargetUri = Services.io.newURI(uriStr);
181 // It's not strange for this to throw, we just ignore it and fall through.
185 // If the domain is allow listed to allow it to use file:// URIs, then we have
186 // to run it in a file content process, in case it uses file:// sub-resources.
187 const sm = Services.scriptSecurityManager;
188 if (sm.inFileURIAllowlist(aTargetUri)) {
189 return FILE_REMOTE_TYPE;
192 // If we're within a fission window, extract site information from the URI in
193 // question, and use it to generate an isolated origin.
194 if (aRemoteSubframes) {
195 let originAttributes = {};
196 // Only use specific properties of OriginAttributes in our remoteType
197 let { userContextId, privateBrowsingId, geckoViewSessionContextId } =
202 geckoViewSessionContextId,
205 // Get a principal to use for isolation.
207 if (aResultPrincipal) {
208 targetPrincipal = sm.principalWithOA(aResultPrincipal, originAttributes);
210 targetPrincipal = sm.createContentPrincipal(aTargetUri, originAttributes);
213 // If this is a special webCOOP+COEP= remote type that matches the
214 // principal's siteOrigin, we don't want to override it with webIsolated=
215 // as it's already isolated.
217 aPreferredRemoteType &&
218 aPreferredRemoteType.startsWith(
219 `${WEB_REMOTE_COOP_COEP_TYPE_PREFIX}${targetPrincipal.siteOrigin}`
222 return aPreferredRemoteType;
225 return `${FISSION_WEB_REMOTE_TYPE}=${targetPrincipal.siteOrigin}`;
226 // else fall through and probably return WEB_REMOTE_TYPE
229 if (!aPreferredRemoteType) {
230 return WEB_REMOTE_TYPE;
233 if (aPreferredRemoteType.startsWith(WEB_REMOTE_TYPE)) {
234 return aPreferredRemoteType;
237 return WEB_REMOTE_TYPE;
240 export var E10SUtils = {
244 WEB_REMOTE_COOP_COEP_TYPE_PREFIX,
246 EXTENSION_REMOTE_TYPE,
247 PRIVILEGEDABOUT_REMOTE_TYPE,
248 PRIVILEGEDMOZILLA_REMOTE_TYPE,
249 FISSION_WEB_REMOTE_TYPE,
250 SERVICEWORKER_REMOTE_TYPE,
251 STANDARD_SAFE_PROTOCOLS,
254 * @param aURI The URI of the about page
255 * @return The instance of the nsIAboutModule related to this uri
257 getAboutModule(aURL) {
258 // Needs to match NS_GetAboutModuleName
259 let moduleName = aURL.pathQueryRef.replace(/[#?].*/, "").toLowerCase();
260 let contract = "@mozilla.org/network/protocol/about;1?what=" + moduleName;
262 return Cc[contract].getService(Ci.nsIAboutModule);
264 // Either the about module isn't defined or it is broken. In either case
270 useCrossOriginOpenerPolicy() {
271 return lazy.useCrossOriginOpenerPolicy;
275 _uriStr: function uriStr(aUri) {
276 return aUri ? aUri.spec : "undefined";
279 log: function log() {
281 this._log = console.createInstance({
282 prefix: "ProcessSwitch",
283 maxLogLevel: "Error", // Change to "Debug" the process switching code
286 this._log.debug("Setup logger");
293 * Serialize csp data.
295 * @param {nsIContentSecurity} csp. The csp to serialize.
296 * @return {String} The base64 encoded csp data.
299 let serializedCSP = null;
303 serializedCSP = lazy.serializationHelper.serializeToString(csp);
306 this.log().error(`Failed to serialize csp '${csp}' ${e}`);
308 return serializedCSP;
312 * Deserialize a base64 encoded csp (serialized with
313 * Utils::serializeCSP).
315 * @param {String} csp_b64 A base64 encoded serialized csp.
316 * @return {nsIContentSecurityPolicy} A deserialized csp.
318 deserializeCSP(csp_b64) {
324 let csp = lazy.serializationHelper.deserializeObject(csp_b64);
325 csp.QueryInterface(Ci.nsIContentSecurityPolicy);
328 this.log().error(`Failed to deserialize csp_b64 '${csp_b64}' ${e}`);
333 canLoadURIInRemoteType(
336 aRemoteType = DEFAULT_REMOTE_TYPE,
337 aOriginAttributes = {}
339 // aRemoteType cannot be undefined, as that would cause it to default to
340 // `DEFAULT_REMOTE_TYPE`. This means any falsy remote types are
341 // intentionally `NOT_REMOTE`.
345 this.getRemoteTypeForURI(
360 aPreferredRemoteType = DEFAULT_REMOTE_TYPE,
362 aOriginAttributes = {}
364 if (!aMultiProcess) {
368 // loadURI in browser.js treats null as about:blank
370 aURL = "about:blank";
375 uri = Services.uriFixup.getFixupURIInfo(aURL).preferredURI;
377 // If we have an invalid URI, it's still possible that it might get
378 // fixed-up into a valid URI later on. However, we don't want to return
379 // aPreferredRemoteType here, in case the URI gets fixed-up into
380 // something that wouldn't normally run in that process.
381 return DEFAULT_REMOTE_TYPE;
384 return this.getRemoteTypeForURIObject(uri, {
385 multiProcess: aMultiProcess,
386 remoteSubFrames: aRemoteSubframes,
387 preferredRemoteType: aPreferredRemoteType,
388 currentURI: aCurrentUri,
389 originAttributes: aOriginAttributes,
393 getRemoteTypeForURIObject(aURI, options) {
395 multiProcess = Services.appinfo.browserTabsRemoteAutostart,
396 remoteSubFrames = Services.appinfo.fissionAutostart,
397 preferredRemoteType = DEFAULT_REMOTE_TYPE,
399 resultPrincipal = null,
400 originAttributes = {},
406 switch (aURI.scheme) {
408 // javascript URIs can load in any, they apply to the current document.
409 return preferredRemoteType;
413 // We need data: and blob: URIs to load in any remote process, because
414 // they need to be able to load in whatever is the current process
415 // unless it is non-remote. In that case we don't want to load them in
416 // the parent process, so we load them in the default remote process,
417 // which is sandboxed and limits any risk.
418 return preferredRemoteType == NOT_REMOTE
419 ? DEFAULT_REMOTE_TYPE
420 : preferredRemoteType;
423 return lazy.useSeparateFileUriProcess
425 : DEFAULT_REMOTE_TYPE;
428 let module = this.getAboutModule(aURI);
429 // If the module doesn't exist then an error page will be loading, that
430 // should be ok to load in any process
432 return preferredRemoteType;
435 let flags = module.getURIFlags(aURI);
436 if (flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_EXTENSION_PROCESS) {
437 return WebExtensionPolicy.useRemoteWebExtensions
438 ? EXTENSION_REMOTE_TYPE
442 if (flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD) {
444 flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS &&
445 (lazy.useSeparatePrivilegedAboutContentProcess ||
446 aURI.filePath == "logins" ||
447 // Force about:welcome and about:home into the privileged content process to
448 // workaround code coverage test failures which result from the
449 // workaround in bug 161269. Once that bug is fixed for real,
450 // the about:welcome and about:home case below can be removed.
451 aURI.filePath == "welcome" ||
452 aURI.filePath == "home")
454 return PRIVILEGEDABOUT_REMOTE_TYPE;
457 // When loading about:reader, try to display the document in the same
458 // web remote type as the document it's loading.
459 if (aURI.filePath == "reader") {
460 let readerModeURI = getOriginalReaderModeURI(aURI);
462 let innerRemoteType = this.getRemoteTypeForURIObject(
468 innerRemoteType.startsWith(WEB_REMOTE_TYPE)
470 return innerRemoteType;
475 return DEFAULT_REMOTE_TYPE;
478 // If the about page can load in parent or child, it should be safe to
479 // load in any remote type.
480 if (flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD) {
481 return preferredRemoteType;
487 let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(
488 Ci.nsIXULChromeRegistry
490 if (chromeReg.mustLoadURLRemotely(aURI)) {
491 return DEFAULT_REMOTE_TYPE;
495 chromeReg.canLoadURLRemotely(aURI) &&
496 preferredRemoteType != NOT_REMOTE
498 return DEFAULT_REMOTE_TYPE;
503 case "moz-extension":
504 // Extension iframes should load in the same process
505 // as their outer frame, but that's handled elsewhere.
506 return WebExtensionPolicy.useRemoteWebExtensions
507 ? EXTENSION_REMOTE_TYPE
515 // Protocols used by Thunderbird to display email messages.
519 // WebExtensions may set up protocol handlers for protocol names
520 // beginning with ext+. These may redirect to http(s) pages or to
521 // moz-extension pages. We can't actually tell here where one of
522 // these pages will end up loading but Talos tests use protocol
523 // handlers that redirect to extension pages that rely on this
524 // behavior so a pageloader frame script is injected correctly.
525 // Protocols that redirect to http(s) will just flip back to a
526 // regular content process after the redirect.
527 if (aURI.scheme.startsWith("ext+")) {
528 return WebExtensionPolicy.useRemoteWebExtensions
529 ? EXTENSION_REMOTE_TYPE
533 // For any other nested URIs, we use the innerURI to determine the
534 // remote type. In theory we should use the innermost URI, but some URIs
535 // have fake inner URIs (e.g. about URIs with inner moz-safe-about) and
536 // if such URIs are wrapped in other nested schemes like view-source:,
537 // we don't want to "skip" past "about:" by going straight to the
538 // innermost URI. Any URIs like this will need to be handled in the
539 // cases above, so we don't still end up using the fake inner URI here.
540 if (aURI instanceof Ci.nsINestedURI) {
541 let innerURI = aURI.QueryInterface(Ci.nsINestedURI).innerURI;
542 return this.getRemoteTypeForURIObject(innerURI, options);
545 var log = this.log();
546 log.debug("validatedWebRemoteType()");
547 log.debug(` aPreferredRemoteType: ${preferredRemoteType}`);
548 log.debug(` aTargetUri: ${this._uriStr(aURI)}`);
549 log.debug(` aCurrentUri: ${this._uriStr(currentURI)}`);
550 var remoteType = validatedWebRemoteType(
558 log.debug(` validatedWebRemoteType() returning: ${remoteType}`);
563 makeInputStream(data) {
564 if (typeof data == "string") {
565 let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
566 Ci.nsISupportsCString
569 return stream; // XPConnect will QI this to nsIInputStream for us.
572 let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
573 Ci.nsISupportsCString
575 stream.data = data.content;
579 "@mozilla.org/network/mime-input-stream;1"
580 ].createInstance(Ci.nsIMIMEInputStream);
582 mimeStream.setData(stream);
583 for (let [name, value] of data.headers) {
584 mimeStream.addHeader(name, value);
589 return stream; // XPConnect will QI this to nsIInputStream for us.
593 * Serialize principal data.
595 * @param {nsIPrincipal} principal The principal to serialize.
596 * @return {String} The serialized principal data.
598 serializePrincipal(principal) {
599 let serializedPrincipal = null;
603 serializedPrincipal =
604 Services.scriptSecurityManager.principalToJSON(principal);
607 this.log().error(`Failed to serialize principal '${principal}' ${e}`);
610 return serializedPrincipal;
614 * Deserialize a principal (serialized with serializePrincipal).
616 * @param {String} serializedPincipal A serialized principal.
617 * @return {nsIPrincipal} A deserialized principal.
619 deserializePrincipal(serializedPincipal, fallbackPrincipalCallback = null) {
620 if (!serializedPincipal) {
621 if (!fallbackPrincipalCallback) {
623 "No principal passed to deserializePrincipal and no fallbackPrincipalCallback"
628 return fallbackPrincipalCallback();
633 // The current JSON representation of principal is not stored as base64. We start by checking
634 // if the serialized data starts with '{' to determine if we're using the new JSON representation.
635 // If it doesn't we try the two legacy formats, old JSON and nsISerializable.
636 if (serializedPincipal.startsWith("{")) {
638 Services.scriptSecurityManager.JSONToPrincipal(serializedPincipal);
640 // Both the legacy and legacy JSON representation of principals are stored as base64
641 // The legacy JSON kind are the only ones that will start with "{" when decoded.
642 // We check here for the legacy JSON serialized, if it doesn't start with that continue using nsISerializable.
643 // JSONToPrincipal accepts a *non* base64 encoded string and returns a principal or a null.
644 let tmpa = atob(serializedPincipal);
645 if (tmpa.startsWith("{")) {
646 principal = Services.scriptSecurityManager.JSONToPrincipal(tmpa);
649 lazy.serializationHelper.deserializeObject(serializedPincipal);
652 principal.QueryInterface(Ci.nsIPrincipal);
656 `Failed to deserialize serializedPincipal '${serializedPincipal}' ${e}`
659 if (!fallbackPrincipalCallback) {
661 "No principal passed to deserializePrincipal and no fallbackPrincipalCallback"
665 return fallbackPrincipalCallback();
669 * Serialize cookieJarSettings.
671 * @param {nsICookieJarSettings} cookieJarSettings The cookieJarSettings to
673 * @return {String} The base64 encoded cookieJarSettings data.
675 serializeCookieJarSettings(cookieJarSettings) {
676 let serialized = null;
677 if (cookieJarSettings) {
680 lazy.serializationHelper.serializeToString(cookieJarSettings);
683 `Failed to serialize cookieJarSettings '${cookieJarSettings}' ${e}`
691 * Deserialize a base64 encoded cookieJarSettings
693 * @param {String} cookieJarSettings_b64 A base64 encoded serialized cookieJarSettings.
694 * @return {nsICookieJarSettings} A deserialized cookieJarSettings.
696 deserializeCookieJarSettings(cookieJarSettings_b64) {
697 let deserialized = null;
698 if (cookieJarSettings_b64) {
700 deserialized = lazy.serializationHelper.deserializeObject(
701 cookieJarSettings_b64
703 deserialized.QueryInterface(Ci.nsICookieJarSettings);
706 `Failed to deserialize cookieJarSettings_b64 '${cookieJarSettings_b64}' ${e}`
713 wrapHandlingUserInput(aWindow, aIsHandling, aCallback) {
714 var handlingUserInput;
716 handlingUserInput = aWindow.windowUtils.setHandlingUserInput(aIsHandling);
719 handlingUserInput.destruct();
724 * Serialize referrerInfo.
726 * @param {nsIReferrerInfo} The referrerInfo to serialize.
727 * @return {String} The base64 encoded referrerInfo.
729 serializeReferrerInfo(referrerInfo) {
730 let serialized = null;
733 serialized = lazy.serializationHelper.serializeToString(referrerInfo);
736 `Failed to serialize referrerInfo '${referrerInfo}' ${e}`
743 * Deserialize a base64 encoded referrerInfo
745 * @param {String} referrerInfo_b64 A base64 encoded serialized referrerInfo.
746 * @return {nsIReferrerInfo} A deserialized referrerInfo.
748 deserializeReferrerInfo(referrerInfo_b64) {
749 let deserialized = null;
750 if (referrerInfo_b64) {
753 lazy.serializationHelper.deserializeObject(referrerInfo_b64);
754 deserialized.QueryInterface(Ci.nsIReferrerInfo);
757 `Failed to deserialize referrerInfo_b64 '${referrerInfo_b64}' ${e}`
765 * Returns the pids for a remote browser and its remote subframes.
767 getBrowserPids(aBrowser, aRemoteSubframes) {
768 if (!aBrowser.isRemoteBrowser || !aBrowser.frameLoader) {
771 let tabPid = aBrowser.frameLoader.remoteTab.osPid;
772 let pids = new Set();
773 if (aRemoteSubframes) {
774 let stack = [aBrowser.browsingContext];
775 while (stack.length) {
776 let bc = stack.pop();
777 stack.push(...bc.children);
778 if (bc.currentWindowGlobal) {
779 let pid = bc.currentWindowGlobal.osPid;
786 return [tabPid, ...pids];
790 * The suffix after a `=` in a remoteType is dynamic, and used to control the
791 * process pool to use. The C++ version of this method is mozilla::dom::RemoteTypePrefix().
793 remoteTypePrefix(aRemoteType) {
794 return aRemoteType.split("=")[0];
798 * There are various types of remote types that are for web content processes, but
799 * they all start with "web". The C++ version of this method is
800 * mozilla::dom::IsWebRemoteType().
802 isWebRemoteType(aRemoteType) {
803 return aRemoteType.startsWith(WEB_REMOTE_TYPE);
807 * Assemble or predict originAttributes from available arguments.
809 predictOriginAttributes({
813 geckoViewSessionContextId,
817 if (browser.browsingContext) {
818 return browser.browsingContext.originAttributes;
821 window = browser.contentDocument?.defaultView;
823 if (!userContextId) {
824 userContextId = browser.getAttribute("usercontextid") || 0;
826 if (!geckoViewSessionContextId) {
827 geckoViewSessionContextId =
828 browser.getAttribute("geckoViewSessionContextId") || "";
832 if (window && !privateBrowsingId) {
833 privateBrowsingId = window.browsingContext.usePrivateBrowsing ? 1 : 0;
835 return { privateBrowsingId, userContextId, geckoViewSessionContextId };
839 ChromeUtils.defineLazyGetter(
841 "SERIALIZED_SYSTEMPRINCIPAL",
844 E10SUtils.serializePrincipal(
845 Services.scriptSecurityManager.getSystemPrincipal()