Commit 0b46e332 authored by crystallambert@chromium.org's avatar crystallambert@chromium.org Committed by Commit Bot

[Extension Docs] Update Event/Background Pages.

Current use of background page and event page is confusing.
This document updates the terminology to Background Scripts
with an option to use it as a page in the manifest.
All background scripts should be persistent false,
with little to no exceptions.
This update had a huge focus on how to create effective
non-persistent background scripts.

Change-Id: I985caa739104d3608157c5e39119f9e27d57442e
Reviewed-on: https://chromium-review.googlesource.com/677735
Commit-Queue: Crystal Lambert <crystallambert@chromium.org>
Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#552873}
parent e8ad3202
<h1>Migrate to Event Driven Background Scripts</h1>
<p>
Implementing non-persistent background scripts
will greatly reduce the resource cost of your extension.
Most extension functionality can be supported
by an event based background script.
Only under
<a href="background_pages#persistentWarning">rare circumstances</a>
should an extension have a persistent background,
as they constantly consume system resources
and can cause a strain on lower-powered devices.
<p>
<p>
Enhance an extension’s performance by migrating a persistent background script
to an event-based non-persistent model.
</p>
<h2 id="persistence">Designate Persistence as False</h2>
<p>
Locate the <code>"background"</code> key in the extension
<a href="/manifest">manifest</a> file,
then add or update the <code>"persistent"</code> field to false.
</p>
<pre data-filename="manifest.json">
{
"name": "My extension",
...
<b>"background": {
"scripts": ["background.js"],
"persistent": false
}</b>,
...
}</pre>
<p>
The same applies to background scripts that rely on an HTML file.
</p>
<pre data-filename="manifest.json">
{
"name": "My extension",
...
<b>"background": {
"page": "background.html",
"persistent": false
}</b>,
...
}</pre>
<h2 id="listeners">Surface Event Listeners</h2>
<p>
Listeners must be at the top-level to activate the background script
if an important event is triggered.
Registered listeners may need to be restructred to a synchronous pattern.
Structuring listeners, as below, will not allow them to be invoked
because they are not registered synchronously.
</p>
<pre data-filename="background.js">
chrome.storage.local.get('runtimeEvents', function (events) {
for (let event of events)
chrome.runtime[event].addListener(listener);
});
</pre>
<p>
Instead, keep listeners at top-level and unnested.
</p>
<pre data-filename="background.js">
chrome.runtime.onStartup.addListener(function() {
// run startup function
})
</pre>
<h2 id="storage">Record State Changes in Storage</h2>
<p>
Use the <a href="/storage">storage API</a> to set and return states
and values.
Use <code>local.set</code> to update on the local machine.
</p>
<pre data-filename="background.js">
chrome.storage.local.set({ variable: variableInformation });
</pre>
<p>
Use <code>local.get</code> to grab the value of that variable.
</p>
<pre data-filename="background.js">
chrome.storage.local.get(['variable'], function(result) {
let awesomeVariable = result.variable;
// Do something with awesomeVariable
});
</pre>
<h2 id="timers">Transform Timers into Alarms</h2>
<p>
DOM-based timers,
such as <code>window.setTimeout()</code> or
<code>window.setInterval()</code>,
are not honored in non-persistent background scripts
if they trigger when the event page is dormant.
</p>
<pre data-filename="background.js">
let timeout = 1000 * 60 * 3; // 3 minutes in milliseconds
window.setTimeout(function() {
alert('Hello, world!');
}, timeout);
</pre>
<p>
Instead, use the <a href="alarms">alarms API</a>.
</p>
<pre data-filename="background.js">
chrome.alarms.create({delayInMinutes: 3.0})
</pre>
<p>
Then add a listener.
</p>
<pre data-filename="background.js">
chrome.alarms.onAlarm.addListener(function() {
alert("Hello, world!")
});
</pre>
<h2 id="backgroundFunctions">Update Calls for Background Script Functions</h2>
<p>
If using <code>$(ref:extension.getBackgroundPage)</code>
to call a function from the background page, update to
<code>$(ref:runtime.getBackgroundPage)</code>.
The newer method activates the non-persistent script before returning it.
</p>
<pre data-filename="background.js">
function backgroundFunction() {
alert('Background, reporting for duty!')
}
</pre>
<pre data-filename="popup.js">
document.getElementById('target').addEventListener('click', function(){
chrome.extension.getBackgroundPage().backgroundFunction();
});
</pre>
<p>
This method won't work if the background script is inactive,
which is the default state for a non-persistent script.
The newer method includes a callback function
to ensure the background script has loaded.
</p>
<pre data-filename="popup.js">
document.getElementById('target').addEventListener('click', function() {
chrome.runtime.getBackgroundPage(function(backgroundPage){
backgroundPage.backgroundFunction()
})
});
</pre>
</p>
<h1>Background Pages</h1>
initialization<h1>Manage Events with Background Scripts</h1>
<p id="eventPageWarning" class="warning">
<em>Caution:</em> Consider using event pages instead.
<a href="event_pages">Learn more</a>.
<p>
Extensions are event based programs used to modify
or enhance the Chrome browsing experience.
Events are browser triggers,
such as navigating to a new page,
removing a bookmark,
or closing a tab.
Extensions monitor these events in their background script,
then react with specified instructions.
</p>
<p>
A common need for extensions is to have
a single long-running script to manage some task or state.
Background pages to the rescue.
A background page is loaded when it is needed,
and unloaded when it goes idle.
Some examples of events include:
</p>
<ul>
<li>
The extension is first installed or updated to a new version.
</li>
<li>
The background page was listening for an event, and the event is dispatched.
</li>
<li>
A content script or other extension
<a href="messaging">sends a message.</a>
</li>
<li>
Another view in the extension, such as a popup, calls
<code>$(ref:runtime.getBackgroundPage)</code>.
</li>
</ul>
<p>
As the <a href="overview#arch">architecture overview</a> explains,
the background page is an HTML page that runs in the extension process.
It exists for the lifetime of your extension,
and only one instance of it at a time is active. (Exception: if your
extension uses <a href="manifest/incognito">incognito</a>
"split" mode, a second instance is created for incognito windows.)
Once it has been loaded,
a background page will stay running as long as it is performing an action,
such as calling a Chrome API or issuing a network request.
Additionally, the background page will not unload until all visible views
and all message ports are closed.
Note that opening a view does not cause the event page to load,
but only prevents it from closing once loaded.
</p>
<p>
In a typical extension with a background page,
the UI &mdash;
for example, the browser action or page action
and any options page &mdash;
is implemented by dumb views.
When the view needs some state,
it requests the state from the background page.
When the background page notices a state change,
the background page tells the views to update.
Effective background scripts stay dormant
until an event they are listening for fires,
react with specified instructions,
then unload.
</p>
<h2 id="manifest">Manifest</h2>
<h2 id="manifest">Register Background Scripts</h2>
<p>
Register your background page in the
<a href="manifest">extension manifest</a>.
In the common case, a background page
does not require any HTML markup.
These kind of background pages can be
implemented using JavaScript files alone,
like this:
Background scripts are registered in the <a href="/manifest">manifest</a>
under the <code>"background"</code> field.
They are listed in an array after the <code>"scripts"</code> key,
and <code>"persistent"</code> should be specified as false.
</p>
<pre data-filename="manifest.json">
{
"name": "My extension",
"name": "Awesome Test Extension",
...
<b>"background": {
"scripts": ["background.js"]
"scripts": ["background.js"],
"persistent": false
}</b>,
...
}</pre>
}
</pre>
<p>
A background page will be generated
by the extension system
that includes each of the files listed
in the <code>scripts</code> property.
Multiple background scripts can be registered for modularized code.
</p>
<pre data-filename="manifest.json">
{
"name": "Awesome Test Extension",
...
"background": {
"scripts": [
"backgroundContextMenus.js",
"backgroundOmniBox.js",
"backgroundOauth.js"
],
"persistent": false
},
...
}
</pre>
<p id="persistentWarning" class="warning">
The only occasion to keep a background script persistently active is
if the extension uses <a href="/webRequest">chrome.webRequest</a> API
to block or modify network requests.
The webRequest API is incompatible with non-persistent background pages.
</p>
<p>
If you need to specify HTML
in your background page, you can
do that using the <code>page</code>
property instead:
If an extension currently uses a persistent background page, refer to
<a href="/background_migration">Background Migration Guide</a>
for instruction on how to switch to a non-persistent model.
</p>
<pre data-filename="manifest.json">
{
"name": "My extension",
...
<b>"background": {
"page": "background.html"
}</b>,
...
}</pre>
<h2 id="initialization">Initialize the Extension</h2>
<p>
Listen to the <code>$(ref:runtime.onInstalled)</code> event
to initialize an extension on installation.
Use this event to set a state or for one-time initialization,
such as a <a href="contextMenus">context menu</a>.
</p>
<pre data-filename="background.js">
chrome.runtime.onInstalled.addListener(function() {
chrome.contextMenus.create({
"id": "sampleContextMenu",
"title": "Sample Context Menu",
"contexts": ["selection"]
});
});
</pre>
<h2 id="listeners">Set Up Listeners</h2>
<p>
Structure background scripts around events the extension depends on.
Defining functionally relevant events allows background scripts to lie dormant
until those events are fired
and prevents the extension from missing important triggers.
</p>
<p>
Listeners must be registered synchronously from the start of the page.
</p>
<pre data-filename="background.js">
chrome.runtime.onInstalled.addListener(function() {
chrome.contextMenus.create({
"id": "sampleContextMenu",
"title": "Sample Context Menu",
"contexts": ["selection"]
});
});
// This will run when a bookmark is created.
chrome.bookmarks.onCreated.addListener(function() {
// do something
});
</pre>
<p>
If you need the browser to start up early&mdash;so
you can display notifications, for example&mdash;then
you might also want to specify the
<a href="declare_permissions#background">"background" permission</a>.
Do not register listeners asynchronously,
as they will not be properly triggered.
</p>
<pre data-filename="notGoodBackground.js">
chrome.runtime.onInstalled.addListener(function() {
// ERROR! Events must be registered synchronously from the start of
// the page.
chrome.bookmarks.onCreated.addListener(function() {
// do something
});
});
</pre>
<p>
You should use clear file types - ".js" for background
scripts and ".html" for background pages.
Extensions can remove listeners from their background scripts by calling
<code>removeListener</code>.
If all listeners for an event are removed,
Chrome will no longer load the extension's background script for that event.
</p>
<pre data-filename="background.js">
chrome.runtime.onMessage.addListener(function(message, sender, reply) {
chrome.runtime.onMessage.removeListener(event);
});
<h2 id="details">Details</h2>
</pre>
<h2 id="filters">Filter Events</h2>
<p>
You can communicate between your various pages using direct script calls,
similar to how frames can communicate.
The $(ref:extension.getViews) method
returns a list of window objects
for every active page belonging to your extension,
and the
$(ref:extension.getBackgroundPage) method
returns the background page.
Use APIs that support <a href="events#filtered">event filters</a>
to restrict listeners to the cases the extension cares about.
If an extension is listening for the
<code><a href="extensions/tabs#event-onUpdated">tabs.onUpdated</a></code>
event,
try using the <code>$(ref:webNavigation.onCompleted)</code>
event with filters instead,
as the tabs API does not support filters.
</p>
<h2 id="example">Example</h2>
<pre data-filename="background.js">
chrome.webNavigation.onCompleted.addListener(function() {
alert("This is my favorite website!");
}, {url: [{urlMatches : 'https://www.google.com/'}]});
</pre>
<h2 id="react">React to Listeners</h2>
<p>
The following code snippet demonstrates
how the background page
can interact with other pages in the extension.
It also shows how you can use
the background page to handle events
such as user clicks.
Listeners exist to trigger functionality once an event has fired.
To react to an event,
structure the desired reaction inside of the listener event.
</p>
<pre data-filename="background.js">
chrome.runtime.onMessage.addListener(function(message, callback) {
if (message.data == “setAlarm”) {
chrome.alarms.create({delayInMinutes: 5})
} else if (message.data == “runLogic”) {
chrome.tabs.executeScript({file: 'logic.js'});
} else if (message.data == “changeColor”) {
chrome.tabs.executeScript(
{code: 'document.body.style.backgroundColor="orange"'});
};
});
</pre>
<h2 id="unloading">Unload Background Scripts</h2>
<p>
The extension in this example
has a background page
and multiple pages created
(with
$(ref:tabs.create))
from a file named <code>image.html</code>.
Data should be persisted periodically
so that important information is not lost if an extension crashes
without receiving <code>onSuspend</code>.
Use the <a href="/storage">storage</a> API to assist with this.
</p>
<pre data-filename="background.js">
chrome.storage.local.set({variable: variableInformation});
</pre>
<p>
If an extension uses <a href="messaging">message passing</a>,
ensure all ports are closed.
The background script will not unload until all message ports have shut.
Listening to the $(ref:runtime.Port.onDisconnect) event will give insight to
when open ports are closing.
Manually close them with $(ref:runtime.Port.disconnect).
</p>
<pre data-filename="background.js">
// React when a browser action's icon is clicked.
chrome.browserAction.onClicked.addListener(function(tab) {
var viewTabUrl = chrome.extension.getURL('image.html');
var imageUrl = <em>/* an image's URL */</em>;
// Look through all the pages in this extension to find one we can use.
var views = chrome.extension.getViews();
for (var i = 0; i < views.length; i++) {
var view = views[i];
// If this view has the right URL and hasn't been used yet...
if (view.location.href == viewTabUrl && !view.imageAlreadySet) {
// ...call one of its functions and set a property.
view.setImageUrl(imageUrl);
view.imageAlreadySet = true;
break; // we're done
chrome.runtime.onMessage.addListener(function(message, callback) {
if (message == 'hello') {
sendResponse({greeting: 'welcome!'})
} else if (message == 'goodbye') {
chrome.runtime.Port.disconnect();
}
}
});
});
</pre>
<pre data-filename="image.html">
&lt;html>
&lt;script>
function setImageUrl(url) {
document.getElementById('target').src = url;
}
&lt;/script>
&lt;body>
&lt;p>
Image here:
&lt;/p>
<p>
The lifetime of a background script is observable by monitoring
when an entry for the extension appears
and disappears from Chrome's task manager.
</p>
<img src="{{static}}/images/taskManager"
height="600"
alt="Chrome with an extension's popup open.">
&lt;img id="target" src="white.png" width="640" height="480">
<p>
Open the task manager by clicking the Chrome Menu,
hovering over more tools and selecting "Task Manager".
</p>
&lt;/body>
&lt;/html>
<p>
Background scripts unload on their own after a few seconds of inactivity.
If any last minute cleanup is required,
listen to the <code>$(ref:runtime.onSuspend)</code> event.
</p>
<pre data-filename="background.js">
chrome.runtime.onSuspend.addListener(function() {
console.log("Unloading.");
chrome.browserAction.setBadgeText({text: ""});
});
</pre>
<p>
However, persisting data should be prefered over
relying on <code>$(ref:runtime.onSuspend)</code>.
It doesn't allow for as much cleanup as may be needed
and will not help in case of a crash.
</p>
<h1>Event Pages</h1>
<h1>Update: Event Pages and Background Pages</h1>
<p>
A common need for apps and extensions is to have
a single long-running script to manage some task or state.
Event pages to the rescue.
Event pages are loaded only when they are needed.
When the event page is not actively doing something,
it is unloaded, freeing memory and other system resources.
</p>
{{?is_apps}}
<p>
Chrome Apps always use event pages instead of background pages.
It is not possible for a Chrome App to have a persistent background page.
</p>
{{/is_apps}}
<p>
Event pages are available in the stable channel as of Chrome 22, and the
performance advantages are significant, especially on low-power devices. Please
prefer them to persistent background pages whenever possible for new development
and begin <a href="#transition">migrating existing background pages</a> to this
new model.
</p>
<h2 id="manifest">Manifest</h2>
<p>
Register your event page in the
<a href="manifest">extension manifest</a>:
</p>
{{^is_apps}}
<pre data-filename="manifest.json">
{
"name": "My extension",
...
<b>"background": {
"scripts": ["eventPage.js"],
"persistent": false
}</b>,
...
}
</pre>
<p>
Notice that without the "persistent" key, you have
a regular background page. Persistence is what differentiates
an event page from a background page.
</p>
{{/is_apps}}
{{?is_apps}}
<pre data-filename="manifest.json">
{
"name": "My app",
...
"app": {
<b>"background": {
"scripts": ["eventPage.js"]
}</b>
}
...
}
</pre>
{{/is_apps}}
<h2 id="lifetime">Lifetime</h2>
<p>
The event page is loaded when it is "needed", and unloaded
when it goes idle again. Here are some examples of things
that will cause the event page to load:
</p>
<ul>
<li>The app or extension is first installed or is updated to a new version
(in order to <a href="#registration">register for events</a>).
<li>The event page was listening for an event, and the event is dispatched.
<li>A content script or other extension
<a href="messaging">sends a message.</a>
<li>Another view in the extension (for example, a popup) calls
<code>$(ref:runtime.getBackgroundPage)</code>.
</ul>
<p>
Once it has been loaded, the event page will stay running
as long as it is active (for example, calling an extension
API or issuing a network request). Additionally, the
event page will not unload until all visible views (for example,
popup windows) are closed and all message ports are closed. Note
that opening a view does not cause the event page to load, but
only prevents it from closing once loaded.
</p>
<p>
Make sure your event page closes as soon as the event that
opened it is processed.
You can observe the lifetime of your event page by
opening Chrome's task manager. You can see when your event
page loads and unloads by observing when an entry for your
extension appears in the list of processes.
</p>
<p>
Once the event page has been idle a short time
(a few seconds), the
<code>$(ref:runtime.onSuspend)</code>
event is dispatched. The event page has a few more seconds to handle this
event before it is forcibly unloaded. If during this time an event occurs which
would normally cause the event page to be loaded, the suspend is canceled
and the <code>$(ref:runtime.onSuspendCanceled)</code> event is dispatched.
</p>
<h2 id="registration">Event registration</h2>
<p>
Chrome keeps track of events that an app or extension has added listeners
for. When it dispatches such an event, the event page is
loaded. Conversely, if the app or extension removes all of its listeners for
an event by calling <code>removeListener</code>, Chrome will no longer
load its event page for that event.
</p>
<p>
Because the listeners themselves only exist in the context of the
event page, you must use <code>addListener</code> each time the event
page loads; only doing so at
<code>$(ref:runtime.onInstalled)</code>
by itself is insufficient.
</p>
<p>
For an example of event registration in action, you can view the
<a href="https://chromium.googlesource.com/chromium/src/+/master/chrome/common/extensions/docs/examples/extensions/gmail/">Google Mail
Checker</a> extension.
</p>
{{^is_apps}}
<h2 id="transition">Convert background page to event page</h2>
<p>
Follow this checklist to convert your extension's
(persistent) background page to an event page.
<ol>
<li>Add <code>"persistent": false</code> to your manifest as shown above.
<li>If your extension uses <code>window.setTimeout()</code> or
<code>window.setInterval()</code>, switch to using the
<a href="alarms">alarms API</a> instead. DOM-based timers won't
be honored if the event page shuts down.
<li>Similarly, other asynchronous HTML5 APIs like notifications and
geolocation will not complete if the event page shuts down. Instead,
use equivalent extension APIs, like
<a href="notifications">notifications</a>.
<li>If your extension uses,
<code>$(ref:extension.getBackgroundPage)</a></code>,
switch to
<code>$(ref:runtime.getBackgroundPage)</code>
instead. The newer method is asynchronous so that it can start the event
page if necessary before returning it.
</ol>
</p>
{{/is_apps}}
<h2 id="best-practices">Best practices when using event pages</h2>
<p>
Keep these tips in mind when using event pages to avoid common subtle pitfalls.
<ol>
<li>Register to receive any events your extension is interested in
each time the event page is loaded. The event page will be loaded once
for each new version of your extension. After that it will only be
loaded to deliver events you have registered for. This generally means that
your event listeners should be added at the top level scope of the event
page, otherwise they may not be available when the event page reloads.
<li>If you need to do some initialization when your extension is
installed or upgraded, listen to the
<code>$(ref:runtime.onInstalled)</code>
event. This is a good place to register for
<a href="declarativeWebRequest">declarativeWebRequest</a> rules,
<a href="contextMenus">contextMenu</a> entries, and other such
one-time initialization.
<li>If you need to keep runtime state in memory throughout a browser
session, use the <a href="storage">storage API</a> or
IndexedDB. Since the event page does not stay loaded for long, you
can no longer rely on global variables for runtime state.
{{^is_apps}}
<li>Use <a href="events#filtered">event filters</a> to restrict
your event notifications to the cases you care about. For example, if
you listen to the <code><a href="extensions/tabs#event-onUpdated">tabs.onUpdated</a></code>
event, try using the
<code>$(ref:webNavigation.onCompleted)</code>
event with filters instead (the tabs API does not support filters).
That way, your event page will only be loaded for events that
interest you.
{{/is_apps}}
<li>Listen to the
<code>$(ref:runtime.onSuspend)</code>
event if you need to do last second cleanup before your event page
is shut down. However, we recommend persisting periodically instead.
That way if your extension crashes without receiving
<code>onSuspend</code>, no data will typically be lost.
<li>If you're using <a href="messaging">message passing</a>, be sure
to close unused message ports. The event page will not shut down until all
message ports are closed.
<li>If you're using the <a href="contextMenus">context menus</a> API,
pass a string <code>id</code> parameter to
<code>$(ref:contextMenus.create)</code>,
and use the
<code>$(ref:contextMenus.onClicked)</code>
callback instead of an <code>onclick</code> parameter to
<code>$(ref:contextMenus.create)</code>.
<li>Remember to test that your event page works properly when it is unloaded
and then reloaded, which only happens after several seconds of inactivity.
Common mistakes include doing unnecessary work at page load time (when it
should only be done when the extension is installed); setting an alarm at
page load time (which resets any previous alarm); or not adding event
listeners at page load time.
</ol>
<p id="EventPageBackgroundPageWarning" class="warning">
Event page and background page documentation has been updated.
Please refer to
<a href="/background_pages">Background Scripts</a>
for clarification and best practices.
</p>
......@@ -24,8 +24,8 @@
"href": "/extensions/manifest"
},
{
"title": "Event Pages",
"href": "/extensions/event_pages"
"title": "Manage Events",
"href": "/extensions/background_pages"
},
{
"title": "Content Scripts",
......
{{+partials.standard_extensions_article article:articles.background_migration/}}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment