test_protocol_children.js (17784B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 /* eslint-disable max-nested-callbacks */ 4 5 "use strict"; 6 7 /** 8 * Test simple requests using the protocol helpers. 9 */ 10 const protocol = require("resource://devtools/shared/protocol.js"); 11 const { types, Arg, RetVal } = protocol; 12 13 // Predeclaring the actor type so that it can be used in the 14 // implementation of the child actor. 15 types.addActorType("childActor"); 16 types.addActorType("otherChildActor"); 17 types.addPolymorphicType("polytype", ["childActor", "otherChildActor"]); 18 19 const childSpec = protocol.generateActorSpec({ 20 typeName: "childActor", 21 22 events: { 23 event1: { 24 a: Arg(0), 25 b: Arg(1), 26 c: Arg(2), 27 }, 28 event2: { 29 a: Arg(0), 30 b: Arg(1), 31 c: Arg(2), 32 }, 33 "named-event": { 34 type: "namedEvent", 35 a: Arg(0), 36 b: Arg(1), 37 c: Arg(2), 38 }, 39 "object-event": { 40 type: "objectEvent", 41 detail: Arg(0, "childActor#actorid"), 42 }, 43 "array-object-event": { 44 type: "arrayObjectEvent", 45 detail: Arg(0, "array:childActor#actorid"), 46 }, 47 }, 48 49 methods: { 50 echo: { 51 request: { str: Arg(0) }, 52 response: { str: RetVal("string") }, 53 }, 54 getDetail1: { 55 response: { 56 child: RetVal("childActor#actorid"), 57 }, 58 }, 59 getDetail2: { 60 response: { 61 child: RetVal("childActor#actorid"), 62 }, 63 }, 64 getIDDetail: { 65 response: { 66 idDetail: RetVal("childActor#actorid"), 67 }, 68 }, 69 getIntArray: { 70 request: { inputArray: Arg(0, "array:number") }, 71 response: { 72 intArray: RetVal("array:number"), 73 }, 74 }, 75 getSibling: { 76 request: { id: Arg(0) }, 77 response: { sibling: RetVal("childActor") }, 78 }, 79 emitEvents: { 80 response: { value: RetVal("string") }, 81 }, 82 release: { 83 release: true, 84 }, 85 }, 86 }); 87 88 class ChildActor extends protocol.Actor { 89 constructor(conn, id) { 90 super(conn, childSpec); 91 this.childID = id; 92 } 93 94 // Actors returned by this actor should be owned by the root actor. 95 marshallPool() { 96 return this.getParent(); 97 } 98 99 toString() { 100 return "[ChildActor " + this.childID + "]"; 101 } 102 103 destroy() { 104 super.destroy(); 105 this.destroyed = true; 106 } 107 108 form() { 109 return { 110 actor: this.actorID, 111 childID: this.childID, 112 }; 113 } 114 115 echo(str) { 116 return str; 117 } 118 119 getDetail1() { 120 return this; 121 } 122 123 getDetail2() { 124 return this; 125 } 126 127 getIDDetail() { 128 return this; 129 } 130 131 getIntArray(inputArray) { 132 // Test that protocol.js converts an iterator to an array. 133 const f = function* () { 134 for (const i of inputArray) { 135 yield 2 * i; 136 } 137 }; 138 return f(); 139 } 140 141 getSibling(id) { 142 return this.getParent().getChild(id); 143 } 144 145 emitEvents() { 146 this.emit("event1", 1, 2, 3); 147 this.emit("event2", 4, 5, 6); 148 this.emit("named-event", 1, 2, 3); 149 this.emit("object-event", this); 150 this.emit("array-object-event", [this]); 151 return "correct response"; 152 } 153 154 release() {} 155 } 156 157 class ChildFront extends protocol.FrontClassWithSpec(childSpec) { 158 constructor(client, targetFront, parentFront) { 159 super(client, targetFront, parentFront); 160 this._parentFront = parentFront; 161 162 this.before("event1", this.onEvent1.bind(this)); 163 this.before("event2", this.onEvent2a.bind(this)); 164 this.on("event2", this.onEvent2b.bind(this)); 165 } 166 167 destroy() { 168 this.destroyed = true; 169 // Call parent's destroy, which may be re-entrant and recall this function 170 this._parentFront.destroy(); 171 super.destroy(); 172 } 173 174 marshallPool() { 175 return this.getParent(); 176 } 177 178 toString() { 179 return "[child front " + this.childID + "]"; 180 } 181 182 form(form) { 183 this.childID = form.childID; 184 } 185 186 onEvent1(a, b, c) { 187 this.event1arg3 = c; 188 } 189 190 onEvent2a(a, b, c) { 191 return Promise.resolve().then(() => { 192 this.event2arg3 = c; 193 }); 194 } 195 196 onEvent2b(a, b) { 197 this.event2arg2 = b; 198 } 199 } 200 protocol.registerFront(ChildFront); 201 202 const otherChildSpec = protocol.generateActorSpec({ 203 typeName: "otherChildActor", 204 methods: { 205 getOtherChild: { 206 request: {}, 207 response: { sibling: RetVal("otherChildActor") }, 208 }, 209 }, 210 events: {}, 211 }); 212 213 class OtherChildActor extends protocol.Actor { 214 constructor(conn) { 215 super(conn, otherChildSpec); 216 } 217 218 getOtherChild() { 219 return new OtherChildActor(this.conn); 220 } 221 } 222 223 class OtherChildFront extends protocol.FrontClassWithSpec(otherChildSpec) {} 224 protocol.registerFront(OtherChildFront); 225 226 types.addDictType("manyChildrenDict", { 227 child5: "childActor", 228 more: "array:childActor", 229 }); 230 231 const rootSpec = protocol.generateActorSpec({ 232 typeName: "root", 233 234 methods: { 235 getChild: { 236 request: { str: Arg(0) }, 237 response: { actor: RetVal("childActor") }, 238 }, 239 getOtherChild: { 240 request: {}, 241 response: { sibling: RetVal("otherChildActor") }, 242 }, 243 getChildren: { 244 request: { ids: Arg(0, "array:string") }, 245 response: { children: RetVal("array:childActor") }, 246 }, 247 getChildren2: { 248 request: { ids: Arg(0, "array:childActor") }, 249 response: { children: RetVal("array:childActor") }, 250 }, 251 getManyChildren: { 252 response: RetVal("manyChildrenDict"), 253 }, 254 getPolymorphism: { 255 request: { id: Arg(0, "number") }, 256 response: { child: RetVal("polytype") }, 257 }, 258 requestPolymorphism: { 259 request: { 260 id: Arg(0, "number"), 261 actor: Arg(1, "polytype"), 262 }, 263 response: { child: RetVal("polytype") }, 264 }, 265 }, 266 }); 267 268 let rootActor = null; 269 class RootActor extends protocol.Actor { 270 constructor(conn) { 271 super(conn, rootSpec); 272 273 rootActor = this; 274 this.actorID = "root"; 275 this._children = {}; 276 } 277 278 toString() { 279 return "[root actor]"; 280 } 281 282 sayHello() { 283 return { 284 from: "root", 285 applicationType: "xpcshell-tests", 286 traits: [], 287 }; 288 } 289 290 getChild(id) { 291 if (id in this._children) { 292 return this._children[id]; 293 } 294 const child = new ChildActor(this.conn, id); 295 this._children[id] = child; 296 return child; 297 } 298 299 // Other child actor won't all be own by the root actor 300 // and can have their own children 301 getOtherChild() { 302 return new OtherChildActor(this.conn); 303 } 304 305 getChildren(ids) { 306 return ids.map(id => this.getChild(id)); 307 } 308 309 getChildren2(ids) { 310 const f = function* () { 311 for (const c of ids) { 312 yield c; 313 } 314 }; 315 return f(); 316 } 317 318 getManyChildren() { 319 return { 320 // note that this isn't in the specialization array. 321 foo: "bar", 322 child5: this.getChild("child5"), 323 more: [this.getChild("child6"), this.getChild("child7")], 324 }; 325 } 326 327 getPolymorphism(id) { 328 if (id == 0) { 329 return new ChildActor(this.conn, id); 330 } else if (id == 1) { 331 return new OtherChildActor(this.conn); 332 } 333 throw new Error("Unexpected id"); 334 } 335 336 requestPolymorphism(id, actor) { 337 if (id == 0 && actor instanceof ChildActor) { 338 return actor; 339 } else if (id == 1 && actor instanceof OtherChildActor) { 340 return actor; 341 } 342 throw new Error("Unexpected id or actor"); 343 } 344 } 345 346 class RootFront extends protocol.FrontClassWithSpec(rootSpec) { 347 constructor(client, targetFront, parentFront) { 348 super(client, targetFront, parentFront); 349 this.actorID = "root"; 350 // Root actor owns itself. 351 this.manage(this); 352 } 353 354 toString() { 355 return "[root front]"; 356 } 357 358 connect() {} 359 } 360 361 let rootFront, childFront; 362 function expectRootChildren(size) { 363 Assert.equal(rootActor._poolMap.size, size); 364 Assert.equal(rootFront._poolMap.size, size + 1); 365 if (childFront) { 366 Assert.equal(childFront._poolMap.size, 0); 367 } 368 } 369 protocol.registerFront(RootFront); 370 371 function childrenOfType(pool, type) { 372 const children = [...rootFront.poolChildren()]; 373 return children.filter(child => child instanceof type); 374 } 375 376 add_task(async function () { 377 DevToolsServer.createRootActor = conn => { 378 return new RootActor(conn); 379 }; 380 DevToolsServer.init(); 381 382 const trace = connectPipeTracing(); 383 const client = new DevToolsClient(trace); 384 const [applicationType] = await client.connect(); 385 trace.expectReceive({ 386 from: "<actorid>", 387 applicationType: "xpcshell-tests", 388 traits: [], 389 }); 390 Assert.equal(applicationType, "xpcshell-tests"); 391 392 rootFront = client.mainRoot; 393 394 await testSimpleChildren(trace); 395 await testDetail(trace); 396 await testSibling(trace); 397 await testEvents(trace); 398 await testManyChildren(trace); 399 await testGenerator(trace); 400 await testPolymorphism(trace); 401 await testUnmanageChildren(trace); 402 // Execute that assertion very last as it destroy the root front and actor 403 await testDestroy(trace); 404 405 await client.close(); 406 }); 407 408 async function testSimpleChildren(trace) { 409 childFront = await rootFront.getChild("child1"); 410 trace.expectSend({ type: "getChild", str: "child1", to: "<actorid>" }); 411 trace.expectReceive({ actor: "<actorid>", from: "<actorid>" }); 412 413 Assert.ok(childFront instanceof ChildFront); 414 Assert.equal(childFront.childID, "child1"); 415 expectRootChildren(1); 416 417 // Request the child again, make sure the same is returned. 418 let ret = await rootFront.getChild("child1"); 419 trace.expectSend({ type: "getChild", str: "child1", to: "<actorid>" }); 420 trace.expectReceive({ actor: "<actorid>", from: "<actorid>" }); 421 422 expectRootChildren(1); 423 Assert.strictEqual(ret, childFront); 424 425 ret = await childFront.echo("hello"); 426 trace.expectSend({ type: "echo", str: "hello", to: "<actorid>" }); 427 trace.expectReceive({ str: "hello", from: "<actorid>" }); 428 429 Assert.equal(ret, "hello"); 430 } 431 432 async function testDetail(trace) { 433 let ret = await childFront.getDetail1(); 434 trace.expectSend({ type: "getDetail1", to: "<actorid>" }); 435 trace.expectReceive({ child: childFront.actorID, from: "<actorid>" }); 436 Assert.strictEqual(ret, childFront); 437 438 ret = await childFront.getDetail2(); 439 trace.expectSend({ type: "getDetail2", to: "<actorid>" }); 440 trace.expectReceive({ child: childFront.actorID, from: "<actorid>" }); 441 Assert.strictEqual(ret, childFront); 442 443 ret = await childFront.getIDDetail(); 444 trace.expectSend({ type: "getIDDetail", to: "<actorid>" }); 445 trace.expectReceive({ 446 idDetail: childFront.actorID, 447 from: "<actorid>", 448 }); 449 Assert.strictEqual(ret, childFront); 450 } 451 452 async function testSibling(trace) { 453 await childFront.getSibling("siblingID"); 454 trace.expectSend({ 455 type: "getSibling", 456 id: "siblingID", 457 to: "<actorid>", 458 }); 459 trace.expectReceive({ 460 sibling: { actor: "<actorid>", childID: "siblingID" }, 461 from: "<actorid>", 462 }); 463 464 expectRootChildren(2); 465 } 466 467 async function testEvents(trace) { 468 const ret = await rootFront.getChildren(["child1", "child2"]); 469 trace.expectSend({ 470 type: "getChildren", 471 ids: ["child1", "child2"], 472 to: "<actorid>", 473 }); 474 trace.expectReceive({ 475 children: [ 476 { actor: "<actorid>", childID: "child1" }, 477 { actor: "<actorid>", childID: "child2" }, 478 ], 479 from: "<actorid>", 480 }); 481 482 expectRootChildren(3); 483 Assert.strictEqual(ret[0], childFront); 484 Assert.notStrictEqual(ret[1], childFront); 485 Assert.ok(ret[1] instanceof ChildFront); 486 487 // On both children, listen to events. We're only 488 // going to trigger events on the first child, so an event 489 // triggered on the second should cause immediate failures. 490 491 const set = new Set([ 492 "event1", 493 "event2", 494 "named-event", 495 "object-event", 496 "array-object-event", 497 ]); 498 499 childFront.on("event1", (a, b, c) => { 500 Assert.equal(a, 1); 501 Assert.equal(b, 2); 502 Assert.equal(c, 3); 503 // Verify that the pre-event handler was called. 504 Assert.equal(childFront.event1arg3, 3); 505 set.delete("event1"); 506 }); 507 childFront.on("event2", (a, b, c) => { 508 Assert.equal(a, 4); 509 Assert.equal(b, 5); 510 Assert.equal(c, 6); 511 // Verify that the async pre-event handler was called, 512 // setting the property before this handler was called. 513 Assert.equal(childFront.event2arg3, 6); 514 // And check that the sync preEvent with the same name is also 515 // executed 516 Assert.equal(childFront.event2arg2, 5); 517 set.delete("event2"); 518 }); 519 childFront.on("named-event", (a, b, c) => { 520 Assert.equal(a, 1); 521 Assert.equal(b, 2); 522 Assert.equal(c, 3); 523 set.delete("named-event"); 524 }); 525 childFront.on("object-event", obj => { 526 Assert.strictEqual(obj, childFront); 527 set.delete("object-event"); 528 }); 529 childFront.on("array-object-event", array => { 530 Assert.strictEqual(array[0], childFront); 531 set.delete("array-object-event"); 532 }); 533 534 const fail = function () { 535 do_throw("Unexpected event"); 536 }; 537 ret[1].on("event1", fail); 538 ret[1].on("event2", fail); 539 ret[1].on("named-event", fail); 540 ret[1].on("object-event", fail); 541 ret[1].on("array-object-event", fail); 542 543 await childFront.emitEvents(); 544 trace.expectSend({ type: "emitEvents", to: "<actorid>" }); 545 trace.expectReceive({ 546 type: "event1", 547 a: 1, 548 b: 2, 549 c: 3, 550 from: "<actorid>", 551 }); 552 trace.expectReceive({ 553 type: "event2", 554 a: 4, 555 b: 5, 556 c: 6, 557 from: "<actorid>", 558 }); 559 trace.expectReceive({ 560 type: "namedEvent", 561 a: 1, 562 b: 2, 563 c: 3, 564 from: "<actorid>", 565 }); 566 trace.expectReceive({ 567 type: "objectEvent", 568 detail: childFront.actorID, 569 from: "<actorid>", 570 }); 571 trace.expectReceive({ 572 type: "arrayObjectEvent", 573 detail: [childFront.actorID], 574 from: "<actorid>", 575 }); 576 trace.expectReceive({ value: "correct response", from: "<actorid>" }); 577 578 Assert.equal(set.size, 0); 579 } 580 581 async function testManyChildren(trace) { 582 const ret = await rootFront.getManyChildren(); 583 trace.expectSend({ type: "getManyChildren", to: "<actorid>" }); 584 trace.expectReceive({ 585 foo: "bar", 586 child5: { actor: "<actorid>", childID: "child5" }, 587 more: [ 588 { actor: "<actorid>", childID: "child6" }, 589 { actor: "<actorid>", childID: "child7" }, 590 ], 591 from: "<actorid>", 592 }); 593 594 // Check all the crazy stuff we did in getManyChildren 595 Assert.equal(ret.foo, "bar"); 596 Assert.equal(ret.child5.childID, "child5"); 597 Assert.equal(ret.more[0].childID, "child6"); 598 Assert.equal(ret.more[1].childID, "child7"); 599 } 600 601 async function testGenerator() { 602 // Test accepting a generator. 603 const f = function* () { 604 for (const i of [1, 2, 3, 4, 5]) { 605 yield i; 606 } 607 }; 608 let ret = await childFront.getIntArray(f()); 609 Assert.equal(ret.length, 5); 610 const expected = [2, 4, 6, 8, 10]; 611 for (let i = 0; i < 5; ++i) { 612 Assert.equal(ret[i], expected[i]); 613 } 614 615 const ids = await rootFront.getChildren(["child1", "child2"]); 616 const f2 = function* () { 617 for (const id of ids) { 618 yield id; 619 } 620 }; 621 ret = await rootFront.getChildren2(f2()); 622 Assert.equal(ret.length, 2); 623 Assert.strictEqual(ret[0], childFront); 624 Assert.notStrictEqual(ret[1], childFront); 625 Assert.ok(ret[1] instanceof ChildFront); 626 } 627 628 async function testPolymorphism() { 629 // Check polymorphic types returned by an actor 630 const firstChild = await rootFront.getPolymorphism(0); 631 Assert.ok(firstChild instanceof ChildFront); 632 633 // Check polymorphic types passed to a front 634 const sameFirstChild = await rootFront.requestPolymorphism(0, firstChild); 635 Assert.ok(sameFirstChild instanceof ChildFront); 636 Assert.equal(sameFirstChild, firstChild); 637 638 // Same with the second possible type 639 const secondChild = await rootFront.getPolymorphism(1); 640 Assert.ok(secondChild instanceof OtherChildFront); 641 642 const sameSecondChild = await rootFront.requestPolymorphism(1, secondChild); 643 Assert.ok(sameSecondChild instanceof OtherChildFront); 644 Assert.equal(sameSecondChild, secondChild); 645 646 // Check that any other type is rejected 647 Assert.throws(() => { 648 rootFront.requestPolymorphism(0, null); 649 }, /Was expecting one of these actors 'childActor,otherChildActor' but instead got an empty value/); 650 Assert.throws(() => { 651 rootFront.requestPolymorphism(0, 42); 652 }, /Was expecting one of these actors 'childActor,otherChildActor' but instead got value: '42'/); 653 Assert.throws(() => { 654 rootFront.requestPolymorphism(0, rootFront); 655 }, /Was expecting one of these actors 'childActor,otherChildActor' but instead got an actor of type: 'root'/); 656 } 657 658 async function testUnmanageChildren() { 659 // There is already one front of type OtherChildFront 660 Assert.equal(childrenOfType(rootFront, OtherChildFront).length, 1); 661 662 // Create another front of type OtherChildFront 663 const front = await rootFront.getPolymorphism(1); 664 Assert.ok(front instanceof OtherChildFront); 665 Assert.equal(childrenOfType(rootFront, OtherChildFront).length, 2); 666 667 // Remove all fronts of type OtherChildFront 668 rootFront.unmanageChildren(OtherChildFront); 669 Assert.ok( 670 !front.isDestroyed(), 671 "Unmanaged front is not considered as destroyed" 672 ); 673 Assert.equal(childrenOfType(rootFront, OtherChildFront).length, 0); 674 } 675 676 async function testDestroy() { 677 const front = await rootFront.getOtherChild(); 678 const otherChildFront = await front.getOtherChild(); 679 Assert.equal( 680 otherChildFront.getParent(), 681 front, 682 "the child is a children of first front" 683 ); 684 685 front.destroy(); 686 Assert.ok(front.isDestroyed(), "sibling is correctly reported as destroyed"); 687 Assert.ok(!front.getParent(), "sibling has no more parent declared"); 688 Assert.ok(otherChildFront.isDestroyed(), "the child is also destroyed"); 689 Assert.ok( 690 !otherChildFront.getParent(), 691 "the child also has no more parent declared" 692 ); 693 Assert.ok( 694 !otherChildFront.parentPool, 695 "the child also has its parentPool attribute nullified" 696 ); 697 698 // Verify that re-entrant Front.destroy doesn't throw, nor loop 699 // Execute that very last as it will destroy the root actor and front 700 const sibling = await childFront.getSibling("siblingID"); 701 sibling.destroy(); 702 }