Bug 1209184: Part 1b - [webext] Create a stream converter for localization placeholders. r=billm
☠☠ backed out by 2e446ebafe6c ☠ ☠
authorKris Maglione <maglione.k@gmail.com>
Fri, 20 Nov 2015 22:39:44 -0800
changeset 308624 e75f9f24d0dc7c08131ebc08b0dcfcb4f310269c
parent 308623 343223ce6b34db254f11367e7ffaa1ab652362bc
child 308625 9c63ffd499ebc4ed4f164d4b35e09a7a03c36387
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1209184
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1209184: Part 1b - [webext] Create a stream converter for localization placeholders. r=billm
toolkit/components/extensions/ExtensionManagement.jsm
toolkit/components/utils/simpleServices.js
toolkit/components/utils/utils.manifest
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -130,23 +130,25 @@ var Service = {
 
     // Create the moz-extension://uuid mapping.
     let handler = Services.io.getProtocolHandler("moz-extension");
     handler.QueryInterface(Ci.nsISubstitutingProtocolHandler);
     handler.setSubstitution(uuid, uri);
 
     this.uuidMap.set(uuid, extension);
     this.aps.setAddonLoadURICallback(extension.id, this.checkAddonMayLoad.bind(this, extension));
+    this.aps.setAddonLocalizeCallback(extension.id, extension.localize.bind(extension));
   },
 
   // Called when an extension is unloaded.
   shutdownExtension(uuid) {
     let extension = this.uuidMap.get(uuid);
     this.uuidMap.delete(uuid);
     this.aps.setAddonLoadURICallback(extension.id, null);
+    this.aps.setAddonLocalizeCallback(extension.id, null);
 
     let handler = Services.io.getProtocolHandler("moz-extension");
     handler.QueryInterface(Ci.nsISubstitutingProtocolHandler);
     handler.setSubstitution(uuid, null);
   },
 
   // Return true if the given URI can be loaded from arbitrary web
   // content. The manifest.json |web_accessible_resources| directive
--- a/toolkit/components/utils/simpleServices.js
+++ b/toolkit/components/utils/simpleServices.js
@@ -7,21 +7,27 @@
  * 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;
 
 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 RemoteTagServiceService()
 {
 }
 
 RemoteTagServiceService.prototype = {
   classID: Components.ID("{dfd07380-6083-11e4-9803-0800200c9a66}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIRemoteTagService, Ci.nsISupportsWeakReference]),
 
@@ -43,35 +49,45 @@ RemoteTagServiceService.prototype = {
     return "generic";
   }
 };
 
 function AddonPolicyService()
 {
   this.wrappedJSObject = this;
   this.mayLoadURICallbacks = new Map();
+  this.localizeCallbacks = new Map();
 }
 
 AddonPolicyService.prototype = {
   classID: Components.ID("{89560ed3-72e3-498d-a0e8-ffe50334d7c5}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonPolicyService]),
 
   /*
    * 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[aAddonId];
+    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");
@@ -100,19 +116,31 @@ AddonPolicyService.prototype = {
 
   /*
    * 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[aAddonId] = aCallback;
+      this.mayLoadURICallbacks.set(aAddonId, aCallback);
     } else {
-      delete this.mayLoadURICallbacks[aAddonId];
+      delete this.mayLoadURICallbacks.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 {
+      delete 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.
    */
@@ -129,9 +157,100 @@ AddonPolicyService.prototype = {
    */
   setExtensionURIToAddonIdCallback(aCallback) {
     var old = this.extensionURIToAddonIdCallback;
     this.extensionURIToAddonIdCallback = aCallback;
     return old;
   }
 };
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RemoteTagServiceService, AddonPolicyService]);
+/*
+ * 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 = 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([RemoteTagServiceService, AddonPolicyService,
+                                                     AddonLocalizationConverter]);
--- a/toolkit/components/utils/utils.manifest
+++ b/toolkit/components/utils/utils.manifest
@@ -1,4 +1,6 @@
 component {dfd07380-6083-11e4-9803-0800200c9a66} simpleServices.js
 contract @mozilla.org/addons/remote-tag-service;1 {dfd07380-6083-11e4-9803-0800200c9a66}
 component {89560ed3-72e3-498d-a0e8-ffe50334d7c5} simpleServices.js
 contract @mozilla.org/addons/policy-service;1 {89560ed3-72e3-498d-a0e8-ffe50334d7c5}
+component {ded150e3-c92e-4077-a396-0dba9953e39f} simpleServices.js
+contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.webext.unlocalized&to=text/css {ded150e3-c92e-4077-a396-0dba9953e39f}