Firefox Extensions: Greasemonkey

Apple is publicizing another iTunes contest. Like before, they are giving away a prize for every 100,000 songs download or entries submitted. The latter is the typical alternative that all such contests require to claim that they aren’t a lottery or another form of gambling.

In this case, the free entry form allows up to 25 entries per day. Apple makes it a little tedious by clearing the form when you go Back to it, so you have to re-enter a ton of personal information each time. They also include a captcha image to prevent automated submissions. That’s all cool, but I’d like it to be just a little easier for me to enter for the free iPod goodness.

Among other things, I’m an experienced Javascript programmer, so I know there should be way to automate the form filling a little bit. Enter Greasemonkey. Greasemonkey is a Firefox extension which allows you to customize webpages on the fly. You create a short text file which gives Greasemonkey some URLs to edit and a bit of Javascript to execute on each webpage.

After you install Greasemonkey, you can find some user script you want to install somewhere on the web and right click to “Install User Script…”. Be careful, of course, because user scripts are NOT SAFE! You need to either understand the code you are installing or trust the person who wrote the script.

I created a Greasemonkey user script that fills in some personal information in the iTunes contest form and sets the cursor in the captcha text field. Here’s the code (note that you will have to edit it to insert your own info before using it):

// ==UserScript==
// @name           Apple's iTunes 1 billion contest helper
// @namespace      http://www.chrisdolan.net
// @description    Prefill some data in a contest entry form
// @include        https://phobos.apple.com/WebObjects/MZFinance.woa/wa/billionSongAlternateEntryForm*
// @include        http://www.apple.com/itunes/1billion/entryform/
// ==/UserScript==

var focus_field = "7.1.49";
var data = {};
data["7.1.5.3"] = "Chris";
data["7.1.5.7"] = "Dolan";
data["7.1.9"] = "1234 Main St";
data["7.1.17"] = "Madison";
data["7.1.19.0.3"] = "WI";
data["7.1.21.0.3"] = "537XX";
data["7.1.25"] = "United States";
data["7.1.29"] = "608";
data["7.1.31"] = "555-1212";
data["7.1.35"] = "January";
data["7.1.36"] = "1";
data["7.1.37"] = "1970";
data["7.1.41"] = "chris@example.com";
data["7.1.45"] = "chris@example.com";

var form = document.forms[0];
for (var i=0; i<form.elements.length; i++) {
   var e = form.elements[i];
   if (e.name == focus_field)
      e.focus();
   var val = data[e.name];
   if (val == null)
      continue;
   if (e.type == "text") {
      e.value = val;
   } else if (e.type.indexOf("select") == 0) {
      for (var j=0; j<e.options.length; j++)
         if (e.options[j].text == val) {
            e.selectedIndex = j;
            break;
         }
   } else
      alert("failed to set "+e.name+" = "+val);
}

Save that as a text file called something like foo.user.js and edit it. In Firefox, drag the folder containing that script into the browser pane. You should see a directory listing for a file:// url. Right click on the user.js file and select “Install User Script…”. Then browse to the contest form. Your data should be prefilled.

If you need to edit the script, use “Tools > Manage User Scripts…” to uninstall the current version and install the edited version by the same steps as above.

Disclaimer: I do not feel that this is cheating on Apple’s contest at all. This script is just a helper similar to form auto-fill. You still have to fill in the captcha manually. This just takes a little of the tedium out of form filling.

The FlipBook user interface

The CoverFlow application is a technology demo that explores a clever way to browse your music collection. It attempts to loosely emulate the experience of flipping through a pile of albums or CDs. The application displays the album art for every album in your iTunes library sorted by artist or album title, and lets you click or scroll through that list in a glassy, 3D-looking showcase.

Most everyone is a fan of clever user interfaces, right? Even more so, we’re fans of clever UIs that are intuitive and efficient. CoverFlow achieves that to a remarkable degree. It superficially resembles Apple’s Front Row application, with it’s 3D icons on a glassy surface within an infinite black space. CoverFlow appears to predate Front Row, however, and in my opinion blows away Front Row for usability.

Interested? Well, CoverFlow is for Mac OS X 10.4 only and requires a modern video card. If that’s not you and you want to get a taste, take a look at my Flash implementation. I’ve recreated the basics of CoverFlow’s look in this functional prototype. This experiment is not specific to music, but instead is a general purpose navigation strategy. I’ve tentatively called this user interface a “FlipBook”. I’m not fond of that name, but I haven’t come up with one I like better yet…

Screenshot of FlipBook UI

Screen shot of the Flash implementation in action

My Flash version loads a list of items from an XML file. For each item in the list, the XML file specifies an image (JPG or SWF only), a label and an optional URL that is invoked on a double-click. The label is rendered in Flash’s subset of HTML and can accept one or more CSS files to style the text.

Currently (as of version 0.02), the user interaction is limited to clicking on items to center them and double-clicking on them to launch their associated URLs.

Is there a future in this? Maybe. Coverflow is very popular, and I can easily imagine the author making a nice bit of cash selling the implementation to Apple for inclusion in Front Row itself. As a general-purpose nagivation tool (i.e. not specific to music), this is a little harder since the one-dimensional nature of the flow of art is limiting. Nonetheless, this UI is more browseable than most online product catalogs. One could imagine a bookshelf implemented like this, showing book covers instead of album covers. Or perhaps a collection of videos of university lectures, organized by department and class.

Private Regression Tests

This article discusses two classes of regression tests: public ones that are included with published software and private ones that are for the author’s use only. The public ones naturally get more exposure because they impact a wider audience. However, personal-use tests are quite valuable too. In the text below, I explore the variety of private tests that are available to you, the programmer, to help you produce more usable and reliable code. The context and code is specific to Perl, but the concepts are applicable to any programming language.

About regression tests

One of the greatest assets of the Perl community is the strong tradition of providing regression tests for the commonly used software libraries that are available via CPAN (the largest archive of Perl software). A regression test is a brief snippet of working code that uses the software in question and compares the actual results to expected results to see if everything works according to plans. The name “regression test” comes from their ability to easily detect new bugs introduced that cause software quality to regress. A set of good tests that pass can give you increased confidence in the quality of the software you’ve written.

Most software libraries that you can download from CPAN include these tests in a t/ subdirectory or in a file called test.pl. The programmers who write CPAN libraries typically run these tests to ensure they all pass before uploading the software. They include the same tests in the package you download so that you can run the tests too. Running the tests on your machine may reveal bugs that the programmer didn’t find; maybe you have a different OS or a different Perl version from what the author has.

Public tests

The tests that ship with software are intended to run anywhere. The user of the software may have a rather different operating environment from you, the author. So, you try to write tests for the lowest common denominator. You stick to writing tests that don’t take a long time, don’t access the network more than absolutely necessary, and don’t ask the user for input. Furthermore, you write tests which don’t assume much about what software the user has previously installed. If your software hasn’t insisted that a spellchecker be installed on the user’s computer, for example, your regression tests had best not try to do any spell checking.

This is a good thing, of course. You want your tests to be easy and automatable so that they are run in a wide variety of environments. That way, you can get help from your user community to find bugs that didn’t occur on your own computer.

Private tests

Besides the public tests that your users are expected to run, you can also employ a suite of private tests in your work. These private tests are not included in your final distribution (that is, not listed in the MANIFEST file). Among these are tests that:

  1. require special additional software that’s difficult or expensive to acquire,
  2. require special configuration to run properly,
  3. don’t affect the quality of the final software, or
  4. take too long to run.

Below are several specific tests that you can include in your development. Each exhibits one or more of the above limitations, so you wouldn’t want to include them in a CPAN distribution. In each case, I introduce a short test script that you can include in your t/ subdirectory. Personally, I name these files with a 00_local_ prefix so they always run first and so that I can easily exclude them from my distribution by adding a \d+_local_ line to my MANIFEST.SKIP file.

Example 1: Spell checking your documentation

It’s a good to have readable documentation and correct spelling helps with that readability. Running a traditional spell checker on Perl code is tedious, since the checker will flag most every variable and function name in your code. Even if you run it just on the documentation, your example code and subroutine lists will trigger the spell checker.

Test::Spelling is a CPAN module that knows how to parse your files looking only for POD and then excluding everything that looks like code examples (namely, C<> blocks, indented code, and =item headers). It makes good use of Pod::Spell to accomplish this, and then sends all of the remaining prose out to an external spell checker (like the open source aspell or ispell programs). Any misspellings are reported as test failures.

However, a typo in the documentation is not as serious as a typo in the code. For this reason, you should not include a Test::Spelling-based test in your distribution. You wouldn’t want a user to decide not to use your code because a spelling error caused the regression tests to fail.

Test::Spelling permits you to indicate any uncommon words in your documentation that would normally trip up the spell checker. You can list those words either in the test script or in the module file itself.

Here is the listing my 00_local_spelling.t:

use warnings;
use strict;
use Test::More;
use Test::Spelling;
set_spell_cmd('aspell -l');
add_stopwords(<DATA>);
all_pod_files_spelling_ok();
__DATA__
CGI
CPAN
GPL
Dolan
STDIN
STDOUT

In this test script, I first load the requisite libraries. Note that there is no eval to see if Test::Spelling is loaded. Public tests often use eval statements to be friendly and forgiving to users, but private tests should be harsh and strict instead of forgiving.

After the preliminaries, I use set_spell_cmd() to indicate which external spell checker it should use. I chose aspell (with the needed -l flag that makes it emit misspellings back to STDOUT) because it has a good reputation and it was easy to install on my Mac via fink install aspell-en.

Next, I use add_stopwords() to tell the script to ignore a list of words that I commonly use that aren’t in the dictionary (“stopwords” is spell checking jargon, apparently — it was news to me). These words listed one per line in the __DATA__ section of the program. I can easily retrieve this list as an array with the <DATA> filehandle iterator. If I come across more words that my spellchecker dislikes, I can either add them here or add them to the module file that is being tested (see below).

Finally, my test script calls all_pod_files_spelling_ok(). This function searches through the blib/ subdirectory looking for any file that contains POD. These can be .pm files in the blib/lib/ subdirectory or command-line programs in the blib/script subdirectory. For each file that is found Test::Spelling extracts the documentation, removes all Perl, and sends the words to aspell. Subsequently aspell reports back any mispelling, or nothing if there are none! If Test::Spelling receives any errors, it echos them as test failures like below. [This is a real example from the Perl::Critic project that I’m working on. This is the first time I ran a spell checker on it.]

% perl Build test test_files=t/00_local_spelling.t verbose=1
...
#   Failed test 'POD spelling for blib/lib/Perl/Critic/Config.pm'
not ok 3 - POD spelling for blib/lib/Perl/Critic/Config.pm
#   in /Users/chris/perl/lib/perl5/site_perl/Test/Spelling.pm at line 72.
# Errors:
#     PBP
#     PERLCRITIC
#     Thalhammer
#     inlucde
#     ommit
...
Failed 1/1 test scripts, 0.00% okay. 55/56 subtests failed, 1.79% okay.

As you can see, some of these are obvious typos (like “ommit”) while others are names (like “Thalhammer”, the last name of Jeffrey Ryan Thalhammer, the primary Perl::Critic author).

The typos should be fixed, naturally, and the others should be flagged as harmless. I will add “Thalhammer” to my global list of words, since I’m working with Jeff a lot these days. On the other hand, “PERLCRITIC” is specific to just this project, so I will add that word to the lib/Perl/Critic/Config.pm file itself.

First the global change. I simply append a line to the test script listed above:

...
__DATA__
CGI
CPAN
GPL
Dolan
STDIN
STDOUT
Thalhammer

Next, I edit lib/Perl/Critic/Config.pm. I fix the “inlucde” and “ommit” typos easily. Then I add some stopwords as follows. First, I find the first instance of any POD by searching for =head. The stopwords only apply to POD that comes after them, so I must be sure to add them at the beginning. Here’s the relevant section of Config.pm before my edits:

...
1;
__END__

=pod

=head1 NAME

Perl::Critic::Config - Load Perl::Critic user-preferences

=head1 DESCRIPTION
...

and after:

...
1;
__END__

=pod

=for stopwords PBP PERLCRITIC

=head1 NAME

Perl::Critic::Config - Load Perl::Critic user-preferences

=head1 DESCRIPTION
...

The only change is the =for stopwords ... line. This must be all on one line (see Pod::Spell for more details) and should be a whitespace-separated list of words.

After making those changes and running the spell test again, I see the following happy output:

% perl Build test test_files=t/00_local_spelling.t verbose=1
...
ok 3 - POD spelling for blib/lib/Perl/Critic/Config.pm
...
Failed 1/1 test scripts, 0.00% okay. 33/56 subtests failed, 41.07% okay.

I found that the global fix for “Thalhammer” by itself fixed 21 of the failing tests. Yay! So, these are easy problems to solve.

To recap, why not include this test in the distribution? Most users do not have aspell installed, let alone Test::Spelling. And even more of them don’t care that “Thalhammer” isn’t in the dictionary (sorry, Jeff). On the other hand, this simple test should remain in your t/ arsenal to catch any future typos you may make.

Example 2: Huge PDF tests

I am the primary author of CAM::PDF, a low-level PDF editing toolkit. The PDF specification very rational and straightforward, but it’s huge and has many addenda. Writing tests to cover all of the nuances of Adobe’s intentions for the document format is prohibitive. To make my life easy, one of the tests I wrote is to read, manipulate and rewrite the specification document itself, which is a 14 MB, 1172-page PDF. I figured that if my code can work with that file, it can work with more than half of the PDFs in the world.

However, there are several problems with that test that make it prohibitive to be a public test. First, it’s expensive: it takes tens of minutes on my dual-G5 Mac. Second, it uses a copyrighted document that I am not permitted to redistribute. Third, even if I could distribute it, CPAN users would rightly berate me for adding a 14 MB file to the download just for the test.

So, I made this a private test. My t/pdf.t file computes its test plan dynamically. If the t/PDFReference15_v5.pdf file is present, over 4000 tests are added to the list: four for each page of the document. I accomplished this by declaring use Test::More without the common (tests => <number>) suffix. Instead, I later use the following code to declare my test plan:

...
my $tests = 2 + @testdocs * 33 + @testpages * 4 + @impages * 4;
plan tests => $tests;
...

In the distributed version of CAM::PDF, I only include three short PDFs for a total of five pages and 129 tests. I think that’s a much more reasonable burden for end-user testing, and it still covers a good fraction of the CAM::PDF code.

Example 3: Test::Distribution, Test::Pod and Test::Pod::Coverage

The Test::Distribution module from CPAN is handy: it contains a collection of sanity checks that ensure you haven’t made any blunders before releasing your package to the world:

  1. It checks that your MANIFEST is present and not broken
  2. It checks that your README is present
  3. It checks that your Changes or ChangeLog is present
  4. It checks that your Build.PL or Makefile.PL is present
  5. It checks that all of your .pm files will compile with perl -c
  6. It checks that you specified a $VERSION
  7. It checks that your POD is not broken
  8. It checks that your POD describes all of your functions

Test::Pod and Test::Pod::Coverage do the same as tests number 7 and 8 above, respectively.

Here is an example 00_local_distribution.t:

use Test::More;
eval { require Test::Distribution; };
plan skip_all => 'Optional Test::Distribution not installed' if ($@);
import Test::Distribution;

Most CPAN authors would agree that a distribution must pass these tests to be considered competent. However, once your distribution passes them and is uploaded, these tests are really a waste of time (except perhaps the perl -c test, which presumably implied by your other tests!). If the README is missing, should perl Build.PL && perl Build test && perl Build install really fail for end users? I say no because the README is non-critical. Therefore, tests like these should be private tests.

Some members of the perl-qa email list and the cpants.perl.org community believe that tests like Test::Pod and Test::Pod::Coverage should be public and included in the distribution. Those advocates say that rewarding authors with “Kwalitee” points when they include POD tests in their CPAN packages will encourage them to write better POD, which is better for everyone.

I strongly agree with that goal, but I believe that the means to achieve that goal is flawed. Presumably once the author has achieved 100% POD coverage, it will remain 100% for all users. Therefore, anyone beyond the author who runs Test::Pod is just wasting their CPU time.

However, an alternative effective method of encouraging authors to strive for 100% POD coverage has not yet been discovered, so I do agree that the Kwalitee reward is an acceptable temporary means. Hopefully we can find something better.

Example 4: Version numbers

Several Perl community members, notably including Damian Conway in his “Perl Best Practices” book, have advocated that all .pm files uploaded to CPAN should have a $VERSION number. Instead, the common practice is that at least one .pm file should have a $VERSION, but omitting $VERSION in any other subsidiary .pm files is OK. The advocates of the ubiquitous $VERSION say that all files should be versioned so downstream programmers can test that they have the exact revision of the code that that they need.

If you agree that $VERSION numbers should be everywhere and, further, that they should be the same in all .pm files, then it’s nice to have a test to ensure that uniformity. The following homebrew test checks all Perl files in the blib/ subdirectory for $VERSION numbers. It would be really nice if someone were to write a Test::* module to accomplish this (hint, hint).

Here is 00_local_versionsync.t. Please note that it’s detection of $VERSION works for my coding style, but is far from universal. If you get $VERSION from $REVISION$, for example, this is a bad solution for you. Nonetheless, it’s a good example of a private test.

use warnings;
use strict;
use File::Find;
use File::Slurp;
use Test::More qw(no_plan);

my $last_version = undef;
find({wanted => \&check_version, no_chdir => 1}, 'blib');
if (! defined $last_version) {
    fail('Failed to find any files with $VERSION');
}

sub check_version {
    # $_ is the full path to the file
    return if (! m{blib/script/}xms && ! m{\.pm \z}xms);

    my $content = read_file($_);

    # only look at perl scripts, not sh scripts
    return if (m{blib/script/}xms && $content !~ m/\A \#![^\r\n]+?perl/xms);

    my @version_lines = $content =~ m/ ( [^\n]* \$VERSION [^\n]* ) /gxms;
    if (@version_lines == 0) {
       fail($_);
    }
    for my $line (@version_lines) {
        if (!defined $last_version) {
            $last_version = shift @version_lines;
            pass($_);
        }
        else {
            is($line, $last_version, $_);
        }
    }
}

This test should be private because if it passes on the author’s computer, it should always pass, and the consequences of a failure are minor compared to an algorithmic error.

Example 5: Copyright statements

Licensing is a big deal. As much as people wish they didn’t have to worry about it, they do. Unfortunately, a small subset of CPAN authors have been careless about licensing their code and have omitted any declaration of license or copyright. The lack of a license statement often means that the code cannot be redistributed. So, forget using apt-get or rpm or fink or even use PAR qw(...) to get the software in those cases. Minimally, all CPAN modules should have a LICENSE file or a license statement included in README.

Since Perl files can be installed in widely varying places in end users’ computers, and because README files usually are not installed, I assert that license statements should be present in all .pm and .pl files that are included in a distribution. This way, if a user wants to check if he can give a .pm file to a friend or include it in a .par file, he can just check the .pm instead of having to go back to CPAN (or perhaps even BackPAN).

The following 00_local_copyright.t doesn’t do all that yet, unfortunately. It simply checks README and all files in the blib/ subdirectory for a recognizable “Copyright YYYY” or “Copyright YYYY-YYYY” statement and ensures that “YYYY” must be the current year. It further ensures that at least one copyright statement is found. A better solution would be to ensure that the machine-readable META.yml file has a license: ... field, but the copyright year is important too.

#!perl -w

use warnings;
use strict;
use File::Find;
use File::Slurp;
use Test::More qw(no_plan);

my $this_year = [localtime]->[5]+1900;
my $copyrights_found = 0;
find({wanted => \&check_file, no_chdir => 1}, 'blib');
for (grep {/^readme/i} read_dir('.')) {
    check_file();
}
ok($copyrights_found != 0, 'found a copyright statement');

sub check_file {
    # $_ is the path to a filename, relative to the root of the
    # distribution

    # Only test plain files
    return if (! -f $_);

    # Filter the list of filenames
    return if (! m,^(?: README.*         # docs
                     |  .*/scripts/[^/]+ # programs
                     |  .*/script/[^/]+  # programs
                     |  .*/bin/[^/]+     # programs
                     |  .*\.(?: pl       # program ext
                             |  pm       # module ext
                             |  html     # doc ext
                             |  3pm      # doc ext
                             |  3        # doc ext
                             |  1        # doc ext
                            )
                    )$,xms);

    my $content = read_file($_);
    my @copyright_years = $content =~ m/
                                       (?: copyright | \(c\) )
                                       \s+
                                       (?: \d{4} \- )?
                                       (\d{4})
                                       /gixms;
    if (0 < grep {$_ ne $this_year} @copyright_years) {
        fail("$_ copyrights: @copyright_years");
    }
    elsif (0 == @copyright_years) {
        pass("$_, no copyright found");
    }
    else {
        pass($_);
    }
    $copyrights_found += @copyright_years;
}

Example 6: Test::Perl::Critic

The final example I will present is also the most comprehensive. The Perl::Critic project, along with the [Test::Perl::Critic][] wrapper, is a framework for enforcing a Perl coding style. Inspired by Damian Conway’s “Perl Best Practices” book, this package will parse all of your Perl files in the blib/ subdirectory and evaluate them against a collection of “Policy” modules that you specify. For example, the TestingAndDebugging::RequirePackageStricture policy insists that all files must include use strict;. On the opposite extreme of acceptedness, Miscellanea::RequireRcsKeywords insists that you have $Revision:...$ somewhere in your documentation, as filled in by CVS, SVN or the like. The project has grown to include optional policies beyond what Conway has recommended.

This package is incredibly useful for enforcing style guidelines, whether they be personal or corporate. The policies, being stricter than Perl itself, have even helped me find some subtle bugs in my code (notably via the Subroutines::RequireFinalReturn policy).

Like many of the other tests mentioned in this article, this is clearly an author-time test. The style guidelines are usually not critical to functional code, but instead emphasize readability and maintainability.

Furthermore, the Perl::Critic results may not be repeatable from machine to machine. At three months old as of this writing, the project is still moving very quickly. New policies are being added frequently so code that passed, say, Perl::Critic v0.12 may likely fail under v0.13. Furthermore, the current code uses Module::Pluggable so third party policies may be added at runtime. This makes Perl::Critic a very flexible and useful tool for the author, but a highly unpredictable target for an arbitrary end user.

Here is my 00_local_perlcritic.t:

use warnings;
use strict;

our @pcargs;
BEGIN
{
   my $rc = 't/perlcriticrc';
   @pcargs = -f $rc ? (-profile => $rc) : ();
}
use Test::Perl::Critic (@pcargs);
all_critic_ok();

The only unusual feature of this file is the BEGIN block. At runtime, this test checks for the presence of a t/perlcriticrc configuration file and, if present, it tells Test::Perl::Critic to use that configuration. Lacking that file, Perl::Critic will use its default policy. While it is carefully selected, that default policy is unlikely to be perfect for most authors.

My habit is to enable most of the Perl::Critic policies and then selectively disable problematic ones. For example, my CAM::PDF module uses camelCaseSubroutineNames, which is forbidden by NamingConventions::ProhibitMixedCaseVars. It would be prohibitive to rename the subroutines since I have lots of deployed code that uses that module. So, in my t/perlcriticrc file, I disable that policy like so:

[-NamingConventions::ProhibitMixedCaseSubs]

Conclusion

I have presented several examples of Perl regression tests that are of primary use to the software author, not to the author’s end users. These private tests share a set of common principles that differ from the public tests that are to be distributed with the rest of the code:

  1. They can be strict and unforgiving because test failures have a small price for the author
  2. They can employ any supporting modules or programs that the author has at his disposal
  3. They should only test aspects of the software that are non-critical or will not vary from computer to computer (like code style or documentation)

My hope is that a dialogue begins in the Perl community to support and encourage the creation and use of private tests.

One possibility is a common framework for private tests that asserts which tests have succeeded in a YAML format. For example, if I have a private Pod::Coverage test which adds a line to a tests.yml file like pod_coverage: ok, then I could omit the pod-coverage.t file from my distribution and still get credit for having high “Kwalitee” in my work. Furthermore, this can extend to event more author-dependent tests like code coverage via Devel::Cover. Wouldn’t it be great if CPAN authors could assert devel_cover: 100% in a machine-readable way?

Perhaps there could even be a public author.t file that declares these successes in a human-readable way like so:

t/author.t..........1..16
ok 1 - Test::Pod
ok 2 - Test::Pod::Coverage
ok 3 - Devel::Cover 100%
ok 4 - Test::Spelling
ok 5 - Test::Perl::Critic, 57 policies in effect
ok 6 - Test::Distribution
ok 8 - Version numbers all match
ok 9 - Copyright date is 2005
ok 10 - Works on darwin
ok 11 - Works on linux
nok 12 - Works on win32 # TODO needs some Windows lovin'!
ok 13 - Works on Perl 5.005
ok 14 - Works on Perl 5.6.1
ok 15 - Works on Perl 5.8.7
ok 16 - Works under mod_perl 1.x

Firefox Extensions: undoclosetab

This is the third installment of my exploration of useful Firefox extensions

The Mozilla folks are proud of being the popularizers of tabbed browsing, which caused a bit of a revolution in browsing UI. Such a simple concept, but so effective… Tabbed browsing encourages the user to open a bunch of pages in their own tab for a branching thread of surfing.

Warning: stretched analogy coming

This is like how we used to read Choose-Your-Own-Adventure books in the 1980s. (“If you want to read about RFID chips in you Passport, turn to page 87. If you want to continue reading Slashdot, turn to page 134.”) In the old CYOA books,1 you could only follow as many branches as you had fingers to mark your pages (unless you cheated by dogearring or using bookmarks). If a finger slipped out, you had to backtrack a lot to find out where you left off.

A screenshot of the undoclosetab option

The same thing happens if you mistakenly close a tab. “Argh, I didn’t finish reading that article! Now where did I get that link from?” You can use the History or recreate your browsing, but Undo Close Tab is an easier solution. This simple extension allows you to right-click on any tab to re-open the most recently closed tab where you left off. It even keeps a history so you can undo the last 2-3 closes, for example. Be warned, though: the tab closure history is kept per-window so if you close the whole Firefox window you can’t undo.

So here’s a new motto for this extension: “Simple but effective, just like fingers.”

Next time: GeoURL


1 Personal note: one of my first programming projects was writing Choose-Your-Own-Adventures and MadLibs programs in BASIC on my friend’s Apple ][.

Future web: XHTML 2

Background

The current common recommendation is that websites be written in standards-compliant XHTML 1.1 and CSS 2 with optional Javascript 1.5 support. What’s next for the web?

The XTech conference generated a lot of buzz this past summer in the web-techie community. There were presentations on XHTML 2 and the so-called HTML 5, which are both extrapolations of current popular standards, from groups which are (sort-of) competing for mindshare. There were also talks by technologist trying to transcend HTML entirely: both Microsoft’s XAML and Mozilla’s XUL are XML-and-script models for rapid GUI application development.

I hope to talk about all of these eventually, but right now I’m interested in XHTML 2. This article will delve into a few of the more interesting changes that are proposed in that draft. For readers from the future (hi cool future people!), here’s a permalink to the latest XHTML 2 docs, which is thesame as the above as of this writing.

XHTML 2

The main goal of XHTML 1.0 and 1.1 was to be as HTML-like as possible while enjoying the improved parsability of XML, which is stricter than HTML.

In contrast, XHTML 2 is a small evolutionary step away from HTML. The goals are to reduce the amount of presentation information encoded in the data (instead pushing presentation details to CSS), reducing the amount of scripting needed to accomplish common tasks, improving accessibility, and improving hooks for semantic content (that is, metadata).

Sections

HTML included h1, h2, .. h6 tags to mark headers along with p, span and div tags to mark sections. Unfortunately, the h1h6 and p tags have strong presentation meanings (titles and paragraphs) and therefore are harder than necessary to use for general blocks of text.

XHTML 2 generalizes this with the section and h tags. The section tags can nest (unlike p tags) so you no longer need to specify a header level for the h tag — it’s implied by the depth of the section. For example:

<!-- HTML -->
<h1>Title</h1>
<p>Intro text...</p>
<h2>Chapter 1</h2>
<p> Chapter body </p>

<!-- XHTML 2 -->
<section>
  <h> Title </h>
  <p>Intro text...</p>
  <section>
    <h> Chapter 1 </h>
    <p> Chapter body </p>
  </section>
</section>

The gains from this slightly-more-verbose syntax include:

  1. The nested sections are much easier to parse for a non-human. It would be trivial to write a program to extract Chapter 1, for example.
  2. The header levels are now relative, so if you later demote Chapter 1 to a subsection, you don’t need to renumber the header tags.

XHTML 2 also changes the p tag a little bit to allow nested lists (like ul and ol) and the like, to make them more like the human concept of a paragraph as a small block of related idea than a page layout concept of a paragraph as text offset from other text by whitespace.

Code

XHTML 2 offers a new blockcode tag for presenting computer source code verbatim., instead of the generic pre tag offered by HTML. This is a minor change, but it can be a big plus for search engines looking for code examples. In conjunction with metadata markup, an author could indicate the language of the source code. For example (this is probably bad syntax):

<blockcode xmlns:prog="http://example.com/ProgrammingLanguages/"
                      property="prog:language" content="Perl5">
    sub hello {
        print "Hello, World!\n";
    }
</blockquote>
Semantic text

In HTML, the tt tag is commonly used for keyboard input, variable names, blocks of code and more. XHTML 2 adds or enhances a few tags to disambiguate those usages. Respective to the above list, one could use kbd, var and code tags.

Anchors

XHTML 2 allows href attributes to be added to any tag instead of just to a tags. This allows you to link, say, a blockquote to the source of the quote, or to link an image without wrapping it in an a tag. In the case of the quote, it would be even better to use the new cite attribute. cite works just like href but makes it more clear that the specified URL is included as reference instead of as a recommended navigation point.

XHTML 2 also adds the hreflang to go along with href to let users know that the target may be in a different language. Along the same vein, the hreftype can specify the MIME type of the target, for example application/pdf. Since HTML lacks this latter attribute, authors often tell their users the target type in prose. For example <a href="foo.pdf">Foo</a> (PDF).

Embedding

HTML coders are very familar with alt tags on images — that is displayed if the image is unavailable or if the user has a text-only browser. XHTML 2 generalizes that concept via the embedding concept. Consider this example from the XHTML 2 docs:

<p src="holiday.png" srctype="image/png">
    <span src="holiday.gif" srctype="image/gif">
        An image of us on holiday.
    </span>
</p>

This means:

  1. Load and display holiday.png
  2. If that fails, load and display holiday.gif
  3. If that fails, display the text “An image of us on holiday.”

The src attribute can be applied to any tag. Consequently, the img tag is no longer special. This is like how a is no longer special since href attributes can be applied to any tag.

Media

HTML coders are familiar with the media attribute on link tags that let you, say, write a CSS file that applies only to media="print. Now, media can be applied to any tag. For example:

<span src="photo.jpg" media="screen">Me at work</span>
<span src="photo-hires.jpg" media="print">Me at work</span>

Personally, I think this one may be a step backward since it entangles presentation and data. But it’s undeniably clever. Doing this stuff inline is easier than in a separate CSS file that has to declare display="none" or set a background image on some tag.

Metadata

XHTML 2 has copious support for metadata. If you’re interested, I recommend just reading the Metainformation Module in the XHTML 2 specification. I’ll just share a few clever examples:

<link media="print" title="The manual in PostScript"
  hreftype="application/postscript"
  rel="alternate" href="http://example.com/manual/postscript.ps"/>

<meta property="dc:creator">Chris Dolan</meta>
<meta property="dc:created" datatype="xsd:date">2005-10-28</meta>
<link rel="author" href="http://www.chrisdolan.net/" />

Note: the dc: prefix is a reference to the Dublin Core XML namespace.

Accessibility

XHTML 2 introduces “roles” to assist with accessibility. You can, for example, tag your search box as follows to make it easier for a user to get to it (note the form below is not quite real XHTML 2 syntax):

<html>
  <head>
    <access key="s" title="Search" targetrole="search" />
  </head>
  <body>
    ...
    <form role="search" action="search.cgi">
      Search: <input type="text" name="q" />
    </form>
    ...
  </body>
</html>

There is a predefined, standard set of roles (like “search”) that any page can implement.

Forms and Scripting

XHTML 2 uses XForms, which is intended to be an improvement on HTML forms. I haven’t delved into this that much, but a key point seems to be separation of model from interface. Also, the new system allows for some standard filters that can be implemented without Javascript.

XHTML 2 also uses XML Events. This generalizes the Javascript-specific event notation (think onclick="JS code...") to support any event in any language. You can optionally specify listeners to events separately from the tags that generate those events.

A major gain from the XML Events change is that the mutually-incompatible Netscape and IE event handling models are discarded in favor of a standard model. Yay!

The script tag is now called handler and is intended to support any language that the user agent can support. (Want to code your XHTML page in Perl or Python?)

An unrelated note on scripting: document.write doesn’t work. Instead, you have to add nodes via the XML DOM API. When it comes to limiting the problem of script injection and cross-site scripting attacks, this is a good thing.