test-content-script.js 30 KB

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