|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 /** |
|
5 * Test simple requests using the protocol helpers. |
|
6 */ |
|
7 let protocol = devtools.require("devtools/server/protocol"); |
|
8 let {method, preEvent, types, Arg, Option, RetVal} = protocol; |
|
9 |
|
10 let events = devtools.require("sdk/event/core"); |
|
11 |
|
12 function simpleHello() { |
|
13 return { |
|
14 from: "root", |
|
15 applicationType: "xpcshell-tests", |
|
16 traits: [], |
|
17 } |
|
18 } |
|
19 |
|
20 let testTypes = {}; |
|
21 |
|
22 // Predeclaring the actor type so that it can be used in the |
|
23 // implementation of the child actor. |
|
24 types.addActorType("childActor"); |
|
25 |
|
26 let ChildActor = protocol.ActorClass({ |
|
27 typeName: "childActor", |
|
28 |
|
29 // Actors returned by this actor should be owned by the root actor. |
|
30 marshallPool: function() { return this.parent() }, |
|
31 |
|
32 toString: function() "[ChildActor " + this.childID + "]", |
|
33 |
|
34 initialize: function(conn, id) { |
|
35 protocol.Actor.prototype.initialize.call(this, conn); |
|
36 this.childID = id; |
|
37 }, |
|
38 |
|
39 destroy: function() { |
|
40 protocol.Actor.prototype.destroy.call(this); |
|
41 this.destroyed = true; |
|
42 }, |
|
43 |
|
44 form: function(detail) { |
|
45 if (detail === "actorid") { |
|
46 return this.actorID; |
|
47 } |
|
48 return { |
|
49 actor: this.actorID, |
|
50 childID: this.childID, |
|
51 detail: detail |
|
52 }; |
|
53 }, |
|
54 |
|
55 echo: method(function(str) { |
|
56 return str; |
|
57 }, { |
|
58 request: { str: Arg(0) }, |
|
59 response: { str: RetVal("string") }, |
|
60 telemetry: "ECHO" |
|
61 }), |
|
62 |
|
63 getDetail1: method(function() { |
|
64 return this; |
|
65 }, { |
|
66 // This also exercises return-value-as-packet. |
|
67 response: RetVal("childActor#detail1"), |
|
68 }), |
|
69 |
|
70 getDetail2: method(function() { |
|
71 return this; |
|
72 }, { |
|
73 // This also exercises return-value-as-packet. |
|
74 response: RetVal("childActor#detail2"), |
|
75 }), |
|
76 |
|
77 getIDDetail: method(function() { |
|
78 return this; |
|
79 }, { |
|
80 response: { |
|
81 idDetail: RetVal("childActor#actorid") |
|
82 } |
|
83 }), |
|
84 |
|
85 getSibling: method(function(id) { |
|
86 return this.parent().getChild(id); |
|
87 }, { |
|
88 request: { id: Arg(0) }, |
|
89 response: { sibling: RetVal("childActor") } |
|
90 }), |
|
91 |
|
92 emitEvents: method(function() { |
|
93 events.emit(this, "event1", 1, 2, 3); |
|
94 events.emit(this, "named-event", 1, 2, 3); |
|
95 events.emit(this, "object-event", this); |
|
96 events.emit(this, "array-object-event", [this]); |
|
97 }, { |
|
98 response: { value: "correct response" }, |
|
99 }), |
|
100 |
|
101 release: method(function() { }, { release: true }), |
|
102 |
|
103 events: { |
|
104 "event1" : { |
|
105 a: Arg(0), |
|
106 b: Arg(1), |
|
107 c: Arg(2) |
|
108 }, |
|
109 "named-event": { |
|
110 type: "namedEvent", |
|
111 a: Arg(0), |
|
112 b: Arg(1), |
|
113 c: Arg(2) |
|
114 }, |
|
115 "object-event": { |
|
116 type: "objectEvent", |
|
117 detail: Arg(0, "childActor#detail1"), |
|
118 }, |
|
119 "array-object-event": { |
|
120 type: "arrayObjectEvent", |
|
121 detail: Arg(0, "array:childActor#detail2"), |
|
122 } |
|
123 } |
|
124 }); |
|
125 |
|
126 let ChildFront = protocol.FrontClass(ChildActor, { |
|
127 initialize: function(client, form) { |
|
128 protocol.Front.prototype.initialize.call(this, client, form); |
|
129 }, |
|
130 |
|
131 destroy: function() { |
|
132 this.destroyed = true; |
|
133 protocol.Front.prototype.destroy.call(this); |
|
134 }, |
|
135 |
|
136 marshallPool: function() { return this.parent() }, |
|
137 |
|
138 toString: function() "[child front " + this.childID + "]", |
|
139 |
|
140 form: function(form, detail) { |
|
141 if (detail === "actorid") { |
|
142 return; |
|
143 } |
|
144 this.childID = form.childID; |
|
145 this.detail = form.detail; |
|
146 }, |
|
147 |
|
148 onEvent1: preEvent("event1", function(a, b, c) { |
|
149 this.event1arg3 = c; |
|
150 }), |
|
151 }); |
|
152 |
|
153 types.addDictType("manyChildrenDict", { |
|
154 child5: "childActor", |
|
155 more: "array:childActor", |
|
156 }); |
|
157 |
|
158 types.addLifetime("temp", "_temporaryHolder"); |
|
159 |
|
160 let rootActor = null; |
|
161 let RootActor = protocol.ActorClass({ |
|
162 typeName: "root", |
|
163 |
|
164 toString: function() "[root actor]", |
|
165 |
|
166 initialize: function(conn) { |
|
167 rootActor = this; |
|
168 this.actorID = "root"; |
|
169 this._children = {}; |
|
170 protocol.Actor.prototype.initialize.call(this, conn); |
|
171 // Root actor owns itself. |
|
172 this.manage(this); |
|
173 }, |
|
174 |
|
175 sayHello: simpleHello, |
|
176 |
|
177 getChild: method(function(id) { |
|
178 if (id in this._children) { |
|
179 return this._children[id]; |
|
180 } |
|
181 let child = new ChildActor(this.conn, id); |
|
182 this._children[id] = child; |
|
183 return child; |
|
184 }, { |
|
185 request: { str: Arg(0) }, |
|
186 response: { actor: RetVal("childActor") }, |
|
187 }), |
|
188 |
|
189 getChildren: method(function(ids) { |
|
190 return [this.getChild(id) for (id of ids)]; |
|
191 }, { |
|
192 request: { ids: Arg(0, "array:string") }, |
|
193 response: { children: RetVal("array:childActor") }, |
|
194 }), |
|
195 |
|
196 getManyChildren: method(function() { |
|
197 return { |
|
198 foo: "bar", // note that this isn't in the specialization array. |
|
199 child5: this.getChild("child5"), |
|
200 more: [ this.getChild("child6"), this.getChild("child7") ] |
|
201 } |
|
202 }, { |
|
203 response: RetVal("manyChildrenDict") |
|
204 }), |
|
205 |
|
206 // This should remind you of a pause actor. |
|
207 getTemporaryChild: method(function(id) { |
|
208 if (!this._temporaryHolder) { |
|
209 this._temporaryHolder = this.manage(new protocol.Actor(this.conn)); |
|
210 } |
|
211 return new ChildActor(this.conn, id); |
|
212 }, { |
|
213 request: { id: Arg(0) }, |
|
214 response: { child: RetVal("temp:childActor") } |
|
215 }), |
|
216 |
|
217 clearTemporaryChildren: method(function(id) { |
|
218 if (!this._temporaryHolder) { |
|
219 return; |
|
220 } |
|
221 this._temporaryHolder.destroy(); |
|
222 delete this._temporaryHolder; |
|
223 }) |
|
224 }); |
|
225 |
|
226 let RootFront = protocol.FrontClass(RootActor, { |
|
227 toString: function() "[root front]", |
|
228 initialize: function(client) { |
|
229 this.actorID = "root"; |
|
230 protocol.Front.prototype.initialize.call(this, client); |
|
231 // Root actor owns itself. |
|
232 this.manage(this); |
|
233 }, |
|
234 |
|
235 getTemporaryChild: protocol.custom(function(id) { |
|
236 if (!this._temporaryHolder) { |
|
237 this._temporaryHolder = this.manage(new protocol.Front(this.conn, {actor: this.actorID + "_temp"})); |
|
238 } |
|
239 return this._getTemporaryChild(id); |
|
240 },{ |
|
241 impl: "_getTemporaryChild" |
|
242 }), |
|
243 |
|
244 clearTemporaryChildren: protocol.custom(function() { |
|
245 if (!this._temporaryHolder) { |
|
246 return promise.resolve(undefined); |
|
247 } |
|
248 this._temporaryHolder.destroy(); |
|
249 delete this._temporaryHolder; |
|
250 return this._clearTemporaryChildren(); |
|
251 }, { |
|
252 impl: "_clearTemporaryChildren" |
|
253 }) |
|
254 }); |
|
255 |
|
256 function run_test() |
|
257 { |
|
258 DebuggerServer.createRootActor = (conn => { |
|
259 return RootActor(conn); |
|
260 }); |
|
261 DebuggerServer.init(() => true); |
|
262 |
|
263 let trace = connectPipeTracing(); |
|
264 let client = new DebuggerClient(trace); |
|
265 client.connect((applicationType, traits) => { |
|
266 trace.expectReceive({"from":"<actorid>","applicationType":"xpcshell-tests","traits":[]}) |
|
267 do_check_eq(applicationType, "xpcshell-tests"); |
|
268 |
|
269 let rootFront = RootFront(client); |
|
270 let childFront = null; |
|
271 |
|
272 let expectRootChildren = size => { |
|
273 do_check_eq(rootActor._poolMap.size, size + 1); |
|
274 do_check_eq(rootFront._poolMap.size, size + 1); |
|
275 if (childFront) { |
|
276 do_check_eq(childFront._poolMap.size, 0); |
|
277 } |
|
278 }; |
|
279 |
|
280 rootFront.getChild("child1").then(ret => { |
|
281 trace.expectSend({"type":"getChild","str":"child1","to":"<actorid>"}) |
|
282 trace.expectReceive({"actor":"<actorid>","from":"<actorid>"}) |
|
283 |
|
284 childFront = ret; |
|
285 do_check_true(childFront instanceof ChildFront); |
|
286 do_check_eq(childFront.childID, "child1"); |
|
287 expectRootChildren(1); |
|
288 }).then(() => { |
|
289 // Request the child again, make sure the same is returned. |
|
290 return rootFront.getChild("child1"); |
|
291 }).then(ret => { |
|
292 trace.expectSend({"type":"getChild","str":"child1","to":"<actorid>"}) |
|
293 trace.expectReceive({"actor":"<actorid>","from":"<actorid>"}) |
|
294 |
|
295 expectRootChildren(1); |
|
296 do_check_true(ret === childFront); |
|
297 }).then(() => { |
|
298 return childFront.echo("hello"); |
|
299 }).then(ret => { |
|
300 trace.expectSend({"type":"echo","str":"hello","to":"<actorid>"}) |
|
301 trace.expectReceive({"str":"hello","from":"<actorid>"}) |
|
302 |
|
303 do_check_eq(ret, "hello"); |
|
304 }).then(() => { |
|
305 return childFront.getDetail1(); |
|
306 }).then(ret => { |
|
307 trace.expectSend({"type":"getDetail1","to":"<actorid>"}); |
|
308 trace.expectReceive({"actor":"<actorid>","childID":"child1","detail":"detail1","from":"<actorid>"}); |
|
309 do_check_true(ret === childFront); |
|
310 do_check_eq(childFront.detail, "detail1"); |
|
311 }).then(() => { |
|
312 return childFront.getDetail2(); |
|
313 }).then(ret => { |
|
314 trace.expectSend({"type":"getDetail2","to":"<actorid>"}); |
|
315 trace.expectReceive({"actor":"<actorid>","childID":"child1","detail":"detail2","from":"<actorid>"}); |
|
316 do_check_true(ret === childFront); |
|
317 do_check_eq(childFront.detail, "detail2"); |
|
318 }).then(() => { |
|
319 return childFront.getIDDetail(); |
|
320 }).then(ret => { |
|
321 trace.expectSend({"type":"getIDDetail","to":"<actorid>"}); |
|
322 trace.expectReceive({"idDetail": childFront.actorID,"from":"<actorid>"}); |
|
323 do_check_true(ret === childFront); |
|
324 }).then(() => { |
|
325 return childFront.getSibling("siblingID"); |
|
326 }).then(ret => { |
|
327 trace.expectSend({"type":"getSibling","id":"siblingID","to":"<actorid>"}); |
|
328 trace.expectReceive({"sibling":{"actor":"<actorid>","childID":"siblingID"},"from":"<actorid>"}); |
|
329 |
|
330 expectRootChildren(2); |
|
331 }).then(ret => { |
|
332 return rootFront.getTemporaryChild("temp1").then(temp1 => { |
|
333 trace.expectSend({"type":"getTemporaryChild","id":"temp1","to":"<actorid>"}); |
|
334 trace.expectReceive({"child":{"actor":"<actorid>","childID":"temp1"},"from":"<actorid>"}); |
|
335 |
|
336 // At this point we expect two direct children, plus the temporary holder |
|
337 // which should hold 1 itself. |
|
338 do_check_eq(rootActor._temporaryHolder.__poolMap.size, 1); |
|
339 do_check_eq(rootFront._temporaryHolder.__poolMap.size, 1); |
|
340 |
|
341 expectRootChildren(3); |
|
342 return rootFront.getTemporaryChild("temp2").then(temp2 => { |
|
343 trace.expectSend({"type":"getTemporaryChild","id":"temp2","to":"<actorid>"}); |
|
344 trace.expectReceive({"child":{"actor":"<actorid>","childID":"temp2"},"from":"<actorid>"}); |
|
345 |
|
346 // Same amount of direct children, and an extra in the temporary holder. |
|
347 expectRootChildren(3); |
|
348 do_check_eq(rootActor._temporaryHolder.__poolMap.size, 2); |
|
349 do_check_eq(rootFront._temporaryHolder.__poolMap.size, 2); |
|
350 |
|
351 // Get the children of the temporary holder... |
|
352 let checkActors = [entry[1] for (entry of rootActor._temporaryHolder.__poolMap)]; |
|
353 let checkFronts = [entry[1] for (entry of rootFront._temporaryHolder.__poolMap)]; |
|
354 |
|
355 // Now release the temporary holders and expect them to drop again. |
|
356 return rootFront.clearTemporaryChildren().then(() => { |
|
357 trace.expectSend({"type":"clearTemporaryChildren","to":"<actorid>"}); |
|
358 trace.expectReceive({"from":"<actorid>"}); |
|
359 |
|
360 expectRootChildren(2); |
|
361 do_check_false(!!rootActor._temporaryHolder); |
|
362 do_check_false(!!rootFront._temporaryHolder); |
|
363 for (let checkActor of checkActors) { |
|
364 do_check_true(checkActor.destroyed); |
|
365 do_check_true(checkActor.destroyed); |
|
366 } |
|
367 }); |
|
368 }); |
|
369 }) |
|
370 }).then(ret => { |
|
371 return rootFront.getChildren(["child1", "child2"]); |
|
372 }).then(ret => { |
|
373 trace.expectSend({"type":"getChildren","ids":["child1","child2"],"to":"<actorid>"}); |
|
374 trace.expectReceive({"children":[{"actor":"<actorid>","childID":"child1"},{"actor":"<actorid>","childID":"child2"}],"from":"<actorid>"}); |
|
375 |
|
376 expectRootChildren(3); |
|
377 do_check_true(ret[0] === childFront); |
|
378 do_check_true(ret[1] !== childFront); |
|
379 do_check_true(ret[1] instanceof ChildFront); |
|
380 |
|
381 // On both children, listen to events. We're only |
|
382 // going to trigger events on the first child, so an event |
|
383 // triggered on the second should cause immediate failures. |
|
384 |
|
385 let set = new Set(["event1", "named-event", "object-event", "array-object-event"]); |
|
386 |
|
387 childFront.on("event1", (a, b, c) => { |
|
388 do_check_eq(a, 1); |
|
389 do_check_eq(b, 2); |
|
390 do_check_eq(c, 3); |
|
391 // Verify that the pre-event handler was called. |
|
392 do_check_eq(childFront.event1arg3, 3); |
|
393 set.delete("event1"); |
|
394 }); |
|
395 childFront.on("named-event", (a, b, c) => { |
|
396 do_check_eq(a, 1); |
|
397 do_check_eq(b, 2); |
|
398 do_check_eq(c, 3); |
|
399 set.delete("named-event"); |
|
400 }); |
|
401 childFront.on("object-event", (obj) => { |
|
402 do_check_true(obj === childFront); |
|
403 do_check_eq(childFront.detail, "detail1"); |
|
404 set.delete("object-event"); |
|
405 }); |
|
406 childFront.on("array-object-event", (array) => { |
|
407 do_check_true(array[0] === childFront); |
|
408 do_check_eq(childFront.detail, "detail2"); |
|
409 set.delete("array-object-event"); |
|
410 }); |
|
411 |
|
412 let fail = function() { |
|
413 do_throw("Unexpected event"); |
|
414 } |
|
415 ret[1].on("event1", fail); |
|
416 ret[1].on("named-event", fail); |
|
417 ret[1].on("object-event", fail); |
|
418 ret[1].on("array-object-event", fail); |
|
419 |
|
420 return childFront.emitEvents().then(() => { |
|
421 trace.expectSend({"type":"emitEvents","to":"<actorid>"}); |
|
422 trace.expectReceive({"type":"event1","a":1,"b":2,"c":3,"from":"<actorid>"}); |
|
423 trace.expectReceive({"type":"namedEvent","a":1,"b":2,"c":3,"from":"<actorid>"}); |
|
424 trace.expectReceive({"type":"objectEvent","detail":{"actor":"<actorid>","childID":"child1","detail":"detail1"},"from":"<actorid>"}); |
|
425 trace.expectReceive({"type":"arrayObjectEvent","detail":[{"actor":"<actorid>","childID":"child1","detail":"detail2"}],"from":"<actorid>"}); |
|
426 trace.expectReceive({"value":"correct response","from":"<actorid>"}); |
|
427 |
|
428 |
|
429 do_check_eq(set.size, 0); |
|
430 }); |
|
431 }).then(ret => { |
|
432 return rootFront.getManyChildren(); |
|
433 }).then(ret => { |
|
434 trace.expectSend({"type":"getManyChildren","to":"<actorid>"}); |
|
435 trace.expectReceive({"foo":"bar","child5":{"actor":"<actorid>","childID":"child5"},"more":[{"actor":"<actorid>","childID":"child6"},{"actor":"<actorid>","childID":"child7"}],"from":"<actorid>"}); |
|
436 |
|
437 // Check all the crazy stuff we did in getManyChildren |
|
438 do_check_eq(ret.foo, "bar"); |
|
439 do_check_eq(ret.child5.childID, "child5"); |
|
440 do_check_eq(ret.more[0].childID, "child6"); |
|
441 do_check_eq(ret.more[1].childID, "child7"); |
|
442 }).then(() => { |
|
443 client.close(() => { |
|
444 do_test_finished(); |
|
445 }); |
|
446 }).then(null, err => { |
|
447 do_report_unexpected_exception(err, "Failure executing test"); |
|
448 }); |
|
449 }); |
|
450 do_test_pending(); |
|
451 } |