|
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 { PageMod } = require("sdk/page-mod"); |
|
7 const { testPageMod, handleReadyState } = require("./pagemod-test-helpers"); |
|
8 const { Loader } = require('sdk/test/loader'); |
|
9 const tabs = require("sdk/tabs"); |
|
10 const { setTimeout } = require("sdk/timers"); |
|
11 const { Cc, Ci, Cu } = require("chrome"); |
|
12 const { |
|
13 open, |
|
14 getFrames, |
|
15 getMostRecentBrowserWindow, |
|
16 getInnerId |
|
17 } = require('sdk/window/utils'); |
|
18 const { getTabContentWindow, getActiveTab, setTabURL, openTab, closeTab } = require('sdk/tabs/utils'); |
|
19 const xulApp = require("sdk/system/xul-app"); |
|
20 const { isPrivateBrowsingSupported } = require('sdk/self'); |
|
21 const { isPrivate } = require('sdk/private-browsing'); |
|
22 const { openWebpage } = require('./private-browsing/helper'); |
|
23 const { isTabPBSupported, isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils'); |
|
24 const promise = require("sdk/core/promise"); |
|
25 const { pb } = require('./private-browsing/helper'); |
|
26 const { URL } = require("sdk/url"); |
|
27 const { LoaderWithHookedConsole } = require('sdk/test/loader'); |
|
28 |
|
29 const { waitUntil } = require("sdk/test/utils"); |
|
30 const data = require("./fixtures"); |
|
31 |
|
32 const { gDevToolsExtensions } = Cu.import("resource://gre/modules/devtools/DevToolsExtensions.jsm", {}); |
|
33 |
|
34 const testPageURI = data.url("test.html"); |
|
35 |
|
36 // The following adds Debugger constructor to the global namespace. |
|
37 const { addDebuggerToGlobal } = |
|
38 Cu.import('resource://gre/modules/jsdebugger.jsm', {}); |
|
39 addDebuggerToGlobal(this); |
|
40 |
|
41 function Isolate(worker) { |
|
42 return "(" + worker + ")()"; |
|
43 } |
|
44 |
|
45 /* Tests for the PageMod APIs */ |
|
46 |
|
47 exports.testPageMod1 = function(assert, done) { |
|
48 let mods = testPageMod(assert, done, "about:", [{ |
|
49 include: /about:/, |
|
50 contentScriptWhen: 'end', |
|
51 contentScript: 'new ' + function WorkerScope() { |
|
52 window.document.body.setAttribute("JEP-107", "worked"); |
|
53 }, |
|
54 onAttach: function() { |
|
55 assert.equal(this, mods[0], "The 'this' object is the page mod."); |
|
56 } |
|
57 }], |
|
58 function(win, done) { |
|
59 assert.equal( |
|
60 win.document.body.getAttribute("JEP-107"), |
|
61 "worked", |
|
62 "PageMod.onReady test" |
|
63 ); |
|
64 done(); |
|
65 } |
|
66 ); |
|
67 }; |
|
68 |
|
69 exports.testPageMod2 = function(assert, done) { |
|
70 testPageMod(assert, done, "about:", [{ |
|
71 include: "about:*", |
|
72 contentScript: [ |
|
73 'new ' + function contentScript() { |
|
74 window.AUQLUE = function() { return 42; } |
|
75 try { |
|
76 window.AUQLUE() |
|
77 } |
|
78 catch(e) { |
|
79 throw new Error("PageMod scripts executed in order"); |
|
80 } |
|
81 document.documentElement.setAttribute("first", "true"); |
|
82 }, |
|
83 'new ' + function contentScript() { |
|
84 document.documentElement.setAttribute("second", "true"); |
|
85 } |
|
86 ] |
|
87 }], function(win, done) { |
|
88 assert.equal(win.document.documentElement.getAttribute("first"), |
|
89 "true", |
|
90 "PageMod test #2: first script has run"); |
|
91 assert.equal(win.document.documentElement.getAttribute("second"), |
|
92 "true", |
|
93 "PageMod test #2: second script has run"); |
|
94 assert.equal("AUQLUE" in win, false, |
|
95 "PageMod test #2: scripts get a wrapped window"); |
|
96 done(); |
|
97 }); |
|
98 }; |
|
99 |
|
100 exports.testPageModIncludes = function(assert, done) { |
|
101 var asserts = []; |
|
102 function createPageModTest(include, expectedMatch) { |
|
103 // Create an 'onload' test function... |
|
104 asserts.push(function(test, win) { |
|
105 var matches = include in win.localStorage; |
|
106 assert.ok(expectedMatch ? matches : !matches, |
|
107 "'" + include + "' match test, expected: " + expectedMatch); |
|
108 }); |
|
109 // ...and corresponding PageMod options |
|
110 return { |
|
111 include: include, |
|
112 contentScript: 'new ' + function() { |
|
113 self.on("message", function(msg) { |
|
114 window.localStorage[msg] = true; |
|
115 }); |
|
116 }, |
|
117 // The testPageMod callback with test assertions is called on 'end', |
|
118 // and we want this page mod to be attached before it gets called, |
|
119 // so we attach it on 'start'. |
|
120 contentScriptWhen: 'start', |
|
121 onAttach: function(worker) { |
|
122 worker.postMessage(this.include[0]); |
|
123 } |
|
124 }; |
|
125 } |
|
126 |
|
127 testPageMod(assert, done, testPageURI, [ |
|
128 createPageModTest("*", false), |
|
129 createPageModTest("*.google.com", false), |
|
130 createPageModTest("resource:*", true), |
|
131 createPageModTest("resource:", false), |
|
132 createPageModTest(testPageURI, true) |
|
133 ], |
|
134 function (win, done) { |
|
135 waitUntil(function () win.localStorage[testPageURI], |
|
136 testPageURI + " page-mod to be executed") |
|
137 .then(function () { |
|
138 asserts.forEach(function(fn) { |
|
139 fn(assert, win); |
|
140 }); |
|
141 done(); |
|
142 }); |
|
143 } |
|
144 ); |
|
145 }; |
|
146 |
|
147 exports.testPageModValidationAttachTo = function(assert) { |
|
148 [{ val: 'top', type: 'string "top"' }, |
|
149 { val: 'frame', type: 'string "frame"' }, |
|
150 { val: ['top', 'existing'], type: 'array with "top" and "existing"' }, |
|
151 { val: ['frame', 'existing'], type: 'array with "frame" and "existing"' }, |
|
152 { val: ['top'], type: 'array with "top"' }, |
|
153 { val: ['frame'], type: 'array with "frame"' }, |
|
154 { val: undefined, type: 'undefined' }].forEach((attachTo) => { |
|
155 new PageMod({ attachTo: attachTo.val, include: '*.validation111' }); |
|
156 assert.pass("PageMod() does not throw when attachTo is " + attachTo.type); |
|
157 }); |
|
158 |
|
159 [{ val: 'existing', type: 'string "existing"' }, |
|
160 { val: ['existing'], type: 'array with "existing"' }, |
|
161 { val: 'not-legit', type: 'string with "not-legit"' }, |
|
162 { val: ['not-legit'], type: 'array with "not-legit"' }, |
|
163 { val: {}, type: 'object' }].forEach((attachTo) => { |
|
164 assert.throws(() => |
|
165 new PageMod({ attachTo: attachTo.val, include: '*.validation111' }), |
|
166 /The `attachTo` option/, |
|
167 "PageMod() throws when 'attachTo' option is " + attachTo.type + "."); |
|
168 }); |
|
169 }; |
|
170 |
|
171 exports.testPageModValidationInclude = function(assert) { |
|
172 [{ val: undefined, type: 'undefined' }, |
|
173 { val: {}, type: 'object' }, |
|
174 { val: [], type: 'empty array'}, |
|
175 { val: [/regexp/, 1], type: 'array with non string/regexp' }, |
|
176 { val: 1, type: 'number' }].forEach((include) => { |
|
177 assert.throws(() => new PageMod({ include: include.val }), |
|
178 /The `include` option must always contain atleast one rule/, |
|
179 "PageMod() throws when 'include' option is " + include.type + "."); |
|
180 }); |
|
181 |
|
182 [{ val: '*.validation111', type: 'string' }, |
|
183 { val: /validation111/, type: 'regexp' }, |
|
184 { val: ['*.validation111'], type: 'array with length > 0'}].forEach((include) => { |
|
185 new PageMod({ include: include.val }); |
|
186 assert.pass("PageMod() does not throw when include option is " + include.type); |
|
187 }); |
|
188 }; |
|
189 |
|
190 /* Tests for internal functions. */ |
|
191 exports.testCommunication1 = function(assert, done) { |
|
192 let workerDone = false, |
|
193 callbackDone = null; |
|
194 |
|
195 testPageMod(assert, done, "about:", [{ |
|
196 include: "about:*", |
|
197 contentScriptWhen: 'end', |
|
198 contentScript: 'new ' + function WorkerScope() { |
|
199 self.on('message', function(msg) { |
|
200 document.body.setAttribute('JEP-107', 'worked'); |
|
201 self.postMessage(document.body.getAttribute('JEP-107')); |
|
202 }) |
|
203 }, |
|
204 onAttach: function(worker) { |
|
205 worker.on('error', function(e) { |
|
206 assert.fail('Errors where reported'); |
|
207 }); |
|
208 worker.on('message', function(value) { |
|
209 assert.equal( |
|
210 "worked", |
|
211 value, |
|
212 "test comunication" |
|
213 ); |
|
214 workerDone = true; |
|
215 if (callbackDone) |
|
216 callbackDone(); |
|
217 }); |
|
218 worker.postMessage('do it!') |
|
219 } |
|
220 }], |
|
221 function(win, done) { |
|
222 (callbackDone = function() { |
|
223 if (workerDone) { |
|
224 assert.equal( |
|
225 'worked', |
|
226 win.document.body.getAttribute('JEP-107'), |
|
227 'attribute should be modified' |
|
228 ); |
|
229 done(); |
|
230 } |
|
231 })(); |
|
232 } |
|
233 ); |
|
234 }; |
|
235 |
|
236 exports.testCommunication2 = function(assert, done) { |
|
237 let callbackDone = null, |
|
238 window; |
|
239 |
|
240 testPageMod(assert, done, "about:license", [{ |
|
241 include: "about:*", |
|
242 contentScriptWhen: 'start', |
|
243 contentScript: 'new ' + function WorkerScope() { |
|
244 document.documentElement.setAttribute('AUQLUE', 42); |
|
245 window.addEventListener('load', function listener() { |
|
246 self.postMessage('onload'); |
|
247 }, false); |
|
248 self.on("message", function() { |
|
249 self.postMessage(document.documentElement.getAttribute("test")) |
|
250 }); |
|
251 }, |
|
252 onAttach: function(worker) { |
|
253 worker.on('error', function(e) { |
|
254 assert.fail('Errors where reported'); |
|
255 }); |
|
256 worker.on('message', function(msg) { |
|
257 if ('onload' == msg) { |
|
258 assert.equal( |
|
259 '42', |
|
260 window.document.documentElement.getAttribute('AUQLUE'), |
|
261 'PageMod scripts executed in order' |
|
262 ); |
|
263 window.document.documentElement.setAttribute('test', 'changes in window'); |
|
264 worker.postMessage('get window.test') |
|
265 } else { |
|
266 assert.equal( |
|
267 'changes in window', |
|
268 msg, |
|
269 'PageMod test #2: second script has run' |
|
270 ) |
|
271 callbackDone(); |
|
272 } |
|
273 }); |
|
274 } |
|
275 }], |
|
276 function(win, done) { |
|
277 window = win; |
|
278 callbackDone = done; |
|
279 } |
|
280 ); |
|
281 }; |
|
282 |
|
283 exports.testEventEmitter = function(assert, done) { |
|
284 let workerDone = false, |
|
285 callbackDone = null; |
|
286 |
|
287 testPageMod(assert, done, "about:", [{ |
|
288 include: "about:*", |
|
289 contentScript: 'new ' + function WorkerScope() { |
|
290 self.port.on('addon-to-content', function(data) { |
|
291 self.port.emit('content-to-addon', data); |
|
292 }); |
|
293 }, |
|
294 onAttach: function(worker) { |
|
295 worker.on('error', function(e) { |
|
296 assert.fail('Errors were reported : '+e); |
|
297 }); |
|
298 worker.port.on('content-to-addon', function(value) { |
|
299 assert.equal( |
|
300 "worked", |
|
301 value, |
|
302 "EventEmitter API works!" |
|
303 ); |
|
304 if (callbackDone) |
|
305 callbackDone(); |
|
306 else |
|
307 workerDone = true; |
|
308 }); |
|
309 worker.port.emit('addon-to-content', 'worked'); |
|
310 } |
|
311 }], |
|
312 function(win, done) { |
|
313 if (workerDone) |
|
314 done(); |
|
315 else |
|
316 callbackDone = done; |
|
317 } |
|
318 ); |
|
319 }; |
|
320 |
|
321 // Execute two concurrent page mods on same document to ensure that their |
|
322 // JS contexts are different |
|
323 exports.testMixedContext = function(assert, done) { |
|
324 let doneCallback = null; |
|
325 let messages = 0; |
|
326 let modObject = { |
|
327 include: "data:text/html;charset=utf-8,", |
|
328 contentScript: 'new ' + function WorkerScope() { |
|
329 // Both scripts will execute this, |
|
330 // context is shared if one script see the other one modification. |
|
331 let isContextShared = "sharedAttribute" in document; |
|
332 self.postMessage(isContextShared); |
|
333 document.sharedAttribute = true; |
|
334 }, |
|
335 onAttach: function(w) { |
|
336 w.on("message", function (isContextShared) { |
|
337 if (isContextShared) { |
|
338 assert.fail("Page mod contexts are mixed."); |
|
339 doneCallback(); |
|
340 } |
|
341 else if (++messages == 2) { |
|
342 assert.pass("Page mod contexts are different."); |
|
343 doneCallback(); |
|
344 } |
|
345 }); |
|
346 } |
|
347 }; |
|
348 testPageMod(assert, done, "data:text/html;charset=utf-8,", [modObject, modObject], |
|
349 function(win, done) { |
|
350 doneCallback = done; |
|
351 } |
|
352 ); |
|
353 }; |
|
354 |
|
355 exports.testHistory = function(assert, done) { |
|
356 // We need a valid url in order to have a working History API. |
|
357 // (i.e do not work on data: or about: pages) |
|
358 // Test bug 679054. |
|
359 let url = data.url("test-page-mod.html"); |
|
360 let callbackDone = null; |
|
361 testPageMod(assert, done, url, [{ |
|
362 include: url, |
|
363 contentScriptWhen: 'end', |
|
364 contentScript: 'new ' + function WorkerScope() { |
|
365 history.pushState({}, "", "#"); |
|
366 history.replaceState({foo: "bar"}, "", "#"); |
|
367 self.postMessage(history.state); |
|
368 }, |
|
369 onAttach: function(worker) { |
|
370 worker.on('message', function (data) { |
|
371 assert.equal(JSON.stringify(data), JSON.stringify({foo: "bar"}), |
|
372 "History API works!"); |
|
373 callbackDone(); |
|
374 }); |
|
375 } |
|
376 }], |
|
377 function(win, done) { |
|
378 callbackDone = done; |
|
379 } |
|
380 ); |
|
381 }; |
|
382 |
|
383 exports.testRelatedTab = function(assert, done) { |
|
384 let tab; |
|
385 let pageMod = new PageMod({ |
|
386 include: "about:*", |
|
387 onAttach: function(worker) { |
|
388 assert.ok(!!worker.tab, "Worker.tab exists"); |
|
389 assert.equal(tab, worker.tab, "Worker.tab is valid"); |
|
390 pageMod.destroy(); |
|
391 tab.close(done); |
|
392 } |
|
393 }); |
|
394 |
|
395 tabs.open({ |
|
396 url: "about:", |
|
397 onOpen: function onOpen(t) { |
|
398 tab = t; |
|
399 } |
|
400 }); |
|
401 }; |
|
402 |
|
403 exports.testRelatedTabNoRequireTab = function(assert, done) { |
|
404 let loader = Loader(module); |
|
405 let tab; |
|
406 let url = "data:text/html;charset=utf-8," + encodeURI("Test related worker tab 2"); |
|
407 let { PageMod } = loader.require("sdk/page-mod"); |
|
408 let pageMod = new PageMod({ |
|
409 include: url, |
|
410 onAttach: function(worker) { |
|
411 assert.equal(worker.tab.url, url, "Worker.tab.url is valid"); |
|
412 worker.tab.close(function() { |
|
413 pageMod.destroy(); |
|
414 loader.unload(); |
|
415 done(); |
|
416 }); |
|
417 } |
|
418 }); |
|
419 |
|
420 tabs.open(url); |
|
421 }; |
|
422 |
|
423 exports.testRelatedTabNoOtherReqs = function(assert, done) { |
|
424 let loader = Loader(module); |
|
425 let { PageMod } = loader.require("sdk/page-mod"); |
|
426 let pageMod = new PageMod({ |
|
427 include: "about:blank?testRelatedTabNoOtherReqs", |
|
428 onAttach: function(worker) { |
|
429 assert.ok(!!worker.tab, "Worker.tab exists"); |
|
430 pageMod.destroy(); |
|
431 worker.tab.close(function() { |
|
432 worker.destroy(); |
|
433 loader.unload(); |
|
434 done(); |
|
435 }); |
|
436 } |
|
437 }); |
|
438 |
|
439 tabs.open({ |
|
440 url: "about:blank?testRelatedTabNoOtherReqs" |
|
441 }); |
|
442 }; |
|
443 |
|
444 exports.testWorksWithExistingTabs = function(assert, done) { |
|
445 let url = "data:text/html;charset=utf-8," + encodeURI("Test unique document"); |
|
446 let { PageMod } = require("sdk/page-mod"); |
|
447 tabs.open({ |
|
448 url: url, |
|
449 onReady: function onReady(tab) { |
|
450 let pageModOnExisting = new PageMod({ |
|
451 include: url, |
|
452 attachTo: ["existing", "top", "frame"], |
|
453 onAttach: function(worker) { |
|
454 assert.ok(!!worker.tab, "Worker.tab exists"); |
|
455 assert.equal(tab, worker.tab, "A worker has been created on this existing tab"); |
|
456 |
|
457 setTimeout(function() { |
|
458 pageModOnExisting.destroy(); |
|
459 pageModOffExisting.destroy(); |
|
460 tab.close(done); |
|
461 }, 0); |
|
462 } |
|
463 }); |
|
464 |
|
465 let pageModOffExisting = new PageMod({ |
|
466 include: url, |
|
467 onAttach: function(worker) { |
|
468 assert.fail("pageModOffExisting page-mod should not have attached to anything"); |
|
469 } |
|
470 }); |
|
471 } |
|
472 }); |
|
473 }; |
|
474 |
|
475 exports.testExistingFrameDoesntMatchInclude = function(assert, done) { |
|
476 let iframeURL = 'data:text/html;charset=utf-8,UNIQUE-TEST-STRING-42'; |
|
477 let iframe = '<iframe src="' + iframeURL + '" />'; |
|
478 let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iframe); |
|
479 tabs.open({ |
|
480 url: url, |
|
481 onReady: function onReady(tab) { |
|
482 let pagemod = new PageMod({ |
|
483 include: url, |
|
484 attachTo: ['existing', 'frame'], |
|
485 onAttach: function() { |
|
486 assert.fail("Existing iframe URL doesn't match include, must not attach to anything"); |
|
487 } |
|
488 }); |
|
489 setTimeout(function() { |
|
490 assert.pass("PageMod didn't attach to anything") |
|
491 pagemod.destroy(); |
|
492 tab.close(done); |
|
493 }, 250); |
|
494 } |
|
495 }); |
|
496 }; |
|
497 |
|
498 exports.testExistingOnlyFrameMatchesInclude = function(assert, done) { |
|
499 let iframeURL = 'data:text/html;charset=utf-8,UNIQUE-TEST-STRING-43'; |
|
500 let iframe = '<iframe src="' + iframeURL + '" />'; |
|
501 let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iframe); |
|
502 tabs.open({ |
|
503 url: url, |
|
504 onReady: function onReady(tab) { |
|
505 let pagemod = new PageMod({ |
|
506 include: iframeURL, |
|
507 attachTo: ['existing', 'frame'], |
|
508 onAttach: function(worker) { |
|
509 assert.equal(iframeURL, worker.url, |
|
510 "PageMod attached to existing iframe when only it matches include rules"); |
|
511 pagemod.destroy(); |
|
512 tab.close(done); |
|
513 } |
|
514 }); |
|
515 } |
|
516 }); |
|
517 }; |
|
518 |
|
519 exports.testContentScriptWhenDefault = function(assert) { |
|
520 let pagemod = PageMod({include: '*'}); |
|
521 |
|
522 assert.equal(pagemod.contentScriptWhen, 'end', "Default contentScriptWhen is 'end'"); |
|
523 pagemod.destroy(); |
|
524 } |
|
525 |
|
526 // test timing for all 3 contentScriptWhen options (start, ready, end) |
|
527 // for new pages, or tabs opened after PageMod is created |
|
528 exports.testContentScriptWhenForNewTabs = function(assert, done) { |
|
529 const url = "data:text/html;charset=utf-8,testContentScriptWhenForNewTabs"; |
|
530 |
|
531 let count = 0; |
|
532 |
|
533 handleReadyState(url, 'start', { |
|
534 onLoading: (tab) => { |
|
535 assert.pass("PageMod is attached while document is loading"); |
|
536 if (++count === 3) |
|
537 tab.close(done); |
|
538 }, |
|
539 onInteractive: () => assert.fail("onInteractive should not be called with 'start'."), |
|
540 onComplete: () => assert.fail("onComplete should not be called with 'start'."), |
|
541 }); |
|
542 |
|
543 handleReadyState(url, 'ready', { |
|
544 onInteractive: (tab) => { |
|
545 assert.pass("PageMod is attached while document is interactive"); |
|
546 if (++count === 3) |
|
547 tab.close(done); |
|
548 }, |
|
549 onLoading: () => assert.fail("onLoading should not be called with 'ready'."), |
|
550 onComplete: () => assert.fail("onComplete should not be called with 'ready'."), |
|
551 }); |
|
552 |
|
553 handleReadyState(url, 'end', { |
|
554 onComplete: (tab) => { |
|
555 assert.pass("PageMod is attached when document is complete"); |
|
556 if (++count === 3) |
|
557 tab.close(done); |
|
558 }, |
|
559 onLoading: () => assert.fail("onLoading should not be called with 'end'."), |
|
560 onInteractive: () => assert.fail("onInteractive should not be called with 'end'."), |
|
561 }); |
|
562 |
|
563 tabs.open(url); |
|
564 } |
|
565 |
|
566 // test timing for all 3 contentScriptWhen options (start, ready, end) |
|
567 // for PageMods created right as the tab is created (in tab.onOpen) |
|
568 exports.testContentScriptWhenOnTabOpen = function(assert, done) { |
|
569 const url = "data:text/html;charset=utf-8,testContentScriptWhenOnTabOpen"; |
|
570 |
|
571 tabs.open({ |
|
572 url: url, |
|
573 onOpen: function(tab) { |
|
574 let count = 0; |
|
575 |
|
576 handleReadyState(url, 'start', { |
|
577 onLoading: () => { |
|
578 assert.pass("PageMod is attached while document is loading"); |
|
579 if (++count === 3) |
|
580 tab.close(done); |
|
581 }, |
|
582 onInteractive: () => assert.fail("onInteractive should not be called with 'start'."), |
|
583 onComplete: () => assert.fail("onComplete should not be called with 'start'."), |
|
584 }); |
|
585 |
|
586 handleReadyState(url, 'ready', { |
|
587 onInteractive: () => { |
|
588 assert.pass("PageMod is attached while document is interactive"); |
|
589 if (++count === 3) |
|
590 tab.close(done); |
|
591 }, |
|
592 onLoading: () => assert.fail("onLoading should not be called with 'ready'."), |
|
593 onComplete: () => assert.fail("onComplete should not be called with 'ready'."), |
|
594 }); |
|
595 |
|
596 handleReadyState(url, 'end', { |
|
597 onComplete: () => { |
|
598 assert.pass("PageMod is attached when document is complete"); |
|
599 if (++count === 3) |
|
600 tab.close(done); |
|
601 }, |
|
602 onLoading: () => assert.fail("onLoading should not be called with 'end'."), |
|
603 onInteractive: () => assert.fail("onInteractive should not be called with 'end'."), |
|
604 }); |
|
605 |
|
606 } |
|
607 }); |
|
608 } |
|
609 |
|
610 // test timing for all 3 contentScriptWhen options (start, ready, end) |
|
611 // for PageMods created while the tab is interactive (in tab.onReady) |
|
612 exports.testContentScriptWhenOnTabReady = function(assert, done) { |
|
613 const url = "data:text/html;charset=utf-8,testContentScriptWhenOnTabReady"; |
|
614 |
|
615 tabs.open({ |
|
616 url: url, |
|
617 onReady: function(tab) { |
|
618 let count = 0; |
|
619 |
|
620 handleReadyState(url, 'start', { |
|
621 onInteractive: () => { |
|
622 assert.pass("PageMod is attached while document is interactive"); |
|
623 if (++count === 3) |
|
624 tab.close(done); |
|
625 }, |
|
626 onLoading: () => assert.fail("onLoading should not be called with 'start'."), |
|
627 onComplete: () => assert.fail("onComplete should not be called with 'start'."), |
|
628 }); |
|
629 |
|
630 handleReadyState(url, 'ready', { |
|
631 onInteractive: () => { |
|
632 assert.pass("PageMod is attached while document is interactive"); |
|
633 if (++count === 3) |
|
634 tab.close(done); |
|
635 }, |
|
636 onLoading: () => assert.fail("onLoading should not be called with 'ready'."), |
|
637 onComplete: () => assert.fail("onComplete should not be called with 'ready'."), |
|
638 }); |
|
639 |
|
640 handleReadyState(url, 'end', { |
|
641 onComplete: () => { |
|
642 assert.pass("PageMod is attached when document is complete"); |
|
643 if (++count === 3) |
|
644 tab.close(done); |
|
645 }, |
|
646 onLoading: () => assert.fail("onLoading should not be called with 'end'."), |
|
647 onInteractive: () => assert.fail("onInteractive should not be called with 'end'."), |
|
648 }); |
|
649 |
|
650 } |
|
651 }); |
|
652 } |
|
653 |
|
654 // test timing for all 3 contentScriptWhen options (start, ready, end) |
|
655 // for PageMods created after a tab has completed loading (in tab.onLoad) |
|
656 exports.testContentScriptWhenOnTabLoad = function(assert, done) { |
|
657 const url = "data:text/html;charset=utf-8,testContentScriptWhenOnTabLoad"; |
|
658 |
|
659 tabs.open({ |
|
660 url: url, |
|
661 onLoad: function(tab) { |
|
662 let count = 0; |
|
663 |
|
664 handleReadyState(url, 'start', { |
|
665 onComplete: () => { |
|
666 assert.pass("PageMod is attached when document is complete"); |
|
667 if (++count === 3) |
|
668 tab.close(done); |
|
669 }, |
|
670 onLoading: () => assert.fail("onLoading should not be called with 'start'."), |
|
671 onInteractive: () => assert.fail("onInteractive should not be called with 'start'."), |
|
672 }); |
|
673 |
|
674 handleReadyState(url, 'ready', { |
|
675 onComplete: () => { |
|
676 assert.pass("PageMod is attached when document is complete"); |
|
677 if (++count === 3) |
|
678 tab.close(done); |
|
679 }, |
|
680 onLoading: () => assert.fail("onLoading should not be called with 'ready'."), |
|
681 onInteractive: () => assert.fail("onInteractive should not be called with 'ready'."), |
|
682 }); |
|
683 |
|
684 handleReadyState(url, 'end', { |
|
685 onComplete: () => { |
|
686 assert.pass("PageMod is attached when document is complete"); |
|
687 if (++count === 3) |
|
688 tab.close(done); |
|
689 }, |
|
690 onLoading: () => assert.fail("onLoading should not be called with 'end'."), |
|
691 onInteractive: () => assert.fail("onInteractive should not be called with 'end'."), |
|
692 }); |
|
693 |
|
694 } |
|
695 }); |
|
696 } |
|
697 |
|
698 exports.testTabWorkerOnMessage = function(assert, done) { |
|
699 let { browserWindows } = require("sdk/windows"); |
|
700 let tabs = require("sdk/tabs"); |
|
701 let { PageMod } = require("sdk/page-mod"); |
|
702 |
|
703 let url1 = "data:text/html;charset=utf-8,<title>tab1</title><h1>worker1.tab</h1>"; |
|
704 let url2 = "data:text/html;charset=utf-8,<title>tab2</title><h1>worker2.tab</h1>"; |
|
705 let worker1 = null; |
|
706 |
|
707 let mod = PageMod({ |
|
708 include: "data:text/html*", |
|
709 contentScriptWhen: "ready", |
|
710 contentScript: "self.postMessage('#1');", |
|
711 onAttach: function onAttach(worker) { |
|
712 worker.on("message", function onMessage() { |
|
713 this.tab.attach({ |
|
714 contentScriptWhen: "ready", |
|
715 contentScript: "self.postMessage({ url: window.location.href, title: document.title });", |
|
716 onMessage: function onMessage(data) { |
|
717 assert.equal(this.tab.url, data.url, "location is correct"); |
|
718 assert.equal(this.tab.title, data.title, "title is correct"); |
|
719 if (this.tab.url === url1) { |
|
720 worker1 = this; |
|
721 tabs.open({ url: url2, inBackground: true }); |
|
722 } |
|
723 else if (this.tab.url === url2) { |
|
724 mod.destroy(); |
|
725 worker1.tab.close(function() { |
|
726 worker1.destroy(); |
|
727 worker.tab.close(function() { |
|
728 worker.destroy(); |
|
729 done(); |
|
730 }); |
|
731 }); |
|
732 } |
|
733 } |
|
734 }); |
|
735 }); |
|
736 } |
|
737 }); |
|
738 |
|
739 tabs.open(url1); |
|
740 }; |
|
741 |
|
742 exports.testAutomaticDestroy = function(assert, done) { |
|
743 let loader = Loader(module); |
|
744 |
|
745 let pageMod = loader.require("sdk/page-mod").PageMod({ |
|
746 include: "about:*", |
|
747 contentScriptWhen: "start", |
|
748 onAttach: function(w) { |
|
749 assert.fail("Page-mod should have been detroyed during module unload"); |
|
750 } |
|
751 }); |
|
752 |
|
753 // Unload the page-mod module so that our page mod is destroyed |
|
754 loader.unload(); |
|
755 |
|
756 // Then create a second tab to ensure that it is correctly destroyed |
|
757 let tabs = require("sdk/tabs"); |
|
758 tabs.open({ |
|
759 url: "about:", |
|
760 onReady: function onReady(tab) { |
|
761 assert.pass("check automatic destroy"); |
|
762 tab.close(done); |
|
763 } |
|
764 }); |
|
765 }; |
|
766 |
|
767 exports.testAttachToTabsOnly = function(assert, done) { |
|
768 let { PageMod } = require('sdk/page-mod'); |
|
769 let openedTab = null; // Tab opened in openTabWithIframe() |
|
770 let workerCount = 0; |
|
771 |
|
772 let mod = PageMod({ |
|
773 include: 'data:text/html*', |
|
774 contentScriptWhen: 'start', |
|
775 contentScript: '', |
|
776 onAttach: function onAttach(worker) { |
|
777 if (worker.tab === openedTab) { |
|
778 if (++workerCount == 3) { |
|
779 assert.pass('Succesfully applied to tab documents and its iframe'); |
|
780 worker.destroy(); |
|
781 mod.destroy(); |
|
782 openedTab.close(done); |
|
783 } |
|
784 } |
|
785 else { |
|
786 assert.fail('page-mod attached to a non-tab document'); |
|
787 } |
|
788 } |
|
789 }); |
|
790 |
|
791 function openHiddenFrame() { |
|
792 assert.pass('Open iframe in hidden window'); |
|
793 let hiddenFrames = require('sdk/frame/hidden-frame'); |
|
794 let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({ |
|
795 onReady: function () { |
|
796 let element = this.element; |
|
797 element.addEventListener('DOMContentLoaded', function onload() { |
|
798 element.removeEventListener('DOMContentLoaded', onload, false); |
|
799 hiddenFrames.remove(hiddenFrame); |
|
800 |
|
801 if (!xulApp.is("Fennec")) { |
|
802 openToplevelWindow(); |
|
803 } |
|
804 else { |
|
805 openBrowserIframe(); |
|
806 } |
|
807 }, false); |
|
808 element.setAttribute('src', 'data:text/html;charset=utf-8,foo'); |
|
809 } |
|
810 })); |
|
811 } |
|
812 |
|
813 function openToplevelWindow() { |
|
814 assert.pass('Open toplevel window'); |
|
815 let win = open('data:text/html;charset=utf-8,bar'); |
|
816 win.addEventListener('DOMContentLoaded', function onload() { |
|
817 win.removeEventListener('DOMContentLoaded', onload, false); |
|
818 win.close(); |
|
819 openBrowserIframe(); |
|
820 }, false); |
|
821 } |
|
822 |
|
823 function openBrowserIframe() { |
|
824 assert.pass('Open iframe in browser window'); |
|
825 let window = require('sdk/deprecated/window-utils').activeBrowserWindow; |
|
826 let document = window.document; |
|
827 let iframe = document.createElement('iframe'); |
|
828 iframe.setAttribute('type', 'content'); |
|
829 iframe.setAttribute('src', 'data:text/html;charset=utf-8,foobar'); |
|
830 iframe.addEventListener('DOMContentLoaded', function onload() { |
|
831 iframe.removeEventListener('DOMContentLoaded', onload, false); |
|
832 iframe.parentNode.removeChild(iframe); |
|
833 openTabWithIframes(); |
|
834 }, false); |
|
835 document.documentElement.appendChild(iframe); |
|
836 } |
|
837 |
|
838 // Only these three documents will be accepted by the page-mod |
|
839 function openTabWithIframes() { |
|
840 assert.pass('Open iframes in a tab'); |
|
841 let subContent = '<iframe src="data:text/html;charset=utf-8,sub frame" />' |
|
842 let content = '<iframe src="data:text/html;charset=utf-8,' + |
|
843 encodeURIComponent(subContent) + '" />'; |
|
844 require('sdk/tabs').open({ |
|
845 url: 'data:text/html;charset=utf-8,' + encodeURIComponent(content), |
|
846 onOpen: function onOpen(tab) { |
|
847 openedTab = tab; |
|
848 } |
|
849 }); |
|
850 } |
|
851 |
|
852 openHiddenFrame(); |
|
853 }; |
|
854 |
|
855 exports['test111 attachTo [top]'] = function(assert, done) { |
|
856 let { PageMod } = require('sdk/page-mod'); |
|
857 |
|
858 let subContent = '<iframe src="data:text/html;charset=utf-8,sub frame" />' |
|
859 let content = '<iframe src="data:text/html;charset=utf-8,' + |
|
860 encodeURIComponent(subContent) + '" />'; |
|
861 let topDocumentURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(content) |
|
862 |
|
863 let workerCount = 0; |
|
864 |
|
865 let mod = PageMod({ |
|
866 include: 'data:text/html*', |
|
867 contentScriptWhen: 'start', |
|
868 contentScript: 'self.postMessage(document.location.href);', |
|
869 attachTo: ['top'], |
|
870 onAttach: function onAttach(worker) { |
|
871 if (++workerCount == 1) { |
|
872 worker.on('message', function (href) { |
|
873 assert.equal(href, topDocumentURL, |
|
874 "worker on top level document only"); |
|
875 let tab = worker.tab; |
|
876 worker.destroy(); |
|
877 mod.destroy(); |
|
878 tab.close(done); |
|
879 }); |
|
880 } |
|
881 else { |
|
882 assert.fail('page-mod attached to a non-top document'); |
|
883 } |
|
884 } |
|
885 }); |
|
886 |
|
887 require('sdk/tabs').open(topDocumentURL); |
|
888 }; |
|
889 |
|
890 exports['test111 attachTo [frame]'] = function(assert, done) { |
|
891 let { PageMod } = require('sdk/page-mod'); |
|
892 |
|
893 let subFrameURL = 'data:text/html;charset=utf-8,subframe'; |
|
894 let subContent = '<iframe src="' + subFrameURL + '" />'; |
|
895 let frameURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(subContent); |
|
896 let content = '<iframe src="' + frameURL + '" />'; |
|
897 let topDocumentURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(content) |
|
898 |
|
899 let workerCount = 0, messageCount = 0; |
|
900 |
|
901 function onMessage(href) { |
|
902 if (href == frameURL) |
|
903 assert.pass("worker on first frame"); |
|
904 else if (href == subFrameURL) |
|
905 assert.pass("worker on second frame"); |
|
906 else |
|
907 assert.fail("worker on unexpected document: " + href); |
|
908 this.destroy(); |
|
909 if (++messageCount == 2) { |
|
910 mod.destroy(); |
|
911 require('sdk/tabs').activeTab.close(done); |
|
912 } |
|
913 } |
|
914 let mod = PageMod({ |
|
915 include: 'data:text/html*', |
|
916 contentScriptWhen: 'start', |
|
917 contentScript: 'self.postMessage(document.location.href);', |
|
918 attachTo: ['frame'], |
|
919 onAttach: function onAttach(worker) { |
|
920 if (++workerCount <= 2) { |
|
921 worker.on('message', onMessage); |
|
922 } |
|
923 else { |
|
924 assert.fail('page-mod attached to a non-frame document'); |
|
925 } |
|
926 } |
|
927 }); |
|
928 |
|
929 require('sdk/tabs').open(topDocumentURL); |
|
930 }; |
|
931 |
|
932 exports.testContentScriptOptionsOption = function(assert, done) { |
|
933 let callbackDone = null; |
|
934 testPageMod(assert, done, "about:", [{ |
|
935 include: "about:*", |
|
936 contentScript: "self.postMessage( [typeof self.options.d, self.options] );", |
|
937 contentScriptWhen: "end", |
|
938 contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}}, |
|
939 onAttach: function(worker) { |
|
940 worker.on('message', function(msg) { |
|
941 assert.equal( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' ); |
|
942 assert.equal( typeof msg[1], 'object', 'object as contentScriptOptions' ); |
|
943 assert.equal( msg[1].a, true, 'boolean in contentScriptOptions' ); |
|
944 assert.equal( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' ); |
|
945 assert.equal( msg[1].c, 'string', 'string in contentScriptOptions' ); |
|
946 callbackDone(); |
|
947 }); |
|
948 } |
|
949 }], |
|
950 function(win, done) { |
|
951 callbackDone = done; |
|
952 } |
|
953 ); |
|
954 }; |
|
955 |
|
956 exports.testPageModCss = function(assert, done) { |
|
957 let [pageMod] = testPageMod(assert, done, |
|
958 'data:text/html;charset=utf-8,<div style="background: silver">css test</div>', [{ |
|
959 include: ["*", "data:*"], |
|
960 contentStyle: "div { height: 100px; }", |
|
961 contentStyleFile: data.url("css-include-file.css") |
|
962 }], |
|
963 function(win, done) { |
|
964 let div = win.document.querySelector("div"); |
|
965 assert.equal( |
|
966 div.clientHeight, |
|
967 100, |
|
968 "PageMod contentStyle worked" |
|
969 ); |
|
970 assert.equal( |
|
971 div.offsetHeight, |
|
972 120, |
|
973 "PageMod contentStyleFile worked" |
|
974 ); |
|
975 done(); |
|
976 } |
|
977 ); |
|
978 }; |
|
979 |
|
980 exports.testPageModCssList = function(assert, done) { |
|
981 let [pageMod] = testPageMod(assert, done, |
|
982 'data:text/html;charset=utf-8,<div style="width:320px; max-width: 480px!important">css test</div>', [{ |
|
983 include: "data:*", |
|
984 contentStyleFile: [ |
|
985 // Highlight evaluation order in this list |
|
986 "data:text/css;charset=utf-8,div { border: 1px solid black; }", |
|
987 "data:text/css;charset=utf-8,div { border: 10px solid black; }", |
|
988 // Highlight evaluation order between contentStylesheet & contentStylesheetFile |
|
989 "data:text/css;charset=utf-8s,div { height: 1000px; }", |
|
990 // Highlight precedence between the author and user style sheet |
|
991 "data:text/css;charset=utf-8,div { width: 200px; max-width: 640px!important}", |
|
992 ], |
|
993 contentStyle: [ |
|
994 "div { height: 10px; }", |
|
995 "div { height: 100px; }" |
|
996 ] |
|
997 }], |
|
998 function(win, done) { |
|
999 let div = win.document.querySelector("div"), |
|
1000 style = win.getComputedStyle(div); |
|
1001 |
|
1002 assert.equal( |
|
1003 div.clientHeight, |
|
1004 100, |
|
1005 "PageMod contentStyle list works and is evaluated after contentStyleFile" |
|
1006 ); |
|
1007 |
|
1008 assert.equal( |
|
1009 div.offsetHeight, |
|
1010 120, |
|
1011 "PageMod contentStyleFile list works" |
|
1012 ); |
|
1013 |
|
1014 assert.equal( |
|
1015 style.width, |
|
1016 "320px", |
|
1017 "PageMod add-on author/page author style sheet precedence works" |
|
1018 ); |
|
1019 |
|
1020 assert.equal( |
|
1021 style.maxWidth, |
|
1022 "480px", |
|
1023 "PageMod add-on author/page author style sheet precedence with !important works" |
|
1024 ); |
|
1025 |
|
1026 done(); |
|
1027 } |
|
1028 ); |
|
1029 }; |
|
1030 |
|
1031 exports.testPageModCssDestroy = function(assert, done) { |
|
1032 let [pageMod] = testPageMod(assert, done, |
|
1033 'data:text/html;charset=utf-8,<div style="width:200px">css test</div>', [{ |
|
1034 include: "data:*", |
|
1035 contentStyle: "div { width: 100px!important; }" |
|
1036 }], |
|
1037 |
|
1038 function(win, done) { |
|
1039 let div = win.document.querySelector("div"), |
|
1040 style = win.getComputedStyle(div); |
|
1041 |
|
1042 assert.equal( |
|
1043 style.width, |
|
1044 "100px", |
|
1045 "PageMod contentStyle worked" |
|
1046 ); |
|
1047 |
|
1048 pageMod.destroy(); |
|
1049 assert.equal( |
|
1050 style.width, |
|
1051 "200px", |
|
1052 "PageMod contentStyle is removed after destroy" |
|
1053 ); |
|
1054 |
|
1055 done(); |
|
1056 |
|
1057 } |
|
1058 ); |
|
1059 }; |
|
1060 |
|
1061 exports.testPageModCssAutomaticDestroy = function(assert, done) { |
|
1062 let loader = Loader(module); |
|
1063 |
|
1064 let pageMod = loader.require("sdk/page-mod").PageMod({ |
|
1065 include: "data:*", |
|
1066 contentStyle: "div { width: 100px!important; }" |
|
1067 }); |
|
1068 |
|
1069 tabs.open({ |
|
1070 url: "data:text/html;charset=utf-8,<div style='width:200px'>css test</div>", |
|
1071 |
|
1072 onReady: function onReady(tab) { |
|
1073 let browserWindow = getMostRecentBrowserWindow(); |
|
1074 let win = getTabContentWindow(getActiveTab(browserWindow)); |
|
1075 |
|
1076 let div = win.document.querySelector("div"); |
|
1077 let style = win.getComputedStyle(div); |
|
1078 |
|
1079 assert.equal( |
|
1080 style.width, |
|
1081 "100px", |
|
1082 "PageMod contentStyle worked" |
|
1083 ); |
|
1084 |
|
1085 loader.unload(); |
|
1086 |
|
1087 assert.equal( |
|
1088 style.width, |
|
1089 "200px", |
|
1090 "PageMod contentStyle is removed after loader's unload" |
|
1091 ); |
|
1092 |
|
1093 tab.close(done); |
|
1094 } |
|
1095 }); |
|
1096 }; |
|
1097 |
|
1098 |
|
1099 exports.testPageModTimeout = function(assert, done) { |
|
1100 let tab = null |
|
1101 let loader = Loader(module); |
|
1102 let { PageMod } = loader.require("sdk/page-mod"); |
|
1103 |
|
1104 let mod = PageMod({ |
|
1105 include: "data:*", |
|
1106 contentScript: Isolate(function() { |
|
1107 var id = setTimeout(function() { |
|
1108 self.port.emit("fired", id) |
|
1109 }, 10) |
|
1110 self.port.emit("scheduled", id); |
|
1111 }), |
|
1112 onAttach: function(worker) { |
|
1113 worker.port.on("scheduled", function(id) { |
|
1114 assert.pass("timer was scheduled") |
|
1115 worker.port.on("fired", function(data) { |
|
1116 assert.equal(id, data, "timer was fired") |
|
1117 tab.close(function() { |
|
1118 worker.destroy() |
|
1119 loader.unload() |
|
1120 done() |
|
1121 }); |
|
1122 }) |
|
1123 }) |
|
1124 } |
|
1125 }); |
|
1126 |
|
1127 tabs.open({ |
|
1128 url: "data:text/html;charset=utf-8,timeout", |
|
1129 onReady: function($) { tab = $ } |
|
1130 }) |
|
1131 } |
|
1132 |
|
1133 |
|
1134 exports.testPageModcancelTimeout = function(assert, done) { |
|
1135 let tab = null |
|
1136 let loader = Loader(module); |
|
1137 let { PageMod } = loader.require("sdk/page-mod"); |
|
1138 |
|
1139 let mod = PageMod({ |
|
1140 include: "data:*", |
|
1141 contentScript: Isolate(function() { |
|
1142 var id1 = setTimeout(function() { |
|
1143 self.port.emit("failed") |
|
1144 }, 10) |
|
1145 var id2 = setTimeout(function() { |
|
1146 self.port.emit("timeout") |
|
1147 }, 100) |
|
1148 clearTimeout(id1) |
|
1149 }), |
|
1150 onAttach: function(worker) { |
|
1151 worker.port.on("failed", function() { |
|
1152 assert.fail("cancelled timeout fired") |
|
1153 }) |
|
1154 worker.port.on("timeout", function(id) { |
|
1155 assert.pass("timer was scheduled") |
|
1156 tab.close(function() { |
|
1157 worker.destroy(); |
|
1158 mod.destroy(); |
|
1159 loader.unload(); |
|
1160 done(); |
|
1161 }); |
|
1162 }) |
|
1163 } |
|
1164 }); |
|
1165 |
|
1166 tabs.open({ |
|
1167 url: "data:text/html;charset=utf-8,cancell timeout", |
|
1168 onReady: function($) { tab = $ } |
|
1169 }) |
|
1170 } |
|
1171 |
|
1172 exports.testExistingOnFrames = function(assert, done) { |
|
1173 let subFrameURL = 'data:text/html;charset=utf-8,testExistingOnFrames-sub-frame'; |
|
1174 let subIFrame = '<iframe src="' + subFrameURL + '" />' |
|
1175 let iFrameURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(subIFrame) |
|
1176 let iFrame = '<iframe src="' + iFrameURL + '" />'; |
|
1177 let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iFrame); |
|
1178 |
|
1179 // we want all urls related to the test here, and not just the iframe urls |
|
1180 // because we need to fail if the test is applied to the top window url. |
|
1181 let urls = [url, iFrameURL, subFrameURL]; |
|
1182 |
|
1183 let counter = 0; |
|
1184 let tab = openTab(getMostRecentBrowserWindow(), url); |
|
1185 let window = getTabContentWindow(tab); |
|
1186 |
|
1187 function wait4Iframes() { |
|
1188 if (window.document.readyState != "complete" || |
|
1189 getFrames(window).length != 2) { |
|
1190 return; |
|
1191 } |
|
1192 |
|
1193 let pagemodOnExisting = PageMod({ |
|
1194 include: ["*", "data:*"], |
|
1195 attachTo: ["existing", "frame"], |
|
1196 contentScriptWhen: 'ready', |
|
1197 onAttach: function(worker) { |
|
1198 // need to ignore urls that are not part of the test, because other |
|
1199 // tests are not closing their tabs when they complete.. |
|
1200 if (urls.indexOf(worker.url) == -1) |
|
1201 return; |
|
1202 |
|
1203 assert.notEqual(url, |
|
1204 worker.url, |
|
1205 'worker should not be attached to the top window'); |
|
1206 |
|
1207 if (++counter < 2) { |
|
1208 // we can rely on this order in this case because we are sure that |
|
1209 // the frames being tested have completely loaded |
|
1210 assert.equal(iFrameURL, worker.url, '1st attach is for top frame'); |
|
1211 } |
|
1212 else if (counter > 2) { |
|
1213 assert.fail('applied page mod too many times'); |
|
1214 } |
|
1215 else { |
|
1216 assert.equal(subFrameURL, worker.url, '2nd attach is for sub frame'); |
|
1217 // need timeout because onAttach is called before the constructor returns |
|
1218 setTimeout(function() { |
|
1219 pagemodOnExisting.destroy(); |
|
1220 pagemodOffExisting.destroy(); |
|
1221 closeTab(tab); |
|
1222 done(); |
|
1223 }, 0); |
|
1224 } |
|
1225 } |
|
1226 }); |
|
1227 |
|
1228 let pagemodOffExisting = PageMod({ |
|
1229 include: ["*", "data:*"], |
|
1230 attachTo: ["frame"], |
|
1231 contentScriptWhen: 'ready', |
|
1232 onAttach: function(mod) { |
|
1233 assert.fail('pagemodOffExisting page-mod should not have been attached'); |
|
1234 } |
|
1235 }); |
|
1236 } |
|
1237 |
|
1238 window.addEventListener("load", wait4Iframes, false); |
|
1239 }; |
|
1240 |
|
1241 exports.testIFramePostMessage = function(assert, done) { |
|
1242 let count = 0; |
|
1243 |
|
1244 tabs.open({ |
|
1245 url: data.url("test-iframe.html"), |
|
1246 onReady: function(tab) { |
|
1247 var worker = tab.attach({ |
|
1248 contentScriptFile: data.url('test-iframe.js'), |
|
1249 contentScript: 'var iframePath = \'' + data.url('test-iframe-postmessage.html') + '\'', |
|
1250 onMessage: function(msg) { |
|
1251 assert.equal(++count, 1); |
|
1252 assert.equal(msg.first, 'a string'); |
|
1253 assert.ok(msg.second[1], "array"); |
|
1254 assert.equal(typeof msg.third, 'object'); |
|
1255 |
|
1256 worker.destroy(); |
|
1257 tab.close(done); |
|
1258 } |
|
1259 }); |
|
1260 } |
|
1261 }); |
|
1262 }; |
|
1263 |
|
1264 exports.testEvents = function(assert, done) { |
|
1265 let content = "<script>\n new " + function DocumentScope() { |
|
1266 window.addEventListener("ContentScriptEvent", function () { |
|
1267 window.receivedEvent = true; |
|
1268 }, false); |
|
1269 } + "\n</script>"; |
|
1270 let url = "data:text/html;charset=utf-8," + encodeURIComponent(content); |
|
1271 testPageMod(assert, done, url, [{ |
|
1272 include: "data:*", |
|
1273 contentScript: 'new ' + function WorkerScope() { |
|
1274 let evt = document.createEvent("Event"); |
|
1275 evt.initEvent("ContentScriptEvent", true, true); |
|
1276 document.body.dispatchEvent(evt); |
|
1277 } |
|
1278 }], |
|
1279 function(win, done) { |
|
1280 assert.ok( |
|
1281 win.receivedEvent, |
|
1282 "Content script sent an event and document received it" |
|
1283 ); |
|
1284 done(); |
|
1285 } |
|
1286 ); |
|
1287 }; |
|
1288 |
|
1289 exports["test page-mod on private tab"] = function (assert, done) { |
|
1290 let fail = assert.fail.bind(assert); |
|
1291 |
|
1292 let privateUri = "data:text/html;charset=utf-8," + |
|
1293 "<iframe src=\"data:text/html;charset=utf-8,frame\" />"; |
|
1294 let nonPrivateUri = "data:text/html;charset=utf-8,non-private"; |
|
1295 |
|
1296 let pageMod = new PageMod({ |
|
1297 include: "data:*", |
|
1298 onAttach: function(worker) { |
|
1299 if (isTabPBSupported || isWindowPBSupported) { |
|
1300 // When PB isn't supported, the page-mod will apply to all document |
|
1301 // as all of them will be non-private |
|
1302 assert.equal(worker.tab.url, |
|
1303 nonPrivateUri, |
|
1304 "page-mod should only attach to the non-private tab"); |
|
1305 } |
|
1306 |
|
1307 assert.ok(!isPrivate(worker), |
|
1308 "The worker is really non-private"); |
|
1309 assert.ok(!isPrivate(worker.tab), |
|
1310 "The document is really non-private"); |
|
1311 pageMod.destroy(); |
|
1312 |
|
1313 page1.close(). |
|
1314 then(page2.close). |
|
1315 then(done, fail); |
|
1316 } |
|
1317 }); |
|
1318 |
|
1319 let page1, page2; |
|
1320 page1 = openWebpage(privateUri, true); |
|
1321 page1.ready.then(function() { |
|
1322 page2 = openWebpage(nonPrivateUri, false); |
|
1323 }, fail); |
|
1324 } |
|
1325 |
|
1326 exports["test page-mod on private tab in global pb"] = function (assert, done) { |
|
1327 if (!isGlobalPBSupported) { |
|
1328 assert.pass(); |
|
1329 return done(); |
|
1330 } |
|
1331 |
|
1332 let privateUri = "data:text/html;charset=utf-8," + |
|
1333 "<iframe%20src=\"data:text/html;charset=utf-8,frame\"/>"; |
|
1334 |
|
1335 let pageMod = new PageMod({ |
|
1336 include: privateUri, |
|
1337 onAttach: function(worker) { |
|
1338 assert.equal(worker.tab.url, |
|
1339 privateUri, |
|
1340 "page-mod should attach"); |
|
1341 assert.equal(isPrivateBrowsingSupported, |
|
1342 false, |
|
1343 "private browsing is not supported"); |
|
1344 assert.ok(isPrivate(worker), |
|
1345 "The worker is really non-private"); |
|
1346 assert.ok(isPrivate(worker.tab), |
|
1347 "The document is really non-private"); |
|
1348 pageMod.destroy(); |
|
1349 |
|
1350 worker.tab.close(function() { |
|
1351 pb.once('stop', function() { |
|
1352 assert.pass('global pb stop'); |
|
1353 done(); |
|
1354 }); |
|
1355 pb.deactivate(); |
|
1356 }); |
|
1357 } |
|
1358 }); |
|
1359 |
|
1360 let page1; |
|
1361 pb.once('start', function() { |
|
1362 assert.pass('global pb start'); |
|
1363 tabs.open({ url: privateUri }); |
|
1364 }); |
|
1365 pb.activate(); |
|
1366 } |
|
1367 |
|
1368 // Bug 699450: Calling worker.tab.close() should not lead to exception |
|
1369 exports.testWorkerTabClose = function(assert, done) { |
|
1370 let callbackDone; |
|
1371 testPageMod(assert, done, "about:", [{ |
|
1372 include: "about:", |
|
1373 contentScript: '', |
|
1374 onAttach: function(worker) { |
|
1375 assert.pass("The page-mod was attached"); |
|
1376 |
|
1377 worker.tab.close(function () { |
|
1378 // On Fennec, tab is completely destroyed right after close event is |
|
1379 // dispatch, so we need to wait for the next event loop cycle to |
|
1380 // check for tab nulliness. |
|
1381 setTimeout(function () { |
|
1382 assert.ok(!worker.tab, |
|
1383 "worker.tab should be null right after tab.close()"); |
|
1384 callbackDone(); |
|
1385 }, 0); |
|
1386 }); |
|
1387 } |
|
1388 }], |
|
1389 function(win, done) { |
|
1390 callbackDone = done; |
|
1391 } |
|
1392 ); |
|
1393 }; |
|
1394 |
|
1395 exports.testDebugMetadata = function(assert, done) { |
|
1396 let dbg = new Debugger; |
|
1397 let globalDebuggees = []; |
|
1398 dbg.onNewGlobalObject = function(global) { |
|
1399 globalDebuggees.push(global); |
|
1400 } |
|
1401 |
|
1402 let mods = testPageMod(assert, done, "about:", [{ |
|
1403 include: "about:", |
|
1404 contentScriptWhen: "start", |
|
1405 contentScript: "null;", |
|
1406 }], function(win, done) { |
|
1407 assert.ok(globalDebuggees.some(function(global) { |
|
1408 try { |
|
1409 let metadata = Cu.getSandboxMetadata(global.unsafeDereference()); |
|
1410 return metadata && metadata.addonID && metadata.SDKContentScript && |
|
1411 metadata['inner-window-id'] == getInnerId(win); |
|
1412 } catch(e) { |
|
1413 // Some of the globals might not be Sandbox instances and thus |
|
1414 // will cause getSandboxMetadata to fail. |
|
1415 return false; |
|
1416 } |
|
1417 }), "one of the globals is a content script"); |
|
1418 done(); |
|
1419 } |
|
1420 ); |
|
1421 }; |
|
1422 |
|
1423 exports.testDevToolsExtensionsGetContentGlobals = function(assert, done) { |
|
1424 let mods = testPageMod(assert, done, "about:", [{ |
|
1425 include: "about:", |
|
1426 contentScriptWhen: "start", |
|
1427 contentScript: "null;", |
|
1428 }], function(win, done) { |
|
1429 assert.equal(gDevToolsExtensions.getContentGlobals({ 'inner-window-id': getInnerId(win) }).length, 1); |
|
1430 done(); |
|
1431 } |
|
1432 ); |
|
1433 }; |
|
1434 |
|
1435 exports.testDetachOnDestroy = function(assert, done) { |
|
1436 let tab; |
|
1437 const TEST_URL = 'data:text/html;charset=utf-8,detach'; |
|
1438 const loader = Loader(module); |
|
1439 const { PageMod } = loader.require('sdk/page-mod'); |
|
1440 |
|
1441 let mod1 = PageMod({ |
|
1442 include: TEST_URL, |
|
1443 contentScript: Isolate(function() { |
|
1444 self.port.on('detach', function(reason) { |
|
1445 window.document.body.innerHTML += '!' + reason; |
|
1446 }); |
|
1447 }), |
|
1448 onAttach: worker => { |
|
1449 assert.pass('attach[1] happened'); |
|
1450 |
|
1451 worker.on('detach', _ => setTimeout(_ => { |
|
1452 assert.pass('detach happened'); |
|
1453 |
|
1454 let mod2 = PageMod({ |
|
1455 attachTo: [ 'existing', 'top' ], |
|
1456 include: TEST_URL, |
|
1457 contentScript: Isolate(function() { |
|
1458 self.port.on('test', _ => { |
|
1459 self.port.emit('result', { result: window.document.body.innerHTML}); |
|
1460 }); |
|
1461 }), |
|
1462 onAttach: worker => { |
|
1463 assert.pass('attach[2] happened'); |
|
1464 worker.port.once('result', ({ result }) => { |
|
1465 assert.equal(result, 'detach!', 'the body.innerHTML is as expected'); |
|
1466 mod1.destroy(); |
|
1467 mod2.destroy(); |
|
1468 loader.unload(); |
|
1469 tab.close(done); |
|
1470 }); |
|
1471 worker.port.emit('test'); |
|
1472 } |
|
1473 }); |
|
1474 })); |
|
1475 |
|
1476 worker.destroy(); |
|
1477 } |
|
1478 }); |
|
1479 |
|
1480 tabs.open({ |
|
1481 url: TEST_URL, |
|
1482 onOpen: t => tab = t |
|
1483 }) |
|
1484 } |
|
1485 |
|
1486 exports.testDetachOnUnload = function(assert, done) { |
|
1487 let tab; |
|
1488 const TEST_URL = 'data:text/html;charset=utf-8,detach'; |
|
1489 const loader = Loader(module); |
|
1490 const { PageMod } = loader.require('sdk/page-mod'); |
|
1491 |
|
1492 let mod1 = PageMod({ |
|
1493 include: TEST_URL, |
|
1494 contentScript: Isolate(function() { |
|
1495 self.port.on('detach', function(reason) { |
|
1496 window.document.body.innerHTML += '!' + reason; |
|
1497 }); |
|
1498 }), |
|
1499 onAttach: worker => { |
|
1500 assert.pass('attach[1] happened'); |
|
1501 |
|
1502 worker.on('detach', _ => setTimeout(_ => { |
|
1503 assert.pass('detach happened'); |
|
1504 |
|
1505 let mod2 = require('sdk/page-mod').PageMod({ |
|
1506 attachTo: [ 'existing', 'top' ], |
|
1507 include: TEST_URL, |
|
1508 contentScript: Isolate(function() { |
|
1509 self.port.on('test', _ => { |
|
1510 self.port.emit('result', { result: window.document.body.innerHTML}); |
|
1511 }); |
|
1512 }), |
|
1513 onAttach: worker => { |
|
1514 assert.pass('attach[2] happened'); |
|
1515 worker.port.once('result', ({ result }) => { |
|
1516 assert.equal(result, 'detach!shutdown', 'the body.innerHTML is as expected'); |
|
1517 mod2.destroy(); |
|
1518 tab.close(done); |
|
1519 }); |
|
1520 worker.port.emit('test'); |
|
1521 } |
|
1522 }); |
|
1523 })); |
|
1524 |
|
1525 loader.unload('shutdown'); |
|
1526 } |
|
1527 }); |
|
1528 |
|
1529 tabs.open({ |
|
1530 url: TEST_URL, |
|
1531 onOpen: t => tab = t |
|
1532 }) |
|
1533 } |
|
1534 |
|
1535 exports.testConsole = function(assert, done) { |
|
1536 let innerID; |
|
1537 const TEST_URL = 'data:text/html;charset=utf-8,console'; |
|
1538 const { loader } = LoaderWithHookedConsole(module, onMessage); |
|
1539 const { PageMod } = loader.require('sdk/page-mod'); |
|
1540 const system = require("sdk/system/events"); |
|
1541 |
|
1542 let seenMessage = false; |
|
1543 function onMessage(type, msg, msgID) { |
|
1544 seenMessage = true; |
|
1545 innerID = msgID; |
|
1546 } |
|
1547 |
|
1548 let mod = PageMod({ |
|
1549 include: TEST_URL, |
|
1550 contentScriptWhen: "ready", |
|
1551 contentScript: Isolate(function() { |
|
1552 console.log("Hello from the page mod"); |
|
1553 self.port.emit("done"); |
|
1554 }), |
|
1555 onAttach: function(worker) { |
|
1556 worker.port.on("done", function() { |
|
1557 let window = getTabContentWindow(tab); |
|
1558 let id = getInnerId(window); |
|
1559 assert.ok(seenMessage, "Should have seen the console message"); |
|
1560 assert.equal(innerID, id, "Should have seen the right inner ID"); |
|
1561 closeTab(tab); |
|
1562 done(); |
|
1563 }); |
|
1564 }, |
|
1565 }); |
|
1566 |
|
1567 let tab = openTab(getMostRecentBrowserWindow(), TEST_URL); |
|
1568 } |
|
1569 |
|
1570 exports.testSyntaxErrorInContentScript = function(assert, done) { |
|
1571 const url = "data:text/html;charset=utf-8,testSyntaxErrorInContentScript"; |
|
1572 let hitError = null; |
|
1573 let attached = false; |
|
1574 |
|
1575 testPageMod(assert, done, url, [{ |
|
1576 include: url, |
|
1577 contentScript: 'console.log(23', |
|
1578 |
|
1579 onAttach: function() { |
|
1580 attached = true; |
|
1581 }, |
|
1582 |
|
1583 onError: function(e) { |
|
1584 hitError = e; |
|
1585 } |
|
1586 }], |
|
1587 |
|
1588 function(win, done) { |
|
1589 assert.ok(attached, "The worker was attached."); |
|
1590 assert.notStrictEqual(hitError, null, "The syntax error was reported."); |
|
1591 if (hitError) |
|
1592 assert.equal(hitError.name, "SyntaxError", "The error thrown should be a SyntaxError"); |
|
1593 done(); |
|
1594 } |
|
1595 ); |
|
1596 }; |
|
1597 |
|
1598 require('sdk/test').run(exports); |