APIs as stateless service calls

Note: This is the original proposal and is kept for historical reasons. It may be made obsolete by more recent documentation.

We want to give extensions programmatic APIs to interact with many of the browsers' subsystems. Some examples include: tabs, history, bookmarks, downloads, and the omnibar.

An interesting wrinkle in Chrome is that in reality, these data structures exist in another process. Because of this, it's impossible to keep a perfectly consistent view of the world in the extension process. There will at time be conflicts between what the extension thinks the current state of the system is, and what it actually is. We need to design APIs that makes this fundamental problem as easy to deal with as possible.

Proposal

We could model these APIs as stateless service calls, similar to the way AJAX applications work. Each call will be asynchronous, and just dumb data gets passed back and forth across the void.

For example:

window.onload = populate;

function populate() {

extension.bookmarks.getAllBookmarks(function(results) {

// Assuming something MochiKit-ish is present.

forEach(results, function(bookmark) {

var div = DIV(A(bookmark.title, bookmark.url), " ", A("edit"));

div.id = "b" + result.id;

div.lastChild.onclick = bind(edit, result.id);

return div;

});

});

}

function edit(id) {

var newTitle = prompt("Enter new title");

extension.bookmarks.updateBookmark({id: id, title: newTitle});

}

The advantages of this approach:

Chrome doesn't have to send copies of browser state to any extension process until it is requested.

Chrome doesn't have to have complex machinery to keep multiple copies of browser state synchronized across processes.

The API reflects the underlying reality -- that the interaction with the browser is asynchronous.

Fits a wide variety of applications, so many APIs can follow a similar pattern, making new APIs easier for chrome-team to build and easier for developers to learn.

This feels like idiomatic JavaScript.

The disadvantages:

Not object-oriented. The verbs are at the top level and accept nouns as arguments. This could get ugly in cases where a subsystem has many nouns.

More coupled to JavaScript (bad if we ever wanted to make these APIs available to other languages).

May encourage us to be lazy with API design by trying to wedge everything into this CRUD idiom.

Details

Conflicts

Without cross-process locking, conflicts can occur in any of our APIs. For example, you can attempt to navigate a tab that has since been closed. My feeling is that conflicts should *not* be treated as exceptions. For simple things, we should just let the last change win. For more complex cases that make no sense, we should print a warning to the console, but not treat it as a fully-fledged error. In the limit, there is nothing the developer can really do about these cases. We can propagate the real state back to the extension via events and the extension can do something about it if it wants (I think in most cases, it won't care).

Constraints

Many of these data models have constraints and side-effects. For example, it doesn't make sense to set a window's zIndex negative, and changing the index of a tab affects the index of other tabs in the same window. Constraints and side-effects in this system would typically be handled by the server side and propagated back to the client by events, if the extension developer had registered to receive them.

Non-data APIs

Some APIs don't fit this model. For example, interacting with the omnibar or receiving requests from content scripts. We'd just have to handle these on a case-by-cases basis. I've outlined some ideas below.

Alternative Ideas

The main alternative is to have a stateful DOM-style API, like this:

foreach(window.extensions.bookmarks, function(bookmark) {

var div = DIV(A(bookmark.title, bookmark.url), " ", A("edit"));

div.id = "b" + result.id;

div.lastChild.onclick = bind(edit, bookmark);

return div;

});

function edit(bookmark) {

var newTitle = prompt("Enter new title");

bookmark.title = newTitle;

bookmark.save();

}

Themaindownsideofthis approach is that it requires synchronizing the state of the relevant object models between the extension and browser processes, and resolving conflicts. Also, as in my example here, we still might want to have an explicit update step if the operation would require writing to disk in the browser process.

API Hierarchy Proposal

chromium.

// An event is a way to register to be notified when something happens. In the rest of this

// documentation, Events are referred to as first-class, with the following notation:

//

// event name(Arg1Type arg1, Arg2Type arg2, ...);

//

// What this means is that the Event in variable "name" wants handlers with the signature:

//

// void handler(Arg1Type arg1, Arg2Type arg2, ...)

class Event

void addListener(object callback(...))

void removeListener(object callback(...))

bool hasListener(object callback(...))

// Dispatches the event. Mainly this would be used by the framework, but it might be useful

// to expose to developers.

void dispatch(...)

// A class that is essentially a client to an extension "server" process. Provides ways to

// interact with the extension server, usually from a content script, but theoretically from

// content or another extension.

class Extension

constructor(int id)

Port connect()

string getURL(string path)

// One end of a connection between an extension process and a content script (or content, or