Click here to Skip to main content
14,690,956 members
Articles » Web Development » Applications & Tools » Applications
Posted 17 Jun 2016


7 bookmarked

Chrome Development Part 2: Apps

Rate me:
Please Sign up or sign in to vote.
4.78/5 (5 votes)
17 Jun 2016CPOL
This article explains how to code apps that run inside the Chrome browser.

1. Introduction

While the preceding article discussed Chrome extensions, this article focuses on developing Chrome apps. The two topics have a lot in common, and the difference between them may seem subtle at first. It helps to clarify three terms:

  1. Software application — Software intended to help a user perform an activity, such as processing text, editing an image, or playing a game. Usually has a dedicated user interface.
  2. Web app — A software application that runs inside a browser (an application inside an application)
  3. Browser extension — Software that customizes the browsing experience or augments a browser's capabilities. Rarely has a user interface.

A Chrome application (usually just app) is a web app intended to run in the Chrome browser. To a user, it looks and feels like a regular web app, but it can access a wealth of features that regular apps cannot. The drawback is that Chrome apps can only run in Chrome.

From a developer's perspective, the difference between Chrome apps and extensions is clear. In an extension, content scripts provide access to web documents and popups provide a basic user interface. Apps don't have content scripts or popups, but the user interface is much more powerful—an app can run in a window separate from the browser.

In addition to the user interface, another advantage of apps over extensions involves low-level access to the user's system. Chrome apps can access hardware capabilities including Bluetooth, TCP/UDP sockets, and the universal serial bus (USB). They can also read and write to the user's filesystem. Personally, my favorite feature is tts (text-to-speech), which allows an app to generate speech in a number of different languages.

This article explains how to code apps that create windows, access the user's filesystem, and generate speech from text. But before delving into the code, it's important to understand the structure of a Chrome app and the manner in which it can be launched in the browser. These topics are discussed in the next section.

2. Fundamentals of App Development

The file structure of a Chrome app is nearly identical to that of an extension. There are three fundamental characteristics to be aware of:

  1. An app's features and capabilities must be defined in a file called manifest.json.
  2. An app may have background scripts, but no content scripts or popups.
  3. Like extensions, apps can be managed through the Chrome Developer Dashboard at the URL chrome://extensions.

This section starts by discussing the format of manifest.json. Then it walks through the process of installing and launching a simple app.

2.1  manifest.json

An app's manifest accepts many of the same properties as an extension's. These include manifest_version, name, description, version, icons, and permissions. The primary difference is that an app's manifest must have a property named app. This contains a background property that associates a scripts field with an array containing one or more background scripts.

If you download the example archive for this article, you'll find a folder called basic_app. The manifest.json for this app is easy to understand:

  "manifest_version": 2,
  "name": "Trivial app",
  "description": "This app doesn't do anything",
  "version": "1.0",
  "icons": { "16": "images/cog_sm.png",
             "48": "images/cog_med.png",   
             "128": "images/cog_lg.png" },  
  "app": {
    "background": {
      "scripts": ["scripts/background.js"]

This should look familiar, as most of the properties have the same names and values as the extensions presented in the preceding article. Remember that the scripts array must name at least one background script. The rest of this article discusses many features that an app's background script can access.

2.2  Loading and Launching Apps

If you open Chrome and visit chrome://apps, you'll see all of the apps that can be executed. But the page won't let you install new apps. To manage apps, you need to visit the Chrome Developer Dashboard discussed in the preceding article. The URL is chrome://extensions, and if the Developer mode box is checked, the top of the page will look as shown in Figure 1.

Figure 1: The Chrome Developer Dashboard in Developer Mode

Image 1

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

The archive attached to this article contains a folder called basic_app. To load this app into Chrome, follow these four steps:

  1. Download 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_app folder.
  4. Press OK to load the app.

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

Figure 2: New Entry in the Chrome Developer Dashboard

Image 2

It's important to notice the Launch link. Unlike extensions, apps don't start executing when they're installed. An app must be specifically launched.

The Reload link reloads the app into Chrome. At the bottom, the background page link opens the app's background page. This is helpful when you want to view the console associated with a background script.

3. and

A major difference between apps and extensions is that apps can execute in their own windows. To create these windows, an app must call functions from two APIs: and The first API makes it possible to respond to events in the app's lifecycle. The second API provides functions that create and configure windows.


The API (not to be confused with the chrome.runtime API) defines three events. Table 1 lists each of them.

Table 1: Events of
Event Trigger
onLaunched Fires when the app is executed
onRestarted Fires when the app is restarted
onEmbedRequested   Fires when another app seeks to embed the app

Each of these has a function called addListener. This accepts a callback function to be invoked when the event is triggered. For example, the following code defines an anonymous function to be invoked when the app is launched: {

The arguments received by an event's callback function depend on the type of event.


To define its user interface, an app can create one or more windows. In code, an app's window is represented by an AppWindow object. The functions of the API make it possible to create and manage AppWindows, and they're listed in Table 2.

Table 2: Functions of
Function Description
create(string url,
  CreateWindowOptions opts,
  function callback)
Creates a new AppWindow
current() Returns the current AppWindow
get(string id) Returns the AppWindow with the given name
getAll() Returns an array containing all AppWindows
canSetVisibleOnAllWorkspace()  Identifies whether the platform supports displaying
windows on all workspaces

create is the main function to know. Its only required argument is the first, which identifies the HTML file that defines the window's structure and appearance. The third argument is a callback function that receives the newly-created AppWindow.

The second argument of create is a CreateWindowOptions object. This has five optional properties:

  • id — a string that uniquely identifies the window (no relation to the app's ID)
  • outerBounds — A BoundsSpecification that sets the window's position and size
  • innerBounds — A BoundsSpecification that sets the position and size of the window's content
  • frame — Frame type: none or chrome (the default is chrome)
  • state — The window's initial state: normal, fullscreen, maximized, or minimized.

The second and third properties are BoundsSpecifications. A BoundsSpecification has eight properties that define position and size: left, top, width, height, minWidth, minHeight, maxWidth, and maxHeight. For example, the following code creates a 200x200 AppWindow that can't be resized less than 100x100 pixels or greater than 300x300 pixels:"window.html", 
  { "outerBounds": {"width": 200, "height": 200, 
                    "minWidth": 100, "minHeight": 100, 
                    "maxWidth": 300, "maxHeight": 300}},
  function(win) {

As shown in this code, the third argument of create is a callback function that receives the AppWindow after it's created. Table 3 lists the properties of an AppWindow object.

Table 3: AppWindow Properties
Property Description
id The window's ID, if available
outerBounds The window's bounds
innerBounds The bounds of the window's inner content
contentWindow The corresponding JavaScript window object
fullscreen() Resizes the window to fill the screen
isFullscreen() Identifies whether the window occupies the screen
minimize() Minimizes the window size
isMinimized() Identifies whether the window is minimized
maximize() Maximizes the window size
restore() Return the window to its original size/position
drawAttention() Draw attention to the window
clearAttention() Clear attention from the window
close() Close the window
show() Show the window
hide() Hide the window
  (boolean onTop) 
Configures whether the window should always
be displayed on top of others
isAlwaysOnTop() Identifies whether the window is always on top
  (boolean intercept) 
Configures whether the window should receive
all key events
  (boolean visible)
Configures whether the window should be
displayed on all workspaces

These properties are straightforward to use and understand. The contentWindow property is interesting because it provides access to the underlying JavaScript Window. This allows the background script to access common JavaScript Window properties like document, setTimeout(), and alert().

In addition to providing the functions in Table 3, AppWindow provides events that enable scripts to respond to the window's events. Table 4 lists these events and the occurrences that trigger them.

Table 4: AppWindow Events
Event Trigger
onBoundsChanged  Fires when the window's bounds change
onClosed Fires when the window is closed
onFullscreened Fires when the window is set to occupy the screen
onMaximized  Fires when the window is maximized
onMinimized Fires when the window is minimized
onRestored Fires when the window returns to its original bounds

Each window event has a property named addListener that accepts a callback function to be invoked when the event occurs. In all cases, this callback doesn't receive any arguments. To demonstrate, the following code prints a message to the console when the window is moved or resized:"window.html", 
  function(win) {
    win.onBoundsChanged.addListener(function() {
      console.log("Bounds changed");

In this example, the window's content is determined by window.html. This HTML document may include one or more JavaScript files called window scripts, A window script can access the same capabilities as a background script, and the next section presents a window script that accesses the user's filesystem.

4. Interfacing the Filesystem

Most web apps can't interact with the user's files or system hardware. But a Chrome app can access the client's filesystem through the chrome.fileSystem API. This capability is powerful but complex. Therefore, I've split the discussion into four parts:

  1. Obtaining permission
  2. The chooseEntry function
  3. The File API
  4. The chrome.fileSystem API

The last part of this section presents an example app that doubles a file's content. That is, it reads text from a file and writes it to the end of the file,

4.1  Obtaining Permission

To access a user's filesystem, an app must request permission by adding one or more values to the manifest's permissions array:

  • permissions: ["fileSystem"] — requests read-only permission
  • permissions: [{"fileSystem": ["write"]}] — requests read/write permission
  • permissions: [{"fileSystem": ["write", "retainEntries", "directory"]}] — requests permission to read, write, retain files, and access directories in addition to files

If permission is granted, a script can access the user's files. If not, an attempt to access the user's filesystem will result in an error.

4.2  The chooseEntry Function

In the chrome.fileSystem API, the chooseEntry function opens a dialog that lets the user choose an existing file/directory or identify a new file/directory. Its signature is given as follows:

chooseEntry(Object opts, Function callback)

The first argument is an object that configures the dialog's appearance and behavior. This has five optional properties:

  • type — Type of file dialog (openFile, openWritableFile, saveFile, or openDirectory)
  • suggestedName — Suggested name for the file to be opened
  • accepts — An array of objects that filter which files can be selected. Each has three optional properties: description, mimeTypes, and extensions
  • acceptsAllTypes — Whether the dialog accepts files of all types
  • acceptsMultiple — Whether the dialog accepts multiple selections

The second argument of chooseEntry is a callback function that receives the result of the user's selection. The selection result is provided as an Entry or as an array of FileEntry objects.

An example will clarify how chooseEntry works. The following code, executed in a window script, displays a file selection dialog when the user clicks on the button whose ID is browse.

document.getElementById("browse").onclick = function() {
  chrome.fileSystem.chooseEntry( {
    type: "openFile",
    accepts: [ { description: "Data files (*.dat)",
                 extensions: ["dat"]} ]
  }, function(entry) {
    console.log("Entry name: " +;

The dialog's appearance and the initial search directory depends on the user's operating system. Figure 3 shows what the resulting dialog looks like on my Windows 7 machine:

Figure 3: File Selection Dialog Created by chooseEntry

Image 3

If acceptsAllTypes had been set to true, the dialog would allow the user to select any file, regardless of its extension. If acceptsMultiple had been set to true, the dialog would allow the user to select multiple files. But neither property was set, so the user can open one, and only one, *.dat file. The required suffix was configured by setting the extensions field to ["dat"].

When the user selects a file, the example callback function receives a corresponding Entry and obtains the file's name using the name property. To use these Entry objects, you need to be familiar with Google's File API.

4.3  The File API

When HTML5 was being developed, Google proposed a File API that enables web applications to access a user's files. You can read the specification here. The File API wasn't included in HTML5, but the chrome.fileSystem API relies on it to interact with the client's filesystem. This discussion looks at five objects of the File API: Entry, FileEntry, File, FileReader, and FileWriter.

According to the File API, files are represented by FileEntry objects and directories are represented by DirectoryEntry objects. Both inherit from the Entry type, whose properties are listed in Table 5.

Table 5: Entry Properties
Function Description
name The entry's name
fullPath The entry's full path
filesystem The FileSystem containing the entry
isFile Identifies if the entry is a file
isDirectory Identifies if the entry is a directory
toURL(String mimeType) Returns the entry's URL
  (Function successCallback, 
  Function errorCallback)
Provides the entry's last modification date
  (Function successCallback,
   Function errorCallback)
Provides the entry's parent
  (Function successCallback,
   Function errorCallback)
Removes the entry from the filesystem
moveTo(DirectoryEntry parent,
  String newName,
  Function successCallback,
  Function errorCallback)
Moves the entry to the given directory
copyTo(DirectoryEntry parent, 
  String newName,
  Function successCallback,
  Function errorCallback)
Copies the entry to the given directory

Many of these functions accept two callback functions: one to be invoked if the operation completes successfully and one to be invoked if an error occurs. The following code demonstrates how this works. When the user selects the entry in the dialog, the remove function deletes it from the filesystem and prints a message to the log. If an error occurs, the app logs an error message.

chrome.fileSystem.chooseEntry( {
  type: "openFile",
  accepts: [ { description: "Text files (*.text)",
               extensions: ["txt"]} ]
  }, function(entry) {
      function() {console.log("Deletion successful")},
      function() {console.log("Error occurred")}

A FileEntry represents a file in the user's filesystem, and it has two functions beyond those listed in Table 5:

  • file(Function successCallback, Function errorCallback) — Provides the entry's underlying File object
  • createWriter(Function successCallback, Function errorCallback) — Provides a FileWriter capable of writing to the file

By accessing the FileEntry's File, an app can read a file's content using a FileReader. Similarly, the FileWriter makes it possible to write data to a file. The following subsections discuss both capabilities.

4.3.1  Reading Data from a File

Google's File API defines a FileReader object capable of reading data inside a File. In general, using this object requires four steps:

  1. Create a new FileReader with its constructor: var reader = new FileReader();
  2. Invoke one of the FileReader's three read functions.
  3. Assign a function to respond when the FileReader has finished reading data.
  4. Inside the function, access the data through the FileReader's result property.

For the second step, a FileReader object has three functions for reading data:

  • readAsArrayBuffer(File f) — Returns the file's content in an ArrayBuffer (used for binary data)
  • readAsText(File f, String encoding) — Returns the file's content as a string
  • readAsDataURL(File f) — Returns the file's content as a Base64-encoded string

A FileReader has a number of properties related to events, and the onload property identifies a function to be called when the reader has finished loading data from the file. The following code shows how this can be used:

// Create the new FileReader
var reader = new FileReader();

// Assign a function to be called when the read is complete
reader.onload = function() {
  console.log("Result: " + reader.result);

// Call the read function when the File is available
entry.file(function(f) {

To be precise, the FileReader's read functions operate on Blobs, and a File is a type of Blob. The following discussion explains Blobs in greater detail.

4.3.2  Writing Data to a File

The createWriter function of the FileEntry object provides a FileWriter capable of writing data to the underlying file. This is easier to work with than the FileReader, and Table 6 lists its properties.

Table 6: FileWriter Properties
Function Description
length The length of the file in bytes
position The current write position in the file
readyState The writer's status (INIT, WRITING, or DONE)
error An error condition
write(Blob data) Writes data to the file
seek(long long offset) Move the write position to the given location
truncate(unsigned long long size)   Changes the file length to the given size
abort() Terminate the write operation

The write function accepts a Blob. According to the documentation, a Blob is a "file-like object of immutable, raw data". The important thing to know about Blobs is that the Blob constructor accepts an array of strings, Blobs, ArrayBuffers, ArrayBufferViews, or any combination of these objects. For example, the following code writes a string to the file associated with the FileWriter:

writer.write(new Blob(["Example text"]);

Like a FileReader, a FileWriter provides events that can be assigned to functions. If a function is set equal to the writer's onwriteend event, that function will be called when the write operation has completed. If a function is set equal to the writer's onerror event, the function will be called if an error occurs. If entry is a FileEntry, the following code demonstrates how to create a FileWriter and write a Blob to the corresponding file:

// Write the received data to the file
entry.createWriter(function(writer) {

  // Called when the write operation is complete
  writer.onwriteend = function() {
    console.log("Write finished.");

  // Called if an error occurs
  writer.onerror = function(error) {
    console.log("Write error: " + error.toString());

  // Create a new Blob and write its text to the file
  writer.write(new Blob(["Example text"]));

By default, the text will be written to the start of the file. To change where text is written, the app should call the FileWriter's seek function with the desired offset. The next example app demonstrates how this is done.

4.4  The chrome.fileSystem API

The chrome.fileSystem API provides a number of other functions in addition to the chooseEntry function discussed earlier. Table 7 lists chooseEntry and six other functions.

Table 7: Functions of chrome.fileSystem (Abridged)
Function Description
chooseEntry(Options opts,
  Function callback)
Creates a file selection dialog
getDisplayPath(Entry entry,
  Function callback)
Provides the full path for the given entry
getWritableEntry(Entry entry, 
  Function callback)
Provides a writeable entry for the given entry
isWritableEntry(Entry entry,
  Function callback)
Identifies whether the app has permission to
write to the entry
retainEntry(Entry entry) Returns a string identifier for the entry
restoreEntry(String id,
  Function callback) 
Accesses an entry according to its identifier
isRestorable(String id,
  Function callback)
Identifies whether the app has permission to
restore the entry according to its identifier

In most of these functions, the second argument is a callback function to be invoked when the operation is complete. For example, the second argument of getWritableEntry is a callback function that provides the Entry if the app has permission to write to it.

If an app needs to operate on multiple Entry objects, it can assign an identifier to each. retainEntry returns an identifier for an Entry, and then restoreEntry can be used to access Entry objects according to their identifiers.

4.5  Example application

Within the example archive, the code in the file_demo app shows how a file can be read and written to. This contains a background script, background.js, which creates a 200x200 window to serve as the app's user interface. Its code is given as follows:

// Responds when the app is launched {
  // Create the window
    { "outerBounds": {"width": 200, "height": 200} 

The window's appearance is defined by the window.html file, whose markup is given as:

    <button id="browse">Select File</button>
    <script src="scripts/window.js"></script>  

This creates a button and injects the JavaScript code in the window.js script. When the button is clicked, this code invokes chooseEntry to allow the user to select a text file. The script's code is as follows:

document.getElementById("browse").onclick = function() {
  // Open a dialog box
  chrome.fileSystem.chooseEntry( {
    type: "openFile",
    accepts: [ { description: "Text files (*.txt)",
                 extensions: ["txt"]} ]
  }, function(entry) {

    // Create a FileReader
    var reader = new FileReader();

    // Called when the read is complete
    reader.onload = function() {
      // Create a FileWriter
      entry.createWriter(function(writer) {
        // Write to the end of the file;        
        // Log a message when the write operation is completed
        writer.onwriteend = function() {
          console.log("Write successful");

        // Log a message if an error occurs       
        writer.onerror = function(error) {
          console.log("Write error: " + error.toString());

        // Create a Blob and write its text to the file
        writer.write(new Blob([reader.result]));

    // Call the read function when the File is available
    entry.file(function(f) {

After the user selects a file, the app creates a FileReader and associates its onload property with an anonymous function. When the file's text is read, this function creates a FileWriter and calls its seek function to skip to the end of the file. The script writes the file's text to the end of the file, and if the operation completes successfully, it prints a message to the console.

5. Text to Speech

Since Version 14, the Chrome browser has contained an engine for converting text to speech. If the permissions array in an app's manifest contains "tts", the app can access the speech engine using the chrome.tts API. Table 8 lists the API's functions and provides a description of each.

Table 8: Functions of chrome.tts
Function Description
speak(String utterance,
  Object options,
  Function callback)
Generates and emits speech for the given text
getVoices(Function callback)  Returns the set of available voices
stop() Halts speech in progress
pause() Pause speech in progress
resume() Resumes speaking after a pause
isSpeaking(Function callback)  Identifies whether the engine is currently speaking

As its name implies, the speak function generates speech from text and emits the speech. The only required argument is the first, which identifies the text to be uttered. The following code utters the browser's name:


The third argument identifies an function to be invoked before the speech finishes. The second argument of speak is an object that configures how the speech is generated. All of its properties are optional:

  • voiceName — Name of the voice to be used
  • lang — Language to be used
  • gender — Desired gender (male or female)
  • rate — Speaking rate relative to the default rate (2 - twice as fast, 0.5 - half as fast)
  • pitch — Speaking pitch relative to the default pitch (2 - higher pitch, 0 - lower pitch)
  • volume — Speaking volume (between 0 and 1)
  • enqueue — Enqueues the speech after current speech if true, interrupts the current speech if false (default)
  • extensionId — ID of the extension containing the desired speech engine
  • requiredEventTypes — Event types that must be supported
  • desiredEventTypes — Event types of interest
  • onEvent — A function to be called on desired events

As an example, the following code speaks in a British male voice at high speed:

chrome.tts.speak("It's just a flesh wound", {voiceName: "Google UK English Male", rate: 1.5});

getVoices provides an array containing all of the voices that can be generated. Each voice is represented by a TtsVoice object, and the following subsection discusses this in detail.

5.1  The TtsVoice

A Chrome app can generate speech using a number of different voices. Each is represented by a TtsVoice, and Table 9 lists its different properties.

Table 9: TtsVoice Properties
Function Description
voiceName The name of the voice
lang The voice's language
gender The voice's gender: male or female
remote Whether the voice is provided over a network
extensionId   The ID of the extension providing this voice
eventTypes Events that can be triggered by the voice

For example, the following code uses getVoices to access Chrome's pre-installed voices and display their properties:

chrome.tts.getVoices(function(voices) {
  for (var i = 0; i < voices.length; i++) {
    console.log("Name: " + voices[i].voiceName);
    console.log("Language: " + voices[i].lang);
    console.log("Gender: " + voices[i].gender);
    console.log("Extension: " + voices[i].extensionId);
    console.log("Events: " + voices[i].eventTypes);

When this executes in Chrome 51, the array contains twenty voices. Table 10 lists twelve of them along with their gender, language, and supported events.

Table 10: Built-in Voices
Voice Name Gender/Language Events
Google US English female/en-US start, end, interrupted, cancelled, error
Google UK English Male male/en-GB start, end, interrupted, cancelled, error
Google UK English Female female/en-GB start, end, interrupted, cancelled, error
Google español  female/es-ES start, end, interrupted, cancelled, error
Google español de Estados Unidos  female/en-US start, end, interrupted, cancelled, error
Google français female/fr-FR start, end, interrupted, cancelled, error
Google Deutsch female/de-DE start, end, interrupted, cancelled, error
Google italiano female/it-IT start, end, interrupted, cancelled, error
Google Bahasa Indonesia female/id-ID start, end, interrupted, cancelled, error
Google Nederlands female/nl-NL start, end, interrupted, cancelled, error
Google polski female/pl-PL start, end, interrupted, cancelled, error
Google português do Brasil female/pt-BR start, end, interrupted, cancelled, error

When an app selects a voice for text-to-speech generation, that voice determines which speech events the app can respond to. Event handling is configured by setting the desiredEventTypes property in the second argument of speak and by assigning a function to the onEvent property.

5.2  Example Code

In this article's example code, the tts_demo shows how a Chrome app can generate speech from text. The window provides controls for setting the utterance and selecting the voice. Figure 4 shows what this window looks like.

Figure 4: The tts_demo App Window

Image 4

When the Speak button is pressed, the speak function is invoked with the text in the text box. The name of the voice is defined by the combo box selection. The code in the project's scripts/window.js shows how this is accomplished:

var voiceselect = document.getElementById("voiceselect");

// Access available voices
chrome.tts.getVoices(function(voices) {

  // Add voice options
  for (var i = 0; i < voices.length; i++) {
    voiceselect.options[i] = new Option(voices[i].voiceName, i);
  voiceselect.selectedIndex = 2;

// Stop the speech
document.getElementById("stop").onclick = function() {
  // Stop if speaking
  chrome.tts.isSpeaking(function(speaking) {
    if (speaking) {

// Speak in the desired language
document.getElementById("speak").onclick = function() {
  var text = document.getElementById("utterance").value;
  var voice = voiceselect.options[voiceselect.selectedIndex].text;
  chrome.tts.speak(text, {voiceName: voice});

This demonstrates how the isSpeaking and stop functions are used. If the user clicks on the stop image, the app calls isSpeaking to make sure the engine is currently generating speech. If the value provided by the callback function is true, the app calls the stop function. This not only halts the current speech, but also flushes the queue of any pending utterances.


6/17/2016 - Initial article submission


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


About the Author

Matt Scarpino
United States United States
No Biography provided

Comments and Discussions

-- There are no messages in this forum --