/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const hiddenFrames = require("sdk/frame/hidden-frame");
const xulApp = require("sdk/system/xul-app");
const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion,
"17.0a2", "*");
const { Loader } = require('sdk/test/loader');
/*
* Utility function that allow to easily run a proxy test with a clean
* new HTML document. See first unit test for usage.
*/
function createProxyTest(html, callback) {
return function (assert, done) {
let url = 'data:text/html;charset=utf-8,' + encodeURI(html);
let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
onReady: function () {
function onDOMReady() {
hiddenFrame.element.removeEventListener("DOMContentLoaded", onDOMReady,
false);
let xrayWindow = hiddenFrame.element.contentWindow;
let rawWindow = xrayWindow.wrappedJSObject;
let isDone = false;
let helper = {
xrayWindow: xrayWindow,
rawWindow: rawWindow,
createWorker: function (contentScript) {
return createWorker(assert, xrayWindow, contentScript, helper.done);
},
done: function () {
if (isDone)
return;
isDone = true;
hiddenFrames.remove(hiddenFrame);
done();
}
}
callback(helper, assert);
}
hiddenFrame.element.addEventListener("DOMContentLoaded", onDOMReady, false);
hiddenFrame.element.setAttribute("src", url);
}
}));
};
}
function createWorker(assert, xrayWindow, contentScript, done) {
// We have to use Sandboxed loader in order to get access to the private
// unlock key `PRIVATE_KEY`. This key should not be used anywhere else.
// See `PRIVATE_KEY` definition in worker.js
let loader = Loader(module);
let Worker = loader.require("sdk/content/worker").Worker;
let key = loader.sandbox("sdk/content/worker").PRIVATE_KEY;
let worker = Worker({
exposeUnlockKey : USE_JS_PROXIES ? key : null,
window: xrayWindow,
contentScript: [
'new ' + function () {
assert = function assert(v, msg) {
self.port.emit("assert", {assertion:v, msg:msg});
}
done = function done() {
self.port.emit("done");
}
},
contentScript
]
});
worker.port.on("done", done);
worker.port.on("assert", function (data) {
assert.ok(data.assertion, data.msg);
});
return worker;
}
/* Examples for the `createProxyTest` uses */
let html = "";
exports["test Create Proxy Test"] = createProxyTest(html, function (helper, assert) {
// You can get access to regular `test` object in second argument of
// `createProxyTest` method:
assert.ok(helper.rawWindow.documentGlobal,
"You have access to a raw window reference via `helper.rawWindow`");
assert.ok(!("documentGlobal" in helper.xrayWindow),
"You have access to an XrayWrapper reference via `helper.xrayWindow`");
// If you do not create a Worker, you have to call helper.done(),
// in order to say when your test is finished
helper.done();
});
exports["test Create Proxy Test With Worker"] = createProxyTest("", function (helper) {
helper.createWorker(
"new " + function WorkerScope() {
assert(true, "You can do assertions in your content script");
// And if you create a worker, you either have to call `done`
// from content script or helper.done()
done();
}
);
});
exports["test Create Proxy Test With Events"] = createProxyTest("", function (helper, assert) {
let worker = helper.createWorker(
"new " + function WorkerScope() {
self.port.emit("foo");
}
);
worker.port.on("foo", function () {
assert.pass("You can use events");
// And terminate your test with helper.done:
helper.done();
});
});
if (USE_JS_PROXIES) {
// Verify that the attribute `exposeUnlockKey`, that allow this test
// to identify proxies, works correctly.
// See `PRIVATE_KEY` definition in worker.js
exports["test Key Access"] = createProxyTest("", function(helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
assert("UNWRAP_ACCESS_KEY" in window, "have access to `UNWRAP_ACCESS_KEY`");
done();
}
);
});
}
// Bug 714778: There was some issue around `toString` functions
// that ended up being shared between content scripts
exports["test Shared To String Proxies"] = createProxyTest("", function(helper) {
let worker = helper.createWorker(
'new ' + function ContentScriptScope() {
// We ensure that `toString` can't be modified so that nothing could
// leak to/from the document and between content scripts
// It only applies to JS proxies, there isn't any such issue with xrays.
//document.location.toString = function foo() {};
document.location.toString.foo = "bar";
if ('UNWRAP_ACCESS_KEY' in window)
assert(!("foo" in document.location.toString), "document.location.toString can't be modified");
else
assert("foo" in document.location.toString, "document.location.toString can be modified");
assert(document.location.toString() == "data:text/html;charset=utf-8,",
"First document.location.toString()");
self.postMessage("next");
}
);
worker.on("message", function () {
helper.createWorker(
'new ' + function ContentScriptScope2() {
assert(!("foo" in document.location.toString),
"document.location.toString is different for each content script");
assert(document.location.toString() == "data:text/html;charset=utf-8,",
"Second document.location.toString()");
done();
}
);
});
});
// Ensure that postMessage is working correctly across documents with an iframe
let html = '';
exports["test postMessage"] = createProxyTest(html, function (helper, assert) {
let ifWindow = helper.xrayWindow.document.getElementById("iframe").contentWindow;
// Listen without proxies, to check that it will work in regular case
// simulate listening from a web document.
ifWindow.addEventListener("message", function listener(event) {
ifWindow.removeEventListener("message", listener, false);
// As we are in system principal, event is an XrayWrapper
if (USE_JS_PROXIES) {
assert.equal(event.source, ifWindow,
"event.source is the iframe window");
}
else {
// JS proxies had different behavior than xrays, xrays use current
// compartments when calling postMessage method. Whereas js proxies
// was using postMessage method compartment, not the caller one.
assert.equal(event.source, helper.xrayWindow,
"event.source is the top window");
}
assert.equal(event.origin, "null", "origin is null");
assert.equal(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}",
"message data is correct");
helper.done();
}, false);
helper.createWorker(
'new ' + function ContentScriptScope() {
assert(postMessage === postMessage,
"verify that we doesn't generate multiple functions for the same method");
var json = JSON.stringify({foo : "bar\n \"escaped\"."});
document.getElementById("iframe").contentWindow.postMessage(json, "*");
}
);
});
let html = '';
exports["test Object Listener"] = createProxyTest(html, function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// Test objects being given as event listener
let input = document.getElementById("input2");
let myClickListener = {
called: false,
handleEvent: function(event) {
assert(this === myClickListener, "`this` is the original object");
assert(!this.called, "called only once");
this.called = true;
if ('UNWRAP_ACCESS_KEY' in window)
assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped");
assert(event.target, input, "event.target is the wrapped window");
done();
}
};
window.addEventListener("click", myClickListener, true);
input.click();
window.removeEventListener("click", myClickListener, true);
}
);
});
exports["test Object Listener 2"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// Verify object as DOM event listener
let myMessageListener = {
called: false,
handleEvent: function(event) {
window.removeEventListener("message", myMessageListener, true);
assert(this == myMessageListener, "`this` is the original object");
assert(!this.called, "called only once");
this.called = true;
if ('UNWRAP_ACCESS_KEY' in window)
assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped");
assert(event.target == document.defaultView, "event.target is the wrapped window");
assert(event.source == document.defaultView, "event.source is the wrapped window");
assert(event.origin == "null", "origin is null");
assert(event.data == "ok", "message data is correct");
done();
}
};
window.addEventListener("message", myMessageListener, true);
document.defaultView.postMessage("ok", '*');
}
);
});
let html = '' +
'';
/* Disable test to keep tree green until Bug 756214 is fixed.
exports.testStringOverload = createProxyTest(html, function (helper, test) {
// Proxy - toString error
let originalString = "string";
let p = Proxy.create({
get: function(receiver, name) {
if (name == "binded")
return originalString.toString.bind(originalString);
return originalString[name];
}
});
assert.okRaises(function () {
p.toString();
},
/String.prototype.toString called on incompatible Proxy/,
"toString can't be called with this being the proxy");
assert.equal(p.binded(), "string", "but it works if we bind this to the original string");
helper.createWorker(
'new ' + function ContentScriptScope() {
// RightJS is hacking around String.prototype, and do similar thing:
// Pass `this` from a String prototype method.
// It is funny because typeof this == "object"!
// So that when we pass `this` to a native method,
// our proxy code can fail on another even more crazy thing.
// See following test to see what fails around proxies.
String.prototype.update = function () {
assert(typeof this == "object", "in update, `this` is an object");
assert(this.toString() == "input", "in update, `this.toString works");
return document.querySelectorAll(this);
};
assert("input".update().length == 3, "String.prototype overload works");
done();
}
);
});
*/
exports["test MozMatchedSelector"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// Check mozMatchesSelector XrayWrappers bug:
// mozMatchesSelector returns bad results when we are not calling it from the node itself
// SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
assert(document.createElement( "div" ).mozMatchesSelector("div"),
"mozMatchesSelector works while being called from the node");
assert(document.documentElement.mozMatchesSelector.call(
document.createElement( "div" ),
"div"
),
"mozMatchesSelector works while being called from a " +
"function reference to " +
"document.documentElement.mozMatchesSelector.call");
done();
}
);
});
exports["test Events Overload"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// If we add a "____proxy" attribute on XrayWrappers in order to store
// the related proxy to create an unique proxy for each wrapper;
// we end up setting this attribute to prototype objects :x
// And so, instances created with such prototype will be considered
// as equal to the prototype ...
// // Internal method that return the proxy for a given XrayWrapper
// function proxify(obj) {
// if (obj._proxy) return obj._proxy;
// return obj._proxy = Proxy.create(...);
// }
//
// // Get a proxy of an XrayWrapper prototype object
// let proto = proxify(xpcProto);
//
// // Use this proxy as a prototype
// function Constr() {}
// Constr.proto = proto;
//
// // Try to create an instance using this prototype
// let xpcInstance = new Constr();
// let wrapper = proxify(xpcInstance)
//
// xpcProto._proxy = proto and as xpcInstance.__proto__ = xpcProto,
// xpcInstance._proxy = proto ... and profixy(xpcInstance) = proto :(
//
let proto = window.document.createEvent('HTMLEvents').__proto__;
window.Event.prototype = proto;
let event = document.createEvent('HTMLEvents');
assert(event !== proto, "Event should not be equal to its prototype");
event.initEvent('dataavailable', true, true);
assert(event.type === 'dataavailable', "Events are working fine");
done();
}
);
});
exports["test Nested Attributes"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
// XrayWrappers has a bug when you set an attribute on it,
// in some cases, it creates an unnecessary wrapper that introduces
// a different object that refers to the same original object
// Check that our wrappers don't reproduce this bug
// SEE BUG 658560: Fix identity problem with CrossOriginWrappers
let o = {sandboxObject:true};
window.nested = o;
o.foo = true;
assert(o === window.nested, "Nested attribute to sandbox object should not be proxified");
window.nested = document;
assert(window.nested === document, "Nested attribute to proxy should not be double proxified");
done();
}
);
});
exports["test Form nodeName"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
let body = document.body;
// Check form[nodeName]
let form = document.createElement("form");
let input = document.createElement("input");
input.setAttribute("name", "test");
form.appendChild(input);
body.appendChild(form);
assert(form.test == input, "form[nodeName] is valid");
body.removeChild(form);
done();
}
);
});
exports["test localStorage"] = createProxyTest("", function (helper, assert) {
let worker = helper.createWorker(
'new ' + function ContentScriptScope() {
// Check localStorage:
assert(window.localStorage, "has access to localStorage");
window.localStorage.name = 1;
assert(window.localStorage.name == 1, "localStorage appears to work");
self.port.on("step2", function () {
window.localStorage.clear();
assert(window.localStorage.name == undefined, "localStorage really, really works");
done();
});
self.port.emit("step1");
}
);
worker.port.on("step1", function () {
assert.equal(helper.rawWindow.localStorage.name, 1, "localStorage really works");
worker.port.emit("step2");
});
});
exports["test Auto Unwrap Custom Attributes"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
let body = document.body;
// Setting a custom object to a proxy attribute is not wrapped when we get it afterward
let object = {custom: true, enumerable: false};
body.customAttribute = object;
if ('UNWRAP_ACCESS_KEY' in window)
assert(body.customAttribute.valueOf() === body.customAttribute.valueOf(UNWRAP_ACCESS_KEY), "custom JS attributes are not wrapped");
assert(object === body.customAttribute, "custom JS attributes are not wrapped");
done();
}
);
});
exports["test Object Tag"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
//