Wednesday, March 11, 2015

Developing browser extensions using JavaScript with Crossrider framework


Note: Please see my update on Crossrider status (12 Jan 2016).

As developers (or humans) we need to balance our skills. Sharp focus is valuable, yet tunnel-visioning oneself is not a good idea — there's so much to learn!

While honing my HTML/CSS/JS kung-fu, I was unexpectedly tasked with something new. We needed to create a browser extension to go with Roadmap, a product our company works on.

A completely new area — or so I thought. Some searching showed a simpler path. I could create extensions with my existing experience. Even more — those extensions could share a (more or less) common code base across all mainstream browsers.

I do not intend to do a full comparison of possible frameworks. When we made our choice a year ago one company stood out — Crossrider. After having developed (as I mentioned before), tested, deployed and updated an extension that runs on Chrome, Firefox and Safari, I have to say that Crossrider delivers on the promise of, quoting their front page,  “Cross Browser Extensions in Minutes”.

Any serious logic will need more time, sure, but the core can be up and running in minutes. Bonus part — actual development is done using my (currently) favorite JavaScript!

Sold. Here are some notes and observations from this experience.

The Task



Our extension allows accessing Roadmap's data and functions (e.g. task start dates, multiple assignments, estimations, time tracking) when viewing to-do items on integrated 3rd-party sites — Basecamp (New and Classic) to start with, with more to come.

You can see (or use, if you register) the result here: Roadmap Extension in Chrome Web Store. Note that the visual style wasn't developed by me, except for a few personal minor touches it was provided by the skillful Roadmap team. My work was to implement the extension: lock, stock and barrel. I mean — markup, style and logic.

Features & Process

Crossrider is, in layman terms, a wrapper around your JavaScript code with a consistent set of API methods. For each browser the wrapper's implementation could be different, yet it is transparent to our code. The API methods cover most needs for interacting with browser tabs, loaded pages, network requests etc.

This is where it was a discovery process for me: you can't simply bundle all your logic in the same source code, browsers are more nuanced than that. Crossrider introduces three “scopes”, which manage different functional areas of your extension.

Configuration popup
allows setting global values used across tabs.
“Page” scope — JavaScript in this scope gets injected into any page that the browser opens. Here you can have logic to manipulate the DOM — add, remove, modify on-page elements; add user event handlers, inject custom styles, and so on.

Basically, this part of code handles everything that happens on the page. Any page you visit — browser loads it first, then Crossrider injects and runs your code. You can check if this is the page you need (by URL or any other on-page criteria) and do what you need with it.

“Background” scope performs tasks that are outside of the page context, and are instead “closer” to the browser. For example — manipulating browser tabs, monitoring network requests (a very neat and powerful feature!), configuring display of browser button.

“Popup” scope — speaking of browser buttons, pressing on one opens a special HTML page, which is defined in this “popup” scope. This feature is quite limited, yet still needed to provide a visible way to configure your extension.

While these scopes are separated by their nature, Crossrider offers a handy messaging API, which allows sending data-loaded messages between scopes. For example, if background scope of Roadmap extension detects an “update” network request from Basecamp New, then it tells the page scope to check for changes on the page — since it is possible that new todos were added.

All the API is neatly documented, and the actual development happens in Crossrider's online IDE, which is able to connect to already installed extension in the same browser and update it as needed (except for Safari and Internet Explorer — below I'll share my thoughts on browsers). It is possible to add resource files (you may need it to include CSS, HTML or images, and to modularize your JS code), and in general the IDE is on the simple side, but it does its job. There's even integration with JSHint, which warns about most obvious errors before pushing code update to the extension.

The IDE.
It is also possible to configure a “Debug Mode”,
which allows editing files using your editor of choice,
yet I didn't bother with it since the stock IDE works for me.

Roadmap Extension

While figuring everything out took a while, the extension itself is fairly simple.

When a page loads, the extension checks whether it is being run on either of the two Basecamp sites. If yes — it detects the todo items are shown on the page, sends an API request to the Roadmap server, and displays the necessary information.

To start with, it adds neat “R” icons to each todo that RM knows about. Clicking on these opens a popup with detailed todo information, and it is possible to edit this data, start / stop a timer, or log worked time for the task.

The red icon stands for “running timer”.
The todo popup shows minimal required data “at a glance”,
allowing to go directly to detailed page in Roadmap itself.

All of this requires quite a lot of user interaction, a number of API requests to the server etc, but the pleasing outcome is that it works.

Browser Support

Crossrider claims support for desktop versions of Chrome, Firefox, Safari and Internet Explorer. In my experience this is true, but there are nuances to be found with each.

Firefox has been the best one to work with. The extension “just runs”, not compatibility issues or any such thing. The browser itself is democratic, not trying to limit how you install your extension or what you do with it.

Yet one significant weakness is that currently it isn't possible to publish Crossrider extensions to Mozilla Addon Directory. which is a huge shame. Extensions fail automatic checks performed on submission, and, having gone through this process myself, I see that the complaints aren't about my code — they are about the code provided as part of Crossrider framework, and thus out of our control. Crossrider team is aware of this, yet it is hard to say whether any progress is being worked on in this regard.

One more problem that I found with Firefox is an incorrect implementation of Content Security Policy (CSP), which could be problematic in some cases. Briefly — same-URL policy, if set by the site, doesn't allow running browser scripts (which includes bookmarklets or extensions) on pages that don't allow loading scripts from other sources. Thankfully, Basecamp doesn't have this, but we saw this while testing with some other task-tracking systems, and I've shared with Crossrider team the result of my investigation in their GetSatisfaction support system.

Chrome, thus, is our main development and distribution platform. The browser itself causes the least pain, everything is compatible, and submission to Chrome Web Store works wonderfully.

Presentation often means attention.
Google tries hardest to “sell” our extension.
Apple doesn't even bother, and Mozilla doesn't allow us on their store. Sigh.

The only issue that really bothers me is the recent ban on installing extension from sources other than the Web Store. There are “developer” ways to side-step it, so really not an issue, yet I am concerned about the direction that Chrome is heading — from openness to “caring about the users” by limiting them to least possible options.

Which naturally brings us to Safari :) The compatibility of extensions is good when it works, but you'll need to go through several gates of Apple hell before you even get there — running your extension in your browser on your computer requires a special developer certificate, developer tools are quite limited, and there are enough browser quirks to drive a mad person even madder. Best approach I found was to perform all development in Chrome, and then load the result in Safari, testing and debugging the Safari-specific issues.

So while with a bit of sweat and pain, the extension does work on Safari, and we're happily featured in the Safari Extensions Directory.

Did I forget anyone? Ah. Yes…

Internet Explorer. Oh. Theoretically, the extension should work, and for simpler extensions it should be OK to test and claim IE support. In our case, though, it is an uphill battle to try to make it work. Since so few people use it, we decided to leave working out the “IE wrinkles” as lower priority, and then it turned out that the Internet Explorer extension runs in some limited JS context, that even some simplest JavaScript functions fail to run (e.g. .indexOf, .forEach etc). The error reporting is almost absent, too, so even debugging these issues means placing tons of console.log statements around the code, trying to guess where it fails now. On top of this, updating an extension once you change something in the code doesn't always work, sometimes requiring going to Control Panel / Add & Remove Programs, and even that is flaky.

Don't get me wrong, otherwise I think that IE10 is a huge improvement on the previous versions, but its extension support looks to be an afterthought. May be Microsoft's “Spartan” effort will be better — we'll see.

But for now we decided to support Firefox, Chrome and Safari. This covers an overwhelming majority of our user base.

Summary

You can tell that I am pleased with this approach. Some development, especially debugging (especially on some browsers) can be fiddly, yet in general it feels great to know that almost any extension (within reason) can be developed using this approach — given time, perseverance and creativity :)

And this is the main takeaway for me — I am glad that this project came about, and that I didn't try to wriggle out of it because “I don't know how to do it” and “This is outside of my expertise”. Turns out, I can do it, and I hope I'll have more new and interesting projects in the future.

We die the day we stop learning.

One thing that I especially wanted to highlight is Crossrider's wonderful support team. They can't always fix everything, some of the more significant issues were communicated to the development team and are still pending, yet they respond quickly, always pay the maximum attention to your request, are willing to check your code and suggest possible improvements, and left me impressed.

No, I am no affiliated with them in any way. I just enjoyed working with their product and communicating with their staff.

Makes me think that doing your job well and treating your customers with care means so much in our hectic world.

Photo: Emmanuel Huybrechts

2 comments:

  1. Your evaluation about Crossrider is timely, Anton. I created a simple Chrome extension from scratch and was looking to create an IE version. It seems there's no information on making and publishing IE extensions from scratch, so I was considering Crossrider.

    However, I noticed that in their EULA they say my extensions will be copyright work of Crossrider as well. It that typical and should I be worried about it?

    ~CC

    ReplyDelete
    Replies
    1. Hey CC, thanks for reading. In my experience IE was very unstable and unpredictable in development (for example, see the question I created about some strange incompatibilities: https://getsatisfaction.com/crossrider/topics/special-js-code-context-for-extensions-in-ie), so prepare for lots of testing and debugging.

      As for the EULA—I cannot help you since I didn't spend much time studying it (probably an important omission), my customer did review it and confirmed that they're fine with it. Your circumstances may differ. It is an important consideration, though, that you become dependant on the Crossrider ecosystem—using their API for many important functions (like AJAX requests), and delivering the extension from their servers. Even when you download an .xpi file for installing, it contains request to their backend to download the latest code for the extension, so you never properly own it.

      Still, in our case we saved lots of time thanks to cross-browser nature of the resulting code, allowing us to release for Chrome, Firefox and Safari. And so far they've been very reliable and responsive with support. I hope it continues that way.

      Delete