Bug 1319928 - Prevent gDevTools.showToolbox from racing when called multiple times in a row. r=jryans
authorAlexandre Poirot <poirot.alex@gmail.com>
Thu, 24 Nov 2016 10:03:12 -0800
changeset 324549 fc1af61ec6deccae5f6b4b2b36592b84161f0040
parent 324548 eb95159069bb4e50d52a004d6df9fcdf1f9eae42
child 324550 45bcf45afca2fcea5185abdefc8b878f26764a97
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersjryans
bugs1319928
milestone53.0a1
Bug 1319928 - Prevent gDevTools.showToolbox from racing when called multiple times in a row. r=jryans 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();
@@ -412,35 +414,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