|
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 |
|
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; |
|
12 |
|
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; |
|
21 |
|
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 }); |
|
30 |
|
31 element.addEventListener("DOMContentLoaded", onDOMReady, false); |
|
32 |
|
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 } |
|
40 |
|
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; |
|
46 |
|
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 } |
|
66 |
|
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 }); |
|
84 |
|
85 worker.port.on("done", done); |
|
86 worker.port.on("assert", function (data) { |
|
87 assert.ok(data.assertion, data.msg); |
|
88 }); |
|
89 |
|
90 return worker; |
|
91 } |
|
92 |
|
93 /* Examples for the `createProxyTest` uses */ |
|
94 |
|
95 let html = "<script>var documentGlobal = true</script>"; |
|
96 |
|
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`"); |
|
104 |
|
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 }); |
|
109 |
|
110 exports["test Create Proxy Test With Worker"] = createProxyTest("", function (helper) { |
|
111 |
|
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 ); |
|
120 |
|
121 }); |
|
122 |
|
123 exports["test Create Proxy Test With Events"] = createProxyTest("", function (helper, assert) { |
|
124 |
|
125 let worker = helper.createWorker( |
|
126 "new " + function WorkerScope() { |
|
127 self.port.emit("foo"); |
|
128 } |
|
129 ); |
|
130 |
|
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 }); |
|
136 |
|
137 }); |
|
138 |
|
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) { |
|
142 |
|
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 }); |
|
168 |
|
169 |
|
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"); |
|
185 |
|
186 assert.equal(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}", |
|
187 "message data is correct"); |
|
188 |
|
189 helper.done(); |
|
190 }, false); |
|
191 |
|
192 helper.createWorker( |
|
193 'new ' + function ContentScriptScope() { |
|
194 var json = JSON.stringify({foo : "bar\n \"escaped\"."}); |
|
195 |
|
196 document.getElementById("iframe").contentWindow.postMessage(json, "*"); |
|
197 } |
|
198 ); |
|
199 }); |
|
200 |
|
201 let html = '<input id="input2" type="checkbox" />'; |
|
202 exports["test Object Listener"] = createProxyTest(html, function (helper) { |
|
203 |
|
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 }; |
|
218 |
|
219 window.addEventListener("click", myClickListener, true); |
|
220 input.click(); |
|
221 window.removeEventListener("click", myClickListener, true); |
|
222 } |
|
223 ); |
|
224 |
|
225 }); |
|
226 |
|
227 exports["test Object Listener 2"] = createProxyTest("", function (helper) { |
|
228 |
|
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); |
|
238 |
|
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 }; |
|
249 |
|
250 window.addEventListener("message", myMessageListener, true); |
|
251 document.defaultView.postMessage("ok", '*'); |
|
252 } |
|
253 ).replace("TOKEN", testHost)); |
|
254 |
|
255 }); |
|
256 |
|
257 let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' + |
|
258 '<input id="input2" type="checkbox" />'; |
|
259 |
|
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"); |
|
276 |
|
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 }); |
|
295 |
|
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 }); |
|
315 |
|
316 exports["test Events Overload"] = createProxyTest("", function (helper) { |
|
317 |
|
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 ); |
|
354 |
|
355 }); |
|
356 |
|
357 exports["test Nested Attributes"] = createProxyTest("", function (helper) { |
|
358 |
|
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 ); |
|
375 |
|
376 }); |
|
377 |
|
378 exports["test Form nodeName"] = createProxyTest("", function (helper) { |
|
379 |
|
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 ); |
|
394 |
|
395 }); |
|
396 |
|
397 exports["test localStorage"] = createProxyTest("", function (helper, assert) { |
|
398 |
|
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"); |
|
405 |
|
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 ); |
|
414 |
|
415 worker.port.on("step1", function () { |
|
416 assert.equal(helper.rawWindow.localStorage.name, 1, "localStorage really works"); |
|
417 worker.port.emit("step2"); |
|
418 }); |
|
419 |
|
420 }); |
|
421 |
|
422 exports["test Auto Unwrap Custom Attributes"] = createProxyTest("", function (helper) { |
|
423 |
|
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 ); |
|
434 |
|
435 }); |
|
436 |
|
437 exports["test Object Tag"] = createProxyTest("", function (helper) { |
|
438 |
|
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 ); |
|
449 |
|
450 }); |
|
451 |
|
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"); |
|
459 |
|
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"); |
|
463 |
|
464 let o = {__exposedProps__:{}}; |
|
465 let objToString = helper.rawWindow.Object.prototype.toString.call(o); |
|
466 assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects"); |
|
467 |
|
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"); |
|
473 |
|
474 helper.done(); |
|
475 }); |
|
476 |
|
477 exports["test Document TagName"] = createProxyTest("", function (helper) { |
|
478 |
|
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); |
|
488 |
|
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); |
|
494 |
|
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 ); |
|
503 |
|
504 }); |
|
505 |
|
506 let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />'; |
|
507 exports["test Window Frames"] = createProxyTest(html, function (helper) { |
|
508 |
|
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 ); |
|
519 |
|
520 }); |
|
521 |
|
522 exports["test Collections"] = createProxyTest("", function (helper) { |
|
523 |
|
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 ); |
|
538 |
|
539 }); |
|
540 |
|
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) { |
|
544 |
|
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 ); |
|
563 |
|
564 }); |
|
565 |
|
566 exports["test valueOf"] = createProxyTest("", function (helper) { |
|
567 |
|
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 ); |
|
577 |
|
578 }); |
|
579 |
|
580 exports["test XMLHttpRequest"] = createProxyTest("", function (helper) { |
|
581 |
|
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 ); |
|
590 |
|
591 }); |
|
592 |
|
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"]; |
|
602 |
|
603 // Check XPathResult bug with constants being undefined on XPCNativeWrapper |
|
604 let xpcXPathResult = helper.xrayWindow.XPathResult; |
|
605 |
|
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"); |
|
610 |
|
611 assert.equal(xpcXPathResult[type], i, |
|
612 "XPathResult's constants are defined on " + |
|
613 "XPCNativeWrapper (platform bug #)"); |
|
614 }); |
|
615 |
|
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 }); |
|
629 |
|
630 exports["test Prototype Inheritance"] = createProxyTest("", function (helper) { |
|
631 |
|
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 ); |
|
644 |
|
645 }); |
|
646 |
|
647 exports["test Functions"] = createProxyTest("", function (helper) { |
|
648 |
|
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'}; |
|
655 |
|
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"); |
|
661 |
|
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 ); |
|
668 |
|
669 }); |
|
670 |
|
671 let html = '<input id="input2" type="checkbox" />'; |
|
672 exports["test Listeners"] = createProxyTest(html, function (helper) { |
|
673 |
|
674 helper.createWorker( |
|
675 'new ' + function ContentScriptScope() { |
|
676 // Verify listeners: |
|
677 let input = document.getElementById("input2"); |
|
678 assert(input, "proxy.getElementById works"); |
|
679 |
|
680 function onclick() {}; |
|
681 input.onclick = onclick; |
|
682 assert(input.onclick === onclick, "on* attributes are equal to original function set"); |
|
683 |
|
684 let addEventListenerCalled = false; |
|
685 let expandoCalled = false; |
|
686 input.addEventListener("click", function onclick(event) { |
|
687 input.removeEventListener("click", onclick, true); |
|
688 |
|
689 assert(!addEventListenerCalled, "closure given to addEventListener is called once"); |
|
690 if (addEventListenerCalled) |
|
691 return; |
|
692 addEventListenerCalled = true; |
|
693 |
|
694 assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals"); |
|
695 |
|
696 let input2 = document.getElementById("input2"); |
|
697 |
|
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; |
|
703 |
|
704 assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals"); |
|
705 |
|
706 setTimeout(function () { |
|
707 input.click(); |
|
708 done(); |
|
709 }, 0); |
|
710 |
|
711 } |
|
712 |
|
713 setTimeout(function () { |
|
714 input.click(); |
|
715 }, 0); |
|
716 |
|
717 }, true); |
|
718 |
|
719 input.click(); |
|
720 } |
|
721 ); |
|
722 |
|
723 }); |
|
724 |
|
725 exports["test MozRequestAnimationFrame"] = createProxyTest("", function (helper) { |
|
726 |
|
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 ); |
|
735 |
|
736 }); |
|
737 |
|
738 exports["testGlobalScope"] = createProxyTest("", function (helper) { |
|
739 |
|
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 ); |
|
746 |
|
747 }); |
|
748 |
|
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 }); |
|
767 |
|
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); |
|
780 |
|
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 ); |
|
794 |
|
795 worker.port.on("end", function () { |
|
796 server.stop(helper.done); |
|
797 }); |
|
798 |
|
799 worker.postMessage("http://localhost:" + serverPort + "/"); |
|
800 |
|
801 }); |
|
802 |
|
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) { |
|
806 |
|
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]; |
|
815 |
|
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 }); |
|
834 |
|
835 // Modify the DOM |
|
836 link.setAttribute("href", "bar"); |
|
837 } |
|
838 ); |
|
839 |
|
840 }); |
|
841 |
|
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) { |
|
854 |
|
855 helper.createWorker( |
|
856 'let glob = this; new ' + function ContentScriptScope() { |
|
857 |
|
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 ); |
|
865 |
|
866 }); |
|
867 |
|
868 require("test").run(exports); |