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 351576 f29c03c0682adebf120110a654d22eb5130d7bca
parent 351575 2e0180c345940fa1893ef4e26ac68effa024ef48
child 351577 6fb5f161f09c4a75984b053c022fe36a101a7bcc
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1315575
milestone52.0a1
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();
 });