1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 * RemotePageChild is a base class for an unprivileged internal page, typically
7 * an about: page. A specific implementation should subclass the RemotePageChild
8 * actor with a more specific actor for that page. Typically, the child is not
9 * needed, but the parent actor will respond to messages and provide results
10 * directly to the page.
14 ChromeUtils.defineESModuleGetters(lazy, {
15 AsyncPrefs: "resource://gre/modules/AsyncPrefs.sys.mjs",
16 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
17 RemotePageAccessManager:
18 "resource://gre/modules/RemotePageAccessManager.sys.mjs",
21 export class RemotePageChild extends JSWindowActorChild {
23 this.listeners = new Map();
24 this.exportBaseFunctions();
27 exportBaseFunctions() {
28 const exportableFunctions = [
29 "RPMSendAsyncMessage",
31 "RPMAddMessageListener",
32 "RPMRemoveMessageListener",
37 "RPMGetFormatURLPref",
41 this.exportFunctions(exportableFunctions);
45 * Exports a list of functions to be accessible by the privileged page.
46 * Subclasses may call this function to add functions that are specific
47 * to a page. When the page calls a function, a function with the same
48 * name is called within the child actor.
50 * Only functions that appear in the whitelist in the
51 * RemotePageAccessManager for that page will be exported.
53 * @param array of function names.
55 exportFunctions(functions) {
56 let document = this.document;
57 let principal = document.nodePrincipal;
59 // If there is no content principal, don't export any functions.
64 let window = this.contentWindow;
66 for (let fnname of functions) {
67 let allowAccess = lazy.RemotePageAccessManager.checkAllowAccessToFeature(
74 // Wrap each function in an access checking function.
75 function accessCheckedFn(...args) {
76 this.checkAllowAccess(fnname, args[0]);
77 return this[fnname](...args);
80 Cu.exportFunction(accessCheckedFn.bind(this), window, {
88 // Do nothing. The DOMDocElementInserted event is just used to create
92 receiveMessage(messagedata) {
94 name: messagedata.name,
95 data: messagedata.data,
98 let listeners = this.listeners.get(message.name);
103 let clonedMessage = Cu.cloneInto(message, this.contentWindow);
104 for (let listener of listeners.values()) {
106 listener(clonedMessage);
113 wrapPromise(promise) {
114 return new this.contentWindow.Promise((resolve, reject) =>
115 promise.then(resolve, reject)
120 * Returns true if a feature cannot be accessed by the current page.
121 * Throws an exception if the feature may not be accessed.
123 * @param aDocument child process document to call from
124 * @param aFeature to feature to check access to
125 * @param aValue value that must be included with that feature's whitelist
126 * @returns true if access is allowed or throws an exception otherwise
128 checkAllowAccess(aFeature, aValue) {
129 let doc = this.document;
130 if (!lazy.RemotePageAccessManager.checkAllowAccess(doc, aFeature, aValue)) {
132 "RemotePageAccessManager does not allow access to " + aFeature
139 addPage(aUrl, aFunctionMap) {
140 lazy.RemotePageAccessManager.addPage(aUrl, aFunctionMap);
143 // Implementation of functions that are exported into the page.
145 RPMSendAsyncMessage(aName, aData = null) {
146 this.sendAsyncMessage(aName, aData);
149 RPMSendQuery(aName, aData = null) {
150 return this.wrapPromise(
151 new Promise(resolve => {
152 this.sendQuery(aName, aData).then(result => {
153 resolve(Cu.cloneInto(result, this.contentWindow));
160 * Adds a listener for messages. Many callbacks can be registered for the
161 * same message if necessary. An attempt to register the same callback for the
162 * same message twice will be ignored. When called the callback is passed an
163 * object with these properties:
164 * name: The message name
165 * data: Any data sent with the message
167 RPMAddMessageListener(aName, aCallback) {
168 if (!this.listeners.has(aName)) {
169 this.listeners.set(aName, new Set([aCallback]));
171 this.listeners.get(aName).add(aCallback);
176 * Removes a listener for messages.
178 RPMRemoveMessageListener(aName, aCallback) {
179 if (!this.listeners.has(aName)) {
183 this.listeners.get(aName).delete(aCallback);
186 RPMGetIntPref(aPref, defaultValue) {
187 // Only call with a default value if it's defined, to be able to throw
188 // errors for non-existent prefs.
189 if (defaultValue !== undefined) {
190 return Services.prefs.getIntPref(aPref, defaultValue);
192 return Services.prefs.getIntPref(aPref);
195 RPMGetStringPref(aPref) {
196 return Services.prefs.getStringPref(aPref);
199 RPMGetBoolPref(aPref, defaultValue) {
200 // Only call with a default value if it's defined, to be able to throw
201 // errors for non-existent prefs.
202 if (defaultValue !== undefined) {
203 return Services.prefs.getBoolPref(aPref, defaultValue);
205 return Services.prefs.getBoolPref(aPref);
208 RPMSetPref(aPref, aVal) {
209 return this.wrapPromise(lazy.AsyncPrefs.set(aPref, aVal));
212 RPMGetFormatURLPref(aFormatURL) {
213 return Services.urlFormatter.formatURLPref(aFormatURL);
216 RPMIsWindowPrivate() {
217 return lazy.PrivateBrowsingUtils.isContentWindowPrivate(this.contentWindow);