Bug 1692971 [wpt PR 27638] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / toolkit / actors / RemotePageChild.jsm
blobc5a7357a7dc41f78bf538dbf9d4d0147819e1643
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/. */
5 "use strict";
7 var EXPORTED_SYMBOLS = ["RemotePageChild"];
9 /**
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.
15  */
17 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
18 ChromeUtils.defineModuleGetter(
19   this,
20   "AsyncPrefs",
21   "resource://gre/modules/AsyncPrefs.jsm"
23 ChromeUtils.defineModuleGetter(
24   this,
25   "PrivateBrowsingUtils",
26   "resource://gre/modules/PrivateBrowsingUtils.jsm"
28 ChromeUtils.defineModuleGetter(
29   this,
30   "RemotePageAccessManager",
31   "resource://gre/modules/RemotePageAccessManager.jsm"
34 class RemotePageChild extends JSWindowActorChild {
35   actorCreated() {
36     this.listeners = new Map();
37     this.exportBaseFunctions();
38   }
40   exportBaseFunctions() {
41     const exportableFunctions = [
42       "RPMSendAsyncMessage",
43       "RPMSendQuery",
44       "RPMAddMessageListener",
45       "RPMRemoveMessageListener",
46       "RPMGetIntPref",
47       "RPMGetStringPref",
48       "RPMGetBoolPref",
49       "RPMSetBoolPref",
50       "RPMGetFormatURLPref",
51       "RPMIsWindowPrivate",
52     ];
54     this.exportFunctions(exportableFunctions);
55   }
57   /**
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.
62    *
63    * Only functions that appear in the whitelist in the
64    * RemotePageAccessManager for that page will be exported.
65    *
66    * @param array of function names.
67    */
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.
73     if (!principal) {
74       return;
75     }
77     let window = this.contentWindow;
79     for (let fnname of functions) {
80       let allowAccess = RemotePageAccessManager.checkAllowAccessToFeature(
81         principal,
82         fnname,
83         document
84       );
86       if (allowAccess) {
87         // Wrap each function in an access checking function.
88         function accessCheckedFn(...args) {
89           this.checkAllowAccess(fnname, args[0]);
90           return this[fnname](...args);
91         }
93         Cu.exportFunction(accessCheckedFn.bind(this), window, {
94           defineAs: fnname,
95         });
96       }
97     }
98   }
100   handleEvent() {
101     // Do nothing. The DOMWindowCreated event is just used to create
102     // the actor.
103   }
105   receiveMessage(messagedata) {
106     let message = {
107       name: messagedata.name,
108       data: messagedata.data,
109     };
111     let listeners = this.listeners.get(message.name);
112     if (!listeners) {
113       return;
114     }
116     let clonedMessage = Cu.cloneInto(message, this.contentWindow);
117     for (let listener of listeners.values()) {
118       try {
119         listener(clonedMessage);
120       } catch (e) {
121         Cu.reportError(e);
122       }
123     }
124   }
126   wrapPromise(promise) {
127     return new this.contentWindow.Promise((resolve, reject) =>
128       promise.then(resolve, reject)
129     );
130   }
132   /**
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
140    */
141   checkAllowAccess(aFeature, aValue) {
142     let doc = this.document;
143     if (!RemotePageAccessManager.checkAllowAccess(doc, aFeature, aValue)) {
144       throw new Error(
145         "RemotePageAccessManager does not allow access to " + aFeature
146       );
147     }
149     return true;
150   }
152   // Implementation of functions that are exported into the page.
154   RPMSendAsyncMessage(aName, aData = null) {
155     this.sendAsyncMessage(aName, aData);
156   }
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));
163         });
164       })
165     );
166   }
168   /**
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
175    */
176   RPMAddMessageListener(aName, aCallback) {
177     if (!this.listeners.has(aName)) {
178       this.listeners.set(aName, new Set([aCallback]));
179     } else {
180       this.listeners.get(aName).add(aCallback);
181     }
182   }
184   /**
185    * Removes a listener for messages.
186    */
187   RPMRemoveMessageListener(aName, aCallback) {
188     if (!this.listeners.has(aName)) {
189       return;
190     }
192     this.listeners.get(aName).delete(aCallback);
193   }
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);
200     }
201     return Services.prefs.getIntPref(aPref);
202   }
204   RPMGetStringPref(aPref) {
205     return Services.prefs.getStringPref(aPref);
206   }
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);
213     }
214     return Services.prefs.getBoolPref(aPref);
215   }
217   RPMSetBoolPref(aPref, aVal) {
218     return this.wrapPromise(AsyncPrefs.set(aPref, aVal));
219   }
221   RPMGetFormatURLPref(aFormatURL) {
222     return Services.urlFormatter.formatURLPref(aFormatURL);
223   }
225   RPMIsWindowPrivate() {
226     return PrivateBrowsingUtils.isContentWindowPrivate(this.contentWindow);
227   }