Bug 1291815 - Wait for requests to settle between DAMP subtests. r=ochameau draft
authorJ. Ryan Stinnett <jryans@gmail.com>
Thu, 29 Sep 2016 16:51:15 -0500
changeset 439382 47e8a8542dc168ca136d462dbeecb96465398ec0
parent 438469 feddafb5cb546b15b160260da8632beb6b89bd71
child 537158 9b874d6f53aa9f81b6b0550402a403fc97fbc652
push id35995
push userbmo:jryans@gmail.com
push dateWed, 16 Nov 2016 01:00:39 +0000
reviewersochameau
bugs1291815
milestone53.0a1
Bug 1291815 - Wait for requests to settle between DAMP subtests. r=ochameau MozReview-Commit-ID: 2WH1QquOclP
devtools/shared/client/main.js
devtools/shared/protocol.js
testing/talos/talos/tests/devtools/addon/content/damp.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/main.js
@@ -1156,16 +1156,78 @@ DebuggerClient.prototype = {
         return;
       }
       this._activeRequests.delete(actor);
       activeRequestsToReject = activeRequestsToReject.concat(request);
     });
     activeRequestsToReject.forEach(request => reject("active", request));
   },
 
+  /**
+   * Search for all requests in process for this client, including those made via
+   * protocol.js and wait all of them to complete.  Since the requests seen when this is
+   * first called may in turn trigger more requests, we keep recursing through this
+   * function until there is no more activity.
+   *
+   * This is a fairly heavy weight process, so it's only meant to be used in tests.
+   *
+   * @return Promise
+   *         Resolved when all requests have settled.
+   */
+  waitForRequestsToSettle() {
+    let requests = [];
+
+    // Gather all pending and active requests in this client
+    // The request object supports a Promise API for completion (it has .then())
+    this._pendingRequests.forEach(requestsForActor => {
+      // Each value is an array of pending requests
+      requests = requests.concat(requestsForActor);
+    });
+    this._activeRequests.forEach(requestForActor => {
+      // Each value is a single active request
+      requests = requests.concat(requestForActor);
+    });
+
+    // protocol.js
+    // Use a Set because some fronts (like domwalker) seem to have multiple parents.
+    let fronts = new Set();
+    let poolsToVisit = [...this._pools];
+
+    // With protocol.js, each front can potentially have it's own pools containing child
+    // fronts, forming a tree.  Descend through all the pools to locate all child fronts.
+    while (poolsToVisit.length) {
+      let pool = poolsToVisit.shift();
+      fronts.add(pool);
+      for (let child of pool.poolChildren()) {
+        poolsToVisit.push(child);
+      }
+    }
+
+    // For each front, wait for its requests to settle
+    for (let front of fronts) {
+      if (front.hasRequests()) {
+        requests.push(front.waitForRequestsToSettle());
+      }
+    }
+
+    // Abort early if there are no requests
+    if (!requests.length) {
+      return Promise.resolve();
+    }
+
+    return DevToolsUtils.settleAll(requests).catch(() => {
+      // One of the requests might have failed, but ignore that situation here and pipe
+      // both success and failure through the same path.  The important part is just that
+      // we waited.
+    }).then(() => {
+      // Repeat, more requests may have started in response to those we just waited for
+      return this.waitForRequestsToSettle();
+    });
+  },
+
   registerClient: function (client) {
     let actorID = client.actor;
     if (!actorID) {
       throw new Error("DebuggerServer.registerClient expects " +
                       "a client instance with an `actor` attribute.");
     }
     if (!Array.isArray(client.events)) {
       throw new Error("DebuggerServer.registerClient expects " +
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -6,16 +6,17 @@
 
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var {Class} = require("sdk/core/heritage");
 var {EventTarget} = require("sdk/event/target");
 var events = require("sdk/event/core");
 var object = require("sdk/util/object");
 var {getStack, callFunctionWithAsyncStack} = require("devtools/shared/platform/stack");
+var {settleAll} = require("devtools/shared/DevToolsUtils");
 
 exports.emit = events.emit;
 
 /**
  * Types: named marshallers/demarshallers.
  *
  * Types provide a 'write' function that takes a js representation and
  * returns a protocol representation, and a "read" function that
@@ -789,16 +790,30 @@ var Pool = Class({
     return this.__poolMap ? this._poolMap.get(actorID) : null;
   },
 
   // True if this pool has no children.
   isEmpty: function () {
     return !this.__poolMap || this._poolMap.size == 0;
   },
 
+  // Generator that yields each non-self child of the pool.
+  poolChildren: function* () {
+    if (!this.__poolMap) {
+      return;
+    }
+    for (let actor of this.__poolMap.values()) {
+      // Self-owned actors are ok, but don't need visiting twice.
+      if (actor === this) {
+        continue;
+      }
+      yield actor;
+    }
+  },
+
   /**
    * Destroy this item, removing it from a parent if it has one,
    * and destroying all children if necessary.
    */
   destroy: function () {
     let parent = this.parent();
     if (parent) {
       parent.unmanage(this);
@@ -1278,17 +1293,33 @@ var Front = Class({
         } else {
           message = packet.error;
         }
         deferred.reject(message);
       } else {
         deferred.resolve(packet);
       }
     }, stack, "DevTools RDP");
-  }
+  },
+
+  hasRequests() {
+    return !!this._requests.length;
+  },
+
+  /**
+   * Wait for all current requests from this front to settle.  This is especially useful
+   * for tests and other utility environments that may not have events or mechanisms to
+   * await the completion of requests without this utility.
+   *
+   * @return Promise
+   *         Resolved when all requests have settled.
+   */
+  waitForRequestsToSettle() {
+    return settleAll(this._requests.map(({ deferred }) => deferred.promise));
+  },
 });
 exports.Front = Front;
 
 /**
  * A method tagged with preEvent will be called after recieving a packet
  * for that event, and before the front emits the event.
  */
 exports.preEvent = function (eventName, fn) {
--- a/testing/talos/talos/tests/devtools/addon/content/damp.js
+++ b/testing/talos/talos/tests/devtools/addon/content/damp.js
@@ -68,28 +68,27 @@ Damp.prototype = {
       let stopRecordTimestamp = performance.now();
       return {
         toolbox,
         time: stopRecordTimestamp - startRecordTimestamp
       };
     });
   },
 
-  closeToolbox: function() {
+  closeToolbox: Task.async(function*() {
     let tab = getActiveTab(getMostRecentBrowserWindow());
     let target = devtools.TargetFactory.forTab(tab);
+    yield target.client.waitForRequestsToSettle();
     let startRecordTimestamp = performance.now();
-    let closePromise = gDevTools.closeToolbox(target);
-    return closePromise.then(() => {
-      let stopRecordTimestamp = performance.now();
-      return {
-        time: stopRecordTimestamp - startRecordTimestamp
-      };
-    });
-  },
+    yield gDevTools.closeToolbox(target);
+    let stopRecordTimestamp = performance.now();
+    return {
+      time: stopRecordTimestamp - startRecordTimestamp
+    };
+  }),
 
   saveHeapSnapshot: function(label) {
     let tab = getActiveTab(getMostRecentBrowserWindow());
     let target = devtools.TargetFactory.forTab(tab);
     let toolbox = gDevTools.getToolbox(target);
     let panel = toolbox.getCurrentPanel();
     let memoryFront = panel.panelWin.gFront;