diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/actors/WebChannelChild.sys.mjs | 132 |
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."); + } + } +} |