const hiddenFrames = require("hidden-frame");
const xulApp = require("xul-app");
const { Loader } = require('./helpers');
/*
* 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 (test) {
test.waitUntilDone();
let url = 'data:text/html,' + 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 done = false;
let helper = {
xrayWindow: xrayWindow,
rawWindow: rawWindow,
createWorker: function (contentScript) {
return createWorker(test, xrayWindow, contentScript, helper.done);
},
done: function () {
if (done)
return;
done = true;
hiddenFrames.remove(hiddenFrame);
test.done();
}
}
callback(helper, test);
}
hiddenFrame.element.addEventListener("DOMContentLoaded", onDOMReady, false);
hiddenFrame.element.setAttribute("src", url);
}
}));
};
}
function createWorker(test, 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("api-utils/content/worker").Worker;
let key = loader.sandbox("api-utils/content/worker").PRIVATE_KEY;
let worker = Worker({
exposeUnlockKey : key,
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) {
test.assert(data.assertion, data.msg);
});
return worker;
}
/* Examples for the `createProxyTest` uses */
let html = "";
exports.testCreateProxyTest = createProxyTest(html, function (helper, test) {
// You can get access to regular `test` object in second argument of
// `createProxyTest` method:
test.assert(helper.rawWindow.documentGlobal,
"You have access to a raw window reference via `helper.rawWindow`");
test.assert(!("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.testCreateProxyTestWithWorker = 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.testCreateProxyTestWithEvents = createProxyTest("", function (helper, test) {
let worker = helper.createWorker(
"new " + function WorkerScope() {
self.port.emit("foo");
}
);
worker.port.on("foo", function () {
test.pass("You can use events");
// And terminate your test with helper.done:
helper.done();
});
});
// Verify that the attribute `exposeUnlockKey`, that allow this test
// to identify proxies, works correctly.
// See `PRIVATE_KEY` definition in worker.js
exports.testKeyAccess = 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.testSharedToStringProxies = createProxyTest("", function(helper) {
let worker = helper.createWorker(
'new ' + function ContentScriptScope() {
assert(document.location.toString() == "data:text/html,",
"document.location.toString()");
self.postMessage("next");
}
);
worker.on("message", function () {
helper.createWorker(
'new ' + function ContentScriptScope2() {
assert(document.location.toString() == "data:text/html,",
"document.location.toString()");
done();
}
);
});
});
// Ensure that postMessage is working correctly across documents with an iframe
let html = '';
exports.testPostMessage = createProxyTest(html, function (helper, test) {
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) {
//if (event.source.wrappedJSObject == helper.rawWindow) return;
ifWindow.removeEventListener("message", listener, false);
// As we are in system principal, event is an XrayWrapper
test.assertEqual(event.source, ifWindow,
"event.source is the iframe window");
test.assertEqual(event.origin, "null", "origin is null");
test.assertEqual(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.testObjectListener = 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;
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.testObjectListener2 = 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;
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 = '' +
'';
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];
}
});
test.assertRaises(function () {
p.toString();
},
/String.prototype.toString called on incompatible Proxy/,
"toString can't be called with this being the proxy");
test.assertEqual(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.testMozMatchedSelector = 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.testEventsOverload = 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.testNestedAttributes = 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.testFormNodeName = 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.testLocalStorage = createProxyTest("", function (helper, test) {
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 () {
test.assertEqual(helper.rawWindow.localStorage.name, 1, "localStorage really works");
worker.port.emit("step2");
});
});
exports.testAutoUnwrapCustomAttributes = 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;
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.testObjectTag = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
//