7 November 2009 - 23:32Chrome extension: edit Gmail textarea in an external editor
A month or two ago, I switched to using Chrome daily builds (on Debian) as my primary browser. As with most people switching to Chrome, I love the stability and speed, but there are a few extensions from Firefox I sorely miss. A major one is mozex, which allows you to edit textareas in an external editor (It’s All Text also does this). I used this for editing Gmail. I used Gnus as my primary mail and news client for years, and one of the big sticking points to switching to webmail was the anemic textarea editing interface. I’m just used to all of the trappings of Emacs — dict-mode, fill-paragraph, the keybindings, remembrance agent, etc.
Anyway, I decided to see if I could whip up a quick and dirty Chrome extension to substitute for mozex. And when I say quick and dirty, I mean it — this code would make a Perl-happy sysadmin blush. But I hacked something up in a few hours, and I thought I’d put it out there since I find it useful. Maybe someone else can run with it and make it more complete like mozex. With a few one-line edits, this extension can be made to allow the editing of virtually any textarea in any external editor. I limited it to Gmail because I don’t know if there is the possibility of resource leaks or other weird side-effects from running it more extensively. Also, it’s hard-coded to use Emacs, but you could just as easily use Gvim (have it spawn an xterm with an editor or use emacs-client or whatever).
I’ve put the extension source files up here:
http://www.thegibson.org/blog/files/emacs_chrome
Edit: see http://www.thegibson.org/blog/files/emacs_chrome/sync_ver — the original only works right on platforms like Linux due to a serendipitous interaction (see comments).
Edit2: see http://www.thegibson.org/blog/files/emacs_chrome/ba_sync_ver/ — this version uses a Chrome “browser action” rather than a “page action.” The “browser action” extension type is better suited for this. Note that some of the text below is not longer applicable to this new and simpler version, so see comments. I’ll try to make a new post soon with the updated info.
The extension comes with a Python script (you’ll need Python 2.6 to run it). The pycl.py script is a little web server running on port 9292. Run the web server and leave it in the background. Then add the extension to Chrome (go to chrome://extensions). Once you add the extension, you can load up Gmail and either compose or reply to a plain text message (not HTML). Then use the “compose in a new window” icon to open a new window, and you’ll see an Emacs 23 icon appear in the “page action” area (the upper right-hand corner of the address bar, where the SSL lock icon goes). Clicking the icon sends the contents of the text area to the Python web server, which writes it to a temporary file (using tempfile.NamedTemporaryFile) and spawns an editor. Edit your text; when you’re done, save and exit. In the meantime, the background script will start polling the web server to see if the editor has terminated. When the editor finishes, the file contents are read and sent back to Chrome, which modifies the textarea with the new contents.
Anyway, it has several major limitations — first and foremost, it only works consistently if you compose or reply in a new window (click that little diagonal arrow icon in the upper right hand corner of the message frame). Sometimes it works on the main Gmail page, but not always. Due to the hooks it uses, if you open the new window while viewing the message, and then click reply, you’ll have to type something in the textarea or un-focus and re-focus the window before it’ll “notice” the existence of the new textarea. This is a limitation of the way the extension content script is finding the textarea in the page. Maybe the fact that it only works consistently in a new window has to do with Gmail’s crazy dynamic DOM manipulation. Or maybe — perhaps even more likely — I’m missing something obvious. I’m sure someone with more Javascript / DOM / experience could probably fix this. I’ve never used Javascript in any context before, so it was an interesting experience. I basically looked at two Chrome example extensions — 1) the RSS feed subscription and 2) the Gmail checker — and munged their code and techniques together.
Changing editors
To change editors, just edit pycl.py and modify the line:
p = subprocess.Popen(["/usr/bin/emacs", f.name])
Making it work on arbitrary textareas
If you want to change the extension to work on more than just Gmail, first edit majifest.json and change the “matches” line:
"matches": ["http://mail.google.com/*", "https://mail.google.com/*", "file://*/*"],
"matches": ["http://*/*", "https://*/*", "file://*/*"],
The second matches example works on all http and https websites.
Then, in the ta_find.js file, change the getElementsByName(‘body’); line:
result = document.getElementsByName('body');
result = document.getElementsByTagName('textarea');
The second will find any textarea on the page (one limitation of the current incarnation is that it will only deal with the first textarea, however).
Making it not suck
This is my first foray into in-browser Javascript programming. I’m sure there’s a lot of missing exception handling in background.html when dealing with the XmlHttpRequest objects. There might be resource leaks too. And there are corner cases where it won’t get the textarea contents to send to the external editor correct (like if you only use mouse cut and paste to change the contents around without any keypresses). Also, in the Python script, there’s not much error handling either, and using a dedicated private directory (like mozex) rather than tempfile.NamedTemporaryFile is probably advisable on shared systems. Also, it’d be great if some Javascript wizard could help figure out why it only works with Gmail when you open a new window. Ideally I would have liked to add a context menu item to all textareas, but I figured it was easier to use existing Chrome extensions as templates and do it with the “page action” mechanism.
Thoughts on Javascript
Looking at the way that the Gmail checker and the RSS feed subscription work, I see an extensive use of closures and continuation-passing style asynchronous programming. Given JavaScript’s reputation, this is definitely a lot nicer and cleaner than than what I expected. Steve Yegge has also pointed out several times that JavaScript is a nicer language than people actually give it credit for, and now I know firsthand.
19 Comments | Tags: Uncategorized