Modifying Web Pages Based on URL

To follow this tutorial you'll need to have learned the basics of jpm.

To modify any pages that match a particular pattern (for example, "http://example.org/") as they are loaded, use page-mod module.

To create a page-mod, you need to specify two things:

  • One or more content scripts to run whose job is to interact with web content.
  • One or more patterns to match URLs for the pages you want to modify.

A simple code snippet where content script is supplied as contentScript option and URL pattern is given as include option is as follows:

// Import the page-mod API
var pageMod = require("sdk/page-mod");

// Create a page-mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
  include: "*.org",
  contentScript: 'document.body.innerHTML = ' +
                 ' "<h1>Page matches ruleset</h1>";'
});

Do as follows:

  • Create a new directory and navigate to it.
  • Run jpm init, accepting all the defaults
  • Open the file index.js and add the code above
  • Run jpm run
  • Open ietf.org in the browser window that opens.

Below is what you should see.

Specifying the Match Pattern

Match pattern uses match-pattern syntax. You can pass a single match-pattern string, or an array.

Keeping the Content Script in a Separate File

In the example above, we've supplied content script as a string.

Unless the script is extremely simple, maintain the script as a separate file though. This makes the code easier to maintain, debug, and review. To do this, you need to:

  • Save the script in add-on's data directory.
  • Use contentScriptFile option instead of contentScript and pass it script URL which can be obtained using self.data.url("my-script.js"). From Firefox 34 onwards, you can just use "./my-script.js" instead.

For example, if we save the script above under the add-on's data directory in a file called my-script.js:

// Import the page-mod API
var pageMod = require("sdk/page-mod");
// Import the self API
var self = require("sdk/self");

// Create a page-mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
  include: "*.org",
  contentScriptFile: self.data.url("my-script.js")
});

Or from Firefox 34 onwards:

// Import the page-mod API
var pageMod = require("sdk/page-mod");

// Create a page-mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
  include: "*.org",
  contentScriptFile: "./my-script.js"
});

Loading Multiple Content Scripts

You can load more than one script, and the scripts can interact directly with each other.

For example, You could rewrite my-script.js to use jQuery.

$("body").html("<h1>Page matches ruleset</h1>");

Then download jQuery to add-on's data directory, and load the script and jQuery together (making sure to load jQuery first).

// Import the page-mod API
var pageMod = require("sdk/page-mod");
// Import the self API
var self = require("sdk/self");

// Create a page mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
  include: "*.org",
  contentScriptFile: [self.data.url("jquery-1.7.min.js"), self.data.url("my-script.js")]
});

You can use both contentScript and contentScriptFile in the same page-mod. If you do this, scripts loaded using contentScriptFile are loaded first.

// Import the page-mod API
var pageMod = require("sdk/page-mod");
// Import the self API
var self = require("sdk/self");

// Create a page-mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
  include: "*.org",
  contentScriptFile: self.data.url("jquery-1.7.min.js"),
  contentScript: '$("body").html("<h1>Page matches ruleset</h1>");'
});

Note, though, that you can't load a script from a web site. The script must be loaded from data.

Communicating With the Content Script

Your add-on script and content scripts can't directly access each other's variables or call each other's functions, but they can send each other messages.

To send a message from one side to the other, sender calls port.emit() and receiver listens using port.on().

  • In the content script, port is a property of the global self object.
  • In the add-on script, you need to listen for the onAttach event to get passed a worker object that contains port.

Let's rewrite the example above to pass a message from the add-on to the content script. The message will contain the new content to insert into the document.

The content script now needs to look like this:

// "self" is a global object in content scripts
// Listen for a message, and replace the document's
// contents with the message payload.
self.port.on("replacePage", function(message) {
  document.body.innerHTML = "<h1>" + message + "</h1>";
});

In the add-on script, we'll send the content script a message inside onAttach.

// Import the page-mod API
var pageMod = require("sdk/page-mod");
// Import the self API
var self = require("sdk/self");

// Create a page-mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
  include: "*.org",
  contentScriptFile: self.data.url("my-script.js"),
  // Send the content script a message inside onAttach
  onAttach: function(worker) {
    worker.port.emit("replacePage", "Page matches ruleset");
  }
});

The replacePage message isn't a built-in message: it's a message defined by the add-on in the port.emit() call.

Injecting CSS

Note that the feature described in this section is experimental at the moment. We'll most likely continue to support the feature, but API details may change.

Rather than injecting JavaScript into a page, you can inject CSS by setting the page-mod's contentStyle option.

var pageMod = require("sdk/page-mod").PageMod({
  include: "*",
  contentStyle: "body {" +
                "  border: 5px solid green;" +
                "}"
});

As with contentScript, there's a corresponding contentStyleFile option that takes the URL of a CSS file in your "data" directory; It's a good practice to use this option in preference to contentStyle if the CSS is even marginally complex.

var pageMod = require("sdk/page-mod").PageMod({
  include: "*",
  contentStyleFile: require("sdk/self").data.url("my-style.css")
});

Or, from Firefox 34, you can use the simpler version:

var pageMod = require("sdk/page-mod").PageMod({
  include: "*",
  contentStyleFile: "./my-style.css"
});

Learning More

To learn more about page-mod, see its API reference page. In particular, the PageMod constructor takes several additional options to control its behavior:

  • By default, content scripts are not attached to any tabs that are already open when the page-mod is created, and are attached to iframes as well as top-level documents. To control this behavior use the attachTo option.

  • Define read-only values accessible to content scripts using the contentScriptOptions option.

  • By default, content scripts are attached after all the content (DOM, JS, CSS, images) for the page has been loaded, at the time the window.onload event fires. To control this behavior, use contentScriptWhen option.

To learn more about content scripts in general, see content scripts guide.