Bug 1319928 - Prevent gDevTools.showToolbox from racing when called multiple times in a row. r=jryans a=jcristau
authorAlexandre Poirot <poirot.alex@gmail.com>
Thu, 24 Nov 2016 10:03:12 -0800
changeset 352871 c64dab018c9085aeb3cb4ce7ab4910da8d53d0d4
parent 352870 99ce6563e17fcaff256bcb6b31f0fda6d6f91c7f
child 352872 85396a831d9e4c705bc05299c7d12941f2b90338
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)
reviewersjryans, jcristau
bugs1319928
milestone52.0a2
Bug 1319928 - Prevent gDevTools.showToolbox from racing when called multiple times in a row. r=jryans a=jcristau MozReview-Commit-ID: ZQrecrTwb5
devtools/client/framework/devtools.js
--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -27,16 +27,18 @@ const MAX_ORDINAL = 99;
 /**
  * DevTools is a class that represents a set of developer tools, it holds a
  * set of tools and keeps track of open toolboxes in the browser.
  */
 this.DevTools = function DevTools() {
   this._tools = new Map();     // Map<toolId, tool>
   this._themes = new Map();    // Map<themeId, theme>
   this._toolboxes = new Map(); // Map<target, toolbox>
+  // List of toolboxes that are still in process of creation
+  this._creatingToolboxes = new Map(); // Map<target, toolbox Promise>
 
   // destroy() is an observer's handler so we need to preserve context.
   this.destroy = this.destroy.bind(this);
 
   // JSON Viewer for 'application/json' documents.
   JsonView.initialize();
 
   AboutDevTools.register();
@@ -408,35 +410,51 @@ DevTools.prototype = {
       }
 
       if (toolId != null && toolbox.currentToolId != toolId) {
         yield toolbox.selectTool(toolId);
       }
 
       toolbox.raise();
     } else {
-      let manager = new ToolboxHostManager(target, hostType, hostOptions);
+      // As toolbox object creation is async, we have to be careful about races
+      // Check for possible already in process of loading toolboxes before
+      // actually trying to create a new one.
+      let promise = this._creatingToolboxes.get(target);
+      if (promise) {
+        return yield promise;
+      }
+      let toolboxPromise = this.createToolbox(target, toolId, hostType, hostOptions);
+      this._creatingToolboxes.set(target, toolboxPromise);
+      toolbox = yield toolboxPromise;
+      this._creatingToolboxes.delete(target);
+    }
+    return toolbox;
+  }),
 
-      toolbox = yield manager.create(toolId);
-      this._toolboxes.set(target, toolbox);
-
-      this.emit("toolbox-created", toolbox);
+  createToolbox: Task.async(function* (target, toolId, hostType, hostOptions) {
+    let manager = new ToolboxHostManager(target, hostType, hostOptions);
 
-      toolbox.once("destroy", () => {
-        this.emit("toolbox-destroy", target);
-      });
+    let toolbox = yield manager.create(toolId);
+
+    this._toolboxes.set(target, toolbox);
+
+    this.emit("toolbox-created", toolbox);
 
-      toolbox.once("destroyed", () => {
-        this._toolboxes.delete(target);
-        this.emit("toolbox-destroyed", target);
-      });
+    toolbox.once("destroy", () => {
+      this.emit("toolbox-destroy", target);
+    });
 
-      yield toolbox.open();
-      this.emit("toolbox-ready", toolbox);
-    }
+    toolbox.once("destroyed", () => {
+      this._toolboxes.delete(target);
+      this.emit("toolbox-destroyed", target);
+    });
+
+    yield toolbox.open();
+    this.emit("toolbox-ready", toolbox);
 
     return toolbox;
   }),
 
   /**
    * Return the toolbox for a given target.
    *
    * @param  {object} target