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/. */
7 var EXPORTED_SYMBOLS = ["E10SUtils"];
9 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
10 const { XPCOMUtils } = ChromeUtils.import(
11 "resource://gre/modules/XPCOMUtils.jsm"
14 XPCOMUtils.defineLazyPreferenceGetter(
16 "useSeparateFileUriProcess",
17 "browser.tabs.remote.separateFileUriProcess",
20 XPCOMUtils.defineLazyPreferenceGetter(
22 "allowLinkedWebInFileUriProcess",
23 "browser.tabs.remote.allowLinkedWebInFileUriProcess",
26 XPCOMUtils.defineLazyPreferenceGetter(
28 "useSeparatePrivilegedAboutContentProcess",
29 "browser.tabs.remote.separatePrivilegedContentProcess",
32 XPCOMUtils.defineLazyPreferenceGetter(
34 "separatePrivilegedMozillaWebContentProcess",
35 "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess",
38 XPCOMUtils.defineLazyPreferenceGetter(
40 "separatedMozillaDomains",
41 "browser.tabs.remote.separatedMozillaDomains",
46 XPCOMUtils.defineLazyPreferenceGetter(
48 "useHttpResponseProcessSelection",
49 "browser.tabs.remote.useHTTPResponseProcessSelection",
52 XPCOMUtils.defineLazyPreferenceGetter(
54 "useCrossOriginOpenerPolicy",
55 "browser.tabs.remote.useCrossOriginOpenerPolicy",
58 XPCOMUtils.defineLazyServiceGetter(
60 "serializationHelper",
61 "@mozilla.org/network/serialization-helper;1",
62 "nsISerializationHelper"
66 Cu.reportError(new Error("E10SUtils: " + msg));
69 function getAboutModule(aURL) {
70 // Needs to match NS_GetAboutModuleName
71 let moduleName = aURL.pathQueryRef.replace(/[#?].*/, "").toLowerCase();
72 let contract = "@mozilla.org/network/protocol/about;1?what=" + moduleName;
74 return Cc[contract].getService(Ci.nsIAboutModule);
76 // Either the about module isn't defined or it is broken. In either case
82 const NOT_REMOTE = null;
84 // These must match any similar ones in ContentParent.h and ProcInfo.h
85 const WEB_REMOTE_TYPE = "web";
86 const FISSION_WEB_REMOTE_TYPE_PREFIX = "webIsolated=";
87 const FILE_REMOTE_TYPE = "file";
88 const EXTENSION_REMOTE_TYPE = "extension";
89 const PRIVILEGEDABOUT_REMOTE_TYPE = "privilegedabout";
90 const PRIVILEGEDMOZILLA_REMOTE_TYPE = "privilegedmozilla";
92 // This must start with the WEB_REMOTE_TYPE above.
93 const LARGE_ALLOCATION_REMOTE_TYPE = "webLargeAllocation";
94 const DEFAULT_REMOTE_TYPE = WEB_REMOTE_TYPE;
96 function validatedWebRemoteType(
102 // To load into the Privileged Mozilla Content Process you must be https,
103 // and be an exact match or a subdomain of an allowlisted domain.
105 separatePrivilegedMozillaWebContentProcess &&
106 aTargetUri.asciiHost &&
107 aTargetUri.scheme == "https" &&
108 separatedMozillaDomains.some(function(val) {
110 aTargetUri.asciiHost == val || aTargetUri.asciiHost.endsWith("." + val)
114 return PRIVILEGEDMOZILLA_REMOTE_TYPE;
117 // If the domain is whitelisted to allow it to use file:// URIs, then we have
118 // to run it in a file content process, in case it uses file:// sub-resources.
119 const sm = Services.scriptSecurityManager;
120 if (sm.inFileURIAllowlist(aTargetUri)) {
121 return FILE_REMOTE_TYPE;
124 // If we're within a fission window, extract site information from the URI in
125 // question, and use it to generate an isolated origin.
126 if (aRemoteSubframes) {
127 let targetPrincipal = sm.createContentPrincipal(aTargetUri, {});
128 return FISSION_WEB_REMOTE_TYPE_PREFIX + targetPrincipal.siteOrigin;
131 if (!aPreferredRemoteType) {
132 return WEB_REMOTE_TYPE;
135 if (aPreferredRemoteType.startsWith(WEB_REMOTE_TYPE)) {
136 return aPreferredRemoteType;
140 allowLinkedWebInFileUriProcess &&
141 aPreferredRemoteType == FILE_REMOTE_TYPE
143 // If aCurrentUri is passed then we should only allow FILE_REMOTE_TYPE
144 // when it is same origin as target.
147 // checkSameOriginURI throws when not same origin.
148 // todo: if you intend to update CheckSameOriginURI to log the error to the
149 // console you also need to update the 'aFromPrivateWindow' argument.
150 sm.checkSameOriginURI(aCurrentUri, aTargetUri, false, false);
151 return FILE_REMOTE_TYPE;
153 return WEB_REMOTE_TYPE;
157 return FILE_REMOTE_TYPE;
160 return WEB_REMOTE_TYPE;
168 EXTENSION_REMOTE_TYPE,
169 PRIVILEGEDABOUT_REMOTE_TYPE,
170 PRIVILEGEDMOZILLA_REMOTE_TYPE,
171 LARGE_ALLOCATION_REMOTE_TYPE,
173 useHttpResponseProcessSelection() {
174 return useHttpResponseProcessSelection;
176 useCrossOriginOpenerPolicy() {
177 return useCrossOriginOpenerPolicy;
181 * Serialize csp data.
183 * @param {nsIContentSecurity} csp. The csp to serialize.
184 * @return {String} The base64 encoded csp data.
187 let serializedCSP = null;
191 serializedCSP = serializationHelper.serializeToString(csp);
194 debug(`Failed to serialize csp '${csp}' ${e}`);
196 return serializedCSP;
200 * Deserialize a base64 encoded csp (serialized with
201 * Utils::serializeCSP).
203 * @param {String} csp_b64 A base64 encoded serialized csp.
204 * @return {nsIContentSecurityPolicy} A deserialized csp.
206 deserializeCSP(csp_b64) {
212 let csp = serializationHelper.deserializeObject(csp_b64);
213 csp.QueryInterface(Ci.nsIContentSecurityPolicy);
216 debug(`Failed to deserialize csp_b64 '${csp_b64}' ${e}`);
221 canLoadURIInRemoteType(
224 aRemoteType = DEFAULT_REMOTE_TYPE,
225 aPreferredRemoteType = undefined
227 // We need a strict equality here because the value of `NOT_REMOTE` is
228 // `null`, and there is a possibility that `undefined` is passed as an
229 // argument, which might result a load in the parent process.
230 if (aPreferredRemoteType === undefined) {
231 aPreferredRemoteType =
232 aRemoteType === NOT_REMOTE ? NOT_REMOTE : DEFAULT_REMOTE_TYPE;
237 this.getRemoteTypeForURI(
250 aPreferredRemoteType = DEFAULT_REMOTE_TYPE,
253 if (!aMultiProcess) {
257 // loadURI in browser.js treats null as about:blank
259 aURL = "about:blank";
264 uri = Services.uriFixup.createFixupURI(
266 Ci.nsIURIFixup.FIXUP_FLAG_NONE
269 // If we have an invalid URI, it's still possible that it might get
270 // fixed-up into a valid URI later on. However, we don't want to return
271 // aPreferredRemoteType here, in case the URI gets fixed-up into
272 // something that wouldn't normally run in that process.
273 return DEFAULT_REMOTE_TYPE;
276 return this.getRemoteTypeForURIObject(
280 aPreferredRemoteType,
285 getRemoteTypeForURIObject(
289 aPreferredRemoteType = DEFAULT_REMOTE_TYPE,
292 if (!aMultiProcess) {
296 switch (aURI.scheme) {
298 // javascript URIs can load in any, they apply to the current document.
299 return aPreferredRemoteType;
303 // We need data: and blob: URIs to load in any remote process, because
304 // they need to be able to load in whatever is the current process
305 // unless it is non-remote. In that case we don't want to load them in
306 // the parent process, so we load them in the default remote process,
307 // which is sandboxed and limits any risk.
308 return aPreferredRemoteType == NOT_REMOTE
309 ? DEFAULT_REMOTE_TYPE
310 : aPreferredRemoteType;
313 return useSeparateFileUriProcess
315 : DEFAULT_REMOTE_TYPE;
318 let module = getAboutModule(aURI);
319 // If the module doesn't exist then an error page will be loading, that
320 // should be ok to load in any process
322 return aPreferredRemoteType;
325 let flags = module.getURIFlags(aURI);
326 if (flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_EXTENSION_PROCESS) {
327 return WebExtensionPolicy.useRemoteWebExtensions
328 ? EXTENSION_REMOTE_TYPE
332 if (flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD) {
334 flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS &&
335 (useSeparatePrivilegedAboutContentProcess ||
336 aURI.filePath == "logins")
338 return PRIVILEGEDABOUT_REMOTE_TYPE;
340 return DEFAULT_REMOTE_TYPE;
343 // If the about page can load in parent or child, it should be safe to
344 // load in any remote type.
345 if (flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD) {
346 return aPreferredRemoteType;
352 let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(
353 Ci.nsIXULChromeRegistry
355 if (chromeReg.mustLoadURLRemotely(aURI)) {
356 return DEFAULT_REMOTE_TYPE;
360 chromeReg.canLoadURLRemotely(aURI) &&
361 aPreferredRemoteType != NOT_REMOTE
363 return DEFAULT_REMOTE_TYPE;
368 case "moz-extension":
369 return WebExtensionPolicy.useRemoteWebExtensions
370 ? EXTENSION_REMOTE_TYPE
374 // WebExtensions may set up protocol handlers for protocol names
375 // beginning with ext+. These may redirect to http(s) pages or to
376 // moz-extension pages. We can't actually tell here where one of
377 // these pages will end up loading but Talos tests use protocol
378 // handlers that redirect to extension pages that rely on this
379 // behavior so a pageloader frame script is injected correctly.
380 // Protocols that redirect to http(s) will just flip back to a
381 // regular content process after the redirect.
382 if (aURI.scheme.startsWith("ext+")) {
383 return WebExtensionPolicy.useRemoteWebExtensions
384 ? EXTENSION_REMOTE_TYPE
388 // For any other nested URIs, we use the innerURI to determine the
389 // remote type. In theory we should use the innermost URI, but some URIs
390 // have fake inner URIs (e.g. about URIs with inner moz-safe-about) and
391 // if such URIs are wrapped in other nested schemes like view-source:,
392 // we don't want to "skip" past "about:" by going straight to the
393 // innermost URI. Any URIs like this will need to be handled in the
394 // cases above, so we don't still end up using the fake inner URI here.
395 if (aURI instanceof Ci.nsINestedURI) {
396 let innerURI = aURI.QueryInterface(Ci.nsINestedURI).innerURI;
397 return this.getRemoteTypeForURIObject(
401 aPreferredRemoteType,
406 return validatedWebRemoteType(
407 aPreferredRemoteType,
415 getRemoteTypeForPrincipal(
419 aPreferredRemoteType = DEFAULT_REMOTE_TYPE,
422 if (!aMultiProcess) {
426 // We can't pick a process based on a system principal or expanded
427 // principal. In fact, we should never end up with one here!
428 if (aPrincipal.isSystemPrincipal || aPrincipal.isExpandedPrincipal) {
429 throw Cr.NS_ERROR_UNEXPECTED;
432 // Null principals can be loaded in any remote process.
433 if (aPrincipal.isNullPrincipal) {
434 return aPreferredRemoteType == NOT_REMOTE
435 ? DEFAULT_REMOTE_TYPE
436 : aPreferredRemoteType;
439 // We might care about the currently loaded URI. Pull it out of our current
440 // principal. We never care about the current URI when working with a
441 // non-content principal.
443 aCurrentPrincipal && aCurrentPrincipal.isContentPrincipal
444 ? aCurrentPrincipal.URI
446 return E10SUtils.getRemoteTypeForURIObject(
450 aPreferredRemoteType,
455 makeInputStream(data) {
456 if (typeof data == "string") {
457 let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
458 Ci.nsISupportsCString
461 return stream; // XPConnect will QI this to nsIInputStream for us.
464 let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
465 Ci.nsISupportsCString
467 stream.data = data.content;
471 "@mozilla.org/network/mime-input-stream;1"
472 ].createInstance(Ci.nsIMIMEInputStream);
474 mimeStream.setData(stream);
475 for (let [name, value] of data.headers) {
476 mimeStream.addHeader(name, value);
481 return stream; // XPConnect will QI this to nsIInputStream for us.
485 * Serialize principal data.
487 * @param {nsIPrincipal} principal The principal to serialize.
488 * @return {String} The base64 encoded principal data.
490 serializePrincipal(principal) {
491 let serializedPrincipal = null;
495 serializedPrincipal = btoa(
496 Services.scriptSecurityManager.principalToJSON(principal)
500 debug(`Failed to serialize principal '${principal}' ${e}`);
503 return serializedPrincipal;
507 * Deserialize a base64 encoded principal (serialized with
508 * serializePrincipal).
510 * @param {String} principal_b64 A base64 encoded serialized principal.
511 * @return {nsIPrincipal} A deserialized principal.
513 deserializePrincipal(principal_b64, fallbackPrincipalCallback = null) {
514 if (!principal_b64) {
515 if (!fallbackPrincipalCallback) {
517 "No principal passed to deserializePrincipal and no fallbackPrincipalCallback"
522 return fallbackPrincipalCallback();
527 let tmpa = atob(principal_b64);
528 // Both the legacy and new JSON representation of principals are stored as base64
529 // The new kind are the only ones that will start with "{" when decoded.
530 // We check here for the new JSON serialized, if it doesn't start with that continue using nsISerializable.
531 // JSONToPrincipal accepts a *non* base64 encoded string and returns a principal or a null.
532 if (tmpa.startsWith("{")) {
533 principal = Services.scriptSecurityManager.JSONToPrincipal(tmpa);
535 principal = serializationHelper.deserializeObject(principal_b64);
537 principal.QueryInterface(Ci.nsIPrincipal);
540 debug(`Failed to deserialize principal_b64 '${principal_b64}' ${e}`);
542 if (!fallbackPrincipalCallback) {
544 "No principal passed to deserializePrincipal and no fallbackPrincipalCallback"
548 return fallbackPrincipalCallback();
551 shouldLoadURIInBrowser(
555 remoteSubframes = false,
556 flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE
558 let currentRemoteType = browser.remoteType;
559 let requiredRemoteType;
562 let fixupFlags = Ci.nsIURIFixup.FIXUP_FLAG_NONE;
563 if (flags & Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
564 fixupFlags |= Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
566 if (flags & Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS) {
567 fixupFlags |= Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
569 uriObject = Services.uriFixup.createFixupURI(uri, fixupFlags);
570 // Note that I had thought that we could set uri = uriObject.spec here, to
571 // save on fixup later on, but that changes behavior and breaks tests.
572 requiredRemoteType = this.getRemoteTypeForURIObject(
580 // createFixupURI throws if it can't create a URI. If that's the case then
581 // we still need to pass down the uri because docshell handles this case.
582 requiredRemoteType = multiProcess ? DEFAULT_REMOTE_TYPE : NOT_REMOTE;
585 let mustChangeProcess = requiredRemoteType != currentRemoteType;
586 let newFrameloader = false;
588 browser.getAttribute("preloadedState") === "consumed" &&
589 uri != "about:newtab"
591 // Leaving about:newtab from a used to be preloaded browser should run the process
592 // selecting algorithm again.
593 mustChangeProcess = true;
594 newFrameloader = true;
605 shouldLoadURIInThisProcess(aURI, aRemoteSubframes) {
606 let remoteType = Services.appinfo.remoteType;
609 this.getRemoteTypeForURIObject(
618 shouldLoadURI(aDocShell, aURI, aHasPostData) {
619 let remoteSubframes = aDocShell.useRemoteSubframes;
621 // Inner frames should always load in the current process
622 // XXX(nika): Handle shouldLoadURI-triggered process switches for remote
623 // subframes! (bug 1548942)
624 if (aDocShell.sameTypeParent) {
628 let webNav = aDocShell.QueryInterface(Ci.nsIWebNavigation);
629 let sessionHistory = webNav.sessionHistory;
632 Services.appinfo.remoteType == WEB_REMOTE_TYPE &&
633 sessionHistory.count == 1 &&
634 webNav.currentURI.spec == "about:newtab"
636 // This is possibly a preloaded browser and we're about to navigate away for
637 // the first time. On the child side there is no way to tell for sure if that
638 // is the case, so let's redirect this request to the parent to decide if a new
639 // process is needed. But we don't currently properly handle POST data in
640 // redirects (bug 1457520), so if there is POST data, don't return false here.
644 // If we are performing HTTP response process selection, and are loading an
645 // HTTP URI, we can start the load in the current process, and then perform
646 // the switch later-on using the RedirectProcessChooser mechanism.
648 // We should never be sending a POST request from the parent process to a
649 // http(s) uri, so make sure we switch if we're currently in that process.
651 useHttpResponseProcessSelection &&
652 (aURI.scheme == "http" || aURI.scheme == "https") &&
653 Services.appinfo.remoteType != NOT_REMOTE
658 // If we are in a Large-Allocation process, and it wouldn't be content visible
659 // to change processes, we want to load into a new process so that we can throw
660 // this one out. We don't want to move into a new process if we have post data,
661 // because we would accidentally throw out that data.
664 Services.appinfo.remoteType == LARGE_ALLOCATION_REMOTE_TYPE &&
665 !aDocShell.awaitingLargeAlloc &&
666 aDocShell.isOnlyToplevelInTabGroup
671 // Allow history load if loaded in this process before.
672 let requestedIndex = sessionHistory.legacySHistory.requestedIndex;
673 if (requestedIndex >= 0) {
675 sessionHistory.legacySHistory.getEntryAtIndex(requestedIndex)
681 // If not originally loaded in this process allow it if the URI would
682 // normally be allowed to load in this process by default.
683 let remoteType = Services.appinfo.remoteType;
686 this.getRemoteTypeForURIObject(
696 // If the URI can be loaded in the current process then continue
697 return this.shouldLoadURIInThisProcess(aURI, remoteSubframes);
704 aTriggeringPrincipal,
709 // Retarget the load to the correct process
710 let messageManager = aDocShell.messageManager;
711 let sessionHistory = aDocShell.QueryInterface(Ci.nsIWebNavigation)
714 messageManager.sendAsyncMessage("Browser:LoadURI", {
717 flags: aFlags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
718 referrerInfo: this.serializeReferrerInfo(aReferrerInfo),
719 triggeringPrincipal: this.serializePrincipal(
720 aTriggeringPrincipal ||
721 Services.scriptSecurityManager.createNullPrincipal({})
723 csp: aCsp ? this.serializeCSP(aCsp) : null,
724 reloadInFreshProcess: !!aFreshProcess,
726 historyIndex: sessionHistory.legacySHistory.requestedIndex,
731 wrapHandlingUserInput(aWindow, aIsHandling, aCallback) {
732 var handlingUserInput;
734 handlingUserInput = aWindow.windowUtils.setHandlingUserInput(aIsHandling);
737 handlingUserInput.destruct();
742 * Serialize referrerInfo.
744 * @param {nsIReferrerInfo} The referrerInfo to serialize.
745 * @return {String} The base64 encoded referrerInfo.
747 serializeReferrerInfo(referrerInfo) {
748 let serialized = null;
751 serialized = serializationHelper.serializeToString(referrerInfo);
753 debug(`Failed to serialize referrerInfo '${referrerInfo}' ${e}`);
759 * Deserialize a base64 encoded referrerInfo
761 * @param {String} referrerInfo_b64 A base64 encoded serialized referrerInfo.
762 * @return {nsIReferrerInfo} A deserialized referrerInfo.
764 deserializeReferrerInfo(referrerInfo_b64) {
765 let deserialized = null;
766 if (referrerInfo_b64) {
768 deserialized = serializationHelper.deserializeObject(referrerInfo_b64);
769 deserialized.QueryInterface(Ci.nsIReferrerInfo);
772 `Failed to deserialize referrerInfo_b64 '${referrerInfo_b64}' ${e}`
780 * Returns the pids for a remote browser and its remote subframes.
782 getBrowserPids(aBrowser, aRemoteSubframes) {
783 if (!aBrowser.isRemoteBrowser || !aBrowser.frameLoader) {
786 let tabPid = aBrowser.frameLoader.remoteTab.osPid;
787 let pids = new Set();
788 if (aRemoteSubframes) {
789 let stack = [aBrowser.browsingContext];
790 while (stack.length) {
791 let bc = stack.pop();
792 stack.push(...bc.getChildren());
793 if (bc.currentWindowGlobal) {
794 let pid = bc.currentWindowGlobal.osPid;
801 return [tabPid, ...pids];
805 * The suffix after a `=` in a remoteType is dynamic, and used to control the
806 * process pool to use. The C++ version of this method is mozilla::dom::RemoteTypePrefix().
808 remoteTypePrefix(aRemoteType) {
809 return aRemoteType.split("=")[0];
813 * There are various types of remote types that are for web content processes, but
814 * they all start with "web". The C++ version of this method is
815 * mozilla::dom::IsWebRemoteType().
817 isWebRemoteType(aRemoteType) {
818 return aRemoteType.startsWith(WEB_REMOTE_TYPE);
822 XPCOMUtils.defineLazyGetter(
824 "SERIALIZED_SYSTEMPRINCIPAL",
826 return E10SUtils.serializePrincipal(
827 Services.scriptSecurityManager.getSystemPrincipal()