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

Latest commit

 

History

History
175 lines (134 loc) · 6.48 KB

File metadata and controls

175 lines (134 loc) · 6.48 KB

Content Script Access

This page talks about the access content scripts have to:

  • DOM objects in the pages they are attached to
  • other content scripts
  • other scripts loaded by the page they are attached to

Access to the DOM

Content scripts need to be able to access DOM objects in arbitrary web pages, but this could cause two potential security problems:

  1. JavaScript values from the content script could be accessed by the page, enabling a malicious page to steal data or call privileged methods.
  2. a malicious page could redefine standard functions and properties of DOM objects so they don't do what the add-on expects.

To deal with this, content scripts access DOM objects via a proxy. Any changes they make are made to the proxy, and so are not visible to page content.

The proxy is based on XRayWrapper, (also known as XPCNativeWrapper). These wrappers give the user access to the native values of DOM functions and properties, even if they have been redefined by a script.

For example: the page below redefines window.confirm() to return true without showing a confirmation dialog:

<script type="syntaxhighlighter" class="brush: html"> <script> window.confirm = function(message) { return true; } </script> </script>

But thanks to the content proxy, a content script which calls window.confirm() will get the native implementation:

var widgets = require("widget");
var tabs = require("tabs");
var data = require("self").data;

var widget = widgets.Widget({
  id: "transfer",
  label: "Transfer",
  content: "Transfer",
  width: 100,
  onClick: function() {
    tabs.activeTab.attach({
      // native implementation of window.confirm will be used
      contentScript: "console.log(window.confirm('Transfer all my money?'));"
    });
  }
});

tabs.open(data.url("xray.html"));

You can try out this example using the builder.

The proxy is transparent to content scripts: as far as the content script is concerned, it is accessing the DOM directly. But because it's not, some things that you might expect to work, won't. For example, if the page includes a library like jQuery, or any other page script adds other objects to any DOM nodes, they won't be visible to the content script. So to use jQuery you'll typically have to add it as a content script, as in this example.

unsafeWindow

If you really need direct access to the underlying DOM, you can use the global unsafeWindow object.

To see the difference, try editing the example in the builder so the content script uses unsafeWindow.confirm() instead of window.confirm() (to edit the example, you'll need to create an account with the Add-on Builder and clone the original add-on). Alternatively, try out the example here.

Avoid using unsafeWindow if possible: it is the same concept as Greasemonkey's unsafeWindow, and the warnings for that apply equally here. Also, unsafeWindow isn't a supported API, so it could be removed or changed in a future version of the SDK.

Access to Other Content Scripts

Content scripts loaded into the same document can interact with each other directly as well as with the web content itself. However, content scripts which have been loaded into different documents cannot interact with each other.

For example:

  • if an add-on creates a single panel object and loads several content scripts into the panel, then they can interact with each other

  • if an add-on creates two panel objects and loads a script into each one, they can't interact with each other.

  • if an add-on creates a single page-mod object and loads several content scripts into the page mod, then only content scripts associated with the same page can interact with each other: if two different matching pages are loaded, content scripts attached to page A cannot interact with those attached to page B.

The web content has no access to objects created by the content script, unless the content script explicitly makes them available.

Access to Page Scripts

You can communicate between the content script and page scripts using postMessage(), but there's a twist: in early versions of the SDK, the global postMessage() function in content scripts was used for communicating between the content script and the main add-on code. Although this has been deprecated in favor of self.postMessage, the old globals are still supported, so you can't currently use window.postMessage(). You must use document.defaultView.postMessage() instead.

The following page script uses window.addEventListener to listen for messages:

<script type="syntaxhighlighter" class="brush: html"> <script> window.addEventListener("message", function(event) { window.alert(event.data); }, false); </script> </script>

Content scripts can send it messages using document.defaultView.postMessage():

var widgets = require("widget");
var tabs = require("tabs");
var data = require("self").data;

var widget = widgets.Widget({
  id: "postMessage",
  label: "demonstrate document.defaultView.postMessage",
  contentURL: "http://www.mozilla.org/favicon.ico",
  onClick: function() {
    tabs.activeTab.attach({
      contentScript: "document.defaultView.postMessage('hi there!', '*');"
    });
  }
});

tabs.open(data.url("listener.html"));

You can see this add-on at https://builder.addons.mozilla.org/addon/1013849/revision/8/.