зеркало из https://github.com/mozilla/gecko-dev.git
229 строки
6.2 KiB
JavaScript
229 строки
6.2 KiB
JavaScript
/* 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/. */
|
|
|
|
"use strict";
|
|
|
|
var EXPORTED_SYMBOLS = ["RemotePageChild"];
|
|
|
|
/**
|
|
* 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"AsyncPrefs",
|
|
"resource://gre/modules/AsyncPrefs.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"PrivateBrowsingUtils",
|
|
"resource://gre/modules/PrivateBrowsingUtils.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"RemotePageAccessManager",
|
|
"resource://gre/modules/RemotePageAccessManager.jsm"
|
|
);
|
|
|
|
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 = 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 DOMWindowCreated 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) {
|
|
Cu.reportError(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 (!RemotePageAccessManager.checkAllowAccess(doc, aFeature, aValue)) {
|
|
throw new Error(
|
|
"RemotePageAccessManager does not allow access to " + aFeature
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 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(AsyncPrefs.set(aPref, aVal));
|
|
}
|
|
|
|
RPMGetFormatURLPref(aFormatURL) {
|
|
return Services.urlFormatter.formatURLPref(aFormatURL);
|
|
}
|
|
|
|
RPMIsWindowPrivate() {
|
|
return PrivateBrowsingUtils.isContentWindowPrivate(this.contentWindow);
|
|
}
|
|
}
|