summaryrefslogtreecommitdiff
path: root/components/utils/simpleServices.js
diff options
context:
space:
mode:
Diffstat (limited to 'components/utils/simpleServices.js')
-rw-r--r--components/utils/simpleServices.js313
1 files changed, 313 insertions, 0 deletions
diff --git a/components/utils/simpleServices.js b/components/utils/simpleServices.js
new file mode 100644
index 000000000..0b8dfe877
--- /dev/null
+++ b/components/utils/simpleServices.js
@@ -0,0 +1,313 @@
+/* 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/. */
+
+/*
+ * Dumping ground for simple services for which the isolation of a full global
+ * is overkill. Be careful about namespace pollution, and be mindful about
+ * importing lots of JSMs in global scope, since this file will almost certainly
+ * be loaded from enough callsites that any such imports will always end up getting
+ * eagerly loaded at startup.
+ */
+
+"use strict";
+
+const Cc = Components.classes;
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+function AddonPolicyService()
+{
+ this.wrappedJSObject = this;
+ this.cspStrings = new Map();
+ this.backgroundPageUrlCallbacks = new Map();
+ this.checkHasPermissionCallbacks = new Map();
+ this.mayLoadURICallbacks = new Map();
+ this.localizeCallbacks = new Map();
+
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this, "baseCSP", "extensions.webextensions.base-content-security-policy",
+ "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; " +
+ "object-src 'self' https://* moz-extension: blob: filesystem:;");
+
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this, "defaultCSP", "extensions.webextensions.default-content-security-policy",
+ "script-src 'self'; object-src 'self';");
+}
+
+AddonPolicyService.prototype = {
+ classID: Components.ID("{89560ed3-72e3-498d-a0e8-ffe50334d7c5}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonPolicyService]),
+
+ /**
+ * Returns the content security policy which applies to documents belonging
+ * to the extension with the given ID. This may be either a custom policy,
+ * if one was supplied, or the default policy if one was not.
+ */
+ getAddonCSP(aAddonId) {
+ let csp = this.cspStrings.get(aAddonId);
+ return csp || this.defaultCSP;
+ },
+
+ /**
+ * Returns the generated background page as a data-URI, if any. If the addon
+ * does not have an auto-generated background page, an empty string is
+ * returned.
+ */
+ getGeneratedBackgroundPageUrl(aAddonId) {
+ let cb = this.backgroundPageUrlCallbacks.get(aAddonId);
+ return cb && cb(aAddonId) || '';
+ },
+
+ /*
+ * Invokes a callback (if any) associated with the addon to determine whether
+ * the addon is granted the |aPerm| API permission.
+ *
+ * @see nsIAddonPolicyService.addonHasPermission
+ */
+ addonHasPermission(aAddonId, aPerm) {
+ let cb = this.checkHasPermissionCallbacks.get(aAddonId);
+ return cb ? cb(aPerm) : false;
+ },
+
+ /*
+ * Invokes a callback (if any) associated with the addon to determine whether
+ * unprivileged code running within the addon is allowed to perform loads from
+ * the given URI.
+ *
+ * @see nsIAddonPolicyService.addonMayLoadURI
+ */
+ addonMayLoadURI(aAddonId, aURI) {
+ let cb = this.mayLoadURICallbacks.get(aAddonId);
+ return cb ? cb(aURI) : false;
+ },
+
+ /*
+ * Invokes a callback (if any) associated with the addon to loclaize a
+ * resource belonging to that add-on.
+ */
+ localizeAddonString(aAddonId, aString) {
+ let cb = this.localizeCallbacks.get(aAddonId);
+ return cb ? cb(aString) : aString;
+ },
+
+ /*
+ * Invokes a callback (if any) to determine if an extension URI should be
+ * web-accessible.
+ *
+ * @see nsIAddonPolicyService.extensionURILoadableByAnyone
+ */
+ extensionURILoadableByAnyone(aURI) {
+ if (aURI.scheme != "moz-extension") {
+ throw new TypeError("non-extension URI passed");
+ }
+
+ let cb = this.extensionURILoadCallback;
+ return cb ? cb(aURI) : false;
+ },
+
+ /*
+ * Maps an extension URI to an addon ID.
+ *
+ * @see nsIAddonPolicyService.extensionURIToAddonId
+ */
+ extensionURIToAddonId(aURI) {
+ if (aURI.scheme != "moz-extension") {
+ throw new TypeError("non-extension URI passed");
+ }
+
+ let cb = this.extensionURIToAddonIdCallback;
+ if (!cb) {
+ throw new Error("no callback set to map extension URIs to addon Ids");
+ }
+ return cb(aURI);
+ },
+
+ /*
+ * Sets the callbacks used in addonHasPermission above. Not accessible over
+ * XPCOM - callers should use .wrappedJSObject on the service to call it
+ * directly.
+ */
+ setAddonHasPermissionCallback(aAddonId, aCallback) {
+ if (aCallback) {
+ this.checkHasPermissionCallbacks.set(aAddonId, aCallback);
+ } else {
+ this.checkHasPermissionCallbacks.delete(aAddonId);
+ }
+ },
+
+ /*
+ * Sets the callbacks used in addonMayLoadURI above. Not accessible over
+ * XPCOM - callers should use .wrappedJSObject on the service to call it
+ * directly.
+ */
+ setAddonLoadURICallback(aAddonId, aCallback) {
+ if (aCallback) {
+ this.mayLoadURICallbacks.set(aAddonId, aCallback);
+ } else {
+ this.mayLoadURICallbacks.delete(aAddonId);
+ }
+ },
+
+ /*
+ * Sets the custom CSP string to be used for the add-on. Not accessible over
+ * XPCOM - callers should use .wrappedJSObject on the service to call it
+ * directly.
+ */
+ setAddonCSP(aAddonId, aCSPString) {
+ if (aCSPString) {
+ this.cspStrings.set(aAddonId, aCSPString);
+ } else {
+ this.cspStrings.delete(aAddonId);
+ }
+ },
+
+ /**
+ * Set the callback that generates a data-URL for the background page.
+ */
+ setBackgroundPageUrlCallback(aAddonId, aCallback) {
+ if (aCallback) {
+ this.backgroundPageUrlCallbacks.set(aAddonId, aCallback);
+ } else {
+ this.backgroundPageUrlCallbacks.delete(aAddonId);
+ }
+ },
+
+ /*
+ * Sets the callbacks used by the stream converter service to localize
+ * add-on resources.
+ */
+ setAddonLocalizeCallback(aAddonId, aCallback) {
+ if (aCallback) {
+ this.localizeCallbacks.set(aAddonId, aCallback);
+ } else {
+ this.localizeCallbacks.delete(aAddonId);
+ }
+ },
+
+ /*
+ * Sets the callback used in extensionURILoadableByAnyone above. Not
+ * accessible over XPCOM - callers should use .wrappedJSObject on the
+ * service to call it directly.
+ */
+ setExtensionURILoadCallback(aCallback) {
+ var old = this.extensionURILoadCallback;
+ this.extensionURILoadCallback = aCallback;
+ return old;
+ },
+
+ /*
+ * Sets the callback used in extensionURIToAddonId above. Not accessible over
+ * XPCOM - callers should use .wrappedJSObject on the service to call it
+ * directly.
+ */
+ setExtensionURIToAddonIdCallback(aCallback) {
+ var old = this.extensionURIToAddonIdCallback;
+ this.extensionURIToAddonIdCallback = aCallback;
+ return old;
+ }
+};
+
+/*
+ * This class provides a stream filter for locale messages in CSS files served
+ * by the moz-extension: protocol handler.
+ *
+ * See SubstituteChannel in netwerk/protocol/res/ExtensionProtocolHandler.cpp
+ * for usage.
+ */
+function AddonLocalizationConverter()
+{
+ this.aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService)
+ .wrappedJSObject;
+}
+
+AddonLocalizationConverter.prototype = {
+ classID: Components.ID("{ded150e3-c92e-4077-a396-0dba9953e39f}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamConverter]),
+
+ FROM_TYPE: "application/vnd.mozilla.webext.unlocalized",
+ TO_TYPE: "text/css",
+
+ checkTypes(aFromType, aToType) {
+ if (aFromType != this.FROM_TYPE) {
+ throw Components.Exception("Invalid aFromType value", Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller.caller);
+ }
+ if (aToType != this.TO_TYPE) {
+ throw Components.Exception("Invalid aToType value", Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller.caller);
+ }
+ },
+
+ // aContext must be a nsIURI object for a valid moz-extension: URL.
+ getAddonId(aContext) {
+ // In this case, we want the add-on ID even if the URL is web accessible,
+ // so check the root rather than the exact path.
+ let uri = Services.io.newURI("/", null, aContext);
+
+ let id = this.aps.extensionURIToAddonId(uri);
+ if (id == undefined) {
+ throw new Components.Exception("Invalid context", Cr.NS_ERROR_INVALID_ARG);
+ }
+ return id;
+ },
+
+ convertToStream(aAddonId, aString) {
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+
+ stream.data = this.aps.localizeAddonString(aAddonId, aString);
+ return stream;
+ },
+
+ convert(aStream, aFromType, aToType, aContext) {
+ this.checkTypes(aFromType, aToType);
+ let addonId = this.getAddonId(aContext);
+
+ let string = (
+ aStream.available() ?
+ NetUtil.readInputStreamToString(aStream, aStream.available()): ""
+ );
+ return this.convertToStream(addonId, string);
+ },
+
+ asyncConvertData(aFromType, aToType, aListener, aContext) {
+ this.checkTypes(aFromType, aToType);
+ this.addonId = this.getAddonId(aContext);
+ this.listener = aListener;
+ },
+
+ onStartRequest(aRequest, aContext) {
+ this.parts = [];
+ },
+
+ onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount) {
+ this.parts.push(NetUtil.readInputStreamToString(aInputStream, aCount));
+ },
+
+ onStopRequest(aRequest, aContext, aStatusCode) {
+ try {
+ this.listener.onStartRequest(aRequest, null);
+ if (Components.isSuccessCode(aStatusCode)) {
+ let string = this.parts.join("");
+ let stream = this.convertToStream(this.addonId, string);
+
+ this.listener.onDataAvailable(aRequest, null, stream, 0, stream.data.length);
+ }
+ } catch (e) {
+ aStatusCode = e.result || Cr.NS_ERROR_FAILURE;
+ }
+ this.listener.onStopRequest(aRequest, null, aStatusCode);
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonPolicyService,
+ AddonLocalizationConverter]);