Commit 9dd5065f authored by CrystalFaith's avatar CrystalFaith Committed by Commit Bot

[Extension Docs] User Interface

Currently, user interface information is low and scattered. This document
will centralize and explain the types and limits to extension UI.

Bug: None
Change-Id: Ia6acac35fe731b6e76134239202194201e4a9edb
Reviewed-on: https://chromium-review.googlesource.com/768621
Commit-Queue: Crystal Lambert <crystallambert@chromium.org>
Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543833}
parent 4cedfdf4
<h1>Design User Interface</h1>
<p>
The extension user interface should be purposeful and minimal.
Just like extensions themselves,
the UI should customize or enhance the browsing experience
without distracting from it.
</p>
<p>
This guide explores required and optional user interface features.
Use it to understand how
and when to implement different UI elements within an extension.
</p>
<h2 id="browser_action">Activate the extension on all pages</h2>
<p>
Use a <a href="/browserAction">browser_action</a> when an extension's
features are functional in most situations.
</p>
<h3 id="browser">Register browser action</h2>
<p>
The <code>"browser_action"</code> field is registered in the manifest.
</p>
<pre data-filename="manifest.json">
{
"name": "My Awesome browser_action Extension",
...
"browser_action": {
...
}
...
}
</pre>
<p>
Declaring <code>"browser_action"</code> keeps the icon colorized,
indicating the extension is available to users.
</p>
<h3 id="badge">Add a badge</h3>
<p>
Badges display a colored banner with up to four characters
on top of the browser icon.
They can only be used by extensions that declare
<code>"browser_action"</code> in their manifest.
</p>
<p>
Use badges to indicate the state of the extension.
The
<a href="https://developer.chrome.com/extensions/samples#search:drink">
Drink Water Event
</a>
sample displays a badge with “ON” to show the user they
successfully set an alarm
and displays nothing when the extension is idle.
</p>
<img src="{{static}}/images/user_interface/badge_on_example.png"
width="72" height="72"
alt="Badge On" />
<img src="{{static}}/images/user_interface/badge_off_example.png"
width="72" height="72"
alt="Badge Off" />
</p>
Set the text of the badge by calling
<a href="/browserAction#method-setBadgeText">
<code>chrome.browserAction.setBadgeText</code>
</a>
and the banner color by calling
<a href="/browserAction#method-setBadgeBackgroundColor">
<code>chrome.browserAction.setBadgeBackgroundColor</code>
</a>.
</p>
<pre data-filename="background.js">
chrome.browserAction.setBadgeText({text: 'ON'});
chrome.browserAction.setBadgeBackgroundColor({color: '#4688F1'});
</pre>
<h2 id="page_action">Activate the extension on select pages</h2>
<p>
Use <a href="/pageAction">page_action</a> when an extension's
features are only available under defined circumstances.
</p>
<h3 id="page">Declare Page Action</h3>
<p>
The <code>"page_action"</code> field is registered in the manifest.
</p>
<pre data-filename="manifest.json">
{
"name": "My Awesome page_action Extension",
...
"page_action": {
...
}
...
}
</pre>
<p>
Declaring <code>"page_action"</code> will colorize the icon only when the
extension is available to users,
otherwise it will be displayed in greyscale.
</p>
<img src="{{static}}/images/user_interface/page_action_active.png"
width="72" height="72"
alt="Active Page Action Icon" />
<img src="{{static}}/images/user_interface/page_action_inactive.png"
width="72" height="72"
alt="Unusable Page Action Icon" />
<h3 id="rules">Define rules for activating the extension</h3>
<p>
Define rules for when the extension is usable by calling
<a href="/declarativeContent"><code>chrome.declarativeContent</code></a>
under the
<a href="/runtime#event-onInstalled"><code>runtime.onInstalled</code></a>
listener in a <a href="/background_pages">background script</a>.
The <a href="/samples#search:page%20action%20by%20url">Page action by URL</a>
sample extension sets a condition that the url must include a ‘g’.
If the condition is met,
the extension calls <a href="/pageAction#method-show">
<code>declarativeContent.ShowPageAction()</code></a>.
</p>
<pre data-filename="background.js">
chrome.runtime.onInstalled.addListener(function() {
// Replace all rules ...
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
// With a new rule ...
chrome.declarativeContent.onPageChanged.addRules([
{
// That fires when a page's URL contains a 'g' ...
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { urlContains: 'g' },
})
],
// And shows the extension's page action.
actions: [ new chrome.declarativeContent.ShowPageAction() ]
}
]);
});
});
</pre>
<h3 id="enable_disable">Enable or disable the extension</h3>
<p>
Extensions using <code>"page_action"</code>
can activate and disable dynamically by calling
<a href="/pageAction#method-show"><code>pageAction.show</code></a> and
<a href="/pageAction#method-hide"><code>pageAction.hide</code></a>.
</p>
<p>
The
<a href="https://developer.chrome.com/extensions/samples#search:mappy">
Mappy
</a>
sample extension scans a web page for an address
and shows its location on a static map in the <a href="#popup">popup</a>.
Because the extension is dependent on page content,
it cannot declare rules to predict which pages will be relevant.
Instead, if an address is found on a page it calls
<code>pageAction.show</code>
to colorize the icon and signal the extension is usable on that tab.
</p>
<pre data-filename="background.js">
chrome.runtime.onMessage.addListener(function(req, sender) {
chrome.storage.local.set({'address': req.address})
<b>chrome.pageAction.show(sender.tab.id);</b>
chrome.pageAction.setTitle({tabId: sender.tab.id, title: req.address});
});
</pre>
<h2 id="icons">Provide the extension icons</h2>
<p>
Extensions require at least one icon to represent it.
Provide icons in PNG format form the best visual results,
although any format supported by WebKit
including BMP, GIF, ICO, and JPEG is accepted.
</p>
<h3 id="icons">Designate toolbar icons</h3>
<p>
Icons specific to the toolbar are registered in the
<code>"default_icon"</code> field under
<a href="/browserAction"><code>browser_action</code></a> or
<a href="/pageAction"><code>page_action</code></a> in the manifest.
Including multiple sizes is encouraged to scale for the 16-dip space.
At minimum, 16x16 and 32x32 sizes are recommended.
</p>
<pre data-filename="manifest.json">
{
"name": "My Awesome page_action Extension",
...
"page_action": {
"default_icon": {
"16": "extension_toolbar_icon16.png",
"32": "extension_toolbar_icon32.png"
}
}
...
}
</pre>
<p>
All icons should be square or they may be distorted.
If no icons are supplied, Chrome will add a generic one to the toolbar.
</p>
<h3 id="icon_size">Create and register additional icons</h3>
<p>
Include additional icons in the following sizes
for uses outside of the toolbar.
</p>
<table>
<th>Icon Size</th><th>Icon Use</th>
<tr>
<td>16x16</td><td>favicon on the extension's pages</td>
<tr>
<tr>
<td>32x32</td>
<td>Windows computers often require this size.
Providing this option will prevent size distortion from shrinking
the 48x48 option.</td>
<tr>
<tr>
<td>48x48</td><td>displays on the extensions management page</td>
<tr>
<tr>
<td>128x128</td><td>displays on installation and in the Chrome Webstore</td>
<tr>
</table>
<p>
Register icons in the manifest under the <code>"icons"</code> field.
</p>
<pre data-filename="manifest.json">
{
"name": "My Awesome Extension",
...
"icons": {
"16": "extension_icon16.png",
"32": "extension_icon32.png",
"48": "extension_icon48.png",
"128": "extension_icon128.png"
}
...
}
</pre>
<h2 id="additional_features">Additional UI Features</h2>
<h3 id="popup">Popup</h3>
<p>
A popup is an HTML file that is displayed in a special window
when the user clicks the toolbar icon.
A popup works very similarly to a web page;
it can contain links to stylesheets and script tags,
but does not allow inline JavaScript.
</p>
<p>
The
<a href="https://developer.chrome.com/extensions/samples#search:drink">
Drink Water Event
</a>
example popup displays available timer options.
Users set an alarm by clicking one of the provided buttons.
</p>
<img src="{{static}}/images/user_interface/popup.png"
height="300"
alt="Popup sample screen shot" />
<pre data-filename="popup.html">
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Water Popup&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;img src='./stay_hydrated.png' id='hydrateImage'&gt;
&lt;button id='sampleSecond' value='0.1'&gt;Sample Second&lt;/button&gt;
&lt;button id='15min' value='15'>15 Minutes&lt;/button&gt;
&lt;button id='30min' value='30'>30 Minutes&lt;/button&gt;
&lt;button id='cancelAlarm'>Cancel Alarm&lt;/button&gt;
&lt;script src="popup.js"&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p>
The popup can be registered in the manifest,
under browser action or page action.
</p>
<pre data-filename="manifest.json">
{
"name": "Drink Water Event",
...
"browser_action": {
"default_popup": "popup.html"
}
...
}
</pre>
<p>
Popups can also be set dynamically by calling
<a href="/browserAction#method-setPopup">
<code>browserAction.setPopup</code></a> or
<a href="/pageAction#method-setPopup"><code>pageAction.setPopup</code></a>.
</p>
<pre data-filename="background.js">
chrome.storage.local.get('signed_in', function(data) {
if (data.signed_in) {
chrome.browserAction.setPopup({popup: 'popup.html'});
} else {
chrome.browserAction.setPopup({popup: 'popup_sign_in.html'});
}
});
</pre>
<h3 id="tooltip">Tooltip</h3>
<p>
Use a tooltip to give short descriptions or instructions to users
when hovering over the browser icon.
</p>
<img src="{{static}}/images/user_interface/tooltip.png"
height="100"
alt="Tooltip" />
<p>
Tooltips are registered in the <code>"default_title"</code> field
<a href="/browserAction"><code>browser_action</code></a> or
<a href="/pageAction"><code>page_action</code></a> in the manifest.
</p>
<pre data-filename="manifest.json">
{
"name": "Tab Flipper",
...
"browser_action": {
"default_title": "Press Ctrl(Win)/Command(Mac)+Shift+Right/Left to flip tabs"
}
...
}
</pre>
<p>
Tooltips can also be set or updated by calling
<a href="/browserAction#method-setTitle">
<code>browserAction.setTitle</code></a> and
<a href="/pageAction#method-setTitle">
<code>pageAction.setTitle</code></a>.
</p>
<pre data-filename="background.js">
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.browserAction.setTitle({tabId: tab.id, title: "You are on tab:" + tab.id});
});
</pre>
<p>
Specialized locale strings are implemented with
<a href="/i18n">Internationalization</a>.
Create directories to house language specific messages
within a folder called <code>_locales</code>.
The following image shows a filepath for an extension that supports English
and Spanish locales.
</p>
<img src="{{static}}/images/user_interface/locales_messages.png"
height="50"
alt="File structure for locale strings" />
<p>
<a href="/i18n-messages">Format messages</a> inside of each language's
<code>messages.json</code>.
</p>
<pre data-filename="en/messages.json">
{
"__MSG_tooltip__": {
"message": "Hello!",
"description": "Tooltip Greeting."
}
}
</pre>
<pre data-filename="es/messages.json">
{
"__MSG_tooltip__": {
"message": "Hola!",
"description": "Tooltip Greeting."
}
}
</pre>
<p>
Include the name of the message in the tooltip field instead of the message
to enable localization.
</p>
<pre data-filename="manifest.json">
{
"name": "Tab Flipper",
...
"browser_action": {
"default_title": "__MSG_tooltip__"
}
...
}
</pre>
<h3 id="omnibox">Omnibox</h3>
<p>
Users can invoke extension functionality through the
<a href="/omnibox">omnibox</a>.
Include the <code>"omnibox"</code> field in the manifest
and designate a keyword.
The
<a
href="https://developer.chrome.com/extensions/samples#search:omnibox%20new">
Omnibox New Tab Search
</a>
sample extension uses "nt" as the keyword.
</p>
<pre data-filename="manifest.json">
{
"name": "Omnibox New Tab Search",\
...
<b>"omnibox": { "keyword" : "nt" },</b>
"default_icon": {
<b>"16": "newtab_search16.png",</b>
"32": "newtab_search32.png"
}
...
}
</pre>
<p>
When the user types "nt" into the omnibox,
it activates the extension.
To signal this to the user,
it greyscales the provided 16x16 icon
and includes it in the omnibox next to the extension name.
</p>
<img src="{{static}}/images/user_interface/omnibox.png"
alt="Active Omnibox Extension" />
<p>
The extension listens to the <a href="/omnibox#event-onInputEntered">
<code>omnibox.onInputEntered</code></a> event.
After it's triggered, the extension opens a new tab containing
a Google search for the user's entry.
</p>
<pre data-filename="background.js">
chrome.omnibox.onInputEntered.addListener(function(text) {
// Encode user input for special characters , / ? : @ & = + $ #
var newURL = 'https://www.google.com/search?q=' + encodeURIComponent(text);
chrome.tabs.create({ url: newURL });
});
</pre>
<h3 id="context_menu">Context Menu</h3>
<p>
Add new <a href="/contextMenus">context menu</a> options by
granting the <code>"contextMenus"</code> permission in the manifest.
</p>
<pre data-filename="manifest.json">
{
"name": "Global Google Search",
...
<b>"permissions": ["contextMenus",</b> "storage"],
"icons": {
<b>"16": "globalGoogle16.png",</b>
"48": "globalGoogle48.png",
"128": "globalGoogle128.png"
}
...
}
</pre>
<p>
The 16x16 icon is displayed next to the new menu entry.
</p>
<img src="{{static}}/images/user_interface/contexticon.png"
height="300"
alt="Context Menu Icon" />
<p>
Create a context menu by calling
<a href="/contextMenus#method-create"><code>contextMenus.create</code></a>
in the <a href="/background_pages">background script</a>.
This should be done under the
<a href="/runtime#event-onInstalled"><code>runtime.onInstalled</code></a>
listener event.
</p>
<pre data-filename="background.js">
chrome.runtime.onInstalled.addListener(function() {
for (let key of Object.keys(kLocales)) {
chrome.contextMenus.create({
id: key,
title: kLocales[key],
type: 'normal',
contexts: ['selection'],
});
}
});
</pre>
<pre data-filename="locales.js">
const kLocales = {
'com.au': 'Australia',
'com.br': 'Brazil',
'ca': 'Canada',
'cn': 'China',
'fr': 'France',
'it': 'Italy',
'co.in': 'India',
'co.jp': 'Japan',
'com.ms': 'Mexico',
'ru': 'Russia',
'co.za': 'South Africa',
'co.uk': 'United Kingdom'
};
</pre>
<p>
The Global Google Search context menu example
creates multiple options from the list in
<a
href="/extensions/examples/api/contextMenus/global_context_search/locales.js">
locales.js
</a>.
When an extension contains more than one context menu,
Google Chrome automatically collapses them into a single parent menu.
</p>
<img src="{{static}}/images/user_interface/contextmenu.png"
height="300"
alt="Multiple Context Menus will Collapse" />
<h3 id="commands">Commands</h3>
<p>
Extensions can define specific <a href="/commands">commands</a>
and bind them to a key combination.
Register one or more commands in the manifest under the
<code>"commands"</code> field.
</p>
<pre data-filename="manifest.json">
{
"name": "Tab Flipper",
...
"commands": {
"flip-tabs-forward": {
"suggested_key": {
"default": "Ctrl+Shift+Right",
"mac": "Command+Shift+Right"
},
"description": "Flip tabs forward"
},
"flip-tabs-backwards": {
"suggested_key": {
"default": "Ctrl+Shift+Left",
"mac": "Command+Shift+Left"
},
"description": "Flip tabs backwards"
}
}
...
}
</pre>
<p>
Commands can be used to provide new or alternative browser shortcuts.
The <a href="/extensions/samples#search:tab%20flipper">Tab Flipper</a>
sample extension listens to the
<a href="/commands#event-onCommand"><code>commands.onCommand</code></a>
event in the <a href="/background_pages">background script</a>
and defines functionality for each registered combination.
</p>
<pre data-filename="background.js">
chrome.commands.onCommand.addListener(function(command) {
chrome.tabs.query({currentWindow: true}, function(tabs) {
// Sort tabs according to their index in the window.
tabs.sort((a, b) => { return a.index < b.index; });
let activeIndex = tabs.findIndex((tab) => { return tab.active; });
let lastTab = tabs.length - 1;
let newIndex = -1;
if (command === 'flip-tabs-forward')
newIndex = activeIndex === 0 ? lastTab : activeIndex - 1;
else // 'flip-tabs-backwards'
newIndex = activeIndex === lastTab ? 0 : activeIndex + 1;
chrome.tabs.update(tabs[newIndex].id, {active: true, highlighted: true});
});
});
</pre>
<p>
Commands can also create a key binding
that works specially with its extension.
The <a href="/extensions">Hello Extensions</a>
example gives a command to open the popup.
</p>
<pre data-filename="manifest.json">
{
"name": "Hello Extensions",
"description" : "Base Level Extension",
"version": "1.0",
<b>"browser_action"</b>: {
"default_popup": "hello.html",
"default_icon": "hello_extensions.png"
},
"manifest_version": 2,
"commands": {
<b>"_execute_browser_action"</b>: {
"suggested_key": {
"default": "Ctrl+Shift+F",
"mac": "MacCtrl+Shift+F"
},
"description": "Opens hello.html"
}
}
}
</pre>
<p>
Because the extension defines a
<a href="/user_interface#browser"><code>broswer_action</code></a>
it can specify
<code>"execute_browser_action"</code> in the commands to open the popup file
without including a <a href="/background_pages">background script</a>.
If using
<a href="/user_interface#page"><code>page_action</code></a>,
it can be replaced with <code>"execute_page_action"</code>.
Both browser and extension commands can be used in the same extension.
</p>
<h3 id="override">Override Pages</h3>
<p>
An extension can <a href="/override">override</a>
and replace the History, New Tab, or Bookmarks
web page with a custom HTML file.
Like a <a href="#popup">popup</a>,
it can include specialized logic and style,
but does not allow inline JavaScript.
A single extension is limited to overriding only one
of the three possible pages.
</p>
<p>
Register an override page in the manifest under the
<code>"chrome_url_overrides"</code> field.
</p>
<pre data-filename="manifest.json">
{
"name": "Awesome Override Extension",
...
"chrome_url_overrides" : {
"newtab": "override_page.html"
},
...
}
</pre>
<p>
The <code>"newtab"</code>
field should be replaed with <code>"bookmarks"</code> or
<code>"history"</code> when overriding those pages.
</p>
<pre data-filename="override_page.html">
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;New Tab&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Hello World&lt;/h1&gt;
&lt;script src="logic.js">&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
...@@ -516,6 +516,10 @@ ...@@ -516,6 +516,10 @@
"title": "Overview", "title": "Overview",
"href": "/extensions/overview" "href": "/extensions/overview"
}, },
{
"title": "Design User Interface",
"href": "/extensions/user_interface"
},
{ {
"title": "Extension Quality Guidelines FAQ", "title": "Extension Quality Guidelines FAQ",
"href": "/extensions/single_purpose" "href": "/extensions/single_purpose"
......
{{+partials.standard_extensions_article article:articles.user_interface/}}
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