Content scripts need access to the DOM of the pages they are attached to. However, those pages should be considered to be hostile environments: we have no control over any other scripts loaded by the web page that may be executing in the same context. If the content scripts and scripts loaded by the web page were to access the same DOM objects, there are two possible security problems: First, a malicious page might redefine functions and properties of DOM objects so they don't do what the add-on expects. For example, if a content script calls `document.getElementById()` to retrieve a DOM element, then a malicious page could redefine its behavior to return something unexpected:
// If the web document contains the following script:
document.getElementById = function (str) {
// Overload indexOf method of all string instances
str.__proto__.indexOf = function () {return -1;};
// Overload toString method of all object instances
str.__proto__.__proto__.toString = function () {return "evil";};
};
// After the following line, the content script will be compromised:
var node = document.getElementById("element");
// Then your content script is totally out of control.
Second, changes the content script made to the DOM objects would be visible
to the page, leaking information to it.
The general approach to fixing these problems is to wrap DOM objects in
[`XrayWrappers`](https://developer.mozilla.org/en/XPCNativeWrapper)
(also know as `XPCNativeWrapper`). This guarantees that:
* when the content script accesses DOM properties and functions it gets the
original native version of them, ignoring any modifications made by the web
page
* changes to the DOM made by the content script are not visible to scripts
running in the page.
However, `XrayWrapper` has some limitations and bugs, which break many
popular web frameworks. In particular, you can't:
* define attributes like `onclick`: you have to use `addEventListener` syntax
* overload native methods on DOM objects, like this:
proxy.addEventListener = function () {};
* access named elements using properties like `window[framename]` or
`document[formname]`
* use some other features that have bugs in the `XrayWrapper`
implementation, like `mozMatchesSelector`
The `proxy` module uses `XrayWrapper` in combination with the
experimental
[Proxy API](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Proxy)
to address both the security vulnerabilities of content scripts and the
limitations of `XrayWrapper`.
/--------------------\ /------------------------\ | Web document | | Content script sandbox | | http://mozilla.org | | data/worker.js | | | require('content-proxy'). | | | window >-----------|- create(window) -|-> window | \--------------------/ \------------------------/## The Big Picture ## The implementation defines two different kinds of proxy: 1. Content script proxies that wrap DOM objects that are exposed to content scripts as described above. 2. XrayWrapper proxies that wrap objects from content scripts before handing them over to XrayWrapper functions. These proxies are internal and are not exposed to content scripts or document content.
/--------------------\ /------------------------\ | Web document | | Content script sandbox | | http://mozilla.org | | data/worker.js | | | /-------|-> myObject = {} | | | /----------------v--\ | | | | | XrayWrapper Proxy | | - document | | | \---------v---------/ \----^-------------------/ | | v | | | /-------------\ /----------\ | | - document >-------|->| XrayWrapper |<-| CS proxy |-/ \--------------------/ \-------------/ \----------/Everything begins with a single call to the `create` function exported by the content-proxy module: // Retrieve the unwrapped reference to the current web page window object var win = gBrowser.contentDocument.defaultView.wrappedJSObject; // Or in addon sdk style var win = require("tab-browser").activeTab.linkedBrowser.contentWindow.wrappedJSObject; // Now create a content script proxy for the window object var windowProxy = require("api-utils/content/content-proxy").create(win); // We finally use this window object as sandbox prototype, // so that all web page globals are accessible in CS too: var contentScriptSandbox = new Cu.Sandbox(win, { sandboxPrototype: windowProxy }); Then all other proxies are created from this one. Attempts to access DOM attributes of this proxy are trapped, and the proxy constructs and returns content script proxies for those attributes: // For example, if you simply do this: var document = window.document; // accessing the `document` attribute will be trapped by the `window` content script // proxy, and that proxy will that create another content script proxy for `document` So the main responsibility of the content script proxy implementation is to ensure that we always return content script proxies to the content script. ## Internal Implementation ## Each content script proxy keeps a reference to the `XrayWrapper` that enables it to be sure of calling native DOM methods. There are two internal functions to convert between content script proxy values and `XrayWrapper` values. 1. __`wrap`__ takes an XrayWrapper value and wraps it in a content script proxy if needed. This method is called when: * a content script accesses an attribute of a content script proxy. * XrayWrapper code calls a callback function defined in the content script, so that arguments passed into the function by the XrayWrapper are converted into content script proxies. For example, if a content script calls `addEventListener`, then the listener function will expect any arguments to be content script proxies.