/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; /* Exercise prefix-based forwarding of packets to other transports. */ const { RootActor } = require("resource://devtools/server/actors/root.js"); var gMainConnection, gMainTransport; var gSubconnection1, gSubconnection2; var gClient; function run_test() { DevToolsServer.init(); add_test(createMainConnection); add_test(TestNoForwardingYet); add_test(createSubconnection1); add_test(TestForwardPrefix1OnlyRoot); add_test(createSubconnection2); add_test(TestForwardPrefix12OnlyRoot); add_test(TestForwardPrefix12WithActor1); add_test(TestForwardPrefix12WithActor12); run_next_test(); } /* * Create a pipe connection, and return an object |{ conn, transport }|, * where |conn| is the new DevToolsServerConnection instance, and * |transport| is the client side of the transport on which it communicates * (that is, packets sent on |transport| go to the new connection, and * |transport|'s hooks receive replies). * * |prefix| is optional; if present, it's the prefix (minus the '/') for * actors in the new connection. */ function newConnection(prefix) { let conn; DevToolsServer.createRootActor = function (connection) { conn = connection; return new RootActor(connection, {}); }; const transport = DevToolsServer.connectPipe(prefix); return { conn, transport }; } /* Create the main connection for these tests. */ function createMainConnection() { ({ conn: gMainConnection, transport: gMainTransport } = newConnection()); gClient = new DevToolsClient(gMainTransport); gClient.connect().then(([type, traits]) => run_next_test()); } /* * Exchange 'echo' messages with five actors: * - root * - prefix1/root * - prefix1/actor * - prefix2/root * - prefix2/actor * * Expect proper echos from those named in |reachables|, and 'noSuchActor' * errors from the others. When we've gotten all our replies (errors or * otherwise), call |completed|. * * To avoid deep stacks, we call completed from the next tick. */ async function tryActors(reachables, completed) { for (const actor of [ "root", "prefix1/root", "prefix1/actor", "prefix2/root", "prefix2/actor", ]) { let response; try { if (actor.endsWith("root")) { // Root actor doesn't expose any echo method, // so fallback on getRoot which returns `{ from: "root" }`. // For the top level root actor, we have to use its front. if (actor == "root") { response = await gClient.mainRoot.getRoot(); } else { response = await gClient.request({ to: actor, type: "getRoot" }); } } else { response = await gClient.request({ to: actor, type: "echo", value: "tango", }); } } catch (e) { response = e; } if (reachables.has(actor)) { if (actor.endsWith("root")) { // RootActor's getRoot response is almost empty on xpcshell Assert.deepEqual({ from: actor }, response); } else { Assert.deepEqual( { from: actor, to: actor, type: "echo", value: "tango" }, response ); } } else { Assert.deepEqual( { from: actor, error: "noSuchActor", message: "No such actor for ID: " + actor, }, response ); } } executeSoon(completed, "tryActors callback " + completed.name); } /* * With no forwarding established, sending messages to root should work, * but sending messages to prefixed actor names, or anyone else, should get * an error. */ function TestNoForwardingYet() { tryActors(new Set(["root"]), run_next_test); } /* * Create a new pipe connection which forwards its reply packets to * gMainConnection's client, and to which gMainConnection forwards packets * directed to actors whose names begin with |prefix + '/'|, and. * * Return an object { conn, transport }, as for newConnection. */ function newSubconnection(prefix) { const { conn, transport } = newConnection(prefix); transport.hooks = { onPacket: packet => gMainConnection.send(packet), }; gMainConnection.setForwarding(prefix, transport); return { conn, transport }; } /* Create a second root actor, to which we can forward things. */ function createSubconnection1() { const { conn, transport } = newSubconnection("prefix1"); gSubconnection1 = conn; transport.ready(); gClient.expectReply("prefix1/root", reply => run_next_test()); } // Establish forwarding, but don't put any actors in that server. function TestForwardPrefix1OnlyRoot() { tryActors(new Set(["root", "prefix1/root"]), run_next_test); } /* Create a third root actor, to which we can forward things. */ function createSubconnection2() { const { conn, transport } = newSubconnection("prefix2"); gSubconnection2 = conn; transport.ready(); gClient.expectReply("prefix2/root", reply => run_next_test()); } function TestForwardPrefix12OnlyRoot() { tryActors(new Set(["root", "prefix1/root", "prefix2/root"]), run_next_test); } // A dumb actor that implements 'echo'. // // It's okay that both subconnections' actors behave identically, because // the reply-sending code attaches the replying actor's name to the packet, // so simply matching the 'from' field in the reply ensures that we heard // from the right actor. const { Actor } = require("resource://devtools/shared/protocol/Actor.js"); class EchoActor extends Actor { constructor(conn) { super(conn, { typeName: "EchoActor", methods: [] }); this.requestTypes = { echo: EchoActor.prototype.onEcho, }; } onEcho(request) { /* * Request packets are frozen. Copy request, so that * DevToolsServerConnection.onPacket can attach a 'from' property. */ return JSON.parse(JSON.stringify(request)); } } function TestForwardPrefix12WithActor1() { const actor = new EchoActor(gSubconnection1); actor.actorID = "prefix1/actor"; gSubconnection1.addActor(actor); tryActors( new Set(["root", "prefix1/root", "prefix1/actor", "prefix2/root"]), run_next_test ); } function TestForwardPrefix12WithActor12() { const actor = new EchoActor(gSubconnection2); actor.actorID = "prefix2/actor"; gSubconnection2.addActor(actor); tryActors( new Set([ "root", "prefix1/root", "prefix1/actor", "prefix2/root", "prefix2/actor", ]), run_next_test ); }