As part of my Pimp Your Mac With Perl Talk at the London Perl Workshop I talked very briefly about one of my newer Perl modules that I haven’t blogged about before: Mac::Safari::JavaScript. This module allows you to execute JavaScript inside your browser from within a Perl program and it just work.
The Power of Just
Piers Cawley gave a lightning talk at YAPC::Europe::2001 about just in which he explained the power of interfaces that just do what you need whenever you need them to and hide a bunch of complexity behind themselves.
To run JavaScript in Safari you just call safari_js
with the JavaScript you want to execute:
use Mac::Safari::JavaScript qw(safari_js);
safari_js('alert("Hello World");');
This JavaScript is then executed in the current tab of the frontmost Safari window. If you want to return a datascructure to Perl from your JavaScript then you just do so:
my $page_title = safari_js("return document.title;");
No matter how complex[1]: it is:
my $details = safari_js(<<'ENDOFJAVASCRIPT');
return {
title: document.title,
who: jQuery('#assignedto').next().text(),
};
ENDOFJAVASCRIPT
If you want to have variables avalible to you in your JavaScript then you just pass them in:
my $sum = safari_js("return a+b;",{ a => 1, b => 2 });
No matter how complex[2] they are:
use Config;
safari_js("alert(config['version']);",{config => \%Config})
And if you throw an exception in JavaScript, then you just catch it the normal way:
use Try::Tiny;
try {
safari_js("throw 'bang';");
} catch {
print "Exception '$_' thrown from within JavaScript";
};
Peaking Under the Hood
So, how, does this all hang together? Well, it turns out that running JavaScript in your browser from AppleScript isn’t that hard:
tell application "Safari"
do JavaScript "alert('hello world');" in document 1
end tell
And running AppleScript from within Perl isn’t that hard either:
use Mac::AppleScript qw(RunAppleScript);
RunAppleScript($applescript);
So it’s a simple problem, right? Just nest the two inside each other. Er, no, it’s not just that simple.
It turns out that handling the edge cases is actually quite hard. Typical problems that come up that Mac::Safari::JavaScript just deals with you for are:
- How do we encode data structures that we pass to and from JavaScript?
- How do we escape the strings we pass to and from AppleScript?
- For that matter how do we encode the program itself so it doesn’t break?
- What happens if the user supplies invalid JavaScript?
- How do we get exceptions to propogate properly?
- With all this thunking between layers, how do we get the line numbers to match up in our exceptions?
And that’s the power of a module that handles the just for you. Rather than writing a few lines of code to get the job done, you can now just write one and have that one line handle all the weird edge cases for you.
-
Okay, so there are some limits. Anything that can be represented by JSON can be returned, but anything that can’t, can’t. This means you can’t return circular data structures and you can’t return DOM elements. But that would be crazy; Just don’t do that. ↩
-
Likewise we can only pass into JavaScript something that can be represented as JSON. No circular data strucutres. No weird Perl not-really-data things such as objects, filehandles, code refences, etc. ↩