Bug 1852754: part 9) Add tests for dynamically loading <link rel="prefetch"> elements...
[gecko.git] / toolkit / actors / WebChannelChild.sys.mjs
blob563f2566a0df026fecafad1d9e6a5626e1d99ed3
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/. */
6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
8 import { ContentDOMReference } from "resource://gre/modules/ContentDOMReference.sys.mjs";
10 // Preference containing the list (space separated) of origins that are
11 // allowed to send non-string values through a WebChannel, mainly for
12 // backwards compatability. See bug 1238128 for more information.
13 const URL_WHITELIST_PREF = "webchannel.allowObject.urlWhitelist";
15 let _cachedWhitelist = null;
17 const CACHED_PREFS = {};
18 XPCOMUtils.defineLazyPreferenceGetter(
19   CACHED_PREFS,
20   "URL_WHITELIST",
21   URL_WHITELIST_PREF,
22   "",
23   // Null this out so we update it.
24   () => (_cachedWhitelist = null)
27 export class WebChannelChild extends JSWindowActorChild {
28   handleEvent(event) {
29     if (event.type === "WebChannelMessageToChrome") {
30       return this._onMessageToChrome(event);
31     }
32     return undefined;
33   }
35   receiveMessage(msg) {
36     if (msg.name === "WebChannelMessageToContent") {
37       return this._onMessageToContent(msg);
38     }
39     return undefined;
40   }
42   _getWhitelistedPrincipals() {
43     if (!_cachedWhitelist) {
44       let urls = CACHED_PREFS.URL_WHITELIST.split(/\s+/);
45       _cachedWhitelist = urls.map(origin =>
46         Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin)
47       );
48     }
49     return _cachedWhitelist;
50   }
52   _onMessageToChrome(e) {
53     // If target is window then we want the document principal, otherwise fallback to target itself.
54     let principal = e.target.nodePrincipal
55       ? e.target.nodePrincipal
56       : e.target.document.nodePrincipal;
58     if (e.detail) {
59       if (typeof e.detail != "string") {
60         // Check if the principal is one of the ones that's allowed to send
61         // non-string values for e.detail.  They're whitelisted by site origin,
62         // so we compare on originNoSuffix in order to avoid other origin attributes
63         // that are not relevant here, such as containers or private browsing.
64         let objectsAllowed = this._getWhitelistedPrincipals().some(
65           whitelisted => principal.originNoSuffix == whitelisted.originNoSuffix
66         );
67         if (!objectsAllowed) {
68           console.error(
69             "WebChannelMessageToChrome sent with an object from a non-whitelisted principal"
70           );
71           return;
72         }
73       }
75       let eventTarget =
76         e.target instanceof Ci.nsIDOMWindow
77           ? null
78           : ContentDOMReference.get(e.target);
79       this.sendAsyncMessage("WebChannelMessageToChrome", {
80         contentData: e.detail,
81         eventTarget,
82         principal,
83       });
84     } else {
85       console.error("WebChannel message failed. No message detail.");
86     }
87   }
89   _onMessageToContent(msg) {
90     if (msg.data && this.contentWindow) {
91       // msg.objects.eventTarget will be defined if sending a response to
92       // a WebChannelMessageToChrome event. An unsolicited send
93       // may not have an eventTarget defined, in this case send to the
94       // main content window.
95       let { eventTarget, principal } = msg.data;
96       if (!eventTarget) {
97         eventTarget = this.contentWindow;
98       } else {
99         eventTarget = ContentDOMReference.resolve(eventTarget);
100       }
101       if (!eventTarget) {
102         console.error("WebChannel message failed. No target.");
103         return;
104       }
106       // Use nodePrincipal if available, otherwise fallback to document principal.
107       let targetPrincipal =
108         eventTarget instanceof Ci.nsIDOMWindow
109           ? eventTarget.document.nodePrincipal
110           : eventTarget.nodePrincipal;
112       if (principal.subsumes(targetPrincipal)) {
113         let targetWindow = this.contentWindow;
114         eventTarget.dispatchEvent(
115           new targetWindow.CustomEvent("WebChannelMessageToContent", {
116             detail: Cu.cloneInto(
117               {
118                 id: msg.data.id,
119                 message: msg.data.message,
120               },
121               targetWindow
122             ),
123           })
124         );
125       } else {
126         console.error("WebChannel message failed. Principal mismatch.");
127       }
128     } else {
129       console.error("WebChannel message failed. No message data.");
130     }
131   }