Monday, March 26, 2012

Delegated Content, Indirection, and the Two-Way Street

Seems like we just wrapped up the Orion 0.4 release, but the web never sleeps. One point of emphasis for our 0.5 effort is to develop a story for visual plugins. Our first stake in the ground when we started building Orion was that the browser should be the IDE, not Orion itself. In the cloud, your tools might come from anywhere, and our job in Orion is to provide pages for important development tasks (like JavaScript/HTML/CSS editing, managing code contributions, etc.) and an integration platform to let you seamlessly link to the pages needed to perform your development tasks.

In discussions about plugins and extension points, we have resisted the idea of the IDE mashup, where we provide visual extension points in the way that Eclipse defined views and editors. We want the user to use the browser (or browser extensions) to arrange the pages the way they see fit, building an IDE from task-focused pages. That said, we know that simply linking from Orion to tools at other sites is not a well integrated workflow. So one way of dipping our toe into the visual plugin waters is to provide a way for a plug-in to contribute page content that is wrapped inside the common Orion page "chrome." There is little, if any, interaction between the content and the chrome to start with. But by starting with some very shallow integrations that solve real world problems, we hope to advance the level of integration between third party content and the common Orion services.

Delegated content vs. consumable chrome

There are two directions, outward and inward, in which we can think about integrating third party content with Orion. With delegated content, we can provide an Orion page that has all the common page trim, but delegates its content outward to a plug-in. In a consumable chrome scenario, we provide a javascript library that can be consumed by a third party page. The page brings the Orion chrome inward, by making library calls that assemble the interesting links back to Orion. Both scenarios are reasonable ways to go about the problem, and we think that the things that content and chrome want to talk to each other about are the same in either case. Our first explorations have been with delegated content, for two reasons:
  • With delegated content, we can bring unmodified content into an Orion page that was developed without any knowledge of Orion. We can use sites with existing embedding conventions and web API inside of Orion. It's important to learn how far we can push this kind of integration. Can a usable workflow be built from parts that know nothing about each other?
  • Building a small, performant consumable chrome library requires some refactoring. Today, the Orion banner is rendered by pages in Orion, so the banner code has access to everything from the service registry and extension points to the Orion workspace. Refactoring this code so that a banner library could access Orion services remotely requires a bit more work, and a bit more understanding of the problem.

The most shallow integration

We start with a very shallow integration. We want to link to an external site from Orion, and launch it inside an Orion page so that the user can link back to Orion. Any number of simple integrations can be done in this manner. We'll use a WebChat plugin to show how it works. The plugin source is here.

We start by defining a new service extension point, In its simplest form, this extension point describes the name of the visual plugin, the id of the contribution, and a URI Template that describes how to link to the content. The WebChat plugin looks like this:

provider.registerServiceProvider("", {}, {
    id: "orion.webchat.content",
    name: "Webchat",
    uriTemplate: ""

The URL was obtained by using the "Add webchat to your site" wizard to create a custom URL that opens webchat up on the orion-dev IRC channel. To link to our new page from Orion, we add a link to the main Orion banner using the extension. The main point of interest here is that the href instructs Orion to open the content page (the Orion page that hosts delegated content) and specifies the id of the content provider. This id must match the id in the above extension point. The {OrionHome} variable is used to fully qualify the address to the content page.

provider.registerServiceProvider("", serviceImpl, {
    name: "Webchat",
    id: "",
    href: "{OrionHome}/content/content.html#,contentProvider=orion.webchat.content"

When the Webchat link is clicked, the delegated content page will be opened and the URI specified in the extension will be loaded into the page content. The end result is a webchat page wrapped in the Orion chrome.

This isn't too earth shattering, but already we have external content that looks like it belongs in Orion and can link back to orion via the banner.

Indirection from the workspace and back

A more interesting scenario is that of the remote editor. Web developers often need to edit more than just JavaScript, CSS, or HTML. One common task is to edit the images that are shown on a site. We decided to integrate a web-based image editor using the visual plugin idea. We chose the Pixlr image editor because it is written to be embedded in other pages, and it also has an API for launching the editor on remote content and configuring it in specific ways. Our goal was to implement an open/edit/save workflow using Orion extension points and the existing Pixlr API. In this scenario, the integrating plugin and the use of URI template variables provide a level of indirection that glues together the Orion and Pixlr API.

We start with two existing extension points, orion.edit.editor and orion.navigate.openWith. The editor extension defines an image editor and the link for opening it. You'll notice that the link is very similar to the Webchat link. The navigator extension associates this editor with files that have image content types.

provider.registerServiceProvider("orion.edit.editor", {}, {
    id: "orion.pixlr",
    name: "Pixlr Image Editor",
    uriTemplate: "{OrionHome}/content/content.html#{Location},contentProvider=orion.pixlr.content"
provider.registerServiceProvider("orion.navigate.openWith", {}, {
    editor: "orion.pixlr",
    contentType: ["image.gif", "image.jpeg", "image.ico", "image.png","image.tiff"]

If you followed the first example carefully, you'll already expect to see an extension point whose id is "orion.pixlr.content." The uriTemplate for the editor is using the delegated content page with the pixlr contentProvider id. There is also a new variable in the URI template, the {Location} variable. This variable will be filled in with the location of the resource on which the editor is opened.

The URI template used in the pixlr content extension is a bit more complicated than Webchat, because it uses the pixlr API to open pixlr on the image that was selected in the navigator.

provider.registerServiceProvider("", {}, {
    id: "orion.pixlr.content",
    name: "Pixlr",
    saveToken: ["imgapi?image=","imgapi&image="],
    saveTokenTerminator: "&",
    uriTemplate: "{OrionHome}{Location}&referrer=Orion&title={Name}&locktype=true&exit={ExitURL}&target={SaveURL}imgapi&locktitle=true,contentProvider=orion.pixlr.content"

The template opens pixlr, but uses the {OrionHome} and {Location} URI template parameters to tell Pixlr where to find the image file. Those variables will be set by the content page to the Orion installation and the location of the resource that was selected in the navigator. The {Name} variable fills in the name of the file, which Pixlr can use when showing the file. Armed with this information, Pixlr can be opened on the proper file. Since the hosting content page knows where the file lives in the workspace, the banner includes a complete breadcrumb and all related pages associated with that file.

While you're in the pixlr editor, there's nothing specific to Orion going on. However, when you get ready to save the file, you'll see that Orion is a possible destination for the save.

The appearance of Orion in the save dialog comes about because of the Pixlr invocation API. The referrer and target parameters give Pixlr a name and URL to use for saving the resulting file. When calling the provided URL, Pixlr provides a parameter that specifies the location of the saved file. The saveToken and saveTokenTerminator parameters for the extension point provide additional information for parsing the location out of the save URL. All of this means that when the user saves the file, Pixlr calls the provided Save URL, which is an Orion page. The content iframe now contains the save page, which can parse the URL to determine the location of the file and confirm with the user that the content should be saved back to Orion.

The workflow is not ideal, but is somewhat usable. Because the file save URL is invoked by Pixlr with an HTTP GET, the URL should be stable. It cannot actually store the file. Instead, we put up a page that lets the user approve the saving of the file back to Orion. A link to the file is provided so that the user can look at the file and make sure that it should be saved. These extra confirmation steps mean that the workflow is not ideal for an "edit/save/edit/save/edit/save" lifecycle, but it definitely saves the user the step of downloading an image, editing it, and uploading it back into the Orion workspace.

Toward a two-way street

So far, we've shown how you can integrate an editor using delegated content. Though the workflow is a bit clunky, what's promising about this scenario is that three domains are involved, and only one (the plugin code) knows about the integration. Orion hosts the page and renders the common page elements. A plugin hosted at github uses knowledge of the Pixlr API to describe an editor. The Pixlr site provides the content, using existing API, without any knowledge of its hosting page. We imagine that many more integrations of this type can be developed independently of Orion, providing some shallow but useful integrations. Add some additional indirection, such as web intents actions, and we could have a breadth of editing scenarios available in Orion.

The next step is to start working on a "two way street" concept. The examples here still use the "don't call us, we'll call you" model of Orion plugins. The Pixlr plugin extends Orion in specific ways, and it is called when Orion needs to do something with the plugin. We also make a lot of simplifying assumptions in the code that allow this scenario to work. The fact that we implement our own page to receive the save request means that we can develop our own internal handshake for having the save page signal the outer page that the workspace save should be performed. If instead, we implemented some service APIs that plugins could use to talk to the host page, we could provide even better integration.

  • So far, the remote content is available to Pixlr only when the Orion workspace is "world readable." So data hosted on orionhub will be visible to Pixlr, but data from your localhost would not be. We need to come up with a more general story for exporting data from Orion to a publicly readable space on the web (with user permission.)
  • The save scenario only works because Pixlr lets us specify a save URL, and we have developed a page that performs the save. We also use a specific window.postMessage handshake for the inner save page to signal the outer page that save should happen. We'd like to expose the file service in some way to the plugin so that the save could be performed directly by the plugin. Even for plugins unaware of Orion, the Orion save page could use API to perform the save rather than signal the host page to do it.
  • You likely noticed that the Pixlr editor has its own menu bar, while the Orion toolbar is empty. Imagine an Orion-aware remote editor that instead contributed commands to the Orion toolbar.
  • Allowing Pixlr to post progress, status, or error information to the Orion page would improve the integration. Likewise, it would be nice if any prompts or confirmations would be performed by the outer page.
  • The keybindings inside and outside the content iframe are completely different. It'd be nice to see some of the common Orion bindings work even when focus is in the inner content area.

As you can see, even the simplest integration attempts produce a pretty good laundry list of what we'd like to see. We'll continue to deepen these integrations throughout the 0.5 release. In the meantime, we invite you to try to integrate your favorite tool into Orion and let us know how it goes.