Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
5 const hiddenFrames = require("sdk/frame/hidden-frame");
6 const { create: makeFrame } = require("sdk/frame/utils");
7 const { window } = require("sdk/addon/window");
8 const { Loader } = require('sdk/test/loader');
9 const { URL } = require("sdk/url");
10 const testURI = require("./fixtures").url("test.html");
11 const testHost = URL(testURI).scheme + '://' + URL(testURI).host;
13 /*
14 * Utility function that allow to easily run a proxy test with a clean
15 * new HTML document. See first unit test for usage.
16 */
17 function createProxyTest(html, callback) {
18 return function (assert, done) {
19 let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
20 let principalLoaded = false;
22 let element = makeFrame(window.document, {
23 nodeName: "iframe",
24 type: "content",
25 allowJavascript: true,
26 allowPlugins: true,
27 allowAuth: true,
28 uri: testURI
29 });
31 element.addEventListener("DOMContentLoaded", onDOMReady, false);
33 function onDOMReady() {
34 // Reload frame after getting principal from `testURI`
35 if (!principalLoaded) {
36 element.setAttribute("src", url);
37 principalLoaded = true;
38 return;
39 }
41 assert.equal(element.getAttribute("src"), url, "correct URL loaded");
42 element.removeEventListener("DOMContentLoaded", onDOMReady,
43 false);
44 let xrayWindow = element.contentWindow;
45 let rawWindow = xrayWindow.wrappedJSObject;
47 let isDone = false;
48 let helper = {
49 xrayWindow: xrayWindow,
50 rawWindow: rawWindow,
51 createWorker: function (contentScript) {
52 return createWorker(assert, xrayWindow, contentScript, helper.done);
53 },
54 done: function () {
55 if (isDone)
56 return;
57 isDone = true;
58 element.parentNode.removeChild(element);
59 done();
60 }
61 };
62 callback(helper, assert);
63 }
64 };
65 }
67 function createWorker(assert, xrayWindow, contentScript, done) {
68 let loader = Loader(module);
69 let Worker = loader.require("sdk/content/worker").Worker;
70 let worker = Worker({
71 window: xrayWindow,
72 contentScript: [
73 'new ' + function () {
74 assert = function assert(v, msg) {
75 self.port.emit("assert", {assertion:v, msg:msg});
76 }
77 done = function done() {
78 self.port.emit("done");
79 }
80 },
81 contentScript
82 ]
83 });
85 worker.port.on("done", done);
86 worker.port.on("assert", function (data) {
87 assert.ok(data.assertion, data.msg);
88 });
90 return worker;
91 }
93 /* Examples for the `createProxyTest` uses */
95 let html = "<script>var documentGlobal = true</script>";
97 exports["test Create Proxy Test"] = createProxyTest(html, function (helper, assert) {
98 // You can get access to regular `test` object in second argument of
99 // `createProxyTest` method:
100 assert.ok(helper.rawWindow.documentGlobal,
101 "You have access to a raw window reference via `helper.rawWindow`");
102 assert.ok(!("documentGlobal" in helper.xrayWindow),
103 "You have access to an XrayWrapper reference via `helper.xrayWindow`");
105 // If you do not create a Worker, you have to call helper.done(),
106 // in order to say when your test is finished
107 helper.done();
108 });
110 exports["test Create Proxy Test With Worker"] = createProxyTest("", function (helper) {
112 helper.createWorker(
113 "new " + function WorkerScope() {
114 assert(true, "You can do assertions in your content script");
115 // And if you create a worker, you either have to call `done`
116 // from content script or helper.done()
117 done();
118 }
119 );
121 });
123 exports["test Create Proxy Test With Events"] = createProxyTest("", function (helper, assert) {
125 let worker = helper.createWorker(
126 "new " + function WorkerScope() {
127 self.port.emit("foo");
128 }
129 );
131 worker.port.on("foo", function () {
132 assert.pass("You can use events");
133 // And terminate your test with helper.done:
134 helper.done();
135 });
137 });
139 // Bug 714778: There was some issue around `toString` functions
140 // that ended up being shared between content scripts
141 exports["test Shared To String Proxies"] = createProxyTest("", function(helper) {
143 let worker = helper.createWorker(
144 'new ' + function ContentScriptScope() {
145 // We ensure that `toString` can't be modified so that nothing could
146 // leak to/from the document and between content scripts
147 // It only applies to JS proxies, there isn't any such issue with xrays.
148 //document.location.toString = function foo() {};
149 document.location.toString.foo = "bar";
150 assert("foo" in document.location.toString, "document.location.toString can be modified");
151 assert(document.location.toString() == "data:text/html;charset=utf-8,",
152 "First document.location.toString()");
153 self.postMessage("next");
154 }
155 );
156 worker.on("message", function () {
157 helper.createWorker(
158 'new ' + function ContentScriptScope2() {
159 assert(!("foo" in document.location.toString),
160 "document.location.toString is different for each content script");
161 assert(document.location.toString() == "data:text/html;charset=utf-8,",
162 "Second document.location.toString()");
163 done();
164 }
165 );
166 });
167 });
170 // Ensure that postMessage is working correctly across documents with an iframe
171 let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';
172 exports["test postMessage"] = createProxyTest(html, function (helper, assert) {
173 let ifWindow = helper.xrayWindow.document.getElementById("iframe").contentWindow;
174 // Listen without proxies, to check that it will work in regular case
175 // simulate listening from a web document.
176 ifWindow.addEventListener("message", function listener(event) {
177 ifWindow.removeEventListener("message", listener, false);
178 // As we are in system principal, event is an XrayWrapper
179 // xrays use current compartments when calling postMessage method.
180 // Whereas js proxies was using postMessage method compartment,
181 // not the caller one.
182 assert.strictEqual(event.source, helper.xrayWindow,
183 "event.source is the top window");
184 assert.equal(event.origin, testHost, "origin matches testHost");
186 assert.equal(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}",
187 "message data is correct");
189 helper.done();
190 }, false);
192 helper.createWorker(
193 'new ' + function ContentScriptScope() {
194 var json = JSON.stringify({foo : "bar\n \"escaped\"."});
196 document.getElementById("iframe").contentWindow.postMessage(json, "*");
197 }
198 );
199 });
201 let html = '<input id="input2" type="checkbox" />';
202 exports["test Object Listener"] = createProxyTest(html, function (helper) {
204 helper.createWorker(
205 'new ' + function ContentScriptScope() {
206 // Test objects being given as event listener
207 let input = document.getElementById("input2");
208 let myClickListener = {
209 called: false,
210 handleEvent: function(event) {
211 assert(this === myClickListener, "`this` is the original object");
212 assert(!this.called, "called only once");
213 this.called = true;
214 assert(event.target, input, "event.target is the wrapped window");
215 done();
216 }
217 };
219 window.addEventListener("click", myClickListener, true);
220 input.click();
221 window.removeEventListener("click", myClickListener, true);
222 }
223 );
225 });
227 exports["test Object Listener 2"] = createProxyTest("", function (helper) {
229 helper.createWorker(
230 ('new ' + function ContentScriptScope() {
231 // variable replaced with `testHost`
232 let testHost = "TOKEN";
233 // Verify object as DOM event listener
234 let myMessageListener = {
235 called: false,
236 handleEvent: function(event) {
237 window.removeEventListener("message", myMessageListener, true);
239 assert(this == myMessageListener, "`this` is the original object");
240 assert(!this.called, "called only once");
241 this.called = true;
242 assert(event.target == document.defaultView, "event.target is the wrapped window");
243 assert(event.source == document.defaultView, "event.source is the wrapped window");
244 assert(event.origin == testHost, "origin matches testHost");
245 assert(event.data == "ok", "message data is correct");
246 done();
247 }
248 };
250 window.addEventListener("message", myMessageListener, true);
251 document.defaultView.postMessage("ok", '*');
252 }
253 ).replace("TOKEN", testHost));
255 });
257 let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
258 '<input id="input2" type="checkbox" />';
260 exports.testStringOverload = createProxyTest(html, function (helper, assert) {
261 // Proxy - toString error
262 let originalString = "string";
263 let p = Proxy.create({
264 get: function(receiver, name) {
265 if (name == "binded")
266 return originalString.toString.bind(originalString);
267 return originalString[name];
268 }
269 });
270 assert.throws(function () {
271 p.toString();
272 },
273 /toString method called on incompatible Proxy/,
274 "toString can't be called with this being the proxy");
275 assert.equal(p.binded(), "string", "but it works if we bind this to the original string");
277 helper.createWorker(
278 'new ' + function ContentScriptScope() {
279 // RightJS is hacking around String.prototype, and do similar thing:
280 // Pass `this` from a String prototype method.
281 // It is funny because typeof this == "object"!
282 // So that when we pass `this` to a native method,
283 // our proxy code can fail on another even more crazy thing.
284 // See following test to see what fails around proxies.
285 String.prototype.update = function () {
286 assert(typeof this == "object", "in update, `this` is an object");
287 assert(this.toString() == "input", "in update, `this.toString works");
288 return document.querySelectorAll(this);
289 };
290 assert("input".update().length == 3, "String.prototype overload works");
291 done();
292 }
293 );
294 });
296 exports["test MozMatchedSelector"] = createProxyTest("", function (helper) {
297 helper.createWorker(
298 'new ' + function ContentScriptScope() {
299 // Check mozMatchesSelector XrayWrappers bug:
300 // mozMatchesSelector returns bad results when we are not calling it from the node itself
301 // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
302 assert(document.createElement( "div" ).mozMatchesSelector("div"),
303 "mozMatchesSelector works while being called from the node");
304 assert(document.documentElement.mozMatchesSelector.call(
305 document.createElement( "div" ),
306 "div"
307 ),
308 "mozMatchesSelector works while being called from a " +
309 "function reference to " +
310 "document.documentElement.mozMatchesSelector.call");
311 done();
312 }
313 );
314 });
316 exports["test Events Overload"] = createProxyTest("", function (helper) {
318 helper.createWorker(
319 'new ' + function ContentScriptScope() {
320 // If we add a "____proxy" attribute on XrayWrappers in order to store
321 // the related proxy to create an unique proxy for each wrapper;
322 // we end up setting this attribute to prototype objects :x
323 // And so, instances created with such prototype will be considered
324 // as equal to the prototype ...
325 // // Internal method that return the proxy for a given XrayWrapper
326 // function proxify(obj) {
327 // if (obj._proxy) return obj._proxy;
328 // return obj._proxy = Proxy.create(...);
329 // }
330 //
331 // // Get a proxy of an XrayWrapper prototype object
332 // let proto = proxify(xpcProto);
333 //
334 // // Use this proxy as a prototype
335 // function Constr() {}
336 // Constr.proto = proto;
337 //
338 // // Try to create an instance using this prototype
339 // let xpcInstance = new Constr();
340 // let wrapper = proxify(xpcInstance)
341 //
342 // xpcProto._proxy = proto and as xpcInstance.__proto__ = xpcProto,
343 // xpcInstance._proxy = proto ... and profixy(xpcInstance) = proto :(
344 //
345 let proto = window.document.createEvent('HTMLEvents').__proto__;
346 window.Event.prototype = proto;
347 let event = document.createEvent('HTMLEvents');
348 assert(event !== proto, "Event should not be equal to its prototype");
349 event.initEvent('dataavailable', true, true);
350 assert(event.type === 'dataavailable', "Events are working fine");
351 done();
352 }
353 );
355 });
357 exports["test Nested Attributes"] = createProxyTest("", function (helper) {
359 helper.createWorker(
360 'new ' + function ContentScriptScope() {
361 // XrayWrappers has a bug when you set an attribute on it,
362 // in some cases, it creates an unnecessary wrapper that introduces
363 // a different object that refers to the same original object
364 // Check that our wrappers don't reproduce this bug
365 // SEE BUG 658560: Fix identity problem with CrossOriginWrappers
366 let o = {sandboxObject:true};
367 window.nested = o;
368 o.foo = true;
369 assert(o === window.nested, "Nested attribute to sandbox object should not be proxified");
370 window.nested = document;
371 assert(window.nested === document, "Nested attribute to proxy should not be double proxified");
372 done();
373 }
374 );
376 });
378 exports["test Form nodeName"] = createProxyTest("", function (helper) {
380 helper.createWorker(
381 'new ' + function ContentScriptScope() {
382 let body = document.body;
383 // Check form[nodeName]
384 let form = document.createElement("form");
385 let input = document.createElement("input");
386 input.setAttribute("name", "test");
387 form.appendChild(input);
388 body.appendChild(form);
389 assert(form.test == input, "form[nodeName] is valid");
390 body.removeChild(form);
391 done();
392 }
393 );
395 });
397 exports["test localStorage"] = createProxyTest("", function (helper, assert) {
399 let worker = helper.createWorker(
400 'new ' + function ContentScriptScope() {
401 // Check localStorage:
402 assert(window.localStorage, "has access to localStorage");
403 window.localStorage.name = 1;
404 assert(window.localStorage.name == 1, "localStorage appears to work");
406 self.port.on("step2", function () {
407 window.localStorage.clear();
408 assert(window.localStorage.name == undefined, "localStorage really, really works");
409 done();
410 });
411 self.port.emit("step1");
412 }
413 );
415 worker.port.on("step1", function () {
416 assert.equal(helper.rawWindow.localStorage.name, 1, "localStorage really works");
417 worker.port.emit("step2");
418 });
420 });
422 exports["test Auto Unwrap Custom Attributes"] = createProxyTest("", function (helper) {
424 helper.createWorker(
425 'new ' + function ContentScriptScope() {
426 let body = document.body;
427 // Setting a custom object to a proxy attribute is not wrapped when we get it afterward
428 let object = {custom: true, enumerable: false};
429 body.customAttribute = object;
430 assert(object === body.customAttribute, "custom JS attributes are not wrapped");
431 done();
432 }
433 );
435 });
437 exports["test Object Tag"] = createProxyTest("", function (helper) {
439 helper.createWorker(
440 'new ' + function ContentScriptScope() {
441 // <object>, <embed> and other tags return typeof 'function'
442 let flash = document.createElement("object");
443 assert(typeof flash == "function", "<object> is typeof 'function'");
444 assert(flash.toString().match(/\[object HTMLObjectElement.*\]/), "<object> is HTMLObjectElement");
445 assert("setAttribute" in flash, "<object> has a setAttribute method");
446 done();
447 }
448 );
450 });
452 exports["test Highlight toString Behavior"] = createProxyTest("", function (helper, assert) {
453 // We do not have any workaround this particular use of toString
454 // applied on <object> elements. So disable this test until we found one!
455 //assert.equal(helper.rawWindow.Object.prototype.toString.call(flash), "[object HTMLObjectElement]", "<object> is HTMLObjectElement");
456 function f() {};
457 let funToString = Object.prototype.toString.call(f);
458 assert.ok(/\[object Function.*\]/.test(funToString), "functions are functions 1");
460 // This is how jquery call toString:
461 let strToString = helper.rawWindow.Object.prototype.toString.call("");
462 assert.ok(/\[object String.*\]/.test(strToString), "strings are strings");
464 let o = {__exposedProps__:{}};
465 let objToString = helper.rawWindow.Object.prototype.toString.call(o);
466 assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects");
468 // Make sure to pass a function from the same compartments
469 // or toString will return [object Object] on FF8+
470 let f2 = helper.rawWindow.eval("(function () {})");
471 let funToString2 = helper.rawWindow.Object.prototype.toString.call(f2);
472 assert.ok(/\[object Function.*\]/.test(funToString2), "functions are functions 2");
474 helper.done();
475 });
477 exports["test Document TagName"] = createProxyTest("", function (helper) {
479 helper.createWorker(
480 'new ' + function ContentScriptScope() {
481 let body = document.body;
482 // Check document[tagName]
483 let div = document.createElement("div");
484 div.setAttribute("name", "test");
485 body.appendChild(div);
486 assert(!document.test, "document[divName] is undefined");
487 body.removeChild(div);
489 let form = document.createElement("form");
490 form.setAttribute("name", "test");
491 body.appendChild(form);
492 assert(document.test == form, "document[formName] is valid");
493 body.removeChild(form);
495 let img = document.createElement("img");
496 img.setAttribute("name", "test");
497 body.appendChild(img);
498 assert(document.test == img, "document[imgName] is valid");
499 body.removeChild(img);
500 done();
501 }
502 );
504 });
506 let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';
507 exports["test Window Frames"] = createProxyTest(html, function (helper) {
509 helper.createWorker(
510 'let glob = this; new ' + function ContentScriptScope() {
511 // Check window[frameName] and window.frames[i]
512 let iframe = document.getElementById("iframe");
513 //assert(window.frames.length == 1, "The iframe is reported in window.frames check1");
514 //assert(window.frames[0] == iframe.contentWindow, "The iframe is reported in window.frames check2");
515 assert(window.test == iframe.contentWindow, "window[frameName] is valid");
516 done();
517 }
518 );
520 });
522 exports["test Collections"] = createProxyTest("", function (helper) {
524 helper.createWorker(
525 'new ' + function ContentScriptScope() {
526 // Highlight XPCNativeWrapper bug with HTMLCollection
527 // tds[0] is only defined on first access :o
528 let body = document.body;
529 let div = document.createElement("div");
530 body.appendChild(div);
531 div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
532 let tds = div.getElementsByTagName("td");
533 assert(tds[0] == tds[0], "We can get array element multiple times");
534 body.removeChild(div);
535 done();
536 }
537 );
539 });
541 let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
542 '<input id="input2" type="checkbox" />';
543 exports["test Collections 2"] = createProxyTest(html, function (helper) {
545 helper.createWorker(
546 'new ' + function ContentScriptScope() {
547 // Verify that NodeList/HTMLCollection are working fine
548 let body = document.body;
549 let inputs = body.getElementsByTagName("input");
550 assert(body.childNodes.length == 3, "body.childNodes length is correct");
551 assert(inputs.length == 3, "inputs.length is correct");
552 assert(body.childNodes[0] == inputs[0], "body.childNodes[0] is correct");
553 assert(body.childNodes[1] == inputs[1], "body.childNodes[1] is correct");
554 assert(body.childNodes[2] == inputs[2], "body.childNodes[2] is correct");
555 let count = 0;
556 for(let i in body.childNodes) {
557 count++;
558 }
559 assert(count == 6, "body.childNodes is iterable");
560 done();
561 }
562 );
564 });
566 exports["test valueOf"] = createProxyTest("", function (helper) {
568 helper.createWorker(
569 'new ' + function ContentScriptScope() {
570 // Bug 787013: Until this bug is fixed, we are missing some methods
571 // on JS objects that comes from global `Object` object
572 assert(!('valueOf' in window), "valueOf is missing");
573 assert(!('toLocateString' in window), "toLocaleString is missing");
574 done();
575 }
576 );
578 });
580 exports["test XMLHttpRequest"] = createProxyTest("", function (helper) {
582 helper.createWorker(
583 'new ' + function ContentScriptScope() {
584 // XMLHttpRequest doesn't support XMLHttpRequest.apply,
585 // that may break our proxy code
586 assert(new window.XMLHttpRequest(), "we are able to instantiate XMLHttpRequest object");
587 done();
588 }
589 );
591 });
593 exports["test XPathResult"] = createProxyTest("", function (helper, assert) {
594 const XPathResultTypes = ["ANY_TYPE",
595 "NUMBER_TYPE", "STRING_TYPE", "BOOLEAN_TYPE",
596 "UNORDERED_NODE_ITERATOR_TYPE",
597 "ORDERED_NODE_ITERATOR_TYPE",
598 "UNORDERED_NODE_SNAPSHOT_TYPE",
599 "ORDERED_NODE_SNAPSHOT_TYPE",
600 "ANY_UNORDERED_NODE_TYPE",
601 "FIRST_ORDERED_NODE_TYPE"];
603 // Check XPathResult bug with constants being undefined on XPCNativeWrapper
604 let xpcXPathResult = helper.xrayWindow.XPathResult;
606 XPathResultTypes.forEach(function(type, i) {
607 assert.equal(xpcXPathResult.wrappedJSObject[type],
608 helper.rawWindow.XPathResult[type],
609 "XPathResult's constants are valid on unwrapped node");
611 assert.equal(xpcXPathResult[type], i,
612 "XPathResult's constants are defined on " +
613 "XPCNativeWrapper (platform bug #)");
614 });
616 let value = helper.rawWindow.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE;
617 let worker = helper.createWorker(
618 'new ' + function ContentScriptScope() {
619 self.port.on("value", function (value) {
620 // Check that our work around is working:
621 assert(window.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE === value,
622 "XPathResult works correctly on Proxies");
623 done();
624 });
625 }
626 );
627 worker.port.emit("value", value);
628 });
630 exports["test Prototype Inheritance"] = createProxyTest("", function (helper) {
632 helper.createWorker(
633 'new ' + function ContentScriptScope() {
634 // Verify that inherited prototype function like initEvent
635 // are handled correctly. (e2.type will return an error if it's not the case)
636 let event1 = document.createEvent( 'MouseEvents' );
637 event1.initEvent( "click", true, true );
638 let event2 = document.createEvent( 'MouseEvents' );
639 event2.initEvent( "click", true, true );
640 assert(event2.type == "click", "We are able to create an event");
641 done();
642 }
643 );
645 });
647 exports["test Functions"] = createProxyTest("", function (helper) {
649 helper.rawWindow.callFunction = function callFunction(f) f();
650 helper.rawWindow.isEqual = function isEqual(a, b) a == b;
651 // bug 784116: workaround in order to allow proxy code to cache proxies on
652 // these functions:
653 helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'};
654 helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'};
656 helper.createWorker(
657 'new ' + function ContentScriptScope() {
658 // Check basic usage of functions
659 let closure2 = function () {return "ok";};
660 assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work");
662 // Ensure that functions are cached when being wrapped to native code
663 let closure = function () {};
664 assert(window.wrappedJSObject.isEqual(closure, closure), "Function references are cached before being wrapped to native");
665 done();
666 }
667 );
669 });
671 let html = '<input id="input2" type="checkbox" />';
672 exports["test Listeners"] = createProxyTest(html, function (helper) {
674 helper.createWorker(
675 'new ' + function ContentScriptScope() {
676 // Verify listeners:
677 let input = document.getElementById("input2");
678 assert(input, "proxy.getElementById works");
680 function onclick() {};
681 input.onclick = onclick;
682 assert(input.onclick === onclick, "on* attributes are equal to original function set");
684 let addEventListenerCalled = false;
685 let expandoCalled = false;
686 input.addEventListener("click", function onclick(event) {
687 input.removeEventListener("click", onclick, true);
689 assert(!addEventListenerCalled, "closure given to addEventListener is called once");
690 if (addEventListenerCalled)
691 return;
692 addEventListenerCalled = true;
694 assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
696 let input2 = document.getElementById("input2");
698 input.onclick = function (event) {
699 input.onclick = null;
700 assert(!expandoCalled, "closure set to expando is called once");
701 if (expandoCalled) return;
702 expandoCalled = true;
704 assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
706 setTimeout(function () {
707 input.click();
708 done();
709 }, 0);
711 }
713 setTimeout(function () {
714 input.click();
715 }, 0);
717 }, true);
719 input.click();
720 }
721 );
723 });
725 exports["test MozRequestAnimationFrame"] = createProxyTest("", function (helper) {
727 helper.createWorker(
728 'new ' + function ContentScriptScope() {
729 window.mozRequestAnimationFrame(function callback() {
730 assert(callback == this, "callback is equal to `this`");
731 done();
732 });
733 }
734 );
736 });
738 exports["testGlobalScope"] = createProxyTest("", function (helper) {
740 helper.createWorker(
741 'let toplevelScope = true;' +
742 'assert(window.toplevelScope, "variables in toplevel scope are set to `window` object");' +
743 'assert(this.toplevelScope, "variables in toplevel scope are set to `this` object");' +
744 'done();'
745 );
747 });
749 // Bug 715755: proxy code throw an exception on COW
750 // Create an http server in order to simulate real cross domain documents
751 exports["test Cross Domain Iframe"] = createProxyTest("", function (helper) {
752 let serverPort = 8099;
753 let server = require("sdk/test/httpd").startServerAsync(serverPort);
754 server.registerPathHandler("/", function handle(request, response) {
755 // Returns the webpage that receive a message and forward it back to its
756 // parent document by appending ' world'.
757 let content = "<html><head><meta charset='utf-8'></head>\n";
758 content += "<script>\n";
759 content += " window.addEventListener('message', function (event) {\n";
760 content += " parent.postMessage(event.data + ' world', '*');\n";
761 content += " }, true);\n";
762 content += "</script>\n";
763 content += "<body></body>\n";
764 content += "</html>\n";
765 response.write(content);
766 });
768 let worker = helper.createWorker(
769 'new ' + function ContentScriptScope() {
770 // Waits for the server page url
771 self.on("message", function (url) {
772 // Creates an iframe with this page
773 let iframe = document.createElement("iframe");
774 iframe.addEventListener("load", function onload() {
775 iframe.removeEventListener("load", onload, true);
776 try {
777 // Try to communicate with iframe's content
778 window.addEventListener("message", function onmessage(event) {
779 window.removeEventListener("message", onmessage, true);
781 assert(event.data == "hello world", "COW works properly");
782 self.port.emit("end");
783 }, true);
784 iframe.contentWindow.postMessage("hello", "*");
785 } catch(e) {
786 assert(false, "COW fails : "+e.message);
787 }
788 }, true);
789 iframe.setAttribute("src", url);
790 document.body.appendChild(iframe);
791 });
792 }
793 );
795 worker.port.on("end", function () {
796 server.stop(helper.done);
797 });
799 worker.postMessage("http://localhost:" + serverPort + "/");
801 });
803 // Bug 769006: Ensure that MutationObserver works fine with proxies
804 let html = '<a href="foo">link</a>';
805 exports["test MutationObvserver"] = createProxyTest(html, function (helper) {
807 helper.createWorker(
808 'new ' + function ContentScriptScope() {
809 if (typeof MutationObserver == "undefined") {
810 assert(true, "No MutationObserver for this FF version");
811 done();
812 return;
813 }
814 let link = document.getElementsByTagName("a")[0];
816 // Register a Mutation observer
817 let obs = new MutationObserver(function(mutations){
818 // Ensure that mutation data are valid
819 assert(mutations.length == 1, "only one attribute mutation");
820 let mutation = mutations[0];
821 assert(mutation.type == "attributes", "check `type`");
822 assert(mutation.target == link, "check `target`");
823 assert(mutation.attributeName == "href", "check `attributeName`");
824 assert(mutation.oldValue == "foo", "check `oldValue`");
825 obs.disconnect();
826 done();
827 });
828 obs.observe(document, {
829 subtree: true,
830 attributes: true,
831 attributeOldValue: true,
832 attributeFilter: ["href"]
833 });
835 // Modify the DOM
836 link.setAttribute("href", "bar");
837 }
838 );
840 });
842 let html = '<script>' +
843 'var accessCheck = function() {' +
844 ' assert(true, "exporting function works");' +
845 ' try{' +
846 ' exportedObj.prop;' +
847 ' assert(false, "content should not have access to content-script");' +
848 ' } catch(e) {' +
849 ' assert(e.toString().indexOf("Permission denied") != -1,' +
850 ' "content should not have access to content-script");' +
851 ' }' +
852 '}</script>';
853 exports["test nsEp for content-script"] = createProxyTest(html, function (helper) {
855 helper.createWorker(
856 'let glob = this; new ' + function ContentScriptScope() {
858 exportFunction(assert, unsafeWindow, { defineAs: "assert" });
859 window.wrappedJSObject.assert(true, "assert exported");
860 window.wrappedJSObject.exportedObj = { prop: 42 };
861 window.wrappedJSObject.accessCheck();
862 done();
863 }
864 );
866 });
868 require("test").run(exports);