It occurred to me that I never blogged about my Source Quicklinks
extension (source). I created it back in 2010, when I started working on the Chrome team, focusing on the WebKit side of things. I was spending a lot of time in Code Search trying to understand how things worked. Chromium's code search instance searches not just the Chromium repository itself, but also all its dependencies (WebKit, V8, Skia, etc.). A lot of times the only way to understand a piece of code was to look at the commit that added it (especially in WebKit code, where comments are scarce but the bug associated with the commit often provides the back story). I was therefore doing a lot of URL mangling to go from Code Search results to Trac pages that had "blame" (annotation) views.
The extension made this process easier, adding a page action that provides back and forth links between Code Search, the Chromium repository (both the ViewVC and the Gitweb sites), WebKit's Trac setup and V8. Later, when the omnibox API became available, it also gained a "sql" search shortcut for the Chromium repository and commits.
Though I no longer work on Chrome, I still find myself going through those code bases quite often. For example, the best way to know whether something triggers layout in Blink/WebKit is by reading the code. I've therefore revved the extension to handle the Blink fork, in addition to cleaning up some other things that had started to bitrot. I also attempted to add cross-links between the WebKit and Blink repositories that takes into account the Blink reorganization, though we'll see how useful that ends up being as the codebases diverge more and more.
A few months ago, as I was making yet another test app to demonstrate a Chrome packaged app API, I wished for a REPL. In theory the Chrome Dev Tools would fit the bill, since the console lets you run arbitrary JavaScript statements. However, using the dev tools would still involve making a manifest with the right permissions and loading an unpacked app, and at least a background page if not an actual window to inspect. Once you inspected the right page, invoking and inspecting the results of asynchronous APIs would be tedious, with a lot of boilerplate to type every time.
I started to think about creating a purpose-built REPL for Chrome apps APIs. A generic REPL seemed out of the question, due to eval being disallowed due to the strict Content Security Policy used by apps. My initial thought involved a dropdown listing all functions in the chrome.* namespace and a way to invoke them with canned values (eval may be disallowed, but dynamic invocation of the form chrome[namespace][methodName](arg) is still possible). However, that seemed clunky, and wouldn't help with APIs like the socket one that need to chain several method calls with the parameters for one depending on the results of another.
I then thought more about the eval limitation, and if I could use sandboxed pages to create the REPL environment. In some ways that seemed contradictory; the whole point of sandboxed pages is that they don't have access to Chrome APIs (unlike the main frame/page). In exchange they can use less safe mechanisms such as eval (a form of privilege separation). However, sandboxed pages can communicate with the containing page and get data from them via postMessage1. In theory the input code could be eval-ed in the sandboxed frame, and when it tried to invoke Chrome APIs, the sandboxed frame would postMessage to the main frame, ask it to run that API method, get the result, and plug it back in the expression that was being evaluated.2
This plan hinged on fact that nearly all Chrome apps APIs are asynchronous already, thus it should be possible to create seemingly functionally identical proxies in the sandboxed frame. That way, as far as the user is concerned, they're running the original API methods directly. There would need to be some additional bookkeeping to make callback parameters work, but there was no technical barrier anymore.
Before talking about that bookkeeping, since we're now five paragraphs into the blog post, I should cut to the chase and give you a link to the REPL app that I ended up building: App APIs REPL
(source). And if you'd like to see it in action, here's a screencast of it showing basic JavaScript expression evaluation and then a more complex example playing around with the socket API to mimic HTTP requests to www.google.com.
Here's how eval-ing the following statement works:
The main frame (also referred to as the "host" in the source code) gets the input and sends it to the sandboxed frame via a EVAL message. The sandbox dutifully evals it.
chrome.socket.create is a stub that was created in the sandboxed frame: at application startup, the main frame walks over the chrome.* namespace and gathers all properties into a map and sends them to the sandbox (via a INIT_APIS message). The sandbox re-creates them, and for function properties and events a stub is generated.
When the stub is invoked, it sends a RUN_API_FUNCTION message to the main frame with the API method (chrome.socket.create in this case) that should be run and its parameters. Most parameters can be copied directly via the structured clone algorithm that is used by postMessage.
However, the second parameter is function that cannot be copied. Instead we generate an ID for it, put it in a pending callbacks map, and send the ID in its place.
On the main frame side, the list of parameters is reconstructed. For function parameters, we generate a stub based on the ID that was passed in. Once we have the parameters, we invoke the API function (via dynamic invocation, see above) with them.
When the stub function that was used as the callback parameter is invoked, it takes its arguments (if any), serializes them and then sends them and its function ID back to the sandboxed frame via a RUN_API_FUNCTION_CALLBACK message.
The sandboxed frame looks up the function ID in the callbacks map, deserializes the parameters, and then invokes the function with them.
The callback function uses the log() built-in function. That ends up sending a LOG message to the main frame with the data that it wants logged to the console.
Events work in a similar manner, with stubs being generated for add/removeListener() in the sandbox that end up adding/removing listeners in the main frame. There are two maps of listener functions, one in the sandboxed frame from ID to real listener, and one in the main frame from ID to stub/forwarding listener. This allows removing of listeners to work as expected.
The console functionality of the REPL is provided by jqconsole, which proved to the very easy to drop in and hook up input and output to. History of the console is persisted across app restarts via the storage API. Additional built-in commands like help and methods (which dumps a list of all available API methods) as implemented as custom getters getters in the global JavaScript namespace of the sandboxed frame. There's also a magic _ placeholder that can be used as a callback parameter or event listener; it will be replaced with a generated function that logs invocations.
In addition to being a useful developer and leaning tool, I hope that this REPL also helps with thinking with a sandboxed mindset. I know that the Content Security Policy that's used in apps has been controversial, with some taking it better than others. However, I think that privilege separation, declarative permissions, tying capabilities to user gestures/intent and other security features of the Chrome apps runtime are here to stay. CSP is applicable to the web in general, not just apps. Windows 8 requires sandboxing for store apps and its web-based apps are taking an approach similar to CSP to deter XSS. Sandboxing was one of the main themes for Mac desktop developers this year, with Apple finally pulling the trigger on sandbox requirements. Developers of large, complex applications were able to adapt them to the Mac OS X sandbox. That gives me hope that the Chrome app sandbox will not prevent real apps from being created. It's is starting with the even more restrictive web platform sandbox and relaxing it slightly, but is generally aiming for the same spot as the Mac one.
I'm also hopeful that there will be improvements that make it even easier to write secure apps. For example, the privilege isolation provided by sandboxed pages was inspired by a USENIX presentation (the paper presupposed no browser modifications, the Chrome team just paved the cowpath).
pkg.js is a library that's cropped up recently for making such main/sandboxed frame communication easier.
Note that this not the desired pattern for communication between the main and sandboxed frames. Ideally messages that are passed between the two should be as high-level as possible, with application semantics, not low-level Chrome API semantics. For example, if your sandboxed frame does image processing, it shouldn't get to pick the image paths that it reads/writes from; instead it should be given (and return) a blob of image data; it's up to the main frame to decide where it gets that image data (by reading a path on disk, from the webcam, etc.). Otherwise if the code in the sandbox is malicious, it could abuse the file I/O capability.
Ann and I have been using Avocado for the past few months to stay in touch while we're apart. It's been working well for us, but it didn't have a great workflow for sharing links (beyond copy-and-paste). Since it now has an API, I thought I could perhaps fix this with a Chrome extension.
Cilantro is that extension (source). It's a straighforward browser action that lets you share the current tab's URL to Avocado. It relies on you being signed into Avocado to make requests (and scrapes a bit of the homepage's JavaScript to get the current user's signature -- Taylor and/or Chris, please don't rename your variables).
About the only clever thing that the extension does is that if you're currently in a Google Reader tab, it will pick up the currently selected item's link to share. It relies on Reader exposing a getPermalink() JavaScript hook1. Chrome extension code normally runs in an isolated world when pointed at a page, so it can't see that function. However, by "navigating" the tab to a javascript: URL, it's able to invoke it, since that code runs in the "main" world. To get at the result, it adds a message listener and then has the javascript: snippet do a postMessage (since the main world can invoke the isolated world's event handlers). This is described in more detail in the extensions documentation.
For the sake of full disclosure, lest you think my graphics skills have suddenly improved, Avocado co-founders Jenna and Chris are friends of mine and they spruced up Cilantro's UI to look less programmer-y minimalist.
This was originally added so that Google Notebook (R.I.P.) could extract the current item when clipping from Reader. Since then, Instapaper's bookmarklet has also started to use it.
A couple of years ago, I switched from the Google Reader team to the Chrome team1. I focused on the WebKit side, working a bunch on layouttests and associated tooling. I became a reviewer, messedaroundwithevents and generally scratched my "move down the stack" itch. However, I realized I missed a few things: Spec compliance and tooling are very important, but they don't necessarily trigger that awesome "we've shipped!" feeling (cf. HTML5 being a living document). I also missed having something concrete to put in front of users (or developers).
Those thoughts were beginning to coalesce when, fortuitously, Aaron approached me in the spring of 2011 about joining Chrome's "Apps and Extensions" team. I'd been a fan of Aaron's work since my Greasemonkey days, and the Chrome extension system had impressed me with its solid foundations. After spending some time getting to know life outside of src/third_party/WebKit, I ended up focusing more on the apps side of things, implementing things like inline installation and tweaking app processes.
Historically, apps and extensions were handled by the same team since Chrome apps rose out of the extension system. They share the manifest format, the packaging mechanism, the infrastructure to definetheirAPIs, etc. The lines were further blurred since apps could use extension APIs to affect the rest of the browser.
As we were discussing in the fall of 2011 where extensions and apps were heading, it became apparent that both areas had big enough goals2 that having doing them all with one team would result in a lack of focus and/or an unwieldy group with lots of overhead. Instead, there was a (soft) split: Aaron remained as tech lead of extensions, and I took on apps.
We've now had a few months to prototype, iterate, and start to ship code (in the canary channel for now). Erik and I gave an overview of what we've been up to with Chrome apps at last week's Google I/O:
It's still the early days for these "evolved" Chrome packaged apps. We're pretty confident about some things, like the app programming model with background pages that receive events. We know we have a bunch of work in other areas like OS integration, bugs and missing features. There are also some things we haven't quite figured out yet (known unknowns if you will), like cross-app communication and embedding (though I'm getting pretty good at waving my hands and saying "web intents").
What makes all this less daunting is that we get to build on the web platform, so we get a lot of things for "free". Chrome apps are an especially good opportunity to play with the web's bleeding edge3 and even the future4.
Going back to the impetus for the switch that I described at the start of this post, doing the I/O presentation definitely triggered that "We've launched!" feeling that I was looking for. However, it's still the early days. To extend the "launch" analogy, we've been successful at building sounding rockets, but the long term goal is the moon. We're currently at the orbital launch stage, and hopefully it'll go more like Explorer 1 and less like Vanguard.
The extensions team had its own talk at I/O about their evolution. Highly recommended viewing, since the same factors influenced our thinking about apps.
Chrome 19 (just released to the stable channel) includes support for Web Intents, and the support is further improved in Chrome 20 (currently in the beta channel). One of the improvements is that downloaded RSS and Atom feeds will now dispatch a view intent. This is neat, getting things a bit closer to truly fixing one of the earliest Chrome bug reports. However, if you're occasionally engaged in some technology necrophilia, then you might prefer seeing the angle brackets instead of handing over the feed to another app.
To that end, I've made Feed Intent Viewer, a simple intent handler that shows the feed as pretty-printed XML. Once you install it, clicking on links such as this one will trigger the sheet shown on the right, and choosing the "Feed Intent Viewer" app will show you your beloved angle brackets.
It's implemented as a packaged app (source), so that all of the feed data is processed locally, instead of being sent to a server. Even better, the download system includes the downloaded data with the intent (as a Blob) so that it doesn't have to be re-fetched at all. When the intent is dispatched with just a URL, then the data is fetched via XMLHttpRequest (this explains why the app has the "your data on all websites" permission). The new-ishresponseType property of XHR is used, so that it can also be read as a blob. The feed blob data is read via a FileReader into a string, so that some light pre-processing can happen (currently, just removal of stylesheets, allowing the raw XML can be displayed). Finally, the feed text is put back into a blob that's served with a text/xml MIME type. This makes WebKit's XML viewer kick in, saving me the trouble of actually having to pretty-print anything.
While writing this up, I got a sense of déjà vu, which turned out to be warranted: In 2007, I created a similar hack to get Firefox 2.0 to show pretty-printed XML for feeds, instead of something friendlier.
Discovery (i.e., how a user finds apps to install) is an interesting aspect of app stores*. In some ways, discovery is not necessary: a significant appeal of the store is that it catalogs all the apps, so if the user is looking for a todo list or Twitter client, it's pretty obvious what to search for. However, that assumes that the user has a specific need in mind already, and is aware that that class of application exists.
Ads to promoteapps are one way to expose users to apps that they hadn't heard of before. More generally, it's interesting to think of other "ambient" mechanisms that piggyback on existing user activities.
Along these lines, I thought I would play around with the Chrome Web Store set of apps. Ideally, if one is browsing a web site that has a corresponding app in the store, a page action icon would appear to indicate this, similarly to feed auto-discovery notification. Conveniently, hosted apps have a urls section in their manifest which indicates which URLs they want to include within the app. This seemed like a pretty good proxy for which URLs the app was "about". I extracted the URL patterns for a bunch of apps, cleaned them up a bit, and used that to implement a Chrome extension (source) which shows the aforementioned page action when visiting pages that match a Chrome Web Store entry.
Once I had that working, it seemed like a straightforward extrapolation to use the history API to also match browser history URLs against app data. When launched the extension shows apps that match history entries, sorted by recency (this is also available via the extension's options page). The fact that the app data lives locally means that all this matching can be done without uploading the history to a server, which is preferable from a privacy perspective.
Installing apps based on visited websites brings up the "aren't web apps just bookmarks?" question. As it turns out, some apps actually show a pretty different UI than the regular website. For example, the New York Times app features the Times Skimmer UI while the Vimeo app uses a "Couch Mode". The other aspect to consider is that bookmarks have several use cases. In addition to being launchers for frequently used sites, bookmarks are also used for gathering collections of items, remembering where to come back later, etc. Special-casing the launcher use case so that it implies "pretty icons on the homepage" may not be such a bad thing, even ignoring the other extracapabilities of apps.
The URL matching approach has its limitations. For example, the Foursquare Maps app doesn't show up for someone who has foursquare.com in their browser history, even though it ostensibly shows Foursquare data. That's because the app uses OAuth to accesses the Foursquare data on the server-side, so it doesn't have foursquare.com URL in its manifest. This sort of limitation could be fixed by allowing an explicit "this app is about this collection of URLs" entry in the manifest, though there are "interesting" implications to allowing an app to associated itself with a website that it doesn't necessarily own. On the plus side, such a mechanism would also allow this approach to be extended to any app store, even non-web app ones.
* "App store" is used generically in this post. Also, these are my idle weekend thoughts, not official Google promulgations.
The extension code itself is nothing interesting (making the icon probably took longer). However, it does showcase a limitation of the current Chrome extension system. Since this extension needs to run code at startup, it needs a background page. The extension system architecture overview has a few more details, but briefly, background pages (and any other extension pages) end up in their own process. In this particular case, the background page is not needed after startup, but there is no way to indicate that, so the process hangs around indefinitely, wasting a bit of memory. There is a bug filed for this, part of a broader collection of changes that would enable certain classes of extensions to remove the need for (long-lived) background pages.
I was recently involved* in investigating a Chrome performance issue that I thought was worth sharing.
This page has a simple CSS 2D animation, with a ball moving back and forth (taking a second to go across the screen). At the left and right endpoints, the location fragment is updated to #left and #right. The intent is to simulate a multi-section page with transitions between the section and each section having a bookmarkable URL.
The puzzling part is that the animation skips a few frames in the middle (right as the ball is crossing the thin line). This may not be noticeable depending on your setup, so here's a video of it. This only happens in Chrome, not in Safari or WebKit nightlies.
Here's some hints as to what's going on, each revealing more and more.
The jerkiness always happens 500ms into the animation (which is the halfway point in the one second version, but one quarter of the way into the 2 second version.
Even though the animating area stays the same, the larger the window size, the bigger the hiccup.
The inspector's timeline view for the page shows regular, small, evenly-spaced repaints, but then suddenly 500ms into the animation, there's a full screen repaint, followed by a large gap before updates resume.
Taking out the location fragment update fixes the jerkiness.
Chrome has a few things that happen with a 500ms delay.
Watching the counter on about:histograms/Renderer4.Thumbnail is helpful.
As it turns out, what's happening is that 500ms after a load finishes, Chrome captures the current page, so that it can be shown on the New Tab Page. This includes getting a thumbnail of the page (which involves repainting all of it and then scaling it down using a high quality filter). Updating the location with the fragment triggers this logic, and the larger the window, the more time is spent painting the page and then scaling it down.
In addition to fixing this on the Chrome side, the best way to avoid this is to update the location at the end of a transition, instead of at the beginning.
* Credit for figuring out what was going on goes to JamesRobinson.