Bug 1354941: Pre-load all cached schema data in one DB operation. r?mixedpuppy draft
authorKris Maglione <maglione.k@gmail.com>
Sun, 09 Apr 2017 14:06:55 -0700
changeset 559374 9415c28f47e30ad292128b44c336eba7d516606b
parent 559373 970365dbd6e8dcb0e017fc71d0b562d141de54bc
child 623365 77cf2938d50ae09bae4ad68cd91c7752fcf03ff4
push id53060
push usermaglione.k@gmail.com
push dateSun, 09 Apr 2017 21:44:06 +0000
reviewersmixedpuppy
bugs1354941
milestone55.0a1
Bug 1354941: Pre-load all cached schema data in one DB operation. r?mixedpuppy MozReview-Commit-ID: JyBOcR2Ea15
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/Schemas.jsm
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -53,32 +53,32 @@ XPCOMUtils.defineLazyGetter(this, "uniqu
 
 function getUniqueId() {
   return `${nextId++}-${uniqueProcessID}`;
 }
 
 let StartupCache = {
   DB_NAME: "ExtensionStartupCache",
 
-  SCHEMA_VERSION: 1,
+  SCHEMA_VERSION: 2,
 
   STORE_NAMES: Object.freeze(["locales", "manifests", "schemas"]),
 
   dbPromise: null,
 
   cacheInvalidated: 0,
 
   initDB(db) {
     for (let name of StartupCache.STORE_NAMES) {
       try {
         db.deleteObjectStore(name);
       } catch (e) {
         // Don't worry if the store doesn't already exist.
       }
-      db.createObjectStore(name);
+      db.createObjectStore(name, {keyPath: "key"});
     }
   },
 
   clearAddonData(id) {
     let range = IDBKeyRange.bound([id], [id, "\uFFFF"]);
 
     return Promise.all([
       this.locales.delete(range),
@@ -129,36 +129,54 @@ Services.obs.addObserver(StartupCache, "
 
 class CacheStore {
   constructor(storeName) {
     this.storeName = storeName;
   }
 
   async get(key, createFunc) {
     let db;
-    let value;
+    let result;
     try {
       db = await StartupCache.open();
 
-      value = await db.objectStore(this.storeName)
+      result = await db.objectStore(this.storeName)
                       .get(key);
     } catch (e) {
       Cu.reportError(e);
 
       return createFunc(key);
     }
 
-    if (value === undefined) {
-      value = await createFunc(key);
+    if (result === undefined) {
+      let value = await createFunc(key);
+      result = {key, value};
 
       db.objectStore(this.storeName, "readwrite")
-        .put(value, key);
+        .put(result);
     }
 
-    return value;
+    return result && result.value;
+  }
+
+  async getAll() {
+    let result = new Map();
+    try {
+      let db = await StartupCache.open();
+
+      let results = await db.objectStore(this.storeName)
+                            .getAll();
+      for (let {key, value} of results) {
+        result.set(key, value);
+      }
+    } catch (e) {
+      Cu.reportError(e);
+    }
+
+    return result;
   }
 
   async delete(key) {
     let db = await StartupCache.open();
 
     return db.objectStore(this.storeName, "readwrite").delete(key);
   }
 }
--- a/toolkit/components/extensions/Schemas.jsm
+++ b/toolkit/components/extensions/Schemas.jsm
@@ -30,16 +30,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyServiceGetter(this, "contentPolicyService",
                                    "@mozilla.org/addons/content-policy;1",
                                    "nsIAddonContentPolicy");
 
 this.EXPORTED_SYMBOLS = ["Schemas"];
 
 const {DEBUG} = AppConstants;
 
+const isParentProcess = Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
+
 /* globals Schemas, URL */
 
 function readJSON(url) {
   return new Promise((resolve, reject) => {
     NetUtil.asyncFetch({uri: url, loadUsingSystemPrincipal: true}, (inputStream, status) => {
       if (!Components.isSuccessCode(status)) {
         // Convert status code to a string
         let e = Components.Exception("", status);
@@ -2549,22 +2551,28 @@ this.Schemas = {
 
       case "Schema:Delete":
         this.schemaJSON.delete(msg.data.url);
         this.flushSchemas();
         break;
     }
   },
 
+  _needFlush: true,
   flushSchemas() {
-    XPCOMUtils.defineLazyGetter(this, "rootNamespace",
-                                () => this.parseSchemas());
+    if (this._needFlush) {
+      this._needFlush = false;
+      XPCOMUtils.defineLazyGetter(this, "rootNamespace",
+                                  () => this.parseSchemas());
+    }
   },
 
   parseSchemas() {
+    this._needFlush = true;
+
     Object.defineProperty(this, "rootNamespace", {
       enumerable: true,
       configurable: true,
       value: new Namespace("", []),
     });
 
     for (let json of this.schemaJSON.values()) {
       try {
@@ -2579,29 +2587,51 @@ this.Schemas = {
 
   loadSchema(json) {
     for (let namespace of json) {
       this.getNamespace(namespace.namespace)
           .addSchema(namespace);
     }
   },
 
-  load(url) {
-    if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_CONTENT) {
-      return StartupCache.schemas.get(url, readJSON).then(json => {
-        this.schemaJSON.set(url, json);
-
-        let data = Services.ppmm.initialProcessData;
-        data["Extension:Schemas"] = this.schemaJSON;
-
-        Services.ppmm.broadcastAsyncMessage("Schema:Add", {url, schema: json});
-
-        this.flushSchemas();
+  _loadCachedSchemasPromise: null,
+  loadCachedSchemas() {
+    if (!this._loadCachedSchemasPromise) {
+      this._loadCachedSchemasPromise = StartupCache.schemas.getAll().then(results => {
+        for (let [url, json] of results) {
+          this.addSchema(url, json);
+        }
       });
     }
+
+    return this._loadCachedSchemasPromise;
+  },
+
+  addSchema(url, json) {
+    this.schemaJSON.set(url, json);
+
+    let data = Services.ppmm.initialProcessData;
+    data["Extension:Schemas"] = this.schemaJSON;
+
+    Services.ppmm.broadcastAsyncMessage("Schema:Add", {url, schema: json});
+
+    this.flushSchemas();
+  },
+
+  async load(url) {
+    if (!isParentProcess) {
+      return;
+    }
+
+    await this.loadCachedSchemas();
+
+    if (!this.schemaJSON.has(url)) {
+      let json = await StartupCache.schemas.get(url, readJSON);
+      this.addSchema(url, json);
+    }
   },
 
   unload(url) {
     this.schemaJSON.delete(url);
 
     let data = Services.ppmm.initialProcessData;
     data["Extension:Schemas"] = this.schemaJSON;