How to Jump Through Hoops and Make a Chrome Extension

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.

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:

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.

Hi Citizen, I haven’t used this feature but as I do use the base64 encoded image embedded in CSS trick mentioned in the post. It’s worked well for Chrome and as I don’t update my images between releases it hasn’t been an inconvenience.