From 78e2ca28f0fd8b78ed7a8f245be57e60ec69a64c Mon Sep 17 00:00:00 2001 From: Alexandre Poirot Date: Thu, 15 Nov 2018 10:23:00 +0000 Subject: [PATCH] Bug 1506549 - Introduce API to listen for new child fronts of a given type. r=yulia MozReview-Commit-ID: EKWTGhGo0VM Depends on D11624 Differential Revision: https://phabricator.services.mozilla.com/D11625 --HG-- extra : moz-landing-system : lando --- .../tests/unit/test_protocol_onFront.js | 131 ++++++++++++++++++ devtools/server/tests/unit/xpcshell.ini | 1 + devtools/shared/protocol.js | 21 +++ 3 files changed, 153 insertions(+) create mode 100644 devtools/server/tests/unit/test_protocol_onFront.js diff --git a/devtools/server/tests/unit/test_protocol_onFront.js b/devtools/server/tests/unit/test_protocol_onFront.js new file mode 100644 index 000000000000..3da5eee47ce5 --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_onFront.js @@ -0,0 +1,131 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Test Front.onFront method. + */ + +const protocol = require("devtools/shared/protocol"); +const {RetVal} = protocol; + +const childSpec = protocol.generateActorSpec({ + typeName: "childActor", +}); + +const ChildActor = protocol.ActorClassWithSpec(childSpec, { + initialize(conn, id) { + protocol.Actor.prototype.initialize.call(this, conn); + this.childID = id; + }, + + form: function(detail) { + if (detail === "actorid") { + return this.actorID; + } + return { + actor: this.actorID, + childID: this.childID, + }; + }, +}); + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + methods: { + createChild: { + request: {}, + response: { actor: RetVal("childActor") }, + }, + }, +}); + +const RootActor = protocol.ActorClassWithSpec(rootSpec, { + typeName: "root", + + initialize: function(conn) { + protocol.Actor.prototype.initialize.call(this, conn); + + this.actorID = "root"; + + // Root actor owns itself. + this.manage(this); + + this.sequence = 0; + }, + + sayHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; + }, + + createChild() { + return new ChildActor(this.conn, this.sequence++); + }, +}); + +const ChildFront = protocol.FrontClassWithSpec(childSpec, { + form(form, detail) { + if (detail === "actorid") { + return; + } + this.childID = form.childID; + }, +}); + +const RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize(client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root owns itself. + this.manage(this); + }, +}); + +add_task(async function run_test() { + DebuggerServer.createRootActor = RootActor; + DebuggerServer.init(); + + const trace = connectPipeTracing(); + const client = new DebuggerClient(trace); + await client.connect(); + + const rootFront = new RootFront(client); + + const fronts = []; + rootFront.onFront("childActor", front => { + fronts.push(front); + }); + + const firstChild = await rootFront.createChild(); + ok(firstChild instanceof ChildFront, "createChild returns a ChildFront instance"); + equal(firstChild.childID, 0, "First child has ID=0"); + + equal(fronts.length, 1, + "onFront fires the callback, even if the front is created in the future"); + equal(fronts[0], firstChild, + "onFront fires the callback with the right front instance"); + + const onFrontAfter = await new Promise(resolve => { + rootFront.onFront("childActor", resolve); + }); + equal(onFrontAfter, firstChild, + "onFront fires the callback, even if the front is already created, " + + " with the same front instance"); + + equal(fronts.length, 1, + "There is still only one front reported from the first listener"); + + const secondChild = await rootFront.createChild(); + + equal(fronts.length, 2, "After a second call to createChild, two fronts are reported"); + equal(fronts[1], secondChild, "And the new front is the right instance"); + + trace.close(); + await client.close(); +}); diff --git a/devtools/server/tests/unit/xpcshell.ini b/devtools/server/tests/unit/xpcshell.ini index 9247263f995e..06720fc0e9ef 100644 --- a/devtools/server/tests/unit/xpcshell.ini +++ b/devtools/server/tests/unit/xpcshell.ini @@ -106,6 +106,7 @@ skip-if = coverage # bug 1336670 [test_protocol_children.js] [test_protocol_formtype.js] [test_protocol_longstring.js] +[test_protocol_onFront.js] [test_protocol_simple.js] [test_protocol_stack.js] [test_protocol_unregister.js] diff --git a/devtools/shared/protocol.js b/devtools/shared/protocol.js index 4da6a43bc038..6c30bdee94eb 100644 --- a/devtools/shared/protocol.js +++ b/devtools/shared/protocol.js @@ -1285,6 +1285,10 @@ var Front = function(conn = null, form = null, detail = null, context = null) { Pool.call(this, conn); this._requests = []; + // Front listener functions registered via `onFront` get notified + // of new fronts via this dedicated EventEmitter object. + this._frontListeners = new EventEmitter(); + // protocol.js no longer uses this data in the constructor, only external // uses do. External usage of manually-constructed fronts will be // drastically reduced if we convert the root and target actors to @@ -1314,6 +1318,7 @@ Front.prototype = extend(Pool.prototype, { } Pool.prototype.destroy.call(this); this.actorID = null; + this._frontListeners = null; }, manage: function(front) { @@ -1322,6 +1327,22 @@ Front.prototype = extend(Pool.prototype, { "Ensure server supports " + front.typeName + "."); } Pool.prototype.manage.call(this, front); + + // Call listeners registered via `onFront` method + this._frontListeners.emit(front.typeName, front); + }, + + // Run callback on every front of this type that currently exists, and on every + // instantiation of front type in the future. + onFront(typeName, callback) { + // First fire the callback on already instantiated fronts + for (const front of this.poolChildren()) { + if (front.typeName == typeName) { + callback(front); + } + } + // Then register the callback for fronts instantiated in the future + this._frontListeners.on(typeName, callback); }, toString: function() {