Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Chrome Development Part 1: Extensions

5.00/5 (4 votes)
17 Jun 2016CPOL29 min read 23.6K   240  
This article explains how to code extensions that customize, access, and augment the capabilities of the Chrome browser.

1. Introduction

According to W3Counter, Chrome is the most popular browser in the world. Many use it to visit web sites, but few know how to extend its features with extensions. Extensions provide a wealth of exciting capabilities, such as customizing Chrome's appearance, interacting with the current page, or sending messages to applications outside the browser.

This article explores Google's API for developing Chrome extensions. JavaScript is central to this discussion, so before proceeding, you should have a solid familiarity with the language. The second article in the series explains how to code Chrome apps.

2. Overview of Chrome Extensions

A Chrome extension is a zipped folder whose files configure Chrome's behavior or appearance. There are three important points to understand:

  1. Every extension must have a file named manifest.json. This describes the extension's capabilities and identifies how it interacts with the browser.
  2. An extension's behavior is configured with JavaScript files, which come in two types. A background script can access all of Chrome's capabilities, but can't interact with the browser's document. A content script can read or modify the browser's document, but can only access a limited set of Chrome's capabilities.
  3. Before Chrome can access an extension, the folder must be zipped into a special archive called a *.crx file. Then it must be loaded into the browser.

This section starts by discussing the format of manifest.json. Then it introduces a simple extension and explains how to load it into Chrome.

2.1  The Extension Manifest

The format of manifest.json is based on JavaScript Object Notation (JSON). It defines of a list of properties inside a pair of curly braces. Many different properties can be added to manifest.json, and you can see the full list here. Only three are required:

  • manifest_version — The version of the manifest format, should be set to 2
  • name — A string that identifies the extension's name
  • version — A string that identifies the extension's version

To further describe the extension, two properties are recommended:

  • description — A string that describes the extension
  • icons — One or more images that represent the extension

The following JSON shows what a basic manifest looks like:

{
  "manifest_version": 2,
  "name": "Trivial extension example",
  "version": "1.0",
  "description": "This extension doesn't do anything",
  "icons": { "16": "images/smiley_sm.png",
             "48": "images/smiley_med.png",
            "128": "images/smiley_lg.png" }
}

The icons property requires explanation. According to Google's recommendations, every extension should provide at least three images:

  • 128x128 image — displayed during installation and by the Google Web Store.
  • 48x48 image — displayed in the Chrome Developer Dashboard
  • 16x16 image — displayed as a favicon for the extension's pages

In the example manifest, icons has three properties. Each associates an image dimension (128, 48, or 16) with an image file. The example images are all PNGs, but Chrome also supports the BMP, GIF, ICO, and JPEG formats.

Nontrivial extensions must define additional properties. Table 1 lists ten properties and their purposes.

Table 1: Optional Properties of a Chrome Extension Manifest (Abridged)
Field Purpose
content_scripts Defines a content script
background Defines a background script
permissions Identifies permissions available to the extension
browser_action Defines a browser action
page_action Defines a page action
chrome_settings_overrides Overrides settings of the Chrome browser
chrome_ui_overrides Overrides aspects of the Chrome user interface
content_security_policy Defines rules related to the extension's content
externally_connectable Extensions and apps that can connect to the extension
storage Assign callback function for writing received data

Some of these identify resources that define the extension's behavior, such as content_scripts and background. Others provide settings that configure the extension's properties, such as permissions and chrome_settings_overrides. This article discusses a subset of these properties, including content_scripts, background, permissions, browser_action, and page_action.

2.2  Loading Extensions

Chrome makes it easy to load new extensions into the browser. Extensions are managed through the Chrome Developer Dashboard, which can be reached by visiting chrome://extensions in a Chrome browser. The upper-right of the dashboard has a checkbox called Developer Mode and Figure 1 shows what the dashboard looks like when it's checked.

Figure 1: The Chrome Developer Dashboard in Developer Mode

Figure 1: The Chrome Developer Dashboard

On the left, the Load unpacked extension... button makes it possible to select an unzipped folder and load the extension into Chrome. The best way to understand the loading process is to walk through a basic example.

The example_code.zip archive attached to this article contains a folder called basic_demo, which consists of a simple manifest.json file and image files. This extension can be loaded into Chrome by following four steps:

  1. Download example_code.zip and decompress the archive.
  2. In Chrome, open the Chrome Developer Dashboard by navigating to chrome://extensions.
  3. Press the Load unpacked extension... button and select the basic_demo folder in the decompressed archive.
  4. Press OK to load the extension.

When the extension is loaded, an entry will be added to the dashboard's list of extensions. Figure 2 shows what the new entry looks like.

Figure 2: New Entry in the Chrome Developer Dashboard

Image 2

The Reload link is particularly helpful. After an extension's files have changed, clicking this link automatically reloads the extension with the updated files.

2.3  Packaging Extensions

Many developers make their extensions available to others through the Chrome Web Store. This requires packaging the extension as a signed archive called a *.crx file. This can be accomplished by clicking the Pack Extension... button in the dashboard (see Figure 1). When Chrome packages an extension, it creates two files: a *.crx file containing the extension and a *.pem file containing the private key. This key is required when you want to update the packaged extension or upload it to the Chrome Web Store.

If an extension has been packaged, it can't be updated by clicking the Reload link. Instead, you need to increment the extension's version and repackage the extension. A full discussion of extension packaging is available here.

3.  Extension Scripts

As mentioned earlier, a Chrome extension may contain JavaScript files that define its behavior. These come in two types: content scripts and background scripts. This section discusses both types of scripts.

If manifest.json associates one or more files with the content_scripts property, the files are content scripts. These can access the browser's document but can only use a subset of the API's capabilities. Content scripts execute every time the browser's page changes.

If manifest.json associates one or more files with the background property, the files are background scripts. These scripts can access all capabilities provided by the Chrome Extension API, but they can't access the document open in the browser. In general, the code in a background script only executes in response to selected events.

3.1  Content Scripts

Inside manifest.json, content_scripts can be& associated with an array of objects. Each object contains a series of properties including the following:

  • matches — an array of URL patterns or <all_urls>
  • js — an array of JavaScript files to be injected when the browser's URL matches one of the patterns
  • css — an array of CSS files to be injected when the browser's URL matches one of the patterns
  • all_frames — identifies if the scripts should be injected in the top frame or all frames in the page

For example, the following manifest.json states that scripts/jquery-2.2.4.min.js and scripts/contentscript.js should be injected whenever the browser opens a page starting with http://www.google.com:

{
  ...
  "content_scripts": [
    {
      "matches": ["http://www.google.com/*"],
      "js": ["scripts/jquery-2.2.4.min.js", "scripts/contentscript.js"]
    }
  ]
}

The JavaScript files are injected in the listed order, which means contentscript.js can access features from jQuery. A full description of jQuery is beyond the scope of this article, but this site provides a proper introduction.

To see how content scripts work, suppose that the following code is inserted into contentscript.js:

$(document).ready(function() {
  alert($(document).find("title").text());
});

If the extension is loaded into Chrome, the browser will display an alert box displaying the title of the current page whenever the page's URL matches a pattern in the manifest's matches property.

Content scripts can't read or modify the JavaScript variables/functions in the current page and they can't access many of Chrome's features. But they can access a limited set of functions in four APIs: chrome.i18n, chrome.extension, chrome.runtime, and chrome.storage. The following discussion explores two capabilities available to content scripts: obtaining extension information and accessing local storage.

3.1.1  Obtaining Extension Information

Scripts in a Chrome extension can access the browser and its capabilities through properties of a top-level chrome object. These include chrome.bookmarks, chrome.tabs, and chrome.cookies. chrome's fields are generally referred to as APIs and the full list of APIs can be found here.

One of the most important APIs is chrome.runtime, which makes it possible to respond to events and obtain extension information. A content script can't access all the features of chrome.runtime, but it can obtain information about its extension. Table 2 lists the properties of chrome.runtime that make this possible:

Table 2: Information Properties of chrome.runtime for Content Scripts
Property  Information
id The extension's unique identifier
getManifest() Returns a string containing the content of manifest.json
getURL(String path)  Converts the path to a full URL inside the extension

As shown in Figure 1, Chrome assigns a unique string identifier to each of its extensions. The id field of chrome.runtime provides this identifier as a string.

In general, an extension's URL is chrome-extension://id, where id is the extension's unique identifier. If a content script needs a full URL for a file or folder in its extension, getURL can be called with the relative path for the file or folder. For example, getURL("images") returns chrome-extension://id/images.

3.1.2  Local Storage Access

The chrome.storage API enables an extension to load and store data on the client's computer. When using this API, there are four points to be aware of:

  1. To use the API, the extension's manifest must have its permissions field set to an array containing the string "storage".
  2. Data is stored in an unencrypted database file, so this API isn't suitable for confidential information.
  3. A script can load and store data by calling functions of a StorageArea object.
  4. Three types of StorageAreas are available: chrome.sync, chrome.local, and chrome.managed

As stated in the third point, the central object to know is StorageArea. Table 3 lists the five functions provided by this object.

Table 3: StorageArea Functions
Function  Description
set(Object items, Function callback) Stores the given items
get(String|String[]|Object keys,
  Function callback) 
Returns the data identified by the key/keys
getBytesInUse(String|String[] keys, 
  Function callback)
Returns the number of bytes needed to
store the data identified by the key/keys
remove(String|String[] keys, function) Removes the items identified by the key/keys
clear(Function callback) Removes all items in storage

As with a regular database, the data in a StorageArea is loaded and stored using key-value pairs. The following code stores a key-value pair whose key is browser and whose value is chrome:

chrome.storage.local.set({"browser": "chrome"}, function() {
  console.log("Data stored");
});

In the call to set, the first argument defines the data to be stored. The second argument defines a function to be called when the operation is complete. All of the StorageArea functions accept callback functions as arguments, but they're only required for get and getBytesInUse. The following code demonstrates how get can be called to access the value associated with the key, browser.

chrome.storage.local.get("browser", function(data) {
  console.log("Result: " + data.browser);
});

When the read operation is complete, the callback function sets the data variable equal to the received object and accesses the value as pair.browser.

The preceding examples performed load/store operations by accessing storage.local. This is one of three StorageAreas that can be accessed:

  • storage.local — Provides up to 5,242,880 bytes of storage. Can only be accessed on the local device.
  • storage.sync — Provides up to 102,400 bytes of storage (a maximum of 512 items of 8,192 bytes each). If Chrome Sync is enabled, the user can access this data on any device running Chrome.
  • storage.managed — Items are set by the domain administrator. This area is read-only for extensions.

Although the memory size is limited, storage.sync is convenient for users running Chrome on multiple devices. Chrome Sync can be enabled by going to Settings, logging in with a Google account, and selecting the type of data that should be synchronized. If Chrome Sync isn't enabled, storage.sync works like storage.local.

3.2  Background Scripts

As its name implies, a background script runs in the background and responds to events. By default, these scripts are loaded when the extension starts and they occupy resources as long as the extension runs. But a background script can be configured to be loaded only when it's needed. This is accomplished by setting the persistent property of the background property to false. The following declaration in manifest.json shows how this can be configured:

"background": {
  "scripts": ["bground.js"],
  "persistent": false
}

Unlike content scripts, a background script can access all the features of the Chrome API. This section explores three important capabilities: event handling, tab access, and message passing.

3.2.1  Event Handling

Just as JavaScript makes it possible to respond to user events, the chrome.runtime API makes it possible to respond to events related to the extension and the Chrome browser. These include the extension's installation, execution, and unloading.

A background script can respond to these events through properties of chrome.runtime. Table 4 lists five of them and the occurrences that trigger their events.

Table 4: Events of chrome.runtime (Abridged)
Event Trigger
onStartup Fires each time the extension begins executing
onInstalled Fires when the extension or Chrome is installed or updated
onSuspend Fires just before the extension is unloaded
onSuspendCancelled  Fires when the extension's unloading is cancelled
onUpdateAvailable Fires when an update to the extension is available

Each of these has an addListener function that accepts a callback function. This function will be invoked when the corresponding event occurs. Most callbacks don't accept any parameters, but the callback associated with onInstalled receives an object with three properties:

  1. reason — The cause of the installation event. This may be set to "install", "update", "chrome_update", or "shared_module_update".
  2. previousVersion — a string that identifies the version before the installation/update
  3. id — the identifier of the shared module if a shared module was updated

As an example, the following code in a background script logs the reason for the installation event:

chrome.runtime.onInstalled.addListener(function(obj) {
  console.log("Installation event: " + obj.reason);
});

If a background script executes this code, the message won't be printed in the console of the current web page. An extension's background scripts run in a special page that can be accessed by going to the Chrome Developer Dashboard. Clicking the background link in the dashboard opens the background page.

It's important to see the difference between onUpdateAvailable and onInstalled. If an update for the extension is available but it can't be installed because the extension is running, an onUpdateAvailable event will be triggered. If an update is available and can be installed, an onInstalled event will be triggered after the extension is updated.

3.2.2  Tabs

The chrome.tabs API provides a variety of functions for creating, modifying, and removing tabs in a browser. Table 5 lists five helpful functions and provides a short description of each.

Table 5: Functions of chrome.tabs (Abridged)
Function Description
query(Object queryInfo,
  Function callback)
Provides one or more tabs that meet the given criteria
update(Integer id,
  Object updateProps,
  Function callback)
Modifies the properties of the tab with the given ID
create(Object createProps, 
  Function callback)
Creates a new tab in the browser
remove(Integer|Integer[] id, 
  Function callback)
Removes one or more tabs from the window
executeScript(Integer id,
  Object details,
  Function callback)
Injects JavaScript code or CSS in the given tab

The query function provides one or more Tab objects corresponding to the tabs open in a browser window. Its first argument defines a set of criteria for selecting the tabs and the second identifies a callback function to receive matching Tab objects. The queryInfo object provides many properties for selecting tabs and all are optional:

  • index — the position of the tab in the window
  • title — the tab's title
  • url — the tab's URL pattern
  • active/highlighted/pinned/audible/muted — boolean properties representing the tab's state
  • currentWindow/lastFocusedWindow — whether the tab is in the current or previous window

For example, the callback function in the following code receives an array containing one object: the Tab corresponding to the active tab in the current window:

chrome.tabs.query({currentWindow: true, active: true}, function (tabs) { ... });

A Tab object contains many properties that provide information about the underlying browser tab. Table 6 lists twelve of them and the full list can be found here.

Table 6: Properties of the Tab Object (Abridged)
Property Data Type  Description
id Integer Unique identifier for the tab
index Integer The position of the tab in the window (starts at 0)
title String The tab's title
url String The URL displayed in the tab
windowId Integer Unique identifier for the tab's window
active/pinned/
highlighted/audible  
Boolean Characteristics of the browser tab
width/height Integer The tab's dimensions
openerTabId Integer The ID of the tab that opened this tab

These fields are read-only. To change a tab's properties, a script needs to call the update function from Table 5. The second argument is an object whose properties contain the names and desired values for the tab's properties. The properties that can be changed are url, active, highlighted, pinned, muted, and openerTabId. These have the same types as the properties listed in Table 6.

For example, the following code finds the active tab in the current window and opens the Google homepage:

chrome.tabs.query({currentWindow: true, active: true}, function (tabs) {
  chrome.tabs.update(tabs[0].id, {url: "http://www.google.com"});
});

To modify a tab's URL, title, or favicon, an extension must request permission to modify the browser's tabs. This is accomplished by adding a "tabs" string to the manifest's permissions field. The example code discussed at the end of this section demonstrates how this works.

The create function in Table 5 adds a new tab to the current browser window. The properties of the second argument initialize the tab's characteristics: url, active, index, pinned, windowId, and openerTabId. The index value determines the tab's position, and will be clamped between zero and the number of tabs in the window.

The remove function closes one or more tabs according to their IDs. For example, the following code finds and closes every tab open to a page in the Chrome developer documentation:

chrome.tabs.query({url: "https://developer.chrome.com/*"}, function(tabs) {
  for(tab = 0; tab < tabs.length; tab++) {
    chrome.tabs.remove(tabs[tab].id);
  }
});

The last function in Table 5, executeScript, injects JavaScript code or CSS styling into a tab. The second argument identifies the code/CSS and the manner in which it should be injected. This object has six properties and all of them are optional:

  • code — The JavaScript code or CSS rules to be injected into the tab
  • file — The file containing the JavaScript code or CSS rules to be injected
  • allFrames — If true, the JavaScript/CSS will be injected into every frame of the tab instead of just the top frame
  • frameId — The ID of the frame where the JavaScript/CSS should be injected (default: 0, the top frame)
  • matchAboutBlank — If true, the JavaScript/CSS will be injected into about:blank and about:srcdoc frames
  • runAt — The soonest the JavaScript/CSS should be injected (document_start, document_idle, or document_end)

This is a powerful function because it allows a background script to dynamically execute a content script in a specific tab. For example, the following code executes the JavaScript in tab.js in the tab whose index is 1:

chrome.tabs.query({index: 1}, function (tabs) {
  chrome.tabs.executeScript(tabs[0].id, {file: "tab.js"});
});

To successfully inject code or CSS, the manifest must request permission to access the tab's URL. This can be accomplished by adding a URL pattern to the manifest's permissions field. For example, the following manifest code requests permission to access Chrome's tabs and Google's pages:

"permissions": ["tabs", "https://www.google.com/*"]

3.2.3  Passing Messages Between Scripts

As discussed earlier, a background script can access the entire Chrome API but not the content of a web page. A content script can access the content of a web page but only a limited part of the Chrome API. For an extension to take full advantage of both types of scripts, it needs a way to transfer data between them. This is called message passing. Message passing can be used to communicate between different extensions and between extensions and apps. But this section focuses on passing messages between scripts in the same extension.

The chrome.runtime API provides two mechanisms for passing messages between scripts in an extension. The first involves transferring messages one at a time. The second creates a long-term connection for sending and receiving messages.

Table 7 lists the functions of chrome.runtime that make message passing possible. They can be invoked in background scripts and content scripts, and they're frequently employed to transfer data between both types of scripts.

Table 7: Functions for Inter-Script Communication
Function  Information
sendMessage(String id,
  any message, 
  Object options,
  Function callback)
Sends a message to the extension with the given ID
onMessage.addListener  
  (Function callback) 
Assigns a function to receive an incoming message
connect(String id,
  Object connectInfo) 
Creates a persistent connection to the extension with the given ID
onConnect.addListener
  (Function callback)
Assigns a function to receive data through the connection

The sendMessage function sends a message to an extension's background scripts. It has four arguments:

  • id — the ID of the extension to receive the message. This can be omitted if the sender and recipient belong to the same extension.
  • message — the message to be sent. This can take any type, and is usually a regular JSON object.
  • options — optional configuration object. This only needed when sending data outside of the extension
  • callback — optional function that receives the recipient's response (if available)

For example, the following code sends a simple object to a script within its extension. The callback function logs the data field of the recipient's response:

chrome.runtime.sendMessage({data: "Hi there!"}, function(response) {
  console.log(response.msg);
});

Another script can listen for incoming messages with onMessage.addListener. This function requires a callback function that receives three arguments:

  • message — the incoming message
  • sender — a MessageSender object that identifies the sender. This has five optional fields: id, frameId, tab, url, and tlsChannelId
  • sendResponse — a function that can be called to provide the sender with a response

The following code demonstrates how onMessage.addListener can be used. Whenever a message is received, the script prints the message's data field and the ID of the sender's tab. The response consists of a simple object.

chrome.runtime.onMessage.addListener(
  function(message, sender, sendResponse) {
    console.log("Message: " + message.data);
    console.log("Sender's tab ID: " + sender.tab.id);
    sendResponse({msg: "Hi yourself!"});
  }
);

The preceding code can send a message to a background script, but there's a problem if the recipient is a content script: the tab executing the content script needs to be identified. In this case, the sender needs to call chrome.tabs.sendMessage instead of chrome.runtime.sendMessage. The two functions are almost identical, but the first argument of chrome.tabs.sendMessage is the ID of the tab to receive the message.

If the two parties need to send multiple messages to one another, it's efficient to set up a long-lived connection. To connect to a background script, the chrome.runtime.connect function accepts two arguments:

  • id — the ID of the extension to receive the message. This can be omitted if the sender and recipient belong to the same extension.
  • connectInfo — an optional configuration object. This may contain two properties: a name for the connection (name) and a boolean that identifies whether the TLS channel should be sent (includeTlsChannelId). The second property is only needed when communicating outside of the extension.

To connect to a content script, the chrome.tabs.connect function accepts three arguments. The first is the ID of the recipient's tab and the second and third arguments are identical to the id and connectInfo arguments of chrome.runtime.connect. Both connect functions return a Port, which represents a long-lived connection between two parties. The following code shows how chrome.tabs.connect can be used:

chrome.tabs.query({currentWindow: true, active: true}, function (tabs) {   
  chrome.tabs.connect(tabs[0].id); 
});

Background scripts and content scripts can listen for connections with onConnect.addListener. This requires a callback function that receives the Port that represents the new connection. A Port object has seven properties and Table 8 lists each of them.

Table 8: Properties of the Port Object
Property Data Type  Description
name String Name of the port (set by the connect function)
postMessage Function Sends a message to the other party
onMessage   Object Adds a listener for incoming messages
disconnect Function Terminates the connection
onDisconnect  Object Adds a listener for disconnection
disconnect Function Terminates the connection
sender MessageSender   The party that initiated the connection (optional)

Like the sendMessage function discussed earlier, postMessage transfers data in the form of a JSON object. The difference is that postMessage has only one argument: the message object. This is shown in the following code, which posts a message as soon as a connection is established:

chrome.runtime.onConnect.addListener(function(port) {
  port.postMessage({"name": "value"});
});

The Port's onMessage object works the same way as chrome.runtime.onMessage. It provides an addListener method that defines a callback function to be invoked when a message is received. The callback has no reason to access a response function because Port communication makes the response unnecessary. The following code shows how the Port's onMessage object can be used:

port.onMessage.addListener(function(message) {
  console.log("Message: " + message.name);
});

Either party can call disconnect() to terminate the connection. In addition, the onDisconnect object provides an addListener function that responds when the script is no longer connected to a Port.

3.3  Example Application

This article's example archive contains a folder called script_demo. This contains an extension that demonstrates how a content script can communicate with a background script. More specifically, it demonstrates event handling, storage access, and message passing.

The script_demo/scripts folder contains two scripts: content.js and background.js. The code in content.js runs as a content script, and is given as:

// Connect to background script
var port = chrome.runtime.connect();

// Listen for response
port.onMessage.addListener(function(message, sender) {

  // Print and store message data
  console.log("Received color: " + message.color);
  chrome.storage.local.set(message, function() {
    port.postMessage({"status": "Communication successful!"});
  });
});

This script starts by creating a connection to the background script. Then it waits for a message, prints a message to the console, and uses the chrome.storage API to store the message's data. After storing the message, it sends a message to the background script. The code for the background script is given as:

// Wait for connection from content script
chrome.runtime.onConnect.addListener(function(port) {

  // Send data through port
  port.postMessage({"color": "green"});
  
  // Wait for incoming message
  port.onMessage.addListener(function(message) {
  
    // Retrieve data from storage
    chrome.storage.local.get("color", function(obj) {
      if (obj.color === "green") {
        console.log("Status: " + message.status);
      }
    });
  });
});

The background script waits for an incoming connection, posts a message to the content script, and then waits for a message to arrive. When it receives the message, the background script accesses local storage, checks the returned value, and prints a message to the console.

Before leaving the subject, there are three points I'd like to mention regarding the scripts an extension:

  1. Instead of sending messages from a background script to a content script, it can be more convenient for the background script to execute a script using chrome.tabs.executeScript. The background script can access the processing result in executeScript's callback function.
  2. Make sure the manifest's permissions are set correctly. If a script needs to access storage, "storage" should be in the permissions array. If a script needs to modify a tab's URL, title, or favicon, "tabs" should be in the array.
  3. The "o" in onMessage and onConnect is always lowercase. I accidentally typed it in uppercase and it took me over an hour to figure out why the extension wasn't working.

4.  Actions

In a Chrome extension, an action is represented by an icon in the toolbar that triggers a reaction from the extension when clicked. Every extension can have at most one action, and this can be a browser action or a page action. A browser action remains active regardless of the page open in the browser. A page action is only active for specific pages.

4.1  Browser Actions

An extension's browser action can be defined by adding a browser_action property to manifest.json. This can contain three properties that define aspects of the action (all are optional):

  • default_icon — The action's icon can be an HTML5 canvas or a static image. Static images must be 19x19 or 38x38 pixels in size, and supported formats include BMP, GIF, ICO, JPEG, or PNG. For unpacked extensions, only the PNG format is supported.
  • default_title — Assigns a tooltip to be displayed when the user's mouse hovers over the icon.
  • default_popup — Identifies an HTML file that displays a popup menu.

A popup is a page that appears when the user clicks the action. The page's appearance is set using the HTML page associated with the default_popup property. For example, the following HTML defines three checkboxes in the popup:

<!DOCTYPE html>
<html>
  <body style="width: 80px">
    <input type="checkbox" id="opt1">Option 1<br>
    <input type="checkbox" id="opt2">Option 2<br>
    <input type="checkbox" id="opt3">Option 3<br>  
  </body>
</html>

Figure 3 shows what the resulting popup looks like.

Figure 3: Browser Action with a Popup

Image 3

If a browser action doesn't have a popup, a background script can respond when the action is clicked. This is made possible by calling chrome.browserAction.onClicked.addListener. This accepts a callback function that returns the Tab object representing the active tab when the action was clicked. For example, the following code prints the URL of the current tab whenever the browser action is clicked:

chrome.browserAction.onClicked.addListener(function(tab) {
  console.log("The tab's URL is " + tab.url);
});

The chrome.browserAction API provides more capabilities than just event handling. Its functions make it possible to obtain information about the action and dynamically configure its appearance. Table 9 lists the API's functions and provides a description of each.

Table 9: Functions of the chrome.browserAction API
Function Description
enable(Integer tabId)  Enable the browser action for the given tab
disable(Integer tabId)  Disable the browser action for the given tab
getTitle(Object props,
  Function callback) 
Obtains the action's title through the callback function
setTitle(Object props) Sets the action's title
setIcon(Object props)   Sets the action's icon
getBadgeText(Object props, 
  Function callback)
Obtains the action's badge text through the callback function
setBadgeText(Object props)  Sets the action's badge text
getBadgeBackgroundColor
  (Object props,
   Function callback)
Obtains the background color of the action's badge
through the callback function
setBadgeBackgroundColor
  (Object props)
Sets the background color of the action's badge
getPopup(Object props,
  Function callback)
Obtains the action's popup document through the
callback function
setPopup(Object props) Sets the action's popup document

In general, a browser action has the same appearance and behavior for every tab in the browser. But these functions make it possible to specify which tab should be accessed. The first two functions enable or disable a browser action according to the tab's ID. A disabled action appears grayed in the toolbar.

Most of the getXYZ and setXYZ functions work in the same way. The getter functions accept an optional object that identifies which tab the function applies to. The second argument is a callback function that receives the desired information. For example, the following code shows how getTitle can be called to obtain the action's title for the active tab:

chrome.tabs.query({currentWindow: true, active: true}, function (tabs) {
  chrome.browserAction.getTitle({tabId: tabs[0].id}, function (title) {
    console.log("Title: " + title);
  });
});

Setter functions like setTitle accept a similar configuration object, but the first property provides the new configuration data. For example, the following code sets the browser action's title for the tab whose index is zero.

chrome.tabs.query({index: 0}, function (tabs) {
  chrome.browserAction.setTitle({title: "New title", tabId: tabs[0].id});
});

A browser action can have text printed next to its icon. This is called a badge, and can be configured with the setBadgeText function. For example, the following code prints HI on top of the action's icon:

chrome.browserAction.setBadgeText({text: "HI"});

Figure 4 shows what the action looks like with the badge:

Figure 4: Browser Action with a Badge

Image 4

By default, an action's badge is displayed inside of a red rectangle. The rectangle's color can be changed with setBadgeBackgroundColor. This accepts a configuration object whose color property can be given as a CSS color string or as an array of four integers between 0 and 255 in RGBA order. For example, the color green can be given as "#0f0", "#00ff00", or [0, 255, 0, 255]. The following code shows how this can be used:

chrome.browserAction.setBadgeBackgroundColor({color: "#0F0"});

If a browser action has a popup, getPopup provides the popup's URL. The optional configuration object accepts a tab ID and the callback function receives the URL as a string. The following code shows how this can be used:

chrome.browserAction.getPopup({}, function(url) {
  console.log("The popup's URL is " + url);
});

chrome.browserAction doesn't provide any functions for communicating with a popup or obtaining its state. But this can be accomplished using the message passing mechanisms discussed earlier. Communicating with a popup will be discussed at the end of this section.

4.2  Page Actions

Page actions nearly identical to browser actions, and can have icons, titles, and popups. In manifest.json, the only difference is that the browser_action field is replaced by page_action. This can be shown as follows:

"page_action": {
  "default_icon": {
    "19": "images/icon_19.png",
    "38": "images/icon_38.png"
  },
  "default_title": "Page action",
  "default_popup": "popup.html"
}

The primary difference between the two types of actions is that page actions are disabled (grayed out) by default. To enable a page action, a background script needs to call chrome.pageAction.show with a tab ID. This means a page action can only be enabled for one tab at a time. Similarly, chrome.pageAction.hide disables the page action for the specified tab.

An example will clarify how this works. Suppose a page action should only be enabled if a tab's URL contains the string google. A background script can respond to tab changes through the chrome.tabs.onUpdate event, and when a tab changes, the script can check the URL. This is shown in the following code:

// Assign a callback to respond to tab changes
chrome.tabs.onUpdated.addListener(testUrl);

function testUrl(id, info, tab) {
  if (tab.url.indexOf("google") !== -1) {
    chrome.pageAction.show(id);
  }
};

Note that, for this code to work properly, the "tabs" string must be placed in the manifest's permissions array.

Besides show and hide, the functions in chrome.pageAction are essentially similar to those of chrome.browserAction. There are two important differences:

  1. The tab ID property is never optional. Page actions are always configured for individual tabs.
  2. Page actions don't support badges, so there are no functions like setBadgeText or getBadgeBackgroundColor.

Popups in a page action work like those in a browser action. The following discussion explains how both kinds of popups can communicate with a background script.

4.3  Popup Communication

Popup pages are configured with HTML, which means they can include JavaScript code using <script> tags. Conveniently, scripts in a popup page can access all the capabilities of a background script. This means a popup script can communicate with other scripts using message passing.

Popup communication is demonstrated in the action_demo extension contained in this article's example archive. The popup.html page has three checkboxes whose IDs are opt1, opt2, and opt3. It executes the code in popup.js, which is given as follows:

// Create listeners for the three checkboxes
document.getElementById("opt1").addEventListener("change", opt1changed);
document.getElementById("opt2").addEventListener("change", opt2changed);
document.getElementById("opt3").addEventListener("change", opt3changed);

// Send a message when the first box changes
function opt1changed() {
  var box1 = document.getElementById("opt1");
  chrome.runtime.sendMessage({box: 1, checked: box1.checked});
}

// Send a message when the second box changes
function opt2changed() {
  var box2 = document.getElementById("opt2");
  chrome.runtime.sendMessage({box: 2, checked: box2.checked});
}

// Send a message when the third box changes
function opt3changed() {
  var box3 = document.getElementById("opt3");
  chrome.runtime.sendMessage({box: 3, checked: box3.checked});
}

When any of the three boxes is checked or unchecked, popup.js calls chrome.runtime.sendMessage. This sends a message to the background script that identifies the number of the checkbox and its state. The background script code is given as follows:

// Configure the browser action
chrome.runtime.onInstalled.addListener(function(obj) {
  chrome.browserAction.setBadgeText({text: "YES"});
  chrome.browserAction.setBadgeBackgroundColor({color: "#00F"});
});

// Wait for an incoming message
chrome.runtime.onMessage.addListener(

  // Receive a message from the popup script
  function(message, sender, sendResponse) {
    console.log("Box" + message.box + " checked state: " + message.checked);
  }
);

This background script uses the chrome.browserAction API to set the action's badge text and background color. Then it waits for a message by calling chrome.runtime.onMessage.addListener. When a message is received, the script logs a message to the console.

This extension demonstrates how an popup can receive user settings and send them to the extension. But there's a problem: the popup's page resets every time it's closed. To make up for this, the popup's state can be persistently stored accessing the chrome.storage API discussed earlier.

History

6/17/2016 - Initial article submission

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)