1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/test/test-content-script.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,868 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +const hiddenFrames = require("sdk/frame/hidden-frame"); 1.9 +const { create: makeFrame } = require("sdk/frame/utils"); 1.10 +const { window } = require("sdk/addon/window"); 1.11 +const { Loader } = require('sdk/test/loader'); 1.12 +const { URL } = require("sdk/url"); 1.13 +const testURI = require("./fixtures").url("test.html"); 1.14 +const testHost = URL(testURI).scheme + '://' + URL(testURI).host; 1.15 + 1.16 +/* 1.17 + * Utility function that allow to easily run a proxy test with a clean 1.18 + * new HTML document. See first unit test for usage. 1.19 + */ 1.20 +function createProxyTest(html, callback) { 1.21 + return function (assert, done) { 1.22 + let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(html); 1.23 + let principalLoaded = false; 1.24 + 1.25 + let element = makeFrame(window.document, { 1.26 + nodeName: "iframe", 1.27 + type: "content", 1.28 + allowJavascript: true, 1.29 + allowPlugins: true, 1.30 + allowAuth: true, 1.31 + uri: testURI 1.32 + }); 1.33 + 1.34 + element.addEventListener("DOMContentLoaded", onDOMReady, false); 1.35 + 1.36 + function onDOMReady() { 1.37 + // Reload frame after getting principal from `testURI` 1.38 + if (!principalLoaded) { 1.39 + element.setAttribute("src", url); 1.40 + principalLoaded = true; 1.41 + return; 1.42 + } 1.43 + 1.44 + assert.equal(element.getAttribute("src"), url, "correct URL loaded"); 1.45 + element.removeEventListener("DOMContentLoaded", onDOMReady, 1.46 + false); 1.47 + let xrayWindow = element.contentWindow; 1.48 + let rawWindow = xrayWindow.wrappedJSObject; 1.49 + 1.50 + let isDone = false; 1.51 + let helper = { 1.52 + xrayWindow: xrayWindow, 1.53 + rawWindow: rawWindow, 1.54 + createWorker: function (contentScript) { 1.55 + return createWorker(assert, xrayWindow, contentScript, helper.done); 1.56 + }, 1.57 + done: function () { 1.58 + if (isDone) 1.59 + return; 1.60 + isDone = true; 1.61 + element.parentNode.removeChild(element); 1.62 + done(); 1.63 + } 1.64 + }; 1.65 + callback(helper, assert); 1.66 + } 1.67 + }; 1.68 +} 1.69 + 1.70 +function createWorker(assert, xrayWindow, contentScript, done) { 1.71 + let loader = Loader(module); 1.72 + let Worker = loader.require("sdk/content/worker").Worker; 1.73 + let worker = Worker({ 1.74 + window: xrayWindow, 1.75 + contentScript: [ 1.76 + 'new ' + function () { 1.77 + assert = function assert(v, msg) { 1.78 + self.port.emit("assert", {assertion:v, msg:msg}); 1.79 + } 1.80 + done = function done() { 1.81 + self.port.emit("done"); 1.82 + } 1.83 + }, 1.84 + contentScript 1.85 + ] 1.86 + }); 1.87 + 1.88 + worker.port.on("done", done); 1.89 + worker.port.on("assert", function (data) { 1.90 + assert.ok(data.assertion, data.msg); 1.91 + }); 1.92 + 1.93 + return worker; 1.94 +} 1.95 + 1.96 +/* Examples for the `createProxyTest` uses */ 1.97 + 1.98 +let html = "<script>var documentGlobal = true</script>"; 1.99 + 1.100 +exports["test Create Proxy Test"] = createProxyTest(html, function (helper, assert) { 1.101 + // You can get access to regular `test` object in second argument of 1.102 + // `createProxyTest` method: 1.103 + assert.ok(helper.rawWindow.documentGlobal, 1.104 + "You have access to a raw window reference via `helper.rawWindow`"); 1.105 + assert.ok(!("documentGlobal" in helper.xrayWindow), 1.106 + "You have access to an XrayWrapper reference via `helper.xrayWindow`"); 1.107 + 1.108 + // If you do not create a Worker, you have to call helper.done(), 1.109 + // in order to say when your test is finished 1.110 + helper.done(); 1.111 +}); 1.112 + 1.113 +exports["test Create Proxy Test With Worker"] = createProxyTest("", function (helper) { 1.114 + 1.115 + helper.createWorker( 1.116 + "new " + function WorkerScope() { 1.117 + assert(true, "You can do assertions in your content script"); 1.118 + // And if you create a worker, you either have to call `done` 1.119 + // from content script or helper.done() 1.120 + done(); 1.121 + } 1.122 + ); 1.123 + 1.124 +}); 1.125 + 1.126 +exports["test Create Proxy Test With Events"] = createProxyTest("", function (helper, assert) { 1.127 + 1.128 + let worker = helper.createWorker( 1.129 + "new " + function WorkerScope() { 1.130 + self.port.emit("foo"); 1.131 + } 1.132 + ); 1.133 + 1.134 + worker.port.on("foo", function () { 1.135 + assert.pass("You can use events"); 1.136 + // And terminate your test with helper.done: 1.137 + helper.done(); 1.138 + }); 1.139 + 1.140 +}); 1.141 + 1.142 +// Bug 714778: There was some issue around `toString` functions 1.143 +// that ended up being shared between content scripts 1.144 +exports["test Shared To String Proxies"] = createProxyTest("", function(helper) { 1.145 + 1.146 + let worker = helper.createWorker( 1.147 + 'new ' + function ContentScriptScope() { 1.148 + // We ensure that `toString` can't be modified so that nothing could 1.149 + // leak to/from the document and between content scripts 1.150 + // It only applies to JS proxies, there isn't any such issue with xrays. 1.151 + //document.location.toString = function foo() {}; 1.152 + document.location.toString.foo = "bar"; 1.153 + assert("foo" in document.location.toString, "document.location.toString can be modified"); 1.154 + assert(document.location.toString() == "data:text/html;charset=utf-8,", 1.155 + "First document.location.toString()"); 1.156 + self.postMessage("next"); 1.157 + } 1.158 + ); 1.159 + worker.on("message", function () { 1.160 + helper.createWorker( 1.161 + 'new ' + function ContentScriptScope2() { 1.162 + assert(!("foo" in document.location.toString), 1.163 + "document.location.toString is different for each content script"); 1.164 + assert(document.location.toString() == "data:text/html;charset=utf-8,", 1.165 + "Second document.location.toString()"); 1.166 + done(); 1.167 + } 1.168 + ); 1.169 + }); 1.170 +}); 1.171 + 1.172 + 1.173 +// Ensure that postMessage is working correctly across documents with an iframe 1.174 +let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />'; 1.175 +exports["test postMessage"] = createProxyTest(html, function (helper, assert) { 1.176 + let ifWindow = helper.xrayWindow.document.getElementById("iframe").contentWindow; 1.177 + // Listen without proxies, to check that it will work in regular case 1.178 + // simulate listening from a web document. 1.179 + ifWindow.addEventListener("message", function listener(event) { 1.180 + ifWindow.removeEventListener("message", listener, false); 1.181 + // As we are in system principal, event is an XrayWrapper 1.182 + // xrays use current compartments when calling postMessage method. 1.183 + // Whereas js proxies was using postMessage method compartment, 1.184 + // not the caller one. 1.185 + assert.strictEqual(event.source, helper.xrayWindow, 1.186 + "event.source is the top window"); 1.187 + assert.equal(event.origin, testHost, "origin matches testHost"); 1.188 + 1.189 + assert.equal(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}", 1.190 + "message data is correct"); 1.191 + 1.192 + helper.done(); 1.193 + }, false); 1.194 + 1.195 + helper.createWorker( 1.196 + 'new ' + function ContentScriptScope() { 1.197 + var json = JSON.stringify({foo : "bar\n \"escaped\"."}); 1.198 + 1.199 + document.getElementById("iframe").contentWindow.postMessage(json, "*"); 1.200 + } 1.201 + ); 1.202 +}); 1.203 + 1.204 +let html = '<input id="input2" type="checkbox" />'; 1.205 +exports["test Object Listener"] = createProxyTest(html, function (helper) { 1.206 + 1.207 + helper.createWorker( 1.208 + 'new ' + function ContentScriptScope() { 1.209 + // Test objects being given as event listener 1.210 + let input = document.getElementById("input2"); 1.211 + let myClickListener = { 1.212 + called: false, 1.213 + handleEvent: function(event) { 1.214 + assert(this === myClickListener, "`this` is the original object"); 1.215 + assert(!this.called, "called only once"); 1.216 + this.called = true; 1.217 + assert(event.target, input, "event.target is the wrapped window"); 1.218 + done(); 1.219 + } 1.220 + }; 1.221 + 1.222 + window.addEventListener("click", myClickListener, true); 1.223 + input.click(); 1.224 + window.removeEventListener("click", myClickListener, true); 1.225 + } 1.226 + ); 1.227 + 1.228 +}); 1.229 + 1.230 +exports["test Object Listener 2"] = createProxyTest("", function (helper) { 1.231 + 1.232 + helper.createWorker( 1.233 + ('new ' + function ContentScriptScope() { 1.234 + // variable replaced with `testHost` 1.235 + let testHost = "TOKEN"; 1.236 + // Verify object as DOM event listener 1.237 + let myMessageListener = { 1.238 + called: false, 1.239 + handleEvent: function(event) { 1.240 + window.removeEventListener("message", myMessageListener, true); 1.241 + 1.242 + assert(this == myMessageListener, "`this` is the original object"); 1.243 + assert(!this.called, "called only once"); 1.244 + this.called = true; 1.245 + assert(event.target == document.defaultView, "event.target is the wrapped window"); 1.246 + assert(event.source == document.defaultView, "event.source is the wrapped window"); 1.247 + assert(event.origin == testHost, "origin matches testHost"); 1.248 + assert(event.data == "ok", "message data is correct"); 1.249 + done(); 1.250 + } 1.251 + }; 1.252 + 1.253 + window.addEventListener("message", myMessageListener, true); 1.254 + document.defaultView.postMessage("ok", '*'); 1.255 + } 1.256 + ).replace("TOKEN", testHost)); 1.257 + 1.258 +}); 1.259 + 1.260 +let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' + 1.261 + '<input id="input2" type="checkbox" />'; 1.262 + 1.263 +exports.testStringOverload = createProxyTest(html, function (helper, assert) { 1.264 + // Proxy - toString error 1.265 + let originalString = "string"; 1.266 + let p = Proxy.create({ 1.267 + get: function(receiver, name) { 1.268 + if (name == "binded") 1.269 + return originalString.toString.bind(originalString); 1.270 + return originalString[name]; 1.271 + } 1.272 + }); 1.273 + assert.throws(function () { 1.274 + p.toString(); 1.275 + }, 1.276 + /toString method called on incompatible Proxy/, 1.277 + "toString can't be called with this being the proxy"); 1.278 + assert.equal(p.binded(), "string", "but it works if we bind this to the original string"); 1.279 + 1.280 + helper.createWorker( 1.281 + 'new ' + function ContentScriptScope() { 1.282 + // RightJS is hacking around String.prototype, and do similar thing: 1.283 + // Pass `this` from a String prototype method. 1.284 + // It is funny because typeof this == "object"! 1.285 + // So that when we pass `this` to a native method, 1.286 + // our proxy code can fail on another even more crazy thing. 1.287 + // See following test to see what fails around proxies. 1.288 + String.prototype.update = function () { 1.289 + assert(typeof this == "object", "in update, `this` is an object"); 1.290 + assert(this.toString() == "input", "in update, `this.toString works"); 1.291 + return document.querySelectorAll(this); 1.292 + }; 1.293 + assert("input".update().length == 3, "String.prototype overload works"); 1.294 + done(); 1.295 + } 1.296 + ); 1.297 +}); 1.298 + 1.299 +exports["test MozMatchedSelector"] = createProxyTest("", function (helper) { 1.300 + helper.createWorker( 1.301 + 'new ' + function ContentScriptScope() { 1.302 + // Check mozMatchesSelector XrayWrappers bug: 1.303 + // mozMatchesSelector returns bad results when we are not calling it from the node itself 1.304 + // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers 1.305 + assert(document.createElement( "div" ).mozMatchesSelector("div"), 1.306 + "mozMatchesSelector works while being called from the node"); 1.307 + assert(document.documentElement.mozMatchesSelector.call( 1.308 + document.createElement( "div" ), 1.309 + "div" 1.310 + ), 1.311 + "mozMatchesSelector works while being called from a " + 1.312 + "function reference to " + 1.313 + "document.documentElement.mozMatchesSelector.call"); 1.314 + done(); 1.315 + } 1.316 + ); 1.317 +}); 1.318 + 1.319 +exports["test Events Overload"] = createProxyTest("", function (helper) { 1.320 + 1.321 + helper.createWorker( 1.322 + 'new ' + function ContentScriptScope() { 1.323 + // If we add a "____proxy" attribute on XrayWrappers in order to store 1.324 + // the related proxy to create an unique proxy for each wrapper; 1.325 + // we end up setting this attribute to prototype objects :x 1.326 + // And so, instances created with such prototype will be considered 1.327 + // as equal to the prototype ... 1.328 + // // Internal method that return the proxy for a given XrayWrapper 1.329 + // function proxify(obj) { 1.330 + // if (obj._proxy) return obj._proxy; 1.331 + // return obj._proxy = Proxy.create(...); 1.332 + // } 1.333 + // 1.334 + // // Get a proxy of an XrayWrapper prototype object 1.335 + // let proto = proxify(xpcProto); 1.336 + // 1.337 + // // Use this proxy as a prototype 1.338 + // function Constr() {} 1.339 + // Constr.proto = proto; 1.340 + // 1.341 + // // Try to create an instance using this prototype 1.342 + // let xpcInstance = new Constr(); 1.343 + // let wrapper = proxify(xpcInstance) 1.344 + // 1.345 + // xpcProto._proxy = proto and as xpcInstance.__proto__ = xpcProto, 1.346 + // xpcInstance._proxy = proto ... and profixy(xpcInstance) = proto :( 1.347 + // 1.348 + let proto = window.document.createEvent('HTMLEvents').__proto__; 1.349 + window.Event.prototype = proto; 1.350 + let event = document.createEvent('HTMLEvents'); 1.351 + assert(event !== proto, "Event should not be equal to its prototype"); 1.352 + event.initEvent('dataavailable', true, true); 1.353 + assert(event.type === 'dataavailable', "Events are working fine"); 1.354 + done(); 1.355 + } 1.356 + ); 1.357 + 1.358 +}); 1.359 + 1.360 +exports["test Nested Attributes"] = createProxyTest("", function (helper) { 1.361 + 1.362 + helper.createWorker( 1.363 + 'new ' + function ContentScriptScope() { 1.364 + // XrayWrappers has a bug when you set an attribute on it, 1.365 + // in some cases, it creates an unnecessary wrapper that introduces 1.366 + // a different object that refers to the same original object 1.367 + // Check that our wrappers don't reproduce this bug 1.368 + // SEE BUG 658560: Fix identity problem with CrossOriginWrappers 1.369 + let o = {sandboxObject:true}; 1.370 + window.nested = o; 1.371 + o.foo = true; 1.372 + assert(o === window.nested, "Nested attribute to sandbox object should not be proxified"); 1.373 + window.nested = document; 1.374 + assert(window.nested === document, "Nested attribute to proxy should not be double proxified"); 1.375 + done(); 1.376 + } 1.377 + ); 1.378 + 1.379 +}); 1.380 + 1.381 +exports["test Form nodeName"] = createProxyTest("", function (helper) { 1.382 + 1.383 + helper.createWorker( 1.384 + 'new ' + function ContentScriptScope() { 1.385 + let body = document.body; 1.386 + // Check form[nodeName] 1.387 + let form = document.createElement("form"); 1.388 + let input = document.createElement("input"); 1.389 + input.setAttribute("name", "test"); 1.390 + form.appendChild(input); 1.391 + body.appendChild(form); 1.392 + assert(form.test == input, "form[nodeName] is valid"); 1.393 + body.removeChild(form); 1.394 + done(); 1.395 + } 1.396 + ); 1.397 + 1.398 +}); 1.399 + 1.400 +exports["test localStorage"] = createProxyTest("", function (helper, assert) { 1.401 + 1.402 + let worker = helper.createWorker( 1.403 + 'new ' + function ContentScriptScope() { 1.404 + // Check localStorage: 1.405 + assert(window.localStorage, "has access to localStorage"); 1.406 + window.localStorage.name = 1; 1.407 + assert(window.localStorage.name == 1, "localStorage appears to work"); 1.408 + 1.409 + self.port.on("step2", function () { 1.410 + window.localStorage.clear(); 1.411 + assert(window.localStorage.name == undefined, "localStorage really, really works"); 1.412 + done(); 1.413 + }); 1.414 + self.port.emit("step1"); 1.415 + } 1.416 + ); 1.417 + 1.418 + worker.port.on("step1", function () { 1.419 + assert.equal(helper.rawWindow.localStorage.name, 1, "localStorage really works"); 1.420 + worker.port.emit("step2"); 1.421 + }); 1.422 + 1.423 +}); 1.424 + 1.425 +exports["test Auto Unwrap Custom Attributes"] = createProxyTest("", function (helper) { 1.426 + 1.427 + helper.createWorker( 1.428 + 'new ' + function ContentScriptScope() { 1.429 + let body = document.body; 1.430 + // Setting a custom object to a proxy attribute is not wrapped when we get it afterward 1.431 + let object = {custom: true, enumerable: false}; 1.432 + body.customAttribute = object; 1.433 + assert(object === body.customAttribute, "custom JS attributes are not wrapped"); 1.434 + done(); 1.435 + } 1.436 + ); 1.437 + 1.438 +}); 1.439 + 1.440 +exports["test Object Tag"] = createProxyTest("", function (helper) { 1.441 + 1.442 + helper.createWorker( 1.443 + 'new ' + function ContentScriptScope() { 1.444 + // <object>, <embed> and other tags return typeof 'function' 1.445 + let flash = document.createElement("object"); 1.446 + assert(typeof flash == "function", "<object> is typeof 'function'"); 1.447 + assert(flash.toString().match(/\[object HTMLObjectElement.*\]/), "<object> is HTMLObjectElement"); 1.448 + assert("setAttribute" in flash, "<object> has a setAttribute method"); 1.449 + done(); 1.450 + } 1.451 + ); 1.452 + 1.453 +}); 1.454 + 1.455 +exports["test Highlight toString Behavior"] = createProxyTest("", function (helper, assert) { 1.456 + // We do not have any workaround this particular use of toString 1.457 + // applied on <object> elements. So disable this test until we found one! 1.458 + //assert.equal(helper.rawWindow.Object.prototype.toString.call(flash), "[object HTMLObjectElement]", "<object> is HTMLObjectElement"); 1.459 + function f() {}; 1.460 + let funToString = Object.prototype.toString.call(f); 1.461 + assert.ok(/\[object Function.*\]/.test(funToString), "functions are functions 1"); 1.462 + 1.463 + // This is how jquery call toString: 1.464 + let strToString = helper.rawWindow.Object.prototype.toString.call(""); 1.465 + assert.ok(/\[object String.*\]/.test(strToString), "strings are strings"); 1.466 + 1.467 + let o = {__exposedProps__:{}}; 1.468 + let objToString = helper.rawWindow.Object.prototype.toString.call(o); 1.469 + assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects"); 1.470 + 1.471 + // Make sure to pass a function from the same compartments 1.472 + // or toString will return [object Object] on FF8+ 1.473 + let f2 = helper.rawWindow.eval("(function () {})"); 1.474 + let funToString2 = helper.rawWindow.Object.prototype.toString.call(f2); 1.475 + assert.ok(/\[object Function.*\]/.test(funToString2), "functions are functions 2"); 1.476 + 1.477 + helper.done(); 1.478 +}); 1.479 + 1.480 +exports["test Document TagName"] = createProxyTest("", function (helper) { 1.481 + 1.482 + helper.createWorker( 1.483 + 'new ' + function ContentScriptScope() { 1.484 + let body = document.body; 1.485 + // Check document[tagName] 1.486 + let div = document.createElement("div"); 1.487 + div.setAttribute("name", "test"); 1.488 + body.appendChild(div); 1.489 + assert(!document.test, "document[divName] is undefined"); 1.490 + body.removeChild(div); 1.491 + 1.492 + let form = document.createElement("form"); 1.493 + form.setAttribute("name", "test"); 1.494 + body.appendChild(form); 1.495 + assert(document.test == form, "document[formName] is valid"); 1.496 + body.removeChild(form); 1.497 + 1.498 + let img = document.createElement("img"); 1.499 + img.setAttribute("name", "test"); 1.500 + body.appendChild(img); 1.501 + assert(document.test == img, "document[imgName] is valid"); 1.502 + body.removeChild(img); 1.503 + done(); 1.504 + } 1.505 + ); 1.506 + 1.507 +}); 1.508 + 1.509 +let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />'; 1.510 +exports["test Window Frames"] = createProxyTest(html, function (helper) { 1.511 + 1.512 + helper.createWorker( 1.513 + 'let glob = this; new ' + function ContentScriptScope() { 1.514 + // Check window[frameName] and window.frames[i] 1.515 + let iframe = document.getElementById("iframe"); 1.516 + //assert(window.frames.length == 1, "The iframe is reported in window.frames check1"); 1.517 + //assert(window.frames[0] == iframe.contentWindow, "The iframe is reported in window.frames check2"); 1.518 + assert(window.test == iframe.contentWindow, "window[frameName] is valid"); 1.519 + done(); 1.520 + } 1.521 + ); 1.522 + 1.523 +}); 1.524 + 1.525 +exports["test Collections"] = createProxyTest("", function (helper) { 1.526 + 1.527 + helper.createWorker( 1.528 + 'new ' + function ContentScriptScope() { 1.529 + // Highlight XPCNativeWrapper bug with HTMLCollection 1.530 + // tds[0] is only defined on first access :o 1.531 + let body = document.body; 1.532 + let div = document.createElement("div"); 1.533 + body.appendChild(div); 1.534 + div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; 1.535 + let tds = div.getElementsByTagName("td"); 1.536 + assert(tds[0] == tds[0], "We can get array element multiple times"); 1.537 + body.removeChild(div); 1.538 + done(); 1.539 + } 1.540 + ); 1.541 + 1.542 +}); 1.543 + 1.544 +let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' + 1.545 + '<input id="input2" type="checkbox" />'; 1.546 +exports["test Collections 2"] = createProxyTest(html, function (helper) { 1.547 + 1.548 + helper.createWorker( 1.549 + 'new ' + function ContentScriptScope() { 1.550 + // Verify that NodeList/HTMLCollection are working fine 1.551 + let body = document.body; 1.552 + let inputs = body.getElementsByTagName("input"); 1.553 + assert(body.childNodes.length == 3, "body.childNodes length is correct"); 1.554 + assert(inputs.length == 3, "inputs.length is correct"); 1.555 + assert(body.childNodes[0] == inputs[0], "body.childNodes[0] is correct"); 1.556 + assert(body.childNodes[1] == inputs[1], "body.childNodes[1] is correct"); 1.557 + assert(body.childNodes[2] == inputs[2], "body.childNodes[2] is correct"); 1.558 + let count = 0; 1.559 + for(let i in body.childNodes) { 1.560 + count++; 1.561 + } 1.562 + assert(count == 6, "body.childNodes is iterable"); 1.563 + done(); 1.564 + } 1.565 + ); 1.566 + 1.567 +}); 1.568 + 1.569 +exports["test valueOf"] = createProxyTest("", function (helper) { 1.570 + 1.571 + helper.createWorker( 1.572 + 'new ' + function ContentScriptScope() { 1.573 + // Bug 787013: Until this bug is fixed, we are missing some methods 1.574 + // on JS objects that comes from global `Object` object 1.575 + assert(!('valueOf' in window), "valueOf is missing"); 1.576 + assert(!('toLocateString' in window), "toLocaleString is missing"); 1.577 + done(); 1.578 + } 1.579 + ); 1.580 + 1.581 +}); 1.582 + 1.583 +exports["test XMLHttpRequest"] = createProxyTest("", function (helper) { 1.584 + 1.585 + helper.createWorker( 1.586 + 'new ' + function ContentScriptScope() { 1.587 + // XMLHttpRequest doesn't support XMLHttpRequest.apply, 1.588 + // that may break our proxy code 1.589 + assert(new window.XMLHttpRequest(), "we are able to instantiate XMLHttpRequest object"); 1.590 + done(); 1.591 + } 1.592 + ); 1.593 + 1.594 +}); 1.595 + 1.596 +exports["test XPathResult"] = createProxyTest("", function (helper, assert) { 1.597 + const XPathResultTypes = ["ANY_TYPE", 1.598 + "NUMBER_TYPE", "STRING_TYPE", "BOOLEAN_TYPE", 1.599 + "UNORDERED_NODE_ITERATOR_TYPE", 1.600 + "ORDERED_NODE_ITERATOR_TYPE", 1.601 + "UNORDERED_NODE_SNAPSHOT_TYPE", 1.602 + "ORDERED_NODE_SNAPSHOT_TYPE", 1.603 + "ANY_UNORDERED_NODE_TYPE", 1.604 + "FIRST_ORDERED_NODE_TYPE"]; 1.605 + 1.606 + // Check XPathResult bug with constants being undefined on XPCNativeWrapper 1.607 + let xpcXPathResult = helper.xrayWindow.XPathResult; 1.608 + 1.609 + XPathResultTypes.forEach(function(type, i) { 1.610 + assert.equal(xpcXPathResult.wrappedJSObject[type], 1.611 + helper.rawWindow.XPathResult[type], 1.612 + "XPathResult's constants are valid on unwrapped node"); 1.613 + 1.614 + assert.equal(xpcXPathResult[type], i, 1.615 + "XPathResult's constants are defined on " + 1.616 + "XPCNativeWrapper (platform bug #)"); 1.617 + }); 1.618 + 1.619 + let value = helper.rawWindow.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE; 1.620 + let worker = helper.createWorker( 1.621 + 'new ' + function ContentScriptScope() { 1.622 + self.port.on("value", function (value) { 1.623 + // Check that our work around is working: 1.624 + assert(window.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE === value, 1.625 + "XPathResult works correctly on Proxies"); 1.626 + done(); 1.627 + }); 1.628 + } 1.629 + ); 1.630 + worker.port.emit("value", value); 1.631 +}); 1.632 + 1.633 +exports["test Prototype Inheritance"] = createProxyTest("", function (helper) { 1.634 + 1.635 + helper.createWorker( 1.636 + 'new ' + function ContentScriptScope() { 1.637 + // Verify that inherited prototype function like initEvent 1.638 + // are handled correctly. (e2.type will return an error if it's not the case) 1.639 + let event1 = document.createEvent( 'MouseEvents' ); 1.640 + event1.initEvent( "click", true, true ); 1.641 + let event2 = document.createEvent( 'MouseEvents' ); 1.642 + event2.initEvent( "click", true, true ); 1.643 + assert(event2.type == "click", "We are able to create an event"); 1.644 + done(); 1.645 + } 1.646 + ); 1.647 + 1.648 +}); 1.649 + 1.650 +exports["test Functions"] = createProxyTest("", function (helper) { 1.651 + 1.652 + helper.rawWindow.callFunction = function callFunction(f) f(); 1.653 + helper.rawWindow.isEqual = function isEqual(a, b) a == b; 1.654 + // bug 784116: workaround in order to allow proxy code to cache proxies on 1.655 + // these functions: 1.656 + helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'}; 1.657 + helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'}; 1.658 + 1.659 + helper.createWorker( 1.660 + 'new ' + function ContentScriptScope() { 1.661 + // Check basic usage of functions 1.662 + let closure2 = function () {return "ok";}; 1.663 + assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work"); 1.664 + 1.665 + // Ensure that functions are cached when being wrapped to native code 1.666 + let closure = function () {}; 1.667 + assert(window.wrappedJSObject.isEqual(closure, closure), "Function references are cached before being wrapped to native"); 1.668 + done(); 1.669 + } 1.670 + ); 1.671 + 1.672 +}); 1.673 + 1.674 +let html = '<input id="input2" type="checkbox" />'; 1.675 +exports["test Listeners"] = createProxyTest(html, function (helper) { 1.676 + 1.677 + helper.createWorker( 1.678 + 'new ' + function ContentScriptScope() { 1.679 + // Verify listeners: 1.680 + let input = document.getElementById("input2"); 1.681 + assert(input, "proxy.getElementById works"); 1.682 + 1.683 + function onclick() {}; 1.684 + input.onclick = onclick; 1.685 + assert(input.onclick === onclick, "on* attributes are equal to original function set"); 1.686 + 1.687 + let addEventListenerCalled = false; 1.688 + let expandoCalled = false; 1.689 + input.addEventListener("click", function onclick(event) { 1.690 + input.removeEventListener("click", onclick, true); 1.691 + 1.692 + assert(!addEventListenerCalled, "closure given to addEventListener is called once"); 1.693 + if (addEventListenerCalled) 1.694 + return; 1.695 + addEventListenerCalled = true; 1.696 + 1.697 + assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals"); 1.698 + 1.699 + let input2 = document.getElementById("input2"); 1.700 + 1.701 + input.onclick = function (event) { 1.702 + input.onclick = null; 1.703 + assert(!expandoCalled, "closure set to expando is called once"); 1.704 + if (expandoCalled) return; 1.705 + expandoCalled = true; 1.706 + 1.707 + assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals"); 1.708 + 1.709 + setTimeout(function () { 1.710 + input.click(); 1.711 + done(); 1.712 + }, 0); 1.713 + 1.714 + } 1.715 + 1.716 + setTimeout(function () { 1.717 + input.click(); 1.718 + }, 0); 1.719 + 1.720 + }, true); 1.721 + 1.722 + input.click(); 1.723 + } 1.724 + ); 1.725 + 1.726 +}); 1.727 + 1.728 +exports["test MozRequestAnimationFrame"] = createProxyTest("", function (helper) { 1.729 + 1.730 + helper.createWorker( 1.731 + 'new ' + function ContentScriptScope() { 1.732 + window.mozRequestAnimationFrame(function callback() { 1.733 + assert(callback == this, "callback is equal to `this`"); 1.734 + done(); 1.735 + }); 1.736 + } 1.737 + ); 1.738 + 1.739 +}); 1.740 + 1.741 +exports["testGlobalScope"] = createProxyTest("", function (helper) { 1.742 + 1.743 + helper.createWorker( 1.744 + 'let toplevelScope = true;' + 1.745 + 'assert(window.toplevelScope, "variables in toplevel scope are set to `window` object");' + 1.746 + 'assert(this.toplevelScope, "variables in toplevel scope are set to `this` object");' + 1.747 + 'done();' 1.748 + ); 1.749 + 1.750 +}); 1.751 + 1.752 +// Bug 715755: proxy code throw an exception on COW 1.753 +// Create an http server in order to simulate real cross domain documents 1.754 +exports["test Cross Domain Iframe"] = createProxyTest("", function (helper) { 1.755 + let serverPort = 8099; 1.756 + let server = require("sdk/test/httpd").startServerAsync(serverPort); 1.757 + server.registerPathHandler("/", function handle(request, response) { 1.758 + // Returns the webpage that receive a message and forward it back to its 1.759 + // parent document by appending ' world'. 1.760 + let content = "<html><head><meta charset='utf-8'></head>\n"; 1.761 + content += "<script>\n"; 1.762 + content += " window.addEventListener('message', function (event) {\n"; 1.763 + content += " parent.postMessage(event.data + ' world', '*');\n"; 1.764 + content += " }, true);\n"; 1.765 + content += "</script>\n"; 1.766 + content += "<body></body>\n"; 1.767 + content += "</html>\n"; 1.768 + response.write(content); 1.769 + }); 1.770 + 1.771 + let worker = helper.createWorker( 1.772 + 'new ' + function ContentScriptScope() { 1.773 + // Waits for the server page url 1.774 + self.on("message", function (url) { 1.775 + // Creates an iframe with this page 1.776 + let iframe = document.createElement("iframe"); 1.777 + iframe.addEventListener("load", function onload() { 1.778 + iframe.removeEventListener("load", onload, true); 1.779 + try { 1.780 + // Try to communicate with iframe's content 1.781 + window.addEventListener("message", function onmessage(event) { 1.782 + window.removeEventListener("message", onmessage, true); 1.783 + 1.784 + assert(event.data == "hello world", "COW works properly"); 1.785 + self.port.emit("end"); 1.786 + }, true); 1.787 + iframe.contentWindow.postMessage("hello", "*"); 1.788 + } catch(e) { 1.789 + assert(false, "COW fails : "+e.message); 1.790 + } 1.791 + }, true); 1.792 + iframe.setAttribute("src", url); 1.793 + document.body.appendChild(iframe); 1.794 + }); 1.795 + } 1.796 + ); 1.797 + 1.798 + worker.port.on("end", function () { 1.799 + server.stop(helper.done); 1.800 + }); 1.801 + 1.802 + worker.postMessage("http://localhost:" + serverPort + "/"); 1.803 + 1.804 +}); 1.805 + 1.806 +// Bug 769006: Ensure that MutationObserver works fine with proxies 1.807 +let html = '<a href="foo">link</a>'; 1.808 +exports["test MutationObvserver"] = createProxyTest(html, function (helper) { 1.809 + 1.810 + helper.createWorker( 1.811 + 'new ' + function ContentScriptScope() { 1.812 + if (typeof MutationObserver == "undefined") { 1.813 + assert(true, "No MutationObserver for this FF version"); 1.814 + done(); 1.815 + return; 1.816 + } 1.817 + let link = document.getElementsByTagName("a")[0]; 1.818 + 1.819 + // Register a Mutation observer 1.820 + let obs = new MutationObserver(function(mutations){ 1.821 + // Ensure that mutation data are valid 1.822 + assert(mutations.length == 1, "only one attribute mutation"); 1.823 + let mutation = mutations[0]; 1.824 + assert(mutation.type == "attributes", "check `type`"); 1.825 + assert(mutation.target == link, "check `target`"); 1.826 + assert(mutation.attributeName == "href", "check `attributeName`"); 1.827 + assert(mutation.oldValue == "foo", "check `oldValue`"); 1.828 + obs.disconnect(); 1.829 + done(); 1.830 + }); 1.831 + obs.observe(document, { 1.832 + subtree: true, 1.833 + attributes: true, 1.834 + attributeOldValue: true, 1.835 + attributeFilter: ["href"] 1.836 + }); 1.837 + 1.838 + // Modify the DOM 1.839 + link.setAttribute("href", "bar"); 1.840 + } 1.841 + ); 1.842 + 1.843 +}); 1.844 + 1.845 +let html = '<script>' + 1.846 + 'var accessCheck = function() {' + 1.847 + ' assert(true, "exporting function works");' + 1.848 + ' try{' + 1.849 + ' exportedObj.prop;' + 1.850 + ' assert(false, "content should not have access to content-script");' + 1.851 + ' } catch(e) {' + 1.852 + ' assert(e.toString().indexOf("Permission denied") != -1,' + 1.853 + ' "content should not have access to content-script");' + 1.854 + ' }' + 1.855 + '}</script>'; 1.856 +exports["test nsEp for content-script"] = createProxyTest(html, function (helper) { 1.857 + 1.858 + helper.createWorker( 1.859 + 'let glob = this; new ' + function ContentScriptScope() { 1.860 + 1.861 + exportFunction(assert, unsafeWindow, { defineAs: "assert" }); 1.862 + window.wrappedJSObject.assert(true, "assert exported"); 1.863 + window.wrappedJSObject.exportedObj = { prop: 42 }; 1.864 + window.wrappedJSObject.accessCheck(); 1.865 + done(); 1.866 + } 1.867 + ); 1.868 + 1.869 +}); 1.870 + 1.871 +require("test").run(exports);