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/. */
7 var EXPORTED_SYMBOLS = ["RemotePageChild"];
10 * RemotePageChild is a base class for an unprivileged internal page, typically
11 * an about: page. A specific implementation should subclass the RemotePageChild
12 * actor with a more specific actor for that page. Typically, the child is not
13 * needed, but the parent actor will respond to messages and provide results
14 * directly to the page.
17 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
18 ChromeUtils.defineModuleGetter(
21 "resource://gre/modules/AsyncPrefs.jsm"
23 ChromeUtils.defineModuleGetter(
25 "PrivateBrowsingUtils",
26 "resource://gre/modules/PrivateBrowsingUtils.jsm"
28 ChromeUtils.defineModuleGetter(
30 "RemotePageAccessManager",
31 "resource://gre/modules/RemotePageAccessManager.jsm"
34 class RemotePageChild extends JSWindowActorChild {
36 this.listeners = new Map();
37 this.exportBaseFunctions();
40 exportBaseFunctions() {
41 const exportableFunctions = [
42 "RPMSendAsyncMessage",
44 "RPMAddMessageListener",
45 "RPMRemoveMessageListener",
50 "RPMGetFormatURLPref",
54 this.exportFunctions(exportableFunctions);
58 * Exports a list of functions to be accessible by the privileged page.
59 * Subclasses may call this function to add functions that are specific
60 * to a page. When the page calls a function, a function with the same
61 * name is called within the child actor.
63 * Only functions that appear in the whitelist in the
64 * RemotePageAccessManager for that page will be exported.
66 * @param array of function names.
68 exportFunctions(functions) {
69 let document = this.document;
70 let principal = document.nodePrincipal;
72 // If there is no content principal, don't export any functions.
77 let window = this.contentWindow;
79 for (let fnname of functions) {
80 let allowAccess = RemotePageAccessManager.checkAllowAccessToFeature(
87 // Wrap each function in an access checking function.
88 function accessCheckedFn(...args) {
89 this.checkAllowAccess(fnname, args[0]);
90 return this[fnname](...args);
93 Cu.exportFunction(accessCheckedFn.bind(this), window, {
101 // Do nothing. The DOMWindowCreated event is just used to create
105 receiveMessage(messagedata) {
107 name: messagedata.name,
108 data: messagedata.data,
111 let listeners = this.listeners.get(message.name);
116 let clonedMessage = Cu.cloneInto(message, this.contentWindow);
117 for (let listener of listeners.values()) {
119 listener(clonedMessage);
126 wrapPromise(promise) {
127 return new this.contentWindow.Promise((resolve, reject) =>
128 promise.then(resolve, reject)
133 * Returns true if a feature cannot be accessed by the current page.
134 * Throws an exception if the feature may not be accessed.
136 * @param aDocument child process document to call from
137 * @param aFeature to feature to check access to
138 * @param aValue value that must be included with that feature's whitelist
139 * @returns true if access is allowed or throws an exception otherwise
141 checkAllowAccess(aFeature, aValue) {
142 let doc = this.document;
143 if (!RemotePageAccessManager.checkAllowAccess(doc, aFeature, aValue)) {
145 "RemotePageAccessManager does not allow access to " + aFeature
152 // Implementation of functions that are exported into the page.
154 RPMSendAsyncMessage(aName, aData = null) {
155 this.sendAsyncMessage(aName, aData);
158 RPMSendQuery(aName, aData = null) {
159 return this.wrapPromise(
160 new Promise(resolve => {
161 this.sendQuery(aName, aData).then(result => {
162 resolve(Cu.cloneInto(result, this.contentWindow));
169 * Adds a listener for messages. Many callbacks can be registered for the
170 * same message if necessary. An attempt to register the same callback for the
171 * same message twice will be ignored. When called the callback is passed an
172 * object with these properties:
173 * name: The message name
174 * data: Any data sent with the message
176 RPMAddMessageListener(aName, aCallback) {
177 if (!this.listeners.has(aName)) {
178 this.listeners.set(aName, new Set([aCallback]));
180 this.listeners.get(aName).add(aCallback);
185 * Removes a listener for messages.
187 RPMRemoveMessageListener(aName, aCallback) {
188 if (!this.listeners.has(aName)) {
192 this.listeners.get(aName).delete(aCallback);
195 RPMGetIntPref(aPref, defaultValue) {
196 // Only call with a default value if it's defined, to be able to throw
197 // errors for non-existent prefs.
198 if (defaultValue !== undefined) {
199 return Services.prefs.getIntPref(aPref, defaultValue);
201 return Services.prefs.getIntPref(aPref);
204 RPMGetStringPref(aPref) {
205 return Services.prefs.getStringPref(aPref);
208 RPMGetBoolPref(aPref, defaultValue) {
209 // Only call with a default value if it's defined, to be able to throw
210 // errors for non-existent prefs.
211 if (defaultValue !== undefined) {
212 return Services.prefs.getBoolPref(aPref, defaultValue);
214 return Services.prefs.getBoolPref(aPref);
217 RPMSetBoolPref(aPref, aVal) {
218 return this.wrapPromise(AsyncPrefs.set(aPref, aVal));
221 RPMGetFormatURLPref(aFormatURL) {
222 return Services.urlFormatter.formatURLPref(aFormatURL);
225 RPMIsWindowPrivate() {
226 return PrivateBrowsingUtils.isContentWindowPrivate(this.contentWindow);