After the Deadline

How to Jump Through Hoops and Make a Chrome Extension

Posted in Talking to myself by rsmudge on May 14, 2010

Last week, we released After the Deadline for Google Chrome. I like Chrome. It’s low on UI clutter and it’s very fast.

Chrome for the extension developer is a rapidly changing world. I wanted After the Deadline for Google Chrome to match our Firefox add-on feature for feature. I hit and overcame a few road blocks meeting this goal.

This blog post is highly technical and deals with some of the innards of writing Chrome extensions. If you’re not interested in this kind of stuff, then take note that I like Chrome, it’s not done yet, and this makes things hard at times. I’m working to bring the best possible proofreading experience to Google Chrome. You may stop reading now.

If you’re still here, that means you’re a developer. I hope this information helps you in your Chrome extension development adventure.

How to refer to internal images in CSS

Google Chrome, like Firefox, makes extension resources available via a special URL. In Firefox, you set the identifier for your extension and can reference images and other resources using this URL. In Google Chrome this  URL depends on your extension’s ID. This extension ID changes depending on whether the extension is loaded as loose files or packaged. Because of this, you should not hard code the URL to an extension resource in your CSS file.

So how can one refer to internal images or other resources in a CSS file?

One option is to avoid referring to internal images or resources at all. You can set CSS properties using JavaScript and chrome.extension.getUrl(‘resource.ext’). This is kind of hacky and I didn’t want to set and unset hover listeners just to do something CSS already gives me for free.

Another option, discovered in this thread, is to convert your images to base64 and embed them as data URLs in your CSS file. It’s an extra step in the beginning but it solves the problem of referring to internal images.

div.afterthedeadline-button
{
background: url(data:image/png;base64,data goes here) transparent no-repeat top left !important;

Once this hack is in place, you won’t have to worry about extension IDs in your CSS files again.

Good luck with those IFRAMEs

Content scripts (Chrome JS extensions) run in a sandbox separate of the environment scripts attached to a page see. This is good as it reduces the possibility of extensions conflicting with web applications. Content scripts see the same DOM that user scripts see. It is possible to make changes to the DOM and inspect it. I recommend that you read the Chrome extension tutorial and watch Google’s video to understand content scripts.

Unfortunately, Google left a few toys out of the sandbox. It’s nearly impossible to work with an IFRAME. The contentWindow property of any IFRAME DOM element is null. Also window.frames is empty. This is a known bug.

Thankfully, the contentDocument.documentElement property does exist. Through this I can set and get the contents of an IFRAME. That’s close to what I want, but not exact. To proofread an editor, After the Deadline creates an editable DIV and copies style information from the editor to this new DIV. To make this convincing for IFRAMES, I have a need to access style information from the contentWindow property.

I tried to make a content script that figures out if it’s attached to an IFRAME. If it is, the script could communicate the needed information to the extension background script via Chrome’s message passing mechanism.

Unfortunately this didn’t work because Chrome only allows scripts to attach to URLs that have an http:// or https:// scheme. Dynamically generated IFRAMEs used by WYSIWYG editors usually have an empty source attribute which does not match an http:// or https:// scheme.

This thread suggests adding a SCRIPT tag to the DOM to execute a script outside the Chrome extension sandbox. However this isn’t necessarily a straight forward process either.

Execute a Script Outside the Chrome Sandbox

The Chrome extension sandbox exists to protect user scripts from extension scripts and vice versa. It would also be dangerous if a malicious user-land script could get into the Chrome sandbox and manipulate the Chrome extension APIs. For these reasons, it’s natural that Google Chrome would discourage extensions from running scripts outside the sandbox. I tried to insert a SCRIPT tag with a SRC attribute into the site’s DOM using jQuery. This didn’t work.

What did work was injecting inline JavaScript that constructs a SCRIPT tag with a SRC attribute from the site’s DOM. Here is the code:

jQuery('body').append('<script type="text/javascript">(function(l) { 
   var res = document.createElement('SCRIPT'); 
   res.type = 'text/javascript'; 
   res.src = l; 
   document.getElementsByTagName('head')[0].appendChild(res); 
})('"+chrome.extension.getURL("scripts/inherit-style.js");</script>');

You’ll want to replace chrome.extension.getURL('scripts/inherit-style.js') with your resource. This is a convenient way to execute code outside of the extension sandbox.

Beware of WebKit Specific Styles

To make my proofreader look pretty, I inherit as many style properties as I can from the original editor. Mitcho showed me this great trick to copy the styles of one element to another:

var css = node.defaultView.getComputedStyle(node, null);
for (var i = 0; i < css.length; i++) {
    var property = css.item(i);
    /* note that I'm assuming jQuery here, proofreader is the note inheriting the property */
    proofreader.css(property, css.getPropertyValue(property));
}

This trick works fine in Chrome, except I found myself scratching my head when some DIVs were editable even though their contentEditable attribute was undefined. The opposite also held true, sometimes my DIV was not editable even though I defined the contentEditable attribute as true. I learned that WebKit has a CSS property -webkit-user-modify that trumps this attribute.

It’s unlikely you’ll ever encounter this, but one day, someone will do a google search, find this post, and I’ll have given them three hours of life they would have lost otherwise.

Final Thoughts

I like Chrome. It’s a good browser. The world of Chrome extensions is changing and expanding rapidly. On one hand, extensions can’t do simple stuff yet, like add items to the context menu. On the other hand, this is being worked on.

Now for the final hoop. There are three distributions of Google Chrome. These are the stable channel, beta channel, and the developer channel. I started out developing in the developer channel and later downgraded to the beta channel as I continued my development. This was a mistake. There are big differences between the stable channel and beta channels. For example, browser actions (toolbar buttons) are allowed to have popup menus. These popups work in the beta channel (5.x) but not in the stable channel (4.x).

Before you release, be aware of these differences. I recommend developing against the stable channel. If you rely on features from a new version, implement them and then verify that your extension degrades nicely on the old version of Chrome. That’s it.

If you’re willing to jump through some hoops you can make a great Chrome extension. I found the Chrome extension mailing list very helpful.

Thanks to Google and the Chromium community for developing a great browser. I’m ok with jumping through a few hoops.