Skip to content
This repository has been archived by the owner on Feb 26, 2022. It is now read-only.

Commit

Permalink
Merge pull request #216 from ochameau/bug-666547-make-postMessage-wor…
Browse files Browse the repository at this point in the history
…k-in-cs

Bug 666547: Fix postMessage in content scripts. r=irakli
  • Loading branch information
ochameau committed Aug 8, 2011
2 parents c19dd59 + 373a07b commit d92926e
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 90 deletions.
238 changes: 163 additions & 75 deletions packages/api-utils/lib/content/content-proxy.js
Expand Up @@ -431,6 +431,148 @@ function isEventName(id) {
return false;
}

// XrayWrappers miss some attributes.
// Here is a list of functions that return a value when it detects a miss:
const NODES_INDEXED_BY_NAME = ["IMG", "FORM", "APPLET", "EMBED", "OBJECT"];
const xRayWrappersMissFixes = [

// Fix bug with XPCNativeWrapper on HTMLCollection
// We can only access array item once, then it's undefined :o
function (obj, name) {
let i = parseInt(name);
if (obj.toString().match(/HTMLCollection|NodeList/) &&
i >= 0 && i < obj.length) {
return wrap(XPCNativeWrapper(obj.wrappedJSObject[name]), obj, name);
}
return null;
},

// Trap access to document["form name"]
// that may refer to an existing form node
// http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9285
function (obj, name) {
if ("nodeType" in obj && obj.nodeType == 9) {
let node = obj.wrappedJSObject[name];
// List of supported tag:
// http://mxr.mozilla.org/mozilla-central/source/content/html/content/src/nsGenericHTMLElement.cpp#1267
if (node && NODES_INDEXED_BY_NAME.indexOf(node.tagName) != -1)
return wrap(XPCNativeWrapper(node));
}
return null;
},

// Trap access to window["frame name"] and window.frames[i]
// that refer to an (i)frame internal window object
// http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#6824
function (obj, name) {
if (typeof obj == "object" && "document" in obj) {
// Ensure that we are on a window object
try {
obj.QueryInterface(Ci.nsIDOMWindow);
}
catch(e) {
return null;
}
// Integer case:
let i = parseInt(name);
if (i >= 0 && i in obj) {
return wrap(XPCNativeWrapper(obj[i]));
}
// String name case:
if (name in obj.wrappedJSObject) {
let win = obj.wrappedJSObject[name];
let nodes = obj.document.getElementsByName(name);
for (let i = 0, l = nodes.length; i < l; i++) {
let node = nodes[i];
if ("contentWindow" in node &&
node.contentWindow.wrappedJSObject == win)
return wrap(node.contentWindow);
}
}
}
return null;
},

// Trap access to form["node name"]
// http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9477
function (obj, name) {
if (typeof obj == "object" && obj.tagName == "FORM") {
let match = obj.wrappedJSObject[name];
let nodes = obj.ownerDocument.getElementsByName(name);
for (let i = 0, l = nodes.length; i < l; i++) {
let node = nodes[i];
if (node.wrappedJSObject == match)
return wrap(node);
}
}
},

// Fix XPathResult's constants being undefined on XrayWrappers
// these constants are defined here:
// http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/xpath/nsIDOMXPathResult.idl
// and are only numbers.
// See bug 665279 for platform fix progress
function (obj, name) {
if (typeof obj == "object" && name in Ci.nsIDOMXPathResult) {
let value = Ci.nsIDOMXPathResult[name];
if (typeof value == "number" && value === obj.wrappedJSObject[name])
return value;
}
}

];

// XrayWrappers have some buggy methods.
// Here is the list of them with functions returning some replacement
// for a given object `obj`:
const xRayWrappersMethodsFixes = {
// postMessage method is checking the Javascript global
// and it expects it to be a window object.
// But in our case, the global object is our sandbox global object.
// See nsGlobalWindow::CallerInnerWindow():
// http://mxr.mozilla.org/mozilla-central/source/dom/base/nsGlobalWindow.cpp#5893
// nsCOMPtr<nsPIDOMWindow> win = do_QueryWrappedNative(wrapper);
// win is null
postMessage: function (obj) {
// Ensure that we are on a window object
try {
obj.QueryInterface(Ci.nsIDOMWindow);
}
catch(e) {
return null;
}
// Create a wrapper that is going to call `postMessage` through `eval`
let f = function postMessage(message, targetOrigin) {
message = message.toString().replace(/'/g,"\\'");
targetOrigin = targetOrigin.toString().replace(/'/g,"\\'");
let jscode = "this.postMessage('" + message + "', '" +
targetOrigin + "')";
return this.wrappedJSObject.eval(jscode);
};
return getProxyForFunction(f, NativeFunctionWrapper(f));
},

// Fix mozMatchesSelector uses that is broken on XrayWrappers
// when we use document.documentElement.mozMatchesSelector.call(node, expr)
// It's only working if we call mozMatchesSelector on the node itself.
// SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
mozMatchesSelector: function (obj) {
// Ensure that we are on an object to expose this buggy method
try {
obj.QueryInterface(Ci.nsIDOMNSElement);
}
catch(e) {
return null;
}
// We can't use `wrap` function as `f` is not a native function,
// so wrap it manually:
let f = function mozMatchesSelector(selectors) {
return this.mozMatchesSelector(selectors);
};

return getProxyForFunction(f, NativeFunctionWrapper(f));
}
};

/*
* Generate handler for proxy wrapper
Expand All @@ -440,6 +582,8 @@ function handlerMaker(obj) {
let overload = {};
// Expando attributes dictionary (i.e. onclick, onfocus, on* ...)
let expando = {};
// Cache of methods overloaded to fix XrayWrapper bug
let methodFixes = {};
return {
// Fundamental traps
getPropertyDescriptor: function(name) {
Expand Down Expand Up @@ -494,7 +638,9 @@ function handlerMaker(obj) {
// It allows to overload native methods like addEventListener that
// are not saved, even on the wrapper itself.
// (And avoid some methods like toSource from being returned here! [__proto__ test])
if (name in overload && overload[name] != overload.__proto__[name] && name != "__proto__") {
if (name in overload &&
overload[name] != Object.getPrototypeOf(overload)[name] &&
name != "__proto__") {
return overload[name];
}

Expand All @@ -505,85 +651,27 @@ function handlerMaker(obj) {
return name in expando ? expando[name].original : undefined;
}

let o = obj[name];

// Fix bug with XPCNativeWrapper on HTMLCollection
// We can only access array item once, then it's undefined :o
let i = parseInt(name)
if (!o && obj.toString().match(/HTMLCollection|NodeList/) && i >= 0 && i < obj.length) {
o = XPCNativeWrapper(obj.wrappedJSObject[name]);
}

// Trap access to document["form name"]
// that may refer to an existing form node
// http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9285
if (!o && "nodeType" in obj && obj.nodeType == 9) {
let node = obj.wrappedJSObject[name];
// List of supported tag:
// http://mxr.mozilla.org/mozilla-central/source/content/html/content/src/nsGenericHTMLElement.cpp#1267
if (node && ["IMG", "FORM", "APPLET", "EMBED", "OBJECT"].indexOf(node.tagName) != -1)
return wrap(XPCNativeWrapper(node));
// Overload some XrayWrappers method in order to fix its bugs
if (name in methodFixes &&
methodFixes[name] != Object.getPrototypeOf(methodFixes)[name] &&
name != "__proto__")
return methodFixes[name];
if (Object.keys(xRayWrappersMethodsFixes).indexOf(name) !== -1) {
let fix = xRayWrappersMethodsFixes[name](obj);
if (fix)
return methodFixes[name] = fix;
}

// Trap access to window["frame name"] and window.frames[i]
// that refer to an (i)frame internal window object
// http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#6824
if (!o && typeof obj == "object" && "document" in obj) {
try {
obj.QueryInterface(Ci.nsIDOMWindow);
let win = obj[i];
// Integer case:
if (i >= 0 && win) {
return wrap(XPCNativeWrapper(win));
}
// String name case:
win = obj.wrappedJSObject[name];
let nodes = obj.document.getElementsByName(name);
for (let i = 0, l = nodes.length; i < l; i++) {
let node = nodes[i];
if ("contentWindow" in node && node.contentWindow.wrappedJSObject == win)
return wrap(node.contentWindow);
}
}
catch(e) {}
}
let o = obj[name];

// Trap access to form["node name"]
// http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9477
if (!o && typeof obj == "object" && "tagName" in obj &&
obj.tagName == "FORM") {
let match = obj.wrappedJSObject[name];
let nodes = obj.ownerDocument.getElementsByName(name);
for (let i = 0, l = nodes.length; i < l; i++) {
let node = nodes[i];
if (node.wrappedJSObject == match)
return wrap(node);
// XrayWrapper miss some attributes, try to catch these and return a value
if (!o) {
for each(let atttributeFixer in xRayWrappersMissFixes) {
let fix = atttributeFixer(obj, name);
if (fix)
return fix;
}
}

// Fix mozMatchesSelector uses that is broken on XrayWrappers
// when we use document.documentElement.mozMatchesSelector.call(node, expr)
// It's only working if we call mozMatchesSelector on the node itself.
// SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
if (typeof o == "function" && name == "mozMatchesSelector") {
// We can't use `wrap` function as `f` is not a native function,
// so wrap it manually:
let f = function mozMatchesSelector(selectors) {
return this.mozMatchesSelector(selectors);
};
return getProxyForFunction(f, NativeFunctionWrapper(f));
}

// Fix XPathResult's constants being undefined on XrayWrappers
// these constants are defined here:
// http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/xpath/nsIDOMXPathResult.idl
// and are only numbers.
// See bug 665279 for platform fix progress
if (!o && typeof obj == "object" && name in Ci.nsIDOMXPathResult) {
let value = Ci.nsIDOMXPathResult[name];
if (typeof value == "number" && value === obj.wrappedJSObject[name])
return value;
}

// Generic case
return wrap(o, obj, name);
Expand Down
51 changes: 36 additions & 15 deletions packages/api-utils/tests/test-content-proxy.js
Expand Up @@ -4,7 +4,8 @@ const hiddenFrames = require("hidden-frame");
exports.testProxy = function (test) {
let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
'<input id="input2" type="checkbox" />' +
'<script>var documentGlobal = true</script>';
'<script>var documentGlobal = true</script>' +
'<iframe id="iframe" name="test" src="data:text/html," />';
let url = 'data:text/html,' + encodeURI(html);
test.waitUntilDone();

Expand All @@ -21,7 +22,29 @@ exports.testProxy = function (test) {
let wrapped = proxy.create(win);
let document = wrapped.document;
let body = document.body;



// Ensure that postMessage is working correctly.
// 1/ Check across documents with an iframe
let ifWindow = win.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
test.assertEqual(event.source.wrappedJSObject, ifWindow,
"event.source is the iframe window");
test.assertEqual(event.origin, "null", "origin is null");
test.assertEqual(event.data, "ok", "message data is correct");
}, false);
// Dispatch a message from the content script proxy
document.getElementById("iframe").contentWindow.postMessage("ok", "*");

test.assertEqual(wrapped.postMessage, wrapped.postMessage,
"verify that we doesn't generate multiple functions for the same method");


// Test objects being given as event listener
let input = document.getElementById("input2");
let myClickListener = {
called: false,
Expand All @@ -42,19 +65,21 @@ exports.testProxy = function (test) {
let myMessageListener = {
called: false,
handleEvent: function(event) {
wrapped.removeEventListener("message", myMessageListener, true);

test.assertEqual(this, myMessageListener, "`this` is the original object");
test.assert(!this.called, "called only once");
this.called = true;
test.assertNotEqual(event.valueOf(), event.valueOf(proxy.UNWRAP_ACCESS_KEY), "event is wrapped");
test.assertEqual(event.target, wrapped, "event.target is the wrapped window");
test.assertEqual(event.source, wrapped, "event.source is the wrapped window");
test.assertEqual(event.origin, "null", "origin is null");
test.assertEqual(event.data, "ok", "message data is correct");
}
};

wrapped.addEventListener("message", myMessageListener, true);
win.eval("postMessage(true, '*')");
require("timer").setTimeout(function () {
wrapped.removeEventListener("message", myMessageListener, true);
}, 0);
wrapped.postMessage("ok", '*');


// RightJS is hacking around String.prototype, and do similar thing:
Expand Down Expand Up @@ -224,14 +249,10 @@ exports.testProxy = function (test) {
body.removeChild(img);

// Check window[frameName] and window.frames[i]
let iframe = document.createElement("iframe");
iframe.setAttribute("name", "test");
test.assertEqual(wrapped.frames.length, 0, "No frames reported before adding the iframe");
body.appendChild(iframe);
test.assertEqual(wrapped.frames.length, 1, "Iframe is reported in window.frames check1");
test.assertEqual(wrapped.frames[0], iframe.contentWindow, "Iframe is reported in window.frames check2");
let iframe = document.getElementById("iframe");
test.assertEqual(wrapped.frames.length, 1, "The iframe is reported in window.frames check1");
test.assertEqual(wrapped.frames[0], iframe.contentWindow, "The iframe is reported in window.frames check2");
test.assertEqual(wrapped.test, iframe.contentWindow, "window[frameName] is valid");
body.removeChild(iframe);

// Highlight XPCNativeWrapper bug with HTMLCollection
// tds[0] is only defined on first access :o
Expand All @@ -244,7 +265,7 @@ exports.testProxy = function (test) {

// Verify that NodeList/HTMLCollection are working fine
let inputs = body.getElementsByTagName("input");
test.assertEqual(body.childNodes.length, 4, "body.childNodes length is correct");
test.assertEqual(body.childNodes.length, 5, "body.childNodes length is correct");
test.assertEqual(inputs.length, 3, "inputs.length is correct");
test.assertEqual(body.childNodes[0], inputs[0], "body.childNodes[0] is correct");
test.assertEqual(body.childNodes[1], inputs[1], "body.childNodes[1] is correct");
Expand All @@ -253,7 +274,7 @@ exports.testProxy = function (test) {
for(let i in body.childNodes) {
count++;
}
test.assertEqual(count, 4, "body.childNodes is iterable");
test.assertEqual(count, 5, "body.childNodes is iterable");

// Check internal use of valueOf()
test.assertMatches(wrapped.valueOf().toString(), /\[object Window.*\]/, "proxy.valueOf() returns the wrapped version");
Expand Down

0 comments on commit d92926e

Please sign in to comment.