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