|
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 const { Loader } = require('sdk/test/loader'); |
|
7 const { Page } = require("sdk/page-worker"); |
|
8 const { URL } = require("sdk/url"); |
|
9 const fixtures = require("./fixtures"); |
|
10 const testURI = fixtures.url("test.html"); |
|
11 |
|
12 const ERR_DESTROYED = |
|
13 "Couldn't find the worker to receive this message. " + |
|
14 "The script may not be initialized yet, or may already have been unloaded."; |
|
15 |
|
16 exports.testSimplePageCreation = function(assert, done) { |
|
17 let page = new Page({ |
|
18 contentScript: "self.postMessage(window.location.href)", |
|
19 contentScriptWhen: "end", |
|
20 onMessage: function (message) { |
|
21 assert.equal(message, "about:blank", |
|
22 "Page Worker should start with a blank page by default"); |
|
23 assert.equal(this, page, "The 'this' object is the page itself."); |
|
24 done(); |
|
25 } |
|
26 }); |
|
27 } |
|
28 |
|
29 /* |
|
30 * Tests that we can't be tricked by document overloads as we have access |
|
31 * to wrapped nodes |
|
32 */ |
|
33 exports.testWrappedDOM = function(assert, done) { |
|
34 let page = Page({ |
|
35 allow: { script: true }, |
|
36 contentURL: "data:text/html;charset=utf-8,<script>document.getElementById=3;window.scrollTo=3;</script>", |
|
37 contentScript: "window.addEventListener('load', function () " + |
|
38 "self.postMessage([typeof(document.getElementById), " + |
|
39 "typeof(window.scrollTo)]), true)", |
|
40 onMessage: function (message) { |
|
41 assert.equal(message[0], |
|
42 "function", |
|
43 "getElementById from content script is the native one"); |
|
44 |
|
45 assert.equal(message[1], |
|
46 "function", |
|
47 "scrollTo from content script is the native one"); |
|
48 |
|
49 done(); |
|
50 } |
|
51 }); |
|
52 } |
|
53 |
|
54 /* |
|
55 // We do not offer unwrapped access to DOM since bug 601295 landed |
|
56 // See 660780 to track progress of unwrap feature |
|
57 exports.testUnwrappedDOM = function(assert, done) { |
|
58 let page = Page({ |
|
59 allow: { script: true }, |
|
60 contentURL: "data:text/html;charset=utf-8,<script>document.getElementById=3;window.scrollTo=3;</script>", |
|
61 contentScript: "window.addEventListener('load', function () " + |
|
62 "self.postMessage([typeof(unsafeWindow.document.getElementById), " + |
|
63 "typeof(unsafeWindow.scrollTo)]), true)", |
|
64 onMessage: function (message) { |
|
65 assert.equal(message[0], |
|
66 "number", |
|
67 "document inside page is free to be changed"); |
|
68 |
|
69 assert.equal(message[1], |
|
70 "number", |
|
71 "window inside page is free to be changed"); |
|
72 |
|
73 done(); |
|
74 } |
|
75 }); |
|
76 } |
|
77 */ |
|
78 |
|
79 exports.testPageProperties = function(assert) { |
|
80 let page = new Page(); |
|
81 |
|
82 for each (let prop in ['contentURL', 'allow', 'contentScriptFile', |
|
83 'contentScript', 'contentScriptWhen', 'on', |
|
84 'postMessage', 'removeListener']) { |
|
85 assert.ok(prop in page, prop + " property is defined on page."); |
|
86 } |
|
87 |
|
88 assert.ok(function () page.postMessage("foo") || true, |
|
89 "postMessage doesn't throw exception on page."); |
|
90 } |
|
91 |
|
92 exports.testConstructorAndDestructor = function(assert, done) { |
|
93 let loader = Loader(module); |
|
94 let { Page } = loader.require("sdk/page-worker"); |
|
95 let global = loader.sandbox("sdk/page-worker"); |
|
96 |
|
97 let pagesReady = 0; |
|
98 |
|
99 let page1 = Page({ |
|
100 contentScript: "self.postMessage('')", |
|
101 contentScriptWhen: "end", |
|
102 onMessage: pageReady |
|
103 }); |
|
104 let page2 = Page({ |
|
105 contentScript: "self.postMessage('')", |
|
106 contentScriptWhen: "end", |
|
107 onMessage: pageReady |
|
108 }); |
|
109 |
|
110 assert.notEqual(page1, page2, |
|
111 "Page 1 and page 2 should be different objects."); |
|
112 |
|
113 function pageReady() { |
|
114 if (++pagesReady == 2) { |
|
115 page1.destroy(); |
|
116 page2.destroy(); |
|
117 |
|
118 assert.ok(isDestroyed(page1), "page1 correctly unloaded."); |
|
119 assert.ok(isDestroyed(page2), "page2 correctly unloaded."); |
|
120 |
|
121 loader.unload(); |
|
122 done(); |
|
123 } |
|
124 } |
|
125 } |
|
126 |
|
127 exports.testAutoDestructor = function(assert, done) { |
|
128 let loader = Loader(module); |
|
129 let { Page } = loader.require("sdk/page-worker"); |
|
130 |
|
131 let page = Page({ |
|
132 contentScript: "self.postMessage('')", |
|
133 contentScriptWhen: "end", |
|
134 onMessage: function() { |
|
135 loader.unload(); |
|
136 assert.ok(isDestroyed(page), "Page correctly unloaded."); |
|
137 done(); |
|
138 } |
|
139 }); |
|
140 } |
|
141 |
|
142 exports.testValidateOptions = function(assert) { |
|
143 assert.throws( |
|
144 function () Page({ contentURL: 'home' }), |
|
145 /The `contentURL` option must be a valid URL\./, |
|
146 "Validation correctly denied a non-URL contentURL" |
|
147 ); |
|
148 |
|
149 assert.throws( |
|
150 function () Page({ onMessage: "This is not a function."}), |
|
151 /The option "onMessage" must be one of the following types: function/, |
|
152 "Validation correctly denied a non-function onMessage." |
|
153 ); |
|
154 |
|
155 assert.pass("Options validation is working."); |
|
156 } |
|
157 |
|
158 exports.testContentAndAllowGettersAndSetters = function(assert, done) { |
|
159 let content = "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=3;</script>"; |
|
160 |
|
161 // Load up the page with testURI initially for the resource:// principal, |
|
162 // then load the actual data:* content, as data:* URIs no longer |
|
163 // have localStorage |
|
164 let page = Page({ |
|
165 contentURL: testURI, |
|
166 contentScript: "if (window.location.href==='"+testURI+"')" + |
|
167 " self.postMessage('reload');" + |
|
168 "else " + |
|
169 " self.postMessage(window.localStorage.allowScript)", |
|
170 contentScriptWhen: "end", |
|
171 onMessage: step0 |
|
172 }); |
|
173 |
|
174 function step0(message) { |
|
175 if (message === 'reload') |
|
176 return page.contentURL = content; |
|
177 assert.equal(message, "3", |
|
178 "Correct value expected for allowScript - 3"); |
|
179 assert.equal(page.contentURL, content, |
|
180 "Correct content expected"); |
|
181 page.removeListener('message', step0); |
|
182 page.on('message', step1); |
|
183 page.allow = { script: false }; |
|
184 page.contentURL = content = |
|
185 "data:text/html;charset=utf-8,<script>window.localStorage.allowScript='f'</script>"; |
|
186 } |
|
187 |
|
188 function step1(message) { |
|
189 assert.equal(message, "3", |
|
190 "Correct value expected for allowScript - 3"); |
|
191 assert.equal(page.contentURL, content, "Correct content expected"); |
|
192 page.removeListener('message', step1); |
|
193 page.on('message', step2); |
|
194 page.allow = { script: true }; |
|
195 page.contentURL = content = |
|
196 "data:text/html;charset=utf-8,<script>window.localStorage.allowScript='g'</script>"; |
|
197 } |
|
198 |
|
199 function step2(message) { |
|
200 assert.equal(message, "g", |
|
201 "Correct value expected for allowScript - g"); |
|
202 assert.equal(page.contentURL, content, "Correct content expected"); |
|
203 page.removeListener('message', step2); |
|
204 page.on('message', step3); |
|
205 page.allow.script = false; |
|
206 page.contentURL = content = |
|
207 "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=3</script>"; |
|
208 } |
|
209 |
|
210 function step3(message) { |
|
211 assert.equal(message, "g", |
|
212 "Correct value expected for allowScript - g"); |
|
213 assert.equal(page.contentURL, content, "Correct content expected"); |
|
214 page.removeListener('message', step3); |
|
215 page.on('message', step4); |
|
216 page.allow.script = true; |
|
217 page.contentURL = content = |
|
218 "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=4</script>"; |
|
219 } |
|
220 |
|
221 function step4(message) { |
|
222 assert.equal(message, "4", |
|
223 "Correct value expected for allowScript - 4"); |
|
224 assert.equal(page.contentURL, content, "Correct content expected"); |
|
225 done(); |
|
226 } |
|
227 |
|
228 } |
|
229 |
|
230 exports.testOnMessageCallback = function(assert, done) { |
|
231 Page({ |
|
232 contentScript: "self.postMessage('')", |
|
233 contentScriptWhen: "end", |
|
234 onMessage: function() { |
|
235 assert.pass("onMessage callback called"); |
|
236 done(); |
|
237 } |
|
238 }); |
|
239 } |
|
240 |
|
241 exports.testMultipleOnMessageCallbacks = function(assert, done) { |
|
242 let count = 0; |
|
243 let page = Page({ |
|
244 contentScript: "self.postMessage('')", |
|
245 contentScriptWhen: "end", |
|
246 onMessage: () => count += 1 |
|
247 }); |
|
248 page.on('message', () => count += 2); |
|
249 page.on('message', () => count *= 3); |
|
250 page.on('message', () => |
|
251 assert.equal(count, 9, "All callbacks were called, in order.")); |
|
252 page.on('message', done); |
|
253 }; |
|
254 |
|
255 exports.testLoadContentPage = function(assert, done) { |
|
256 let page = Page({ |
|
257 onMessage: function(message) { |
|
258 // The message is an array whose first item is the test method to call |
|
259 // and the rest of whose items are arguments to pass it. |
|
260 let msg = message.shift(); |
|
261 if (msg == "done") |
|
262 return done(); |
|
263 assert[msg].apply(assert, message); |
|
264 }, |
|
265 contentURL: fixtures.url("test-page-worker.html"), |
|
266 contentScriptFile: fixtures.url("test-page-worker.js"), |
|
267 contentScriptWhen: "ready" |
|
268 }); |
|
269 } |
|
270 |
|
271 exports.testAllowScriptDefault = function(assert, done) { |
|
272 let page = Page({ |
|
273 onMessage: function(message) { |
|
274 assert.ok(message, "Script is allowed to run by default."); |
|
275 done(); |
|
276 }, |
|
277 contentURL: "data:text/html;charset=utf-8,<script>document.documentElement.setAttribute('foo', 3);</script>", |
|
278 contentScript: "self.postMessage(document.documentElement.getAttribute('foo'))", |
|
279 contentScriptWhen: "ready" |
|
280 }); |
|
281 } |
|
282 |
|
283 exports.testAllowScript = function(assert, done) { |
|
284 let page = Page({ |
|
285 onMessage: function(message) { |
|
286 assert.ok(message, "Script runs when allowed to do so."); |
|
287 done(); |
|
288 }, |
|
289 allow: { script: true }, |
|
290 contentURL: "data:text/html;charset=utf-8,<script>document.documentElement.setAttribute('foo', 3);</script>", |
|
291 contentScript: "self.postMessage(document.documentElement.hasAttribute('foo') && " + |
|
292 " document.documentElement.getAttribute('foo') == 3)", |
|
293 contentScriptWhen: "ready" |
|
294 }); |
|
295 } |
|
296 |
|
297 exports.testPingPong = function(assert, done) { |
|
298 let page = Page({ |
|
299 contentURL: 'data:text/html;charset=utf-8,ping-pong', |
|
300 contentScript: 'self.on("message", function(message) self.postMessage("pong"));' |
|
301 + 'self.postMessage("ready");', |
|
302 onMessage: function(message) { |
|
303 if ('ready' == message) { |
|
304 page.postMessage('ping'); |
|
305 } |
|
306 else { |
|
307 assert.ok(message, 'pong', 'Callback from contentScript'); |
|
308 done(); |
|
309 } |
|
310 } |
|
311 }); |
|
312 }; |
|
313 |
|
314 exports.testRedirect = function (assert, done) { |
|
315 let page = Page({ |
|
316 contentURL: 'data:text/html;charset=utf-8,first-page', |
|
317 contentScriptWhen: "end", |
|
318 contentScript: '' + |
|
319 'if (/first-page/.test(document.location.href)) ' + |
|
320 ' document.location.href = "data:text/html;charset=utf-8,redirect";' + |
|
321 'else ' + |
|
322 ' self.port.emit("redirect", document.location.href);' |
|
323 }); |
|
324 |
|
325 page.port.on('redirect', function (url) { |
|
326 assert.equal(url, 'data:text/html;charset=utf-8,redirect', 'Reinjects contentScript on reload'); |
|
327 done(); |
|
328 }); |
|
329 }; |
|
330 |
|
331 exports.testRedirectIncludeArrays = function (assert, done) { |
|
332 let firstURL = 'data:text/html;charset=utf-8,first-page'; |
|
333 let page = Page({ |
|
334 contentURL: firstURL, |
|
335 contentScript: '(function () {' + |
|
336 'self.port.emit("load", document.location.href);' + |
|
337 ' self.port.on("redirect", function (url) {' + |
|
338 ' document.location.href = url;' + |
|
339 ' })' + |
|
340 '})();', |
|
341 include: ['about:blank', 'data:*'] |
|
342 }); |
|
343 |
|
344 page.port.on('load', function (url) { |
|
345 if (url === firstURL) { |
|
346 page.port.emit('redirect', 'about:blank'); |
|
347 } else if (url === 'about:blank') { |
|
348 page.port.emit('redirect', 'about:mozilla'); |
|
349 assert.ok('`include` property handles arrays'); |
|
350 assert.equal(url, 'about:blank', 'Redirects work with accepted domains'); |
|
351 done(); |
|
352 } else if (url === 'about:mozilla') { |
|
353 assert.fail('Should not redirect to restricted domain'); |
|
354 } |
|
355 }); |
|
356 }; |
|
357 |
|
358 exports.testRedirectFromWorker = function (assert, done) { |
|
359 let firstURL = 'data:text/html;charset=utf-8,first-page'; |
|
360 let secondURL = 'data:text/html;charset=utf-8,second-page'; |
|
361 let thirdURL = 'data:text/html;charset=utf-8,third-page'; |
|
362 let page = Page({ |
|
363 contentURL: firstURL, |
|
364 contentScript: '(function () {' + |
|
365 'self.port.emit("load", document.location.href);' + |
|
366 ' self.port.on("redirect", function (url) {' + |
|
367 ' document.location.href = url;' + |
|
368 ' })' + |
|
369 '})();', |
|
370 include: 'data:*' |
|
371 }); |
|
372 |
|
373 page.port.on('load', function (url) { |
|
374 if (url === firstURL) { |
|
375 page.port.emit('redirect', secondURL); |
|
376 } else if (url === secondURL) { |
|
377 page.port.emit('redirect', thirdURL); |
|
378 } else if (url === thirdURL) { |
|
379 page.port.emit('redirect', 'about:mozilla'); |
|
380 assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings'); |
|
381 done(); |
|
382 } else { |
|
383 assert.fail('Should not redirect to unauthorized domains'); |
|
384 } |
|
385 }); |
|
386 }; |
|
387 |
|
388 exports.testRedirectWithContentURL = function (assert, done) { |
|
389 let firstURL = 'data:text/html;charset=utf-8,first-page'; |
|
390 let secondURL = 'data:text/html;charset=utf-8,second-page'; |
|
391 let thirdURL = 'data:text/html;charset=utf-8,third-page'; |
|
392 let page = Page({ |
|
393 contentURL: firstURL, |
|
394 contentScript: '(function () {' + |
|
395 'self.port.emit("load", document.location.href);' + |
|
396 '})();', |
|
397 include: 'data:*' |
|
398 }); |
|
399 |
|
400 page.port.on('load', function (url) { |
|
401 if (url === firstURL) { |
|
402 page.contentURL = secondURL; |
|
403 } else if (url === secondURL) { |
|
404 page.contentURL = thirdURL; |
|
405 } else if (url === thirdURL) { |
|
406 page.contentURL = 'about:mozilla'; |
|
407 assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings'); |
|
408 done(); |
|
409 } else { |
|
410 assert.fail('Should not redirect to unauthorized domains'); |
|
411 } |
|
412 }); |
|
413 }; |
|
414 |
|
415 |
|
416 exports.testMultipleDestroys = function(assert) { |
|
417 let page = Page(); |
|
418 page.destroy(); |
|
419 page.destroy(); |
|
420 assert.pass("Multiple destroys should not cause an error"); |
|
421 }; |
|
422 |
|
423 exports.testContentScriptOptionsOption = function(assert, done) { |
|
424 let page = new Page({ |
|
425 contentScript: "self.postMessage( [typeof self.options.d, self.options] );", |
|
426 contentScriptWhen: "end", |
|
427 contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}}, |
|
428 onMessage: function(msg) { |
|
429 assert.equal(msg[0], 'undefined', 'functions are stripped from contentScriptOptions'); |
|
430 assert.equal(typeof msg[1], 'object', 'object as contentScriptOptions'); |
|
431 assert.equal(msg[1].a, true, 'boolean in contentScriptOptions'); |
|
432 assert.equal(msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions'); |
|
433 assert.equal(msg[1].c, 'string', 'string in contentScriptOptions'); |
|
434 done(); |
|
435 } |
|
436 }); |
|
437 }; |
|
438 |
|
439 exports.testMessageQueue = function (assert, done) { |
|
440 let page = new Page({ |
|
441 contentScript: 'self.on("message", function (m) {' + |
|
442 'self.postMessage(m);' + |
|
443 '});', |
|
444 contentURL: 'data:text/html;charset=utf-8,', |
|
445 }); |
|
446 page.postMessage('ping'); |
|
447 page.on('message', function (m) { |
|
448 assert.equal(m, 'ping', 'postMessage should queue messages'); |
|
449 done(); |
|
450 }); |
|
451 }; |
|
452 |
|
453 function isDestroyed(page) { |
|
454 try { |
|
455 page.postMessage("foo"); |
|
456 } |
|
457 catch (err) { |
|
458 if (err.message == ERR_DESTROYED) { |
|
459 return true; |
|
460 } |
|
461 else { |
|
462 throw err; |
|
463 } |
|
464 } |
|
465 return false; |
|
466 } |
|
467 |
|
468 require("test").run(exports); |