https://github.com/mozilla/gecko-dev
Revision bbfccfc57c12acedf8e8831a9800fbcbde76c8b8 authored by Mozilla Releng Treescript on 31 May 2024, 15:46:43 UTC, committed by Mozilla Releng Treescript on 31 May 2024, 15:46:43 UTC
1 parent 07a6af5
Raw File
Tip revision: bbfccfc57c12acedf8e8831a9800fbcbde76c8b8 authored by Mozilla Releng Treescript on 31 May 2024, 15:46:43 UTC
No bug - Tagging 51ec70cc1809cd462def0613b46b660d44088dc0 with FIREFOX-ANDROID_127_0b9_BUILD1 a=release CLOSED TREE DONTBUILD
Tip revision: bbfccfc
WebChannelChild.sys.mjs
/* -*- 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.");
    }
  }
}
back to top