Bug 1315575: Part 2 - Stop generating multiple sets of schema bindings for proxy contexts. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Mon, 07 Nov 2016 22:21:01 -0800
changeset 366633 f29c03c0682adebf120110a654d22eb5130d7bca
parent 366632 2e0180c345940fa1893ef4e26ac68effa024ef48
child 366634 6fb5f161f09c4a75984b053c022fe36a101a7bcc
push id1369
push userjlorenzo@mozilla.com
push dateMon, 27 Feb 2017 14:59:41 +0000
treeherdermozilla-release@d75a1dba431f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1315575
milestone52.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 1315575: Part 2 - Stop generating multiple sets of schema bindings for proxy contexts. r=aswan MozReview-Commit-ID: 2VqYscQAAF6
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionChild.jsm
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -79,19 +79,19 @@ let schemaURLs = new Set();
 if (!AppConstants.RELEASE_OR_BETA) {
   schemaURLs.add("chrome://extensions/content/schemas/experiments.json");
 }
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   BaseContext,
   EventEmitter,
-  LocalAPIImplementation,
   LocaleData,
   SchemaAPIManager,
+  SpreadArgs,
   defineLazyGetter,
   flushJarCache,
   instanceOf,
 } = ExtensionUtils;
 
 XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);
 
 const LOGGER_ID_BASE = "addons.webextension.";
@@ -380,27 +380,32 @@ class ExtensionChildProxyContext extends
 
   shutdown() {
     Management.emit("page-shutdown", this);
     super.shutdown();
   }
 }
 
 function findPathInObject(obj, path, printErrors = true) {
+  let parent;
   for (let elt of path.split(".")) {
     if (!obj || !(elt in obj)) {
       if (printErrors) {
         Cu.reportError(`WebExtension API ${path} not found (it may be unimplemented by Firefox).`);
       }
       return null;
     }
 
+    parent = obj;
     obj = obj[elt];
   }
 
+  if (typeof obj === "function") {
+    return obj.bind(parent);
+  }
   return obj;
 }
 
 ParentAPIManager = {
   proxyContexts: new Map(),
 
   init() {
     Services.obs.addObserver(this, "message-manager-close", false);
@@ -493,41 +498,51 @@ ParentAPIManager = {
   },
 
   call(data, target) {
     let context = this.getContextById(data.childId);
     if (context.currentMessageManager !== target.messageManager) {
       Cu.reportError("WebExtension warning: Message manager unexpectedly changed");
     }
 
-    function callback(...cbArgs) {
-      let lastError = context.lastError;
+    try {
+      let args = Cu.cloneInto(data.args, context.sandbox);
+      let result = findPathInObject(context.apiObj, data.path)(...args);
 
-      context.currentMessageManager.sendAsyncMessage("API:CallResult", {
-        childId: data.childId,
-        callId: data.callId,
-        args: cbArgs,
-        lastError: lastError ? lastError.message : null,
-      });
-    }
+      if (data.callId) {
+        result = result || Promise.resolve();
+
+        result.then(result => {
+          result = result instanceof SpreadArgs ? [...result] : [result];
 
-    let args = data.args;
-    args = Cu.cloneInto(args, context.sandbox);
-    if (data.callId) {
-      args = args.concat(callback);
-    }
-    try {
-      findPathInObject(context.apiObj, data.path)(...args);
+          context.currentMessageManager.sendAsyncMessage("API:CallResult", {
+            childId: data.childId,
+            callId: data.callId,
+            result,
+          });
+        }, error => {
+          error = context.normalizeError(error);
+          context.currentMessageManager.sendAsyncMessage("API:CallResult", {
+            childId: data.childId,
+            callId: data.callId,
+            error: {message: error.message},
+          });
+        });
+      }
     } catch (e) {
-      let msg = e.message || "API failed";
-      context.currentMessageManager.sendAsyncMessage("API:CallResult", {
-        childId: data.childId,
-        callId: data.callId,
-        lastError: msg,
-      });
+      if (data.callId) {
+        let error = context.normalizeError(e);
+        context.currentMessageManager.sendAsyncMessage("API:CallResult", {
+          childId: data.childId,
+          callId: data.callId,
+          error: {message: error.message},
+        });
+      } else {
+        Cu.reportError(e);
+      }
     }
   },
 
   addListener(data, target) {
     let context = this.getContextById(data.childId);
     if (context.currentMessageManager !== target.messageManager) {
       Cu.reportError("WebExtension warning: Message manager unexpectedly changed");
     }
@@ -707,62 +722,18 @@ GlobalManager = {
     `, false);
   },
 
   getExtension(extensionId) {
     return this.extensionMap.get(extensionId);
   },
 
   injectInObject(context, isChromeCompat, dest) {
-    let apis = {
-      extensionTypes: {},
-    };
-    Management.generateAPIs(context, apis);
-    SchemaAPIManager.generateAPIs(context, context.extension.apis, apis);
-
-    // For testing only.
-    context._unwrappedAPIs = apis;
-
-    let schemaWrapper = {
-      isChromeCompat,
-
-      get url() {
-        return context.uri.spec;
-      },
-
-      get principal() {
-        return context.principal;
-      },
-
-      get cloneScope() {
-        return context.cloneScope;
-      },
-
-      hasPermission(permission) {
-        return context.extension.hasPermission(permission);
-      },
-
-      shouldInject(namespace, name, allowedContexts) {
-        // Do not generate content script APIs, unless explicitly allowed.
-        if (context.envType === "content_parent" && !allowedContexts.includes("content")) {
-          return false;
-        }
-        if (context.envType !== "addon_parent" &&
-            allowedContexts.includes("addon_parent_only")) {
-          return false;
-        }
-        return findPathInObject(apis, namespace, false) !== null;
-      },
-
-      getImplementation(namespace, name) {
-        let pathObj = findPathInObject(apis, namespace);
-        return new LocalAPIImplementation(pathObj, name, context);
-      },
-    };
-    Schemas.inject(dest, schemaWrapper);
+    Management.generateAPIs(context, dest);
+    SchemaAPIManager.generateAPIs(context, context.extension.apis, dest);
   },
 };
 
 // Represents the data contained in an extension, contained either
 // in a directory or a zip file, which may or may not be installed.
 // This class implements the functionality of the Extension class,
 // primarily related to manifest parsing and localization, which is
 // useful prior to extension installation or initialization.
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -80,16 +80,18 @@ class PseudoChildAPIManager extends Chil
   createProxyContextInConstructor(originalData) {
     // Create a structured clone to simulate IPC.
     let data = Object.assign({}, originalData, {principal: null});
     data = Cu.cloneInto(data, {});
     // Principals can be structured cloned by message managers, but not
     // by cloneInto.
     data.principal = originalData.principal;
 
+    this.url = data.url;
+
     let name = "API:CreateProxyContext";
     // The <browser> that receives messages from `this.messageManager`.
     let target = this.context.contentWindow
       .QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIDocShell)
       .chromeEventHandler;
     ParentAPIManager.receiveMessage({name, data, target});
 
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -1888,20 +1888,20 @@ class ChildAPIManager {
         let listeners = this.listeners.get(data.path);
         for (let callback of listeners) {
           runSafe(this.context, callback, ...data.args);
         }
         break;
 
       case "API:CallResult":
         let deferred = this.callPromises.get(data.callId);
-        if (data.lastError) {
-          deferred.reject({message: data.lastError});
+        if ("error" in data) {
+          deferred.reject(data.error);
         } else {
-          deferred.resolve(new SpreadArgs(data.args));
+          deferred.resolve(new SpreadArgs(data.result));
         }
         this.callPromises.delete(data.callId);
         break;
     }
   }
 
   /**
    * Call a function in the parent process and ignores its return value.
--- a/toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js
@@ -27,17 +27,17 @@ add_task(function* test_storage_api_with
   let contextPromise = getNextContext();
   yield extension.startup();
 
   let context = yield contextPromise;
 
   // Force API initialization.
   void context.apiObj;
 
-  ok(!("storage" in context._unwrappedAPIs),
+  ok(!("storage" in context.apiObj),
      "The storage API should not be initialized");
 
   yield extension.unload();
 });
 
 add_task(function* test_storage_api_with_permissions() {
   let extension = ExtensionTestUtils.loadExtension({
     background() {
@@ -52,13 +52,13 @@ add_task(function* test_storage_api_with
   let contextPromise = getNextContext();
   yield extension.startup();
 
   let context = yield contextPromise;
 
   // Force API initialization.
   void context.apiObj;
 
-  equal(typeof context._unwrappedAPIs.storage, "object",
+  equal(typeof context.apiObj.storage, "object",
         "The storage API should be initialized");
 
   yield extension.unload();
 });