summaryrefslogtreecommitdiffstats
path: root/toolkit/actors/WebChannelChild.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/actors/WebChannelChild.sys.mjs')
-rw-r--r--toolkit/actors/WebChannelChild.sys.mjs132
1 files changed, 132 insertions, 0 deletions
diff --git a/toolkit/actors/WebChannelChild.sys.mjs b/toolkit/actors/WebChannelChild.sys.mjs
new file mode 100644
index 0000000000..563f2566a0
--- /dev/null
+++ b/toolkit/actors/WebChannelChild.sys.mjs
@@ -0,0 +1,132 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+import { ContentDOMReference } from "resource://gre/modules/ContentDOMReference.sys.mjs";
+
+// Preference containing the list (space separated) of origins that are
+// allowed to send non-string values through a WebChannel, mainly for
+// backwards compatability. See bug 1238128 for more information.
+const URL_WHITELIST_PREF = "webchannel.allowObject.urlWhitelist";
+
+let _cachedWhitelist = null;
+
+const CACHED_PREFS = {};
+XPCOMUtils.defineLazyPreferenceGetter(
+ CACHED_PREFS,
+ "URL_WHITELIST",
+ URL_WHITELIST_PREF,
+ "",
+ // Null this out so we update it.
+ () => (_cachedWhitelist = null)
+);
+
+export class WebChannelChild extends JSWindowActorChild {
+ handleEvent(event) {
+ if (event.type === "WebChannelMessageToChrome") {
+ return this._onMessageToChrome(event);
+ }
+ return undefined;
+ }
+
+ receiveMessage(msg) {
+ if (msg.name === "WebChannelMessageToContent") {
+ return this._onMessageToContent(msg);
+ }
+ return undefined;
+ }
+
+ _getWhitelistedPrincipals() {
+ if (!_cachedWhitelist) {
+ let urls = CACHED_PREFS.URL_WHITELIST.split(/\s+/);
+ _cachedWhitelist = urls.map(origin =>
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin)
+ );
+ }
+ return _cachedWhitelist;
+ }
+
+ _onMessageToChrome(e) {
+ // If target is window then we want the document principal, otherwise fallback to target itself.
+ let principal = e.target.nodePrincipal
+ ? e.target.nodePrincipal
+ : e.target.document.nodePrincipal;
+
+ if (e.detail) {
+ if (typeof e.detail != "string") {
+ // Check if the principal is one of the ones that's allowed to send
+ // non-string values for e.detail. They're whitelisted by site origin,
+ // so we compare on originNoSuffix in order to avoid other origin attributes
+ // that are not relevant here, such as containers or private browsing.
+ let objectsAllowed = this._getWhitelistedPrincipals().some(
+ whitelisted => principal.originNoSuffix == whitelisted.originNoSuffix
+ );
+ if (!objectsAllowed) {
+ console.error(
+ "WebChannelMessageToChrome sent with an object from a non-whitelisted principal"
+ );
+ return;
+ }
+ }
+
+ let eventTarget =
+ e.target instanceof Ci.nsIDOMWindow
+ ? null
+ : ContentDOMReference.get(e.target);
+ this.sendAsyncMessage("WebChannelMessageToChrome", {
+ contentData: e.detail,
+ eventTarget,
+ principal,
+ });
+ } else {
+ console.error("WebChannel message failed. No message detail.");
+ }
+ }
+
+ _onMessageToContent(msg) {
+ if (msg.data && this.contentWindow) {
+ // msg.objects.eventTarget will be defined if sending a response to
+ // a WebChannelMessageToChrome event. An unsolicited send
+ // may not have an eventTarget defined, in this case send to the
+ // main content window.
+ let { eventTarget, principal } = msg.data;
+ if (!eventTarget) {
+ eventTarget = this.contentWindow;
+ } else {
+ eventTarget = ContentDOMReference.resolve(eventTarget);
+ }
+ if (!eventTarget) {
+ console.error("WebChannel message failed. No target.");
+ return;
+ }
+
+ // Use nodePrincipal if available, otherwise fallback to document principal.
+ let targetPrincipal =
+ eventTarget instanceof Ci.nsIDOMWindow
+ ? eventTarget.document.nodePrincipal
+ : eventTarget.nodePrincipal;
+
+ if (principal.subsumes(targetPrincipal)) {
+ let targetWindow = this.contentWindow;
+ eventTarget.dispatchEvent(
+ new targetWindow.CustomEvent("WebChannelMessageToContent", {
+ detail: Cu.cloneInto(
+ {
+ id: msg.data.id,
+ message: msg.data.message,
+ },
+ targetWindow
+ ),
+ })
+ );
+ } else {
+ console.error("WebChannel message failed. Principal mismatch.");
+ }
+ } else {
+ console.error("WebChannel message failed. No message data.");
+ }
+ }
+}