/* 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 { create: makeFrame } = require("sdk/frame/utils");
const { window } = require("sdk/addon/window");
const { Loader } = require('sdk/test/loader');
const { URL } = require("sdk/url");
const testURI = require("./fixtures").url("test.html");
const testHost = URL(testURI).scheme + '://' + URL(testURI).host;
/*
* 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,' + encodeURIComponent(html);
let principalLoaded = false;
let element = makeFrame(window.document, {
nodeName: "iframe",
type: "content",
allowJavascript: true,
allowPlugins: true,
allowAuth: true,
uri: testURI
});
element.addEventListener("DOMContentLoaded", onDOMReady, false);
function onDOMReady() {
// Reload frame after getting principal from `testURI`
if (!principalLoaded) {
element.setAttribute("src", url);
principalLoaded = true;
return;
}
assert.equal(element.getAttribute("src"), url, "correct URL loaded");
element.removeEventListener("DOMContentLoaded", onDOMReady,
false);
let xrayWindow = 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;
element.parentNode.removeChild(element);
done();
}
};
callback(helper, assert);
}
};
}
function createWorker(assert, xrayWindow, contentScript, done) {
let loader = Loader(module);
let Worker = loader.require("sdk/content/worker").Worker;
let worker = Worker({
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();
});
});
// 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";
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
// xrays use current compartments when calling postMessage method.
// Whereas js proxies was using postMessage method compartment,
// not the caller one.
assert.strictEqual(event.source, helper.xrayWindow,
"event.source is the top window");
assert.equal(event.origin, testHost, "origin matches testHost");
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;
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() {
// variable replaced with `testHost`
let testHost = "TOKEN";
// 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.target == document.defaultView, "event.target is the wrapped window");
assert(event.source == document.defaultView, "event.source is the wrapped window");
assert(event.origin == testHost, "origin matches testHost");
assert(event.data == "ok", "message data is correct");
done();
}
};
window.addEventListener("message", myMessageListener, true);
document.defaultView.postMessage("ok", '*');
}
).replace("TOKEN", testHost));
});
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;
assert(object === body.customAttribute, "custom JS attributes are not wrapped");
done();
}
);
});
exports["test Object Tag"] = createProxyTest("", function (helper) {
helper.createWorker(
'new ' + function ContentScriptScope() {
//