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/. */ 4 5"use strict"; 6 7var EXPORTED_SYMBOLS = ["RemotePageChild"]; 8 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 */ 16 17const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); 18ChromeUtils.defineModuleGetter( 19 this, 20 "AsyncPrefs", 21 "resource://gre/modules/AsyncPrefs.jsm" 22); 23ChromeUtils.defineModuleGetter( 24 this, 25 "PrivateBrowsingUtils", 26 "resource://gre/modules/PrivateBrowsingUtils.jsm" 27); 28ChromeUtils.defineModuleGetter( 29 this, 30 "RemotePageAccessManager", 31 "resource://gre/modules/RemotePageAccessManager.jsm" 32); 33 34class RemotePageChild extends JSWindowActorChild { 35 actorCreated() { 36 this.listeners = new Map(); 37 this.exportBaseFunctions(); 38 } 39 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 ]; 53 54 this.exportFunctions(exportableFunctions); 55 } 56 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; 71 72 // If there is no content principal, don't export any functions. 73 if (!principal) { 74 return; 75 } 76 77 let window = this.contentWindow; 78 79 for (let fnname of functions) { 80 let allowAccess = RemotePageAccessManager.checkAllowAccessToFeature( 81 principal, 82 fnname, 83 document 84 ); 85 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 } 92 93 Cu.exportFunction(accessCheckedFn.bind(this), window, { 94 defineAs: fnname, 95 }); 96 } 97 } 98 } 99 100 handleEvent() { 101 // Do nothing. The DOMDocElementInserted event is just used to create 102 // the actor. 103 } 104 105 receiveMessage(messagedata) { 106 let message = { 107 name: messagedata.name, 108 data: messagedata.data, 109 }; 110 111 let listeners = this.listeners.get(message.name); 112 if (!listeners) { 113 return; 114 } 115 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 } 125 126 wrapPromise(promise) { 127 return new this.contentWindow.Promise((resolve, reject) => 128 promise.then(resolve, reject) 129 ); 130 } 131 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. 135 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 } 148 149 return true; 150 } 151 152 // Implementation of functions that are exported into the page. 153 154 RPMSendAsyncMessage(aName, aData = null) { 155 this.sendAsyncMessage(aName, aData); 156 } 157 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 } 167 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 } 183 184 /** 185 * Removes a listener for messages. 186 */ 187 RPMRemoveMessageListener(aName, aCallback) { 188 if (!this.listeners.has(aName)) { 189 return; 190 } 191 192 this.listeners.get(aName).delete(aCallback); 193 } 194 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 } 203 204 RPMGetStringPref(aPref) { 205 return Services.prefs.getStringPref(aPref); 206 } 207 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 } 216 217 RPMSetBoolPref(aPref, aVal) { 218 return this.wrapPromise(AsyncPrefs.set(aPref, aVal)); 219 } 220 221 RPMGetFormatURLPref(aFormatURL) { 222 return Services.urlFormatter.formatURLPref(aFormatURL); 223 } 224 225 RPMIsWindowPrivate() { 226 return PrivateBrowsingUtils.isContentWindowPrivate(this.contentWindow); 227 } 228} 229