summaryrefslogtreecommitdiffstats
path: root/toolkit/actors/RemotePageChild.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/actors/RemotePageChild.sys.mjs')
-rw-r--r--toolkit/actors/RemotePageChild.sys.mjs219
1 files changed, 219 insertions, 0 deletions
diff --git a/toolkit/actors/RemotePageChild.sys.mjs b/toolkit/actors/RemotePageChild.sys.mjs
new file mode 100644
index 0000000000..48cd4d0df9
--- /dev/null
+++ b/toolkit/actors/RemotePageChild.sys.mjs
@@ -0,0 +1,219 @@
+/* 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/. */
+
+/**
+ * RemotePageChild is a base class for an unprivileged internal page, typically
+ * an about: page. A specific implementation should subclass the RemotePageChild
+ * actor with a more specific actor for that page. Typically, the child is not
+ * needed, but the parent actor will respond to messages and provide results
+ * directly to the page.
+ */
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ AsyncPrefs: "resource://gre/modules/AsyncPrefs.sys.mjs",
+ PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
+ RemotePageAccessManager:
+ "resource://gre/modules/RemotePageAccessManager.sys.mjs",
+});
+
+export class RemotePageChild extends JSWindowActorChild {
+ actorCreated() {
+ this.listeners = new Map();
+ this.exportBaseFunctions();
+ }
+
+ exportBaseFunctions() {
+ const exportableFunctions = [
+ "RPMSendAsyncMessage",
+ "RPMSendQuery",
+ "RPMAddMessageListener",
+ "RPMRemoveMessageListener",
+ "RPMGetIntPref",
+ "RPMGetStringPref",
+ "RPMGetBoolPref",
+ "RPMSetBoolPref",
+ "RPMGetFormatURLPref",
+ "RPMIsWindowPrivate",
+ ];
+
+ this.exportFunctions(exportableFunctions);
+ }
+
+ /**
+ * Exports a list of functions to be accessible by the privileged page.
+ * Subclasses may call this function to add functions that are specific
+ * to a page. When the page calls a function, a function with the same
+ * name is called within the child actor.
+ *
+ * Only functions that appear in the whitelist in the
+ * RemotePageAccessManager for that page will be exported.
+ *
+ * @param array of function names.
+ */
+ exportFunctions(functions) {
+ let document = this.document;
+ let principal = document.nodePrincipal;
+
+ // If there is no content principal, don't export any functions.
+ if (!principal) {
+ return;
+ }
+
+ let window = this.contentWindow;
+
+ for (let fnname of functions) {
+ let allowAccess = lazy.RemotePageAccessManager.checkAllowAccessToFeature(
+ principal,
+ fnname,
+ document
+ );
+
+ if (allowAccess) {
+ // Wrap each function in an access checking function.
+ function accessCheckedFn(...args) {
+ this.checkAllowAccess(fnname, args[0]);
+ return this[fnname](...args);
+ }
+
+ Cu.exportFunction(accessCheckedFn.bind(this), window, {
+ defineAs: fnname,
+ });
+ }
+ }
+ }
+
+ handleEvent() {
+ // Do nothing. The DOMDocElementInserted event is just used to create
+ // the actor.
+ }
+
+ receiveMessage(messagedata) {
+ let message = {
+ name: messagedata.name,
+ data: messagedata.data,
+ };
+
+ let listeners = this.listeners.get(message.name);
+ if (!listeners) {
+ return;
+ }
+
+ let clonedMessage = Cu.cloneInto(message, this.contentWindow);
+ for (let listener of listeners.values()) {
+ try {
+ listener(clonedMessage);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ }
+
+ wrapPromise(promise) {
+ return new this.contentWindow.Promise((resolve, reject) =>
+ promise.then(resolve, reject)
+ );
+ }
+
+ /**
+ * Returns true if a feature cannot be accessed by the current page.
+ * Throws an exception if the feature may not be accessed.
+
+ * @param aDocument child process document to call from
+ * @param aFeature to feature to check access to
+ * @param aValue value that must be included with that feature's whitelist
+ * @returns true if access is allowed or throws an exception otherwise
+ */
+ checkAllowAccess(aFeature, aValue) {
+ let doc = this.document;
+ if (!lazy.RemotePageAccessManager.checkAllowAccess(doc, aFeature, aValue)) {
+ throw new Error(
+ "RemotePageAccessManager does not allow access to " + aFeature
+ );
+ }
+
+ return true;
+ }
+
+ addPage(aUrl, aFunctionMap) {
+ lazy.RemotePageAccessManager.addPage(aUrl, aFunctionMap);
+ }
+
+ // Implementation of functions that are exported into the page.
+
+ RPMSendAsyncMessage(aName, aData = null) {
+ this.sendAsyncMessage(aName, aData);
+ }
+
+ RPMSendQuery(aName, aData = null) {
+ return this.wrapPromise(
+ new Promise(resolve => {
+ this.sendQuery(aName, aData).then(result => {
+ resolve(Cu.cloneInto(result, this.contentWindow));
+ });
+ })
+ );
+ }
+
+ /**
+ * Adds a listener for messages. Many callbacks can be registered for the
+ * same message if necessary. An attempt to register the same callback for the
+ * same message twice will be ignored. When called the callback is passed an
+ * object with these properties:
+ * name: The message name
+ * data: Any data sent with the message
+ */
+ RPMAddMessageListener(aName, aCallback) {
+ if (!this.listeners.has(aName)) {
+ this.listeners.set(aName, new Set([aCallback]));
+ } else {
+ this.listeners.get(aName).add(aCallback);
+ }
+ }
+
+ /**
+ * Removes a listener for messages.
+ */
+ RPMRemoveMessageListener(aName, aCallback) {
+ if (!this.listeners.has(aName)) {
+ return;
+ }
+
+ this.listeners.get(aName).delete(aCallback);
+ }
+
+ RPMGetIntPref(aPref, defaultValue) {
+ // Only call with a default value if it's defined, to be able to throw
+ // errors for non-existent prefs.
+ if (defaultValue !== undefined) {
+ return Services.prefs.getIntPref(aPref, defaultValue);
+ }
+ return Services.prefs.getIntPref(aPref);
+ }
+
+ RPMGetStringPref(aPref) {
+ return Services.prefs.getStringPref(aPref);
+ }
+
+ RPMGetBoolPref(aPref, defaultValue) {
+ // Only call with a default value if it's defined, to be able to throw
+ // errors for non-existent prefs.
+ if (defaultValue !== undefined) {
+ return Services.prefs.getBoolPref(aPref, defaultValue);
+ }
+ return Services.prefs.getBoolPref(aPref);
+ }
+
+ RPMSetBoolPref(aPref, aVal) {
+ return this.wrapPromise(lazy.AsyncPrefs.set(aPref, aVal));
+ }
+
+ RPMGetFormatURLPref(aFormatURL) {
+ return Services.urlFormatter.formatURLPref(aFormatURL);
+ }
+
+ RPMIsWindowPrivate() {
+ return lazy.PrivateBrowsingUtils.isContentWindowPrivate(this.contentWindow);
+ }
+}