|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 "use strict"; |
|
5 |
|
6 module.metadata = { |
|
7 "engines": { |
|
8 "Firefox": "*" |
|
9 } |
|
10 }; |
|
11 |
|
12 const { Cu } = require("chrome"); |
|
13 const { Frame } = require("sdk/ui/frame"); |
|
14 const { Toolbar } = require("sdk/ui/toolbar"); |
|
15 const { Loader } = require("sdk/test/loader"); |
|
16 const { identify } = require("sdk/ui/id"); |
|
17 const { setTimeout } = require("sdk/timers"); |
|
18 const { getMostRecentBrowserWindow, open } = require("sdk/window/utils"); |
|
19 const { ready, loaded, close } = require("sdk/window/helpers"); |
|
20 const { defer, all } = require("sdk/core/promise"); |
|
21 const { send } = require("sdk/event/utils"); |
|
22 const { object } = require("sdk/util/sequence"); |
|
23 const { OutputPort } = require("sdk/output/system"); |
|
24 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); |
|
25 const output = new OutputPort({ id: "toolbar-change" }); |
|
26 |
|
27 const wait = (toolbar, event, times) => { |
|
28 let { promise, resolve } = defer(); |
|
29 if (times) { |
|
30 let resolveArray = []; |
|
31 let counter = 0; |
|
32 toolbar.on(event, function onEvent (e) { |
|
33 resolveArray.push(e); |
|
34 if (++counter === times) { |
|
35 toolbar.off(event, onEvent); |
|
36 resolve(resolveArray); |
|
37 } |
|
38 }); |
|
39 } |
|
40 else { |
|
41 toolbar.once(event, resolve); |
|
42 } |
|
43 return promise; |
|
44 }; |
|
45 |
|
46 const stateEventsFor = frame => |
|
47 [wait(frame, "attach"), wait(frame, "ready"), |
|
48 wait(frame, "load"), wait(frame, "detach")]; |
|
49 |
|
50 |
|
51 const isAttached = ({id}, window=getMostRecentBrowserWindow()) => |
|
52 !!window.document.getElementById(id); |
|
53 |
|
54 // Use `Task.spawn` instead of `Task.async` because the returned function does not contain |
|
55 // a length for the test harness to determine whether the test should be executed |
|
56 exports["test frame API"] = function* (assert) { |
|
57 const url = "data:text/html,frame-api"; |
|
58 assert.throws(() => new Frame(), |
|
59 /The `options.url`/, |
|
60 "must provide url"); |
|
61 |
|
62 assert.throws(() => new Frame({ url: "http://mozilla.org/" }), |
|
63 /The `options.url`/, |
|
64 "options.url must be local url"); |
|
65 |
|
66 assert.throws(() => new Frame({url: url, name: "4you" }), |
|
67 /The `option.name` must be a valid/, |
|
68 "can only take valid names"); |
|
69 |
|
70 const f1 = new Frame({ url: url }); |
|
71 |
|
72 assert.ok(f1.id, "frame has an id"); |
|
73 assert.equal(f1.url, void(0), "frame has no url until it's loaded"); |
|
74 assert.equal(typeof(f1.postMessage), "function", |
|
75 "frames can postMessages"); |
|
76 |
|
77 const p1 = wait(f1, "register"); |
|
78 |
|
79 assert.throws(() => new Frame({ url: url }), |
|
80 /Frame with this id already exists/, |
|
81 "can't have two identical frames"); |
|
82 |
|
83 |
|
84 const f2 = new Frame({ name: "frame-2", url: url }); |
|
85 assert.pass("can create frame with same url but diff name"); |
|
86 const p2 = wait(f2, "register"); |
|
87 |
|
88 yield p1; |
|
89 assert.pass("frame#1 was registered"); |
|
90 assert.equal(f1.url, url, "once registered it get's url"); |
|
91 |
|
92 yield p2; |
|
93 assert.pass("frame#2 was registered"); |
|
94 assert.equal(f2.url, url, "once registered it get's url"); |
|
95 |
|
96 f1.destroy(); |
|
97 const f3 = new Frame({ url: url }); |
|
98 assert.pass("frame identical to destroyed one can be created"); |
|
99 |
|
100 yield wait(f3, "register"); |
|
101 assert.equal(f3.url, url, "url is set"); |
|
102 f2.destroy(); |
|
103 f3.destroy(); |
|
104 }; |
|
105 |
|
106 exports["test frame in toolbar"] = function* (assert) { |
|
107 const assertEvent = (event, type) => { |
|
108 assert.ok(event, "`" + type + "` event was dispatched"); |
|
109 assert.equal(event.type, type, "event.type is: " + type); |
|
110 assert.equal(typeof(event.source), "object", |
|
111 "event.source is an object"); |
|
112 assert.equal(typeof(event.source.postMessage), "function", |
|
113 "messages can be posted to event.source"); |
|
114 }; |
|
115 |
|
116 |
|
117 const url = "data:text/html,toolbar-frame"; |
|
118 const f1 = new Frame({ url: url }); |
|
119 const t1 = new Toolbar({ |
|
120 title: "frame toolbar", |
|
121 items: [f1] |
|
122 }); |
|
123 |
|
124 const w1 = getMostRecentBrowserWindow(); |
|
125 const [a1, r1, l1] = stateEventsFor(f1); |
|
126 |
|
127 assertEvent((yield a1), "attach"); |
|
128 assert.ok(isAttached(f1, w1), "frame is in the window#1"); |
|
129 assertEvent((yield r1), "ready"); |
|
130 assertEvent((yield l1), "load"); |
|
131 |
|
132 const [a2, r2, l2] = stateEventsFor(f1); |
|
133 const w2 = open(); |
|
134 |
|
135 assertEvent((yield a2), "attach"); |
|
136 assert.ok(isAttached(f1, w2), "frame is in the window#2"); |
|
137 assertEvent((yield r2), "ready"); |
|
138 assertEvent((yield l2), "load"); |
|
139 assert.pass("frame attached to window#2"); |
|
140 |
|
141 |
|
142 const d1 = wait(f1, "detach"); |
|
143 yield close(w2); |
|
144 assertEvent((yield d1), "detach"); |
|
145 assert.pass("frame detached when window is closed"); |
|
146 |
|
147 t1.destroy(); |
|
148 |
|
149 assertEvent((yield wait(f1, "detach")), "detach"); |
|
150 assert.ok(!isAttached(f1, w1), "frame was removed from window#1"); |
|
151 assert.pass("toolbar destroy detaches frame"); |
|
152 }; |
|
153 |
|
154 |
|
155 exports["test host to content messaging"] = function* (assert) { |
|
156 const url = "data:text/html,<script>new " + function() { |
|
157 window.addEventListener("message", (event) => { |
|
158 if (event.data === "ping!") |
|
159 event.source.postMessage("pong!", event.origin); |
|
160 }); |
|
161 } + "</script>"; |
|
162 const f1 = new Frame({ name: "mailbox", url: url }); |
|
163 const t1 = new Toolbar({ title: "mailbox", items: [f1] }); |
|
164 |
|
165 const e1 = yield wait(f1, "ready"); |
|
166 e1.source.postMessage("ping!", e1.origin); |
|
167 |
|
168 const pong = yield wait(f1, "message"); |
|
169 assert.equal(pong.data, "pong!", "received ping back"); |
|
170 t1.destroy(); |
|
171 |
|
172 yield wait(t1, "detach"); |
|
173 }; |
|
174 |
|
175 |
|
176 exports["test content to host messaging"] = function* (assert) { |
|
177 const url = "data:text/html,<script>new " + function() { |
|
178 window.addEventListener("message", (event) => { |
|
179 if (event.data === "pong!") |
|
180 event.source.postMessage("end", event.origin); |
|
181 }); |
|
182 |
|
183 window.parent.postMessage("ping!", "*"); |
|
184 } + "</script>"; |
|
185 |
|
186 const f1 = new Frame({ name: "inbox", url: url }); |
|
187 const t1 = new Toolbar({ title: "inbox", items: [f1] }); |
|
188 |
|
189 const e1 = yield wait(f1, "message"); |
|
190 assert.equal(e1.data, "ping!", "received ping from content"); |
|
191 |
|
192 e1.source.postMessage("pong!", e1.origin); |
|
193 |
|
194 const e2 = yield wait(f1, "message"); |
|
195 assert.equal(e2.data, "end", "received end message"); |
|
196 |
|
197 t1.destroy(); |
|
198 yield wait(t1, "detach"); |
|
199 |
|
200 }; |
|
201 |
|
202 |
|
203 exports["test direct messaging"] = function* (assert) { |
|
204 const url = "data:text/html,<script>new " + function() { |
|
205 var n = 0; |
|
206 window.addEventListener("message", (event) => { |
|
207 if (event.data === "inc") |
|
208 n = n + 1; |
|
209 if (event.data === "print") |
|
210 event.source.postMessage({ n: n }, event.origin); |
|
211 }); |
|
212 } + "</script>"; |
|
213 |
|
214 const w1 = getMostRecentBrowserWindow(); |
|
215 const f1 = new Frame({ url: url, name: "mail-cluster" }); |
|
216 const t1 = new Toolbar({ title: "claster", items: [f1] }); |
|
217 |
|
218 yield wait(f1, "ready"); |
|
219 assert.pass("document loaded in window#1"); |
|
220 |
|
221 const w2 = open(); |
|
222 |
|
223 yield wait(f1, "ready"); |
|
224 assert.pass("document loaded in window#2"); |
|
225 |
|
226 let messages = wait(f1, "message", 2); |
|
227 f1.postMessage("inc", f1.origin); |
|
228 f1.postMessage("print", f1.origin); |
|
229 |
|
230 const [e1, e2] = yield messages; |
|
231 assert.deepEqual(e1.data, {n: 1}, "received message from window#1"); |
|
232 assert.deepEqual(e2.data, {n: 1}, "received message from window#2"); |
|
233 |
|
234 let message = wait(f1, "message"); |
|
235 e1.source.postMessage("inc", e1.origin); |
|
236 e1.source.postMessage("print", e1.origin); |
|
237 const e3 = yield message; |
|
238 assert.deepEqual(e3.data, {n: 2}, "state changed in window#1"); |
|
239 |
|
240 let message = wait(f1, "message"); |
|
241 e2.source.postMessage("print", e2.origin); |
|
242 yield message; |
|
243 assert.deepEqual(e2.data, {n:1}, "window#2 didn't received inc message"); |
|
244 |
|
245 yield close(w2); |
|
246 t1.destroy(); |
|
247 |
|
248 yield wait(t1, "detach"); |
|
249 |
|
250 }; |
|
251 |
|
252 require("sdk/test").run(exports); |