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:
- Every extension must have a file named manifest.json. This describes the extension's capabilities and identifies how it interacts with the browser.
- 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.
- 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
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:
- Download example_code.zip and decompress the archive.
- In Chrome, open the Chrome Developer Dashboard by navigating to chrome://extensions.
- Press the Load unpacked extension... button and select the basic_demo folder in the decompressed archive.
- 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
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:
- To use the API, the extension's manifest must have its
permissions
field set to an array containing the string "storage"
. - Data is stored in an unencrypted database file, so this API isn't suitable for confidential information.
- A script can load and store data by calling functions of a
StorageArea
object. - Three types of
StorageArea
s 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 StorageArea
s 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:
reason
— The cause of the installation event. This may be set to "install"
, "update"
, "chrome_update"
, or "shared_module_update"
. previousVersion
— a string that identifies the version before the installation/update 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:
var port = chrome.runtime.connect();
port.onMessage.addListener(function(message, sender) {
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:
chrome.runtime.onConnect.addListener(function(port) {
port.postMessage({"color": "green"});
port.onMessage.addListener(function(message) {
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:
- 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. - 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. - 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
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
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:
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:
- The tab ID property is never optional. Page actions are always configured for individual tabs.
- 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:
document.getElementById("opt1").addEventListener("change", opt1changed);
document.getElementById("opt2").addEventListener("change", opt2changed);
document.getElementById("opt3").addEventListener("change", opt3changed);
function opt1changed() {
var box1 = document.getElementById("opt1");
chrome.runtime.sendMessage({box: 1, checked: box1.checked});
}
function opt2changed() {
var box2 = document.getElementById("opt2");
chrome.runtime.sendMessage({box: 2, checked: box2.checked});
}
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:
chrome.runtime.onInstalled.addListener(function(obj) {
chrome.browserAction.setBadgeText({text: "YES"});
chrome.browserAction.setBadgeBackgroundColor({color: "#00F"});
});
chrome.runtime.onMessage.addListener(
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