From a27771ebaa40caa62bb6a6e54e8ee51434930e09 Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Wed, 22 Jun 2011 16:56:31 -0700
Subject: [PATCH 01/45] Change test.py to use an external browser_manifest.json
 file, and use OptionParser to handle the command line.

---
 browser_manifest.json |   7 +++
 test.py               | 100 ++++++++++++++++++++++++++++++------------
 2 files changed, 79 insertions(+), 28 deletions(-)
 create mode 100644 browser_manifest.json

diff --git a/browser_manifest.json b/browser_manifest.json
new file mode 100644
index 000000000..79115d1a4
--- /dev/null
+++ b/browser_manifest.json
@@ -0,0 +1,7 @@
+[
+   {
+    "name":"Firefox 5",
+    "path":"/Applications/Firefox.app",
+    "type":"firefox"
+   }
+]
\ No newline at end of file
diff --git a/test.py b/test.py
index 0c326ec09..8314bd794 100644
--- a/test.py
+++ b/test.py
@@ -1,18 +1,41 @@
-import json, os, sys, subprocess, urllib2
+import json, platform, os, sys, subprocess, urllib, urllib2
 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+from optparse import OptionParser
 from urlparse import urlparse
 
-def prompt(question):
-    '''Return True iff the user answered "yes" to |question|.'''
-    inp = raw_input(question +' [yes/no] > ')
-    return inp == 'yes'
+USAGE_EXAMPLE = "%prog"
 
 ANAL = True
 DEFAULT_MANIFEST_FILE = 'test_manifest.json'
+DEFAULT_BROWSER_MANIFEST_FILE = 'browser_manifest.json'
 REFDIR = 'ref'
 TMPDIR = 'tmp'
 VERBOSE = False
 
+class TestOptions(OptionParser):
+    def __init__(self, **kwargs):
+        OptionParser.__init__(self, **kwargs)
+        self.add_option("-m", "--masterMode", action="store_true", dest="masterMode",
+                        help="Run the script in master mode.", default=False)
+        self.add_option("--manifestFile", action="store", type="string", dest="manifestFile",
+                        help="A JSON file in the form of test_manifest.json (the default).")
+        self.add_option("--browserManifestFile", action="store", type="string",
+                        dest="browserManifestFile",
+                        help="A JSON file in the form of browser_manifest.json (the default).",
+                        default=DEFAULT_BROWSER_MANIFEST_FILE)
+        self.set_usage(USAGE_EXAMPLE)
+
+    def verifyOptions(self, options):
+        if options.masterMode and options.manifestFile:
+            self.error("--masterMode and --manifestFile must not be specified at the same time.")
+        options.manifestFile = DEFAULT_MANIFEST_FILE
+        return options
+        
+def prompt(question):
+    '''Return True iff the user answered "yes" to |question|.'''
+    inp = raw_input(question +' [yes/no] > ')
+    return inp == 'yes'
+
 MIMEs = {
     '.css': 'text/css',
     '.html': 'text/html',
@@ -100,13 +123,34 @@ class PDFTestHandler(BaseHTTPRequestHandler):
 
         State.done = (0 == State.remaining)
 
+# this just does Firefox for now
+class BrowserCommand():
+    def __init__(self, browserRecord):
+        print browserRecord
+        self.name = browserRecord["name"]
+        self.path = browserRecord["path"]
+        self.type = browserRecord["type"]
+
+        if platform.system() == "Darwin" and self.path.endswith(".app"):
+            self._fixupMacPath()
+
+        if not os.path.exists(self.path):
+            throw("Path to browser '%s' does not exist." % self.path)
+
+    def _fixupMacPath(self):
+        self.path = self.path + "/Contents/MacOS/firefox-bin"
 
-def setUp(manifestFile, masterMode):
+def makeBrowserCommands(browserManifestFile):
+    with open(browserManifestFile) as bmf:
+        browsers = [BrowserCommand(browser) for browser in json.load(bmf)]
+    return browsers
+
+def setUp(options):
     # Only serve files from a pdf.js clone
     assert not ANAL or os.path.isfile('pdf.js') and os.path.isdir('.git')
 
-    State.masterMode = masterMode
-    if masterMode and os.path.isdir(TMPDIR):
+    State.masterMode = options.masterMode
+    if options.masterMode and os.path.isdir(TMPDIR):
         print 'Temporary snapshot dir tmp/ is still around.'
         print 'tmp/ can be removed if it has nothing you need.'
         if prompt('SHOULD THIS SCRIPT REMOVE tmp/?  THINK CAREFULLY'):
@@ -114,14 +158,10 @@ def setUp(manifestFile, masterMode):
 
     assert not os.path.isdir(TMPDIR)
 
-    testBrowsers = [ b for b in
-                     ( 'firefox5', )
-#'chrome12', 'chrome13', 'firefox4', 'firefox6','opera11' ):
-                     if os.access(b, os.R_OK | os.X_OK) ]
+    testBrowsers = makeBrowserCommands(options.browserManifestFile)
 
-    mf = open(manifestFile)
-    manifestList = json.load(mf)
-    mf.close()
+    with open(options.manifestFile) as mf:
+        manifestList = json.load(mf)
 
     for item in manifestList:
         f, isLink = item['file'], item.get('link', False)
@@ -140,22 +180,26 @@ def setUp(manifestFile, masterMode):
 
             print 'done'
 
+    print testBrowsers
+
     for b in testBrowsers:
-        State.taskResults[b] = { }
+        State.taskResults[b.name] = { }
         for item in manifestList:
             id, rounds = item['id'], int(item['rounds'])
             State.manifest[id] = item
             taskResults = [ ]
             for r in xrange(rounds):
                 taskResults.append([ ])
-            State.taskResults[b][id] = taskResults
+            State.taskResults[b.name][id] = taskResults
 
     State.remaining = len(manifestList)
 
+    
+
     for b in testBrowsers:
-        print 'Launching', b
-        qs = 'browser='+ b +'&manifestFile='+ manifestFile
-        subprocess.Popen(( os.path.abspath(os.path.realpath(b)),
+        print 'Launching', b.name
+        qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile)
+        subprocess.Popen(( os.path.abspath(os.path.realpath(b.path)),
                            'http://localhost:8080/test_slave.html?'+ qs))
 
 
@@ -285,14 +329,14 @@ def processResults():
                 print 'done'
 
 
-def main(args):
-    masterMode = False
-    manifestFile = DEFAULT_MANIFEST_FILE
-    if len(args) == 1:
-        masterMode = (args[0] == '-m')
-        manifestFile = args[0] if not masterMode else manifestFile
+def main():
+    optionParser = TestOptions()
+    options, args = optionParser.parse_args()
+    options = optionParser.verifyOptions(options)
+    if options == None:
+        sys.exit(1)
 
-    setUp(manifestFile, masterMode)
+    setUp(options)
 
     server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler)
     while not State.done:
@@ -301,4 +345,4 @@ def main(args):
     processResults()
 
 if __name__ == '__main__':
-    main(sys.argv[1:])
+    main()

From aa21228448b549f6cc8ea4522cfb653c98592e01 Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Wed, 22 Jun 2011 17:12:02 -0700
Subject: [PATCH 02/45] remove some accidental print statements.

---
 test.py | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/test.py b/test.py
index 8314bd794..75810f43e 100644
--- a/test.py
+++ b/test.py
@@ -126,7 +126,6 @@ class PDFTestHandler(BaseHTTPRequestHandler):
 # this just does Firefox for now
 class BrowserCommand():
     def __init__(self, browserRecord):
-        print browserRecord
         self.name = browserRecord["name"]
         self.path = browserRecord["path"]
         self.type = browserRecord["type"]
@@ -180,8 +179,6 @@ def setUp(options):
 
             print 'done'
 
-    print testBrowsers
-
     for b in testBrowsers:
         State.taskResults[b.name] = { }
         for item in manifestList:

From 2ac8bbed51a5eace305842a62ebe2527751bfe6d Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Thu, 23 Jun 2011 09:05:51 -0700
Subject: [PATCH 03/45] Add new test directories.

---
 test/.gitignore      | 0
 test/pdfs/.gitignore | 0
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test/.gitignore
 create mode 100644 test/pdfs/.gitignore

diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore
new file mode 100644
index 000000000..e69de29bb

From 2454354b5907778bb4f3fd6db012ab8b8f2c44fc Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Thu, 23 Jun 2011 09:10:06 -0700
Subject: [PATCH 04/45] Move some files around.

---
 browser_manifest.json => test/browser_manifest.json |   0
 {tests => test/pdfs}/canvas.pdf                     | Bin
 {tests => test/pdfs}/pdf.pdf.link                   |   0
 {tests => test/pdfs}/tracemonkey.pdf                | Bin
 test.py => test/test.py                             |   0
 test_manifest.json => test/test_manifest.json       |   0
 test_slave.html => test/test_slave.html             |   0
 7 files changed, 0 insertions(+), 0 deletions(-)
 rename browser_manifest.json => test/browser_manifest.json (100%)
 rename {tests => test/pdfs}/canvas.pdf (100%)
 rename {tests => test/pdfs}/pdf.pdf.link (100%)
 rename {tests => test/pdfs}/tracemonkey.pdf (100%)
 rename test.py => test/test.py (100%)
 rename test_manifest.json => test/test_manifest.json (100%)
 rename test_slave.html => test/test_slave.html (100%)

diff --git a/browser_manifest.json b/test/browser_manifest.json
similarity index 100%
rename from browser_manifest.json
rename to test/browser_manifest.json
diff --git a/tests/canvas.pdf b/test/pdfs/canvas.pdf
similarity index 100%
rename from tests/canvas.pdf
rename to test/pdfs/canvas.pdf
diff --git a/tests/pdf.pdf.link b/test/pdfs/pdf.pdf.link
similarity index 100%
rename from tests/pdf.pdf.link
rename to test/pdfs/pdf.pdf.link
diff --git a/tests/tracemonkey.pdf b/test/pdfs/tracemonkey.pdf
similarity index 100%
rename from tests/tracemonkey.pdf
rename to test/pdfs/tracemonkey.pdf
diff --git a/test.py b/test/test.py
similarity index 100%
rename from test.py
rename to test/test.py
diff --git a/test_manifest.json b/test/test_manifest.json
similarity index 100%
rename from test_manifest.json
rename to test/test_manifest.json
diff --git a/test_slave.html b/test/test_slave.html
similarity index 100%
rename from test_slave.html
rename to test/test_slave.html

From 18afc896f610253a20040067a3dc8c53eaed9f8d Mon Sep 17 00:00:00 2001
From: sbarman <sbarman@L3CWZ5T.(none)>
Date: Thu, 23 Jun 2011 09:41:59 -0700
Subject: [PATCH 05/45] fix for uncompressed flatestream blocks

---
 pdf.js | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/pdf.js b/pdf.js
index d940c4f8b..326c31234 100644
--- a/pdf.js
+++ b/pdf.js
@@ -479,17 +479,17 @@ var FlateStream = (function() {
                 array[i++] = what;
         }
 
-        var bytes = this.bytes;
-        var bytesPos = this.bytesPos;
-
         // read block header
         var hdr = this.getBits(3);
         if (hdr & 1)
             this.eof = true;
         hdr >>= 1;
 
-        var b;
         if (hdr == 0) { // uncompressed block
+            var bytes = this.bytes;
+            var bytesPos = this.bytesPos;
+            var b;
+
             if (typeof (b = bytes[bytesPos++]) == "undefined")
                 error("Bad block header in flate stream");
             var blockLen = b;
@@ -502,18 +502,24 @@ var FlateStream = (function() {
             if (typeof (b = bytes[bytesPos++]) == "undefined")
                 error("Bad block header in flate stream");
             check |= (b << 8);
-            if (check != (~this.blockLen & 0xffff))
+            if (check != (~blockLen & 0xffff))
                 error("Bad uncompressed block length in flate stream");
+
+            this.codeBuf = 0;
+            this.codeSize = 0;
+            
             var bufferLength = this.bufferLength;
             var buffer = this.ensureBuffer(bufferLength + blockLen);
-            this.bufferLength = bufferLength + blockLen;
-            for (var n = bufferLength; n < blockLen; ++n) {
+            var end = bufferLength + blockLen;
+            this.bufferLength = end;
+            for (var n = bufferLength; n < end; ++n) {
                 if (typeof (b = bytes[bytesPos++]) == "undefined") {
                     this.eof = true;
                     break;
                 }
                 buffer[n] = b;
             }
+            this.bytesPos = bytesPos;
             return;
         }
 

From 27935325991d9d38ca47a1564f082cee35a9be16 Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Thu, 23 Jun 2011 09:48:34 -0700
Subject: [PATCH 06/45] Modify paths of web resources to work with test
 resources more buried.

---
 test/browser_manifest.json |  2 +-
 test/test.py               | 18 +++++++++---------
 test/test_manifest.json    |  8 ++++----
 test/test_slave.html       |  8 ++++----
 4 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/test/browser_manifest.json b/test/browser_manifest.json
index 79115d1a4..f11c97c11 100644
--- a/test/browser_manifest.json
+++ b/test/browser_manifest.json
@@ -1,6 +1,6 @@
 [
    {
-    "name":"Firefox 5",
+    "name":"firefox5",
     "path":"/Applications/Firefox.app",
     "type":"firefox"
    }
diff --git a/test/test.py b/test/test.py
index 75810f43e..acdd0c552 100644
--- a/test/test.py
+++ b/test/test.py
@@ -5,6 +5,9 @@ from urlparse import urlparse
 
 USAGE_EXAMPLE = "%prog"
 
+# The local web server uses the git repo as the document root.
+DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
+
 ANAL = True
 DEFAULT_MANIFEST_FILE = 'test_manifest.json'
 DEFAULT_BROWSER_MANIFEST_FILE = 'browser_manifest.json'
@@ -73,15 +76,14 @@ class PDFTestHandler(BaseHTTPRequestHandler):
 
     def do_GET(self):
         url = urlparse(self.path)
+        print "GET",url
         # Ignore query string
         path, _ = url.path, url.query
-        cwd = os.getcwd()
-        path = os.path.abspath(os.path.realpath(cwd + os.sep + path))
-        cwd = os.path.abspath(cwd)
-        prefix = os.path.commonprefix(( path, cwd ))
+        path = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path))
+        prefix = os.path.commonprefix(( path, DOC_ROOT ))
         _, ext = os.path.splitext(path)
 
-        if not (prefix == cwd
+        if not (prefix == DOC_ROOT
                 and os.path.isfile(path) 
                 and ext in MIMEs):
             self.send_error(404)
@@ -146,7 +148,7 @@ def makeBrowserCommands(browserManifestFile):
 
 def setUp(options):
     # Only serve files from a pdf.js clone
-    assert not ANAL or os.path.isfile('pdf.js') and os.path.isdir('.git')
+    assert not ANAL or os.path.isfile('../pdf.js') and os.path.isdir('../.git')
 
     State.masterMode = options.masterMode
     if options.masterMode and os.path.isdir(TMPDIR):
@@ -191,13 +193,11 @@ def setUp(options):
 
     State.remaining = len(manifestList)
 
-    
-
     for b in testBrowsers:
         print 'Launching', b.name
         qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile)
         subprocess.Popen(( os.path.abspath(os.path.realpath(b.path)),
-                           'http://localhost:8080/test_slave.html?'+ qs))
+                           'http://localhost:8080/test/test_slave.html?'+ qs))
 
 
 def check(task, results, browser):
diff --git a/test/test_manifest.json b/test/test_manifest.json
index 036b7aafc..e4a7ada81 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -1,21 +1,21 @@
 [
     {  "id": "tracemonkey-eq",
-       "file": "tests/tracemonkey.pdf",
+       "file": "pdfs/tracemonkey.pdf",
        "rounds": 1,
        "type": "eq"
     },
     {  "id": "tracemonkey-fbf",
-       "file": "tests/tracemonkey.pdf",
+       "file": "pdfs/tracemonkey.pdf",
        "rounds": 2,
        "type": "fbf"
     },
     {  "id": "html5-canvas-cheat-sheet-load",
-       "file": "tests/canvas.pdf",
+       "file": "pdfs/canvas.pdf",
        "rounds": 1,
        "type": "load"
     },
     {  "id": "pdfspec-load",
-       "file": "tests/pdf.pdf",
+       "file": "pdfs/pdf.pdf",
        "link": true,
        "rounds": 1,
        "type": "load"
diff --git a/test/test_slave.html b/test/test_slave.html
index 06b911810..80e374709 100644
--- a/test/test_slave.html
+++ b/test/test_slave.html
@@ -1,9 +1,9 @@
 <html>
 <head>
   <title>pdf.js test slave</title>
-  <script type="text/javascript" src="pdf.js"></script>
-  <script type="text/javascript" src="fonts.js"></script>
-  <script type="text/javascript" src="glyphlist.js"></script>
+  <script type="text/javascript" src="/pdf.js"></script>
+  <script type="text/javascript" src="/fonts.js"></script>
+  <script type="text/javascript" src="/glyphlist.js"></script>
   <script type="application/javascript">
 var browser, canvas, currentTask, currentTaskIdx, failure, manifest, pdfDoc, stdout;
 
@@ -137,7 +137,7 @@ function sendTaskResult(snapshot) {
 
   var r = new XMLHttpRequest();
   // (The POST URI is ignored atm.)
-  r.open("POST", "submit_task_results", false);
+  r.open("POST", "/submit_task_results", false);
   r.setRequestHeader("Content-Type", "application/json");
   // XXX async
   r.send(JSON.stringify(result));

From f31290e00f28dc15ef515446c18461186de36a33 Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Thu, 23 Jun 2011 10:27:53 -0700
Subject: [PATCH 07/45] Add pdf.pdf to .gitignore

---
 test/pdfs/.gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore
index e69de29bb..ef853ef61 100644
--- a/test/pdfs/.gitignore
+++ b/test/pdfs/.gitignore
@@ -0,0 +1 @@
+pdf.pdf

From 984c4d4de61c69999bca8c04230843a5681ba53f Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Thu, 23 Jun 2011 11:24:36 -0700
Subject: [PATCH 08/45] Add a user.js file Firefox profile. Change HTTP server
 to run on background thread.

---
 test/resources/firefox/user.js | 33 ++++++++++++++++++++++++++
 test/test.py                   | 42 ++++++++++++++++++++++++----------
 2 files changed, 63 insertions(+), 12 deletions(-)
 create mode 100644 test/resources/firefox/user.js

diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js
new file mode 100644
index 000000000..55d9ced33
--- /dev/null
+++ b/test/resources/firefox/user.js
@@ -0,0 +1,33 @@
+user_pref("browser.console.showInPanel", true);
+user_pref("browser.dom.window.dump.enabled", true);
+user_pref("browser.firstrun.show.localepicker", false);
+user_pref("browser.firstrun.show.uidiscovery", false);
+user_pref("dom.allow_scripts_to_close_windows", true);
+user_pref("dom.disable_open_during_load", false);
+user_pref("dom.max_script_run_time", 0); // no slow script dialogs
+user_pref("dom.max_chrome_script_run_time", 0);
+user_pref("dom.popup_maximum", -1);
+user_pref("dom.send_after_paint_to_content", true);
+user_pref("dom.successive_dialog_time_limit", 0);
+user_pref("security.warn_submit_insecure", false);
+user_pref("browser.shell.checkDefaultBrowser", false);
+user_pref("shell.checkDefaultClient", false);
+user_pref("browser.warnOnQuit", false);
+user_pref("accessibility.typeaheadfind.autostart", false);
+user_pref("javascript.options.showInConsole", true);
+user_pref("devtools.errorconsole.enabled", true);
+user_pref("layout.debug.enable_data_xbl", true);
+user_pref("browser.EULA.override", true);
+user_pref("javascript.options.tracejit.content", true);
+user_pref("javascript.options.methodjit.content", true);
+user_pref("javascript.options.jitprofiling.content", true);
+user_pref("javascript.options.methodjit_always", false);
+user_pref("gfx.color_management.force_srgb", true);
+user_pref("network.manage-offline-status", false);
+user_pref("test.mousescroll", true);
+user_pref("network.http.prompt-temp-redirect", false);
+user_pref("media.cache_size", 100);
+user_pref("security.warn_viewing_mixed", false);
+user_pref("app.update.enabled", false);
+user_pref("browser.panorama.experienced_first_run", true); // Assume experienced
+user_pref("dom.w3c_touch_events.enabled", true);
\ No newline at end of file
diff --git a/test/test.py b/test/test.py
index d751a58be..c6bb637a2 100644
--- a/test/test.py
+++ b/test/test.py
@@ -1,5 +1,6 @@
-import json, platform, os, sys, subprocess, urllib, urllib2
+import json, platform, os, shutil, sys, subprocess, tempfile, threading, urllib, urllib2
 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+import SocketServer
 from optparse import OptionParser
 from urlparse import urlparse
 
@@ -69,8 +70,11 @@ class Result:
         self.snapshot = snapshot
         self.failure = failure
 
+class TestServer(SocketServer.TCPServer):
+    allow_reuse_address = True
 
 class PDFTestHandler(BaseHTTPRequestHandler):
+
     # Disable annoying noise by default
     def log_request(code=0, size=0):
         if VERBOSE:
@@ -78,7 +82,6 @@ class PDFTestHandler(BaseHTTPRequestHandler):
 
     def do_GET(self):
         url = urlparse(self.path)
-        print "GET",url
         # Ignore query string
         path, _ = url.path, url.query
         path = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path))
@@ -143,6 +146,19 @@ class BrowserCommand():
     def _fixupMacPath(self):
         self.path = self.path + "/Contents/MacOS/firefox-bin"
 
+    def setup(self):
+        self.tempDir = tempfile.mkdtemp()
+        self.profileDir = os.path.join(self.tempDir, "profile")
+        shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"),
+                        self.profileDir)
+
+    def teardown(self):
+        shutil.rmtree(self.tempDir)
+
+    def start(self, url):
+        cmds = [self.path, "-no-remote", "-profile", self.profileDir, url]
+        subprocess.call(cmds)
+
 def makeBrowserCommands(browserManifestFile):
     with open(browserManifestFile) as bmf:
         browsers = [BrowserCommand(browser) for browser in json.load(bmf)]
@@ -196,11 +212,13 @@ def setUp(options):
     State.remaining = len(testBrowsers) * len(manifestList)
 
     for b in testBrowsers:
-        print 'Launching', b.name
-        qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile)
-        subprocess.Popen(( os.path.abspath(os.path.realpath(b.path)),
-                           'http://localhost:8080/test/test_slave.html?'+ qs))
-
+        try:
+            b.setup()
+            print 'Launching', b.name
+            qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile)
+            b.start('http://localhost:8080/test/test_slave.html?'+ qs)
+        finally:
+            b.teardown()
 
 def check(task, results, browser):
     failed = False
@@ -350,12 +368,12 @@ def main():
     if options == None:
         sys.exit(1)
 
-    setUp(options)
-
-    server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler)
-    while not State.done:
-        server.handle_request()
+    httpd = TestServer(('127.0.0.1', 8080), PDFTestHandler)
+    httpd_thread = threading.Thread(target=httpd.serve_forever)
+    httpd_thread.setDaemon(True)
+    httpd_thread.start()
 
+    setUp(options)
     processResults()
 
 if __name__ == '__main__':

From ed574cb6c0cc0ab161723cecbed6c01a0f55f73d Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Thu, 23 Jun 2011 13:12:22 -0700
Subject: [PATCH 09/45] Add SpecialPowers extension to allow the browser to
 quit from content, and a bunch of other exciting things.

---
 test/browser_manifest.json                    |   5 +
 .../chrome.manifest                           |   4 +
 .../specialpowers/content/specialpowers.js    | 372 ++++++++++++++++++
 .../components/SpecialPowersObserver.js       |   1 +
 .../special-powers@mozilla.org/install.rdf    |  26 ++
 test/resources/firefox/user.js                |   3 +-
 test/test.py                                  |   4 +-
 test/test_slave.html                          |   5 +-
 8 files changed, 417 insertions(+), 3 deletions(-)
 create mode 100644 test/resources/firefox/extensions/special-powers@mozilla.org/chrome.manifest
 create mode 100644 test/resources/firefox/extensions/special-powers@mozilla.org/chrome/specialpowers/content/specialpowers.js
 create mode 120000 test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js
 create mode 100644 test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf

diff --git a/test/browser_manifest.json b/test/browser_manifest.json
index f11c97c11..a396b01ce 100644
--- a/test/browser_manifest.json
+++ b/test/browser_manifest.json
@@ -3,5 +3,10 @@
     "name":"firefox5",
     "path":"/Applications/Firefox.app",
     "type":"firefox"
+   },
+   {
+    "name":"firefox6",
+    "path":"/Users/sayrer/firefoxen/Aurora.app",
+    "type":"firefox"
    }
 ]
\ No newline at end of file
diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/chrome.manifest b/test/resources/firefox/extensions/special-powers@mozilla.org/chrome.manifest
new file mode 100644
index 000000000..614f31c3a
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers@mozilla.org/chrome.manifest
@@ -0,0 +1,4 @@
+content specialpowers chrome/specialpowers/content/
+component {59a52458-13e0-4d93-9d85-a637344f29a1} components/SpecialPowersObserver.js
+contract @mozilla.org/special-powers-observer;1 {59a52458-13e0-4d93-9d85-a637344f29a1}
+category profile-after-change @mozilla.org/special-powers-observer;1 @mozilla.org/special-powers-observer;1
diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/chrome/specialpowers/content/specialpowers.js b/test/resources/firefox/extensions/special-powers@mozilla.org/chrome/specialpowers/content/specialpowers.js
new file mode 100644
index 000000000..538b104eb
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers@mozilla.org/chrome/specialpowers/content/specialpowers.js
@@ -0,0 +1,372 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Special Powers code
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Clint Talbert cmtalbert@gmail.com
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK *****/
+/* This code is loaded in every child process that is started by mochitest in
+ * order to be used as a replacement for UniversalXPConnect
+ */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+function SpecialPowers(window) {
+  this.window = window;
+  bindDOMWindowUtils(this, window);
+  this._encounteredCrashDumpFiles = [];
+  this._unexpectedCrashDumpFiles = { };
+  this._crashDumpDir = null;
+  this._pongHandlers = [];
+  this._messageListener = this._messageReceived.bind(this);
+  addMessageListener("SPPingService", this._messageListener);
+}
+
+function bindDOMWindowUtils(sp, window) {
+  var util = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                   .getInterface(Ci.nsIDOMWindowUtils);
+  // This bit of magic brought to you by the letters
+  // B Z, and E, S and the number 5.
+  //
+  // Take all of the properties on the nsIDOMWindowUtils-implementing
+  // object, and rebind them onto a new object with a stub that uses
+  // apply to call them from this privileged scope. This way we don't
+  // have to explicitly stub out new methods that appear on
+  // nsIDOMWindowUtils.
+  var proto = Object.getPrototypeOf(util);
+  var target = {};
+  function rebind(desc, prop) {
+    if (prop in desc && typeof(desc[prop]) == "function") {
+      var oldval = desc[prop];
+      desc[prop] = function() { return oldval.apply(util, arguments); };
+    }
+  }
+  for (var i in proto) {
+    var desc = Object.getOwnPropertyDescriptor(proto, i);
+    rebind(desc, "get");
+    rebind(desc, "set");
+    rebind(desc, "value");
+    Object.defineProperty(target, i, desc);
+  }
+  sp.DOMWindowUtils = target;
+}
+
+SpecialPowers.prototype = {
+  toString: function() { return "[SpecialPowers]"; },
+  sanityCheck: function() { return "foo"; },
+
+  // This gets filled in in the constructor.
+  DOMWindowUtils: undefined,
+
+  // Mimic the get*Pref API
+  getBoolPref: function(aPrefName) {
+    return (this._getPref(aPrefName, 'BOOL'));
+  },
+  getIntPref: function(aPrefName) {
+    return (this._getPref(aPrefName, 'INT'));
+  },
+  getCharPref: function(aPrefName) {
+    return (this._getPref(aPrefName, 'CHAR'));
+  },
+  getComplexValue: function(aPrefName, aIid) {
+    return (this._getPref(aPrefName, 'COMPLEX', aIid));
+  },
+
+  // Mimic the set*Pref API
+  setBoolPref: function(aPrefName, aValue) {
+    return (this._setPref(aPrefName, 'BOOL', aValue));
+  },
+  setIntPref: function(aPrefName, aValue) {
+    return (this._setPref(aPrefName, 'INT', aValue));
+  },
+  setCharPref: function(aPrefName, aValue) {
+    return (this._setPref(aPrefName, 'CHAR', aValue));
+  },
+  setComplexValue: function(aPrefName, aIid, aValue) {
+    return (this._setPref(aPrefName, 'COMPLEX', aValue, aIid));
+  },
+
+  // Mimic the clearUserPref API
+  clearUserPref: function(aPrefName) {
+    var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""};
+    sendSyncMessage('SPPrefService', msg);
+  },
+
+  // Private pref functions to communicate to chrome
+  _getPref: function(aPrefName, aPrefType, aIid) {
+    var msg = {};
+    if (aIid) {
+      // Overloading prefValue to handle complex prefs
+      msg = {'op':'get', 'prefName': aPrefName, 'prefType':aPrefType, 'prefValue':[aIid]};
+    } else {
+      msg = {'op':'get', 'prefName': aPrefName,'prefType': aPrefType};
+    }
+    return(sendSyncMessage('SPPrefService', msg)[0]);
+  },
+  _setPref: function(aPrefName, aPrefType, aValue, aIid) {
+    var msg = {};
+    if (aIid) {
+      msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]};
+    } else {
+      msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue};
+    }
+    return(sendSyncMessage('SPPrefService', msg)[0]);
+  },
+
+  //XXX: these APIs really ought to be removed, they're not e10s-safe.
+  // (also they're pretty Firefox-specific)
+  _getTopChromeWindow: function(window) {
+    return window.QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIWebNavigation)
+                 .QueryInterface(Ci.nsIDocShellTreeItem)
+                 .rootTreeItem
+                 .QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIDOMWindow)
+                 .QueryInterface(Ci.nsIDOMChromeWindow);
+  },
+  _getDocShell: function(window) {
+    return window.QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIWebNavigation)
+                 .QueryInterface(Ci.nsIDocShell);
+  },
+  _getMUDV: function(window) {
+    return this._getDocShell(window).contentViewer
+               .QueryInterface(Ci.nsIMarkupDocumentViewer);
+  },
+  _getAutoCompletePopup: function(window) {
+    return this._getTopChromeWindow(window).document
+                                           .getElementById("PopupAutoComplete");
+  },
+  addAutoCompletePopupEventListener: function(window, listener) {
+    this._getAutoCompletePopup(window).addEventListener("popupshowing",
+                                                        listener,
+                                                        false);
+  },
+  removeAutoCompletePopupEventListener: function(window, listener) {
+    this._getAutoCompletePopup(window).removeEventListener("popupshowing",
+                                                           listener,
+                                                           false);
+  },
+  isBackButtonEnabled: function(window) {
+    return !this._getTopChromeWindow(window).document
+                                      .getElementById("Browser:Back")
+                                      .hasAttribute("disabled");
+  },
+
+  addChromeEventListener: function(type, listener, capture, allowUntrusted) {
+    addEventListener(type, listener, capture, allowUntrusted);
+  },
+  removeChromeEventListener: function(type, listener, capture) {
+    removeEventListener(type, listener, capture);
+  },
+
+  getFullZoom: function(window) {
+    return this._getMUDV(window).fullZoom;
+  },
+  setFullZoom: function(window, zoom) {
+    this._getMUDV(window).fullZoom = zoom;
+  },
+  getTextZoom: function(window) {
+    return this._getMUDV(window).textZoom;
+  },
+  setTextZoom: function(window, zoom) {
+    this._getMUDV(window).textZoom = zoom;
+  },
+
+  createSystemXHR: function() {
+    return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+             .createInstance(Ci.nsIXMLHttpRequest);
+  },
+
+  gc: function() {
+    this.DOMWindowUtils.garbageCollect();
+  },
+
+  hasContentProcesses: function() {
+    try {
+      var rt = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+      return rt.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+    } catch (e) {
+      return true;
+    }
+  },
+
+  registerProcessCrashObservers: function() {
+    addMessageListener("SPProcessCrashService", this._messageListener);
+    sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
+  },
+
+  _messageReceived: function(aMessage) {
+    switch (aMessage.name) {
+      case "SPProcessCrashService":
+        if (aMessage.json.type == "crash-observed") {
+          var self = this;
+          aMessage.json.dumpIDs.forEach(function(id) {
+            self._encounteredCrashDumpFiles.push(id + ".dmp");
+            self._encounteredCrashDumpFiles.push(id + ".extra");
+          });
+        }
+        break;
+
+      case "SPPingService":
+        if (aMessage.json.op == "pong") {
+          var handler = this._pongHandlers.shift();
+          if (handler) {
+            handler();
+          }
+        }
+        break;
+    }
+    return true;
+  },
+
+  removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) {
+    var success = true;
+    if (aExpectingProcessCrash) {
+      var message = {
+        op: "delete-crash-dump-files",
+        filenames: this._encounteredCrashDumpFiles 
+      };
+      if (!sendSyncMessage("SPProcessCrashService", message)[0]) {
+        success = false;
+      }
+    }
+    this._encounteredCrashDumpFiles.length = 0;
+    return success;
+  },
+
+  findUnexpectedCrashDumpFiles: function() {
+    var self = this;
+    var message = {
+      op: "find-crash-dump-files",
+      crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles
+    };
+    var crashDumpFiles = sendSyncMessage("SPProcessCrashService", message)[0];
+    crashDumpFiles.forEach(function(aFilename) {
+      self._unexpectedCrashDumpFiles[aFilename] = true;
+    });
+    return crashDumpFiles;
+  },
+
+  executeAfterFlushingMessageQueue: function(aCallback) {
+    this._pongHandlers.push(aCallback);
+    sendAsyncMessage("SPPingService", { op: "ping" });
+  },
+
+  executeSoon: function(aFunc) {
+    var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+    tm.mainThread.dispatch({
+      run: function() {
+        aFunc();
+      }
+    }, Ci.nsIThread.DISPATCH_NORMAL);
+  },
+
+  /* from http://mxr.mozilla.org/mozilla-central/source/testing/mochitest/tests/SimpleTest/quit.js
+   * by Bob Clary, Jeff Walden, and Robert Sayre.
+   */
+  quitApplication: function() {
+      function canQuitApplication()
+      {
+	  var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+	  if (!os)
+	      return true;
+  
+	  try {
+	      var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+	      os.notifyObservers(cancelQuit, "quit-application-requested", null);
+    
+	      // Something aborted the quit process. 
+	      if (cancelQuit.data)
+		  return false;
+	  } catch (ex) {}
+	  return true;
+      }
+
+      if (!canQuitApplication())
+	  return false;
+	  
+      var appService = Cc['@mozilla.org/toolkit/app-startup;1'].getService(Ci.nsIAppStartup);
+      appService.quit(Ci.nsIAppStartup.eForceQuit);
+      return true;
+  }
+};
+
+// Expose everything but internal APIs (starting with underscores) to
+// web content.
+SpecialPowers.prototype.__exposedProps__ = {};
+for each (i in Object.keys(SpecialPowers.prototype).filter(function(v) {return v.charAt(0) != "_";})) {
+  SpecialPowers.prototype.__exposedProps__[i] = "r";
+}
+
+// Attach our API to the window.
+function attachSpecialPowersToWindow(aWindow) {
+  try {
+    if ((aWindow !== null) &&
+        (aWindow !== undefined) &&
+        (aWindow.wrappedJSObject) &&
+        !(aWindow.wrappedJSObject.SpecialPowers)) {
+      aWindow.wrappedJSObject.SpecialPowers = new SpecialPowers(aWindow);
+    }
+  } catch(ex) {
+    dump("TEST-INFO | specialpowers.js |  Failed to attach specialpowers to window exception: " + ex + "\n");
+  }
+}
+
+// This is a frame script, so it may be running in a content process.
+// In any event, it is targeted at a specific "tab", so we listen for
+// the DOMWindowCreated event to be notified about content windows
+// being created in this context.
+
+function SpecialPowersManager() {
+  addEventListener("DOMWindowCreated", this, false);
+}
+
+SpecialPowersManager.prototype = {
+  handleEvent: function handleEvent(aEvent) {
+    var window = aEvent.target.defaultView;
+
+    // Need to make sure we are called on what we care about -
+    // content windows. DOMWindowCreated is called on *all* HTMLDocuments,
+    // some of which belong to chrome windows or other special content.
+    //
+    var uri = window.document.documentURIObject;
+    if (uri.scheme === "chrome" || uri.spec.split(":")[0] == "about") {
+      return;
+    }
+
+    attachSpecialPowersToWindow(window);
+  }
+};
+
+var specialpowersmanager = new SpecialPowersManager();
diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js
new file mode 120000
index 000000000..6f90832fb
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js
@@ -0,0 +1 @@
+/Users/sayrer/dev/mozilla-central/testing/mochitest/specialpowers/components/SpecialPowersObserver.js
\ No newline at end of file
diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf b/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf
new file mode 100644
index 000000000..db8de988e
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>special-powers@mozilla.org</em:id>
+    <em:version>2010.07.23</em:version>
+    <em:type>2</em:type>
+
+    <!-- Target Application this extension can install into, 
+         with minimum and maximum supported versions. -->
+    <em:targetApplication>
+      <Description>
+        <em:id>toolkit@mozilla.org</em:id>
+       <em:minVersion>3.0</em:minVersion>
+       <em:maxVersion>7.0a1</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+    <!-- Front End MetaData -->
+    <em:name>Special Powers</em:name>
+    <em:description>Special powers for use in testing.</em:description>
+    <em:creator>Mozilla</em:creator>
+  </Description>      
+</RDF>
diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js
index 55d9ced33..2085bcbef 100644
--- a/test/resources/firefox/user.js
+++ b/test/resources/firefox/user.js
@@ -30,4 +30,5 @@ user_pref("media.cache_size", 100);
 user_pref("security.warn_viewing_mixed", false);
 user_pref("app.update.enabled", false);
 user_pref("browser.panorama.experienced_first_run", true); // Assume experienced
-user_pref("dom.w3c_touch_events.enabled", true);
\ No newline at end of file
+user_pref("dom.w3c_touch_events.enabled", true);
+user_pref("extensions.checkCompatibility", false);
\ No newline at end of file
diff --git a/test/test.py b/test/test.py
index c6bb637a2..5a869530e 100644
--- a/test/test.py
+++ b/test/test.py
@@ -33,7 +33,8 @@ class TestOptions(OptionParser):
     def verifyOptions(self, options):
         if options.masterMode and options.manifestFile:
             self.error("--masterMode and --manifestFile must not be specified at the same time.")
-        options.manifestFile = DEFAULT_MANIFEST_FILE
+        if not options.manifestFile:
+            options.manifestFile = DEFAULT_MANIFEST_FILE
         return options
         
 def prompt(question):
@@ -149,6 +150,7 @@ class BrowserCommand():
     def setup(self):
         self.tempDir = tempfile.mkdtemp()
         self.profileDir = os.path.join(self.tempDir, "profile")
+        print self.profileDir
         shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"),
                         self.profileDir)
 
diff --git a/test/test_slave.html b/test/test_slave.html
index 6ab84e52f..1053025e1 100644
--- a/test/test_slave.html
+++ b/test/test_slave.html
@@ -151,7 +151,10 @@ function done() {
   log("Done!\n");
   setTimeout(function() {
       document.body.innerHTML = "Tests are finished.  <h1>CLOSE ME!</h1>";
-      window.close();
+      if (window.SpecialPowers)
+        SpecialPowers.quitApplication();
+      else
+        window.close();
     },
     100
   );

From d19a6ef9838d353d39cbd0a1bd5410e06251de5c Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Thu, 23 Jun 2011 13:47:43 -0700
Subject: [PATCH 10/45] Add sample manifests. Also make a browser path
 argument, so you can just specify one browser without messing with a
 manifest.

---
 .../browser_manifest.json.mac}                |  2 --
 test/test.py                                  | 25 ++++++++++++-------
 2 files changed, 16 insertions(+), 11 deletions(-)
 rename test/{browser_manifest.json => resources/browser_manifests/browser_manifest.json.mac} (78%)

diff --git a/test/browser_manifest.json b/test/resources/browser_manifests/browser_manifest.json.mac
similarity index 78%
rename from test/browser_manifest.json
rename to test/resources/browser_manifests/browser_manifest.json.mac
index a396b01ce..1aad32d26 100644
--- a/test/browser_manifest.json
+++ b/test/resources/browser_manifests/browser_manifest.json.mac
@@ -2,11 +2,9 @@
    {
     "name":"firefox5",
     "path":"/Applications/Firefox.app",
-    "type":"firefox"
    },
    {
     "name":"firefox6",
     "path":"/Users/sayrer/firefoxen/Aurora.app",
-    "type":"firefox"
    }
 ]
\ No newline at end of file
diff --git a/test/test.py b/test/test.py
index 5a869530e..d7a0fa674 100644
--- a/test/test.py
+++ b/test/test.py
@@ -11,7 +11,6 @@ DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
 
 ANAL = True
 DEFAULT_MANIFEST_FILE = 'test_manifest.json'
-DEFAULT_BROWSER_MANIFEST_FILE = 'browser_manifest.json'
 EQLOG_FILE = 'eq.log'
 REFDIR = 'ref'
 TMPDIR = 'tmp'
@@ -24,10 +23,11 @@ class TestOptions(OptionParser):
                         help="Run the script in master mode.", default=False)
         self.add_option("--manifestFile", action="store", type="string", dest="manifestFile",
                         help="A JSON file in the form of test_manifest.json (the default).")
+        self.add_option("-b", "--browser", action="store", type="string", dest="browser",
+                        help="The path to a single browser (right now, only Firefox is supported).")
         self.add_option("--browserManifestFile", action="store", type="string",
                         dest="browserManifestFile",
-                        help="A JSON file in the form of browser_manifest.json (the default).",
-                        default=DEFAULT_BROWSER_MANIFEST_FILE)
+                        help="A JSON file in the form of those found in resources/browser_manifests")
         self.set_usage(USAGE_EXAMPLE)
 
     def verifyOptions(self, options):
@@ -35,6 +35,8 @@ class TestOptions(OptionParser):
             self.error("--masterMode and --manifestFile must not be specified at the same time.")
         if not options.manifestFile:
             options.manifestFile = DEFAULT_MANIFEST_FILE
+        if options.browser and options.browserManifestFile:
+            print "Warning: ignoring browser argument since manifest file was also supplied"
         return options
         
 def prompt(question):
@@ -136,16 +138,15 @@ class BrowserCommand():
     def __init__(self, browserRecord):
         self.name = browserRecord["name"]
         self.path = browserRecord["path"]
-        self.type = browserRecord["type"]
 
-        if platform.system() == "Darwin" and self.path.endswith(".app"):
+        if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")):
             self._fixupMacPath()
 
         if not os.path.exists(self.path):
             throw("Path to browser '%s' does not exist." % self.path)
 
     def _fixupMacPath(self):
-        self.path = self.path + "/Contents/MacOS/firefox-bin"
+        self.path = os.path.join(self.path, "Contents", "MacOS", "firefox-bin")
 
     def setup(self):
         self.tempDir = tempfile.mkdtemp()
@@ -158,7 +159,7 @@ class BrowserCommand():
         shutil.rmtree(self.tempDir)
 
     def start(self, url):
-        cmds = [self.path, "-no-remote", "-profile", self.profileDir, url]
+        cmds = [self.path, "-foreground", "-no-remote", "-profile", self.profileDir, url]
         subprocess.call(cmds)
 
 def makeBrowserCommands(browserManifestFile):
@@ -179,8 +180,14 @@ def setUp(options):
 
     assert not os.path.isdir(TMPDIR)
 
-    testBrowsers = makeBrowserCommands(options.browserManifestFile)
-
+    testBrowsers = []
+    if options.browserManifestFile:
+        testBrowsers = makeBrowserCommands(options.browserManifestFile)
+    elif options.browser:
+        testBrowsers = [BrowserCommand({"path":options.browser, "name":"firefox"})] 
+    else:
+        print "No test browsers found. Use --browserManifest or --browser args."
+              
     with open(options.manifestFile) as mf:
         manifestList = json.load(mf)
 

From 1d5e28347fdb0c5e7f69f4ccc823fb81f654214e Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Thu, 23 Jun 2011 13:59:38 -0700
Subject: [PATCH 11/45] Cleanup newlines and fix a mistakenly symlinked file.

---
 .../browser_manifest.json.mac                 |   2 +-
 .../components/SpecialPowersObserver.js       | 294 +++++++++++++++++-
 test/resources/firefox/user.js                |   2 +-
 3 files changed, 295 insertions(+), 3 deletions(-)
 mode change 120000 => 100755 test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js

diff --git a/test/resources/browser_manifests/browser_manifest.json.mac b/test/resources/browser_manifests/browser_manifest.json.mac
index 1aad32d26..c287ab32a 100644
--- a/test/resources/browser_manifests/browser_manifest.json.mac
+++ b/test/resources/browser_manifests/browser_manifest.json.mac
@@ -7,4 +7,4 @@
     "name":"firefox6",
     "path":"/Users/sayrer/firefoxen/Aurora.app",
    }
-]
\ No newline at end of file
+]
diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js
deleted file mode 120000
index 6f90832fb..000000000
--- a/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js
+++ /dev/null
@@ -1 +0,0 @@
-/Users/sayrer/dev/mozilla-central/testing/mochitest/specialpowers/components/SpecialPowersObserver.js
\ No newline at end of file
diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js
new file mode 100755
index 000000000..90655e2e7
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js
@@ -0,0 +1,293 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Special Powers code
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Jesse Ruderman <jruderman@mozilla.com>
+ *   Robert Sayre <sayrer@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK *****/
+
+// Based on:
+// https://bugzilla.mozilla.org/show_bug.cgi?id=549539
+// https://bug549539.bugzilla.mozilla.org/attachment.cgi?id=429661
+// https://developer.mozilla.org/en/XPCOM/XPCOM_changes_in_Gecko_1.9.3
+// http://mxr.mozilla.org/mozilla-central/source/toolkit/components/console/hudservice/HUDService.jsm#3240
+// https://developer.mozilla.org/en/how_to_build_an_xpcom_component_in_javascript
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const CHILD_SCRIPT = "chrome://specialpowers/content/specialpowers.js"
+
+/**
+ * Special Powers Exception - used to throw exceptions nicely
+ **/
+function SpecialPowersException(aMsg) {
+  this.message = aMsg;
+  this.name = "SpecialPowersException";
+}
+
+SpecialPowersException.prototype.toString = function() {
+  return this.name + ': "' + this.message + '"';
+};
+
+/* XPCOM gunk */
+function SpecialPowersObserver() {
+  this._isFrameScriptLoaded = false;
+  this._messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
+                         getService(Ci.nsIChromeFrameMessageManager);
+}
+
+SpecialPowersObserver.prototype = {
+  classDescription: "Special powers Observer for use in testing.",
+  classID:          Components.ID("{59a52458-13e0-4d93-9d85-a637344f29a1}"),
+  contractID:       "@mozilla.org/special-powers-observer;1",
+  QueryInterface:   XPCOMUtils.generateQI([Components.interfaces.nsIObserver]),
+  _xpcom_categories: [{category: "profile-after-change", service: true }],
+
+  observe: function(aSubject, aTopic, aData)
+  {
+    switch (aTopic) {
+      case "profile-after-change":
+        this.init();
+        break;
+
+      case "chrome-document-global-created":
+        if (!this._isFrameScriptLoaded) {
+          // Register for any messages our API needs us to handle
+          this._messageManager.addMessageListener("SPPrefService", this);
+          this._messageManager.addMessageListener("SPProcessCrashService", this);
+          this._messageManager.addMessageListener("SPPingService", this);
+
+          this._messageManager.loadFrameScript(CHILD_SCRIPT, true);
+          this._isFrameScriptLoaded = true;
+        }
+        break;
+
+      case "xpcom-shutdown":
+        this.uninit();
+        break;
+
+      case "plugin-crashed":
+      case "ipc:content-shutdown":
+        function addDumpIDToMessage(propertyName) {
+          var id = aSubject.getPropertyAsAString(propertyName);
+          if (id) {
+            message.dumpIDs.push(id);
+          }
+        }
+
+        var message = { type: "crash-observed", dumpIDs: [] };
+        aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2);
+        if (aTopic == "plugin-crashed") {
+          addDumpIDToMessage("pluginDumpID");
+          addDumpIDToMessage("browserDumpID");
+        } else { // ipc:content-shutdown
+          addDumpIDToMessage("dumpID");
+        }
+        this._messageManager.sendAsyncMessage("SPProcessCrashService", message);
+        break;
+    }
+  },
+
+  init: function()
+  {
+    var obs = Services.obs;
+    obs.addObserver(this, "xpcom-shutdown", false);
+    obs.addObserver(this, "chrome-document-global-created", false);
+  },
+
+  uninit: function()
+  {
+    var obs = Services.obs;
+    obs.removeObserver(this, "chrome-document-global-created", false);
+    this.removeProcessCrashObservers();
+  },
+  
+  addProcessCrashObservers: function() {
+    if (this._processCrashObserversRegistered) {
+      return;
+    }
+
+    Services.obs.addObserver(this, "plugin-crashed", false);
+    Services.obs.addObserver(this, "ipc:content-shutdown", false);
+    this._processCrashObserversRegistered = true;
+  },
+
+  removeProcessCrashObservers: function() {
+    if (!this._processCrashObserversRegistered) {
+      return;
+    }
+
+    Services.obs.removeObserver(this, "plugin-crashed");
+    Services.obs.removeObserver(this, "ipc:content-shutdown");
+    this._processCrashObserversRegistered = false;
+  },
+
+  getCrashDumpDir: function() {
+    if (!this._crashDumpDir) {
+      var directoryService = Cc["@mozilla.org/file/directory_service;1"]
+                             .getService(Ci.nsIProperties);
+      this._crashDumpDir = directoryService.get("ProfD", Ci.nsIFile);
+      this._crashDumpDir.append("minidumps");
+    }
+    return this._crashDumpDir;
+  },
+
+  deleteCrashDumpFiles: function(aFilenames) {
+    var crashDumpDir = this.getCrashDumpDir();
+    if (!crashDumpDir.exists()) {
+      return false;
+    }
+
+    var success = aFilenames.length != 0;
+    aFilenames.forEach(function(crashFilename) {
+      var file = crashDumpDir.clone();
+      file.append(crashFilename);
+      if (file.exists()) {
+        file.remove(false);
+      } else {
+        success = false;
+      }
+    });
+    return success;
+  },
+
+  findCrashDumpFiles: function(aToIgnore) {
+    var crashDumpDir = this.getCrashDumpDir();
+    var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries;
+    if (!entries) {
+      return [];
+    }
+
+    var crashDumpFiles = [];
+    while (entries.hasMoreElements()) {
+      var file = entries.getNext().QueryInterface(Ci.nsIFile);
+      var path = String(file.path);
+      if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) {
+        crashDumpFiles.push(path);
+      }
+    }
+    return crashDumpFiles.concat();
+  },
+
+  /**
+   * messageManager callback function
+   * This will get requests from our API in the window and process them in chrome for it
+   **/
+  receiveMessage: function(aMessage) {
+    switch(aMessage.name) {
+      case "SPPrefService":
+        var prefs = Services.prefs;
+        var prefType = aMessage.json.prefType.toUpperCase();
+        var prefName = aMessage.json.prefName;
+        var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null;
+
+        if (aMessage.json.op == "get") {
+          if (!prefName || !prefType)
+            throw new SpecialPowersException("Invalid parameters for get in SPPrefService");
+        } else if (aMessage.json.op == "set") {
+          if (!prefName || !prefType  || prefValue === null)
+            throw new SpecialPowersException("Invalid parameters for set in SPPrefService");
+        } else if (aMessage.json.op == "clear") {
+          if (!prefName)
+            throw new SpecialPowersException("Invalid parameters for clear in SPPrefService");
+        } else {
+          throw new SpecialPowersException("Invalid operation for SPPrefService");
+        }
+        // Now we make the call
+        switch(prefType) {
+          case "BOOL":
+            if (aMessage.json.op == "get")
+              return(prefs.getBoolPref(prefName));
+            else 
+              return(prefs.setBoolPref(prefName, prefValue));
+          case "INT":
+            if (aMessage.json.op == "get") 
+              return(prefs.getIntPref(prefName));
+            else
+              return(prefs.setIntPref(prefName, prefValue));
+          case "CHAR":
+            if (aMessage.json.op == "get")
+              return(prefs.getCharPref(prefName));
+            else
+              return(prefs.setCharPref(prefName, prefValue));
+          case "COMPLEX":
+            if (aMessage.json.op == "get")
+              return(prefs.getComplexValue(prefName, prefValue[0]));
+            else
+              return(prefs.setComplexValue(prefName, prefValue[0], prefValue[1]));
+          case "":
+            if (aMessage.json.op == "clear") {
+              prefs.clearUserPref(prefName);
+              return;
+            }
+        }
+        break;
+
+      case "SPProcessCrashService":
+        switch (aMessage.json.op) {
+          case "register-observer":
+            this.addProcessCrashObservers();
+            break;
+          case "unregister-observer":
+            this.removeProcessCrashObservers();
+            break;
+          case "delete-crash-dump-files":
+            return this.deleteCrashDumpFiles(aMessage.json.filenames);
+          case "find-crash-dump-files":
+            return this.findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore);
+          default:
+            throw new SpecialPowersException("Invalid operation for SPProcessCrashService");
+        }
+        break;
+
+      case "SPPingService":
+        if (aMessage.json.op == "ping") {
+          aMessage.target
+                  .QueryInterface(Ci.nsIFrameLoaderOwner)
+                  .frameLoader
+                  .messageManager
+                  .sendAsyncMessage("SPPingService", { op: "pong" });
+        }
+        break;
+
+      default:
+        throw new SpecialPowersException("Unrecognized Special Powers API");
+    }
+  }
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([SpecialPowersObserver]);
diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js
index 2085bcbef..d4b9d4130 100644
--- a/test/resources/firefox/user.js
+++ b/test/resources/firefox/user.js
@@ -31,4 +31,4 @@ user_pref("security.warn_viewing_mixed", false);
 user_pref("app.update.enabled", false);
 user_pref("browser.panorama.experienced_first_run", true); // Assume experienced
 user_pref("dom.w3c_touch_events.enabled", true);
-user_pref("extensions.checkCompatibility", false);
\ No newline at end of file
+user_pref("extensions.checkCompatibility", false);

From 986ef148c47a6d5199a8962c8d07351a4630ff6a Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Tue, 21 Jun 2011 23:33:11 +0200
Subject: [PATCH 12/45] Backup work

---
 canvas_proxy.js    | 109 +++++++++++++++++++++++++++++++++++++++++++
 pdf.js             |  13 +++++-
 viewer_worker.html | 110 +++++++++++++++++++++++++++++++++++++++++++
 worker.js          | 113 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 343 insertions(+), 2 deletions(-)
 create mode 100644 canvas_proxy.js
 create mode 100644 viewer_worker.html
 create mode 100644 worker.js

diff --git a/canvas_proxy.js b/canvas_proxy.js
new file mode 100644
index 000000000..1b100beae
--- /dev/null
+++ b/canvas_proxy.js
@@ -0,0 +1,109 @@
+function CanvasProxy(width, height) {
+    var stack = this.$stack = [];
+
+    // Expose only the minimum of the canvas object - there is no dom to do
+    // more here.
+    this.canvas = {
+        width: width,
+        height: height
+    }
+
+    var ctxFunc = [
+        "createRadialGradient",
+        "arcTo",
+        "arc",
+        "fillText",
+        "strokeText",
+        "drawImage",
+        "getImageData",
+        "putImageData",
+        "createImageData",
+        "drawWindow",
+        "save",
+        "restore",
+        "scale",
+        "rotate",
+        "translate",
+        "transform",
+        "setTransform",
+        "createLinearGradient",
+        "createPattern",
+        "clearRect",
+        "fillRect",
+        "strokeRect",
+        "beginPath",
+        "closePath",
+        "moveTo",
+        "lineTo",
+        "quadraticCurveTo",
+        "bezierCurveTo",
+        "rect",
+        "fill",
+        "stroke",
+        "clip",
+        "measureText",
+        "isPointInPath"
+    ];
+    function buildFuncCall(name) {
+        return function() {
+            console.log("funcCall", name)
+            stack.push([name, Array.prototype.slice.call(arguments)]);
+        }
+    }
+    var name;
+    for (var i = 0; i < ctxFunc.length; i++) {
+        name = ctxFunc[i];
+        this[name] = buildFuncCall(name);
+    }
+
+    var ctxProp = {
+        // "canvas"
+        "globalAlpha": "1",
+        "globalCompositeOperation": "source-over",
+        "strokeStyle": "#000000",
+        "fillStyle": "#000000",
+        "lineWidth": "1",
+        "lineCap": "butt",
+        "lineJoin": "miter",
+        "miterLimit": "10",
+        "shadowOffsetX": "0",
+        "shadowOffsetY": "0",
+        "shadowBlur": "0",
+        "shadowColor": "rgba(0, 0, 0, 0)",
+        "font": "10px sans-serif",
+        "textAlign": "start",
+        "textBaseline": "alphabetic",
+        "mozTextStyle": "10px sans-serif",
+        "mozImageSmoothingEnabled": "true",
+        "DRAWWINDOW_DRAW_CARET": "1",
+        "DRAWWINDOW_DO_NOT_FLUSH": "2",
+        "DRAWWINDOW_DRAW_VIEW": "4",
+        "DRAWWINDOW_USE_WIDGET_LAYERS": "8",
+        "DRAWWINDOW_ASYNC_DECODE_IMAGES": "16",
+    }
+
+    function buildGetter(name) {
+        return function() {
+            return this["$" + name];
+        }
+    }
+
+    function buildSetter(name) {
+        return function(value) {
+            stack.push(["$", name, value]);
+            return this["$" + name] = value;
+        }
+    }
+
+    for (var name in ctxProp) {
+        this["$" + name] = ctxProp[name];
+        this.__defineGetter__(name, buildGetter(name));
+        this.__defineSetter__(name, buildSetter(name));
+    }
+}
+
+CanvasProxy.prototype.flush = function() {
+    postMessage("canvas_proxy_stack");
+    postMessage(JSON.stringify(this.$stack));
+    this.$stack.length = 0;
+}
diff --git a/pdf.js b/pdf.js
index 326c31234..09d1c874e 100644
--- a/pdf.js
+++ b/pdf.js
@@ -2277,7 +2277,10 @@ var CanvasGraphics = (function() {
         this.pendingClip = null;
         this.res = null;
         this.xobjs = null;
-        this.map = {
+    }
+
+    constructor.prototype = {
+        map: {
             // Graphics state
             w: "setLineWidth",
             J: "setLineCap",
@@ -2634,7 +2637,9 @@ var CanvasGraphics = (function() {
             }
 
             var fn = Function("objpool", src);
-            return function (gfx) { fn.call(gfx, objpool); };
+            var ret = function (gfx) { fn.call(gfx, objpool); };
+            ret.src = src;
+            return ret;
         },
 
         endDrawing: function() {
@@ -3041,6 +3046,7 @@ var CanvasGraphics = (function() {
         shadingFill: function(entryRef) {
             var xref = this.xref;
             var res = this.res;
+
             var shadingRes = xref.fetchIfRef(res.get("Shading"));
             if (!shadingRes)
                 error("No shading resource found");
@@ -3468,6 +3474,7 @@ var ColorSpace = (function() {
                 break;
             case "ICCBased":
                 var dict = stream.dict;
+
                 this.stream = stream;
                 this.dict = dict;
                 this.numComps = dict.get("N");
@@ -3574,6 +3581,7 @@ var PDFFunction = (function() {
                     v = encode[i2] + ((v - domain[i2]) *
                                       (encode[i2 + 1] - encode[i2]) /
                                       (domain[i2 + 1] - domain[i2]));
+
                     // clip to the size
                     args[i] = clip(v, 0, size[i] - 1);
                 }
@@ -3601,6 +3609,7 @@ var PDFFunction = (function() {
                     // decode
                     v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) /
                                       ((1 << bps) - 1));
+
                     // clip to the domain
                     output.push(clip(v, range[i2], range[i2 + 1]));
                 }
diff --git a/viewer_worker.html b/viewer_worker.html
new file mode 100644
index 000000000..f9e1f0b32
--- /dev/null
+++ b/viewer_worker.html
@@ -0,0 +1,110 @@
+<html>
+    <head>
+        <title>Simple pdf.js page viewer worker</title>
+<script>
+var myWorker = new Worker('worker.js');
+
+// var array = new Uint8Array(2);
+// array[0] = 1;
+// array[1] = 300;
+//
+const WAIT = 0;
+const CANVAS_PROXY_STACK = 1;
+const LOG = 2;
+
+var onMessageState = WAIT;
+myWorker.onmessage = function(event) {
+    var data = event.data;
+    console.log("onMessageRaw", data);
+    switch (onMessageState) {
+        case WAIT:
+            if (typeof data != "string") {
+                throw "expecting to get an string";
+            }
+            switch (data) {
+                case "log":
+                    onMessageState = LOG;
+                    return;
+                case "canvas_proxy_stack":
+                    onMessageState = CANVAS_PROXY_STACK;
+                    return;
+                default:
+                    throw "unkown state: " + data
+            }
+            break;
+
+        case LOG:
+            console.log.apply(console, JSON.parse(data));
+            onMessageState = WAIT;
+            break;
+
+        case CANVAS_PROXY_STACK:
+            var stack = JSON.parse(data);
+            for (var i = 0; i < stack.length; i++) {
+                var opp = stack[i];
+                if (opp[0] == "$") {
+                    console.log("set property", opp[1], opp[2]);
+                    ctx[opp[1]] = opp[2];
+                } else {
+                    console.log("execute", opp[0], opp[1]);
+                    ctx[opp[0]].apply(ctx, opp[1]);
+                }
+            }
+            onMessageState = WAIT;
+            break;
+    }
+}
+//
+// myWorker.postMessage(array);
+
+function open(url) {
+    document.title = url;
+    var req = new XMLHttpRequest();
+    req.open("GET", url);
+    req.mozResponseType = req.responseType = "arraybuffer";
+    req.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200;
+    req.onreadystatechange = function() {
+      if (req.readyState == 4 && req.status == req.expected) {
+        var data = req.mozResponseArrayBuffer || req.mozResponse ||
+                   req.responseArrayBuffer || req.response;
+        myWorker.postMessage(data);
+      }
+    };
+    req.send(null);
+}
+
+window.onload = function() {
+    var ctx = window.ctx = document.getElementById("canvas").getContext("2d");
+    // for (var name in ctx) {
+    //     if (!(ctx[name] instanceof Function)) {
+    //         console.log('"' + name + '": "' + ctx[name] + '",');
+    //     }
+    // }
+    open("compressed.tracemonkey-pldi-09.pdf");
+}
+</script>
+        <link rel="stylesheet" href="viewer.css"></link>
+  </head>
+
+  <body>
+    <div id="controls">
+    <input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input>
+    <!-- This only opens supported PDFs from the source path...
+      -- Can we use JSONP to overcome the same-origin restrictions? -->
+      <button onclick="prevPage();">Previous</button>
+      <button onclick="nextPage();">Next</button>
+      <input type="text" id="pageNumber" onchange="gotoPage(this.value);"
+             value="1" size="4"></input>
+      <span id="numPages">--</span>
+      <span id="info"></span>
+    </div>
+
+    <div id="viewer">
+      <!-- Canvas dimensions must be specified in CSS pixels.  CSS pixels
+           are always 96 dpi.  816x1056 is 8.5x11in at 96dpi. -->
+      <!-- We're rendering here at 1.5x scale. -->
+      <canvas id="canvas" width="1224" height="1584"></canvas>
+    </div>
+  </body>
+</html>
+
diff --git a/worker.js b/worker.js
new file mode 100644
index 000000000..fdc762afd
--- /dev/null
+++ b/worker.js
@@ -0,0 +1,113 @@
+"use strict";
+
+function log() {
+    var args = Array.prototype.slice.call(arguments);
+    postMessage("log");
+    postMessage(JSON.stringify(args))
+}
+
+var console = {
+    log: log
+}
+
+importScripts("canvas_proxy.js");
+importScripts("pdf.js");
+importScripts("fonts.js");
+importScripts("glyphlist.js")
+
+// var array = new Uint8Array(2);
+// array[0] = 1;
+// array[1] = 300;
+// postMessage(array);
+
+var timer = null;
+function tic() {
+    timer = Date.now();
+}
+
+function toc(msg) {
+    log("Took ", (Date.now() - timer));
+    timer = null;
+}
+
+
+var canvas = new CanvasProxy(1224, 1584);
+// canvas.moveTo(0, 10);
+// canvas.lineTo(0, 20);
+// canvas.lineTo(500, 500);
+// canvas.flush();
+// canvas.stroke();
+// canvas.flush();
+log("test");
+
+onmessage = function(event) {
+    var data = event.data;
+    var pdfDocument = new PDFDoc(new Stream(data));
+    var numPages = pdfDocument.numPages;
+
+    tic();
+    // Let's try to render the first page...
+    var page = pdfDocument.getPage(1);
+
+    // page.compile will collect all fonts for us, once we have loaded them
+    // we can trigger the actual page rendering with page.display
+    var fonts = [];
+
+    var gfx = new CanvasGraphics(canvas);
+    page.compile(gfx, fonts);
+    toc("compiled page");
+
+    //
+    var fontsReady = true;
+        // Inspect fonts and translate the missing one
+        var count = fonts.length;
+        for (var i = 0; i < count; i++) {
+          var font = fonts[i];
+          if (Fonts[font.name]) {
+            fontsReady = fontsReady && !Fonts[font.name].loading;
+            continue;
+          }
+
+          new Font(font.name, font.file, font.properties);
+          fontsReady = false;
+        }
+
+        function delayLoadFont() {
+          for (var i = 0; i < count; i++) {
+            if (Fonts[font.name].loading)
+              return;
+          }
+          clearInterval(pageInterval);
+          page.display(gfx);
+
+          canvas.flush();
+        };
+
+        if (fontsReady) {
+          delayLoadFont();
+        } else {
+          pageInterval = setInterval(delayLoadFont, 10);
+        }
+        postMessage(page.code.src);
+}
+
+// function open(url) {
+//     var req = new XMLHttpRequest();
+//     req.open("GET", url);
+//     // req.responseType = "arraybuffer";
+//     req.expected = 0;//(document.URL.indexOf("file:") == 0) ? 0 : 200;
+//     req.onreadystatechange = function() {
+//       postMessage("loaded");
+//       if (req.readyState == 4 && req.status == req.expected) {
+//         var data = req.mozResponseArrayBuffer || req.mozResponse ||
+//                    req.responseArrayBuffer || req.response;
+//         pdfDocument = new PDFDoc(new Stream(data));
+//         numPages = pdfDocument.numPages;
+//         // document.getElementById("numPages").innerHTML = numPages.toString();
+//         // goToPage(pageNum);
+//       }
+//     };
+//     req.send(null);
+// }
+//
+// open("compressed.tracemonkey-pldi-09.pdf")
\ No newline at end of file

From e15328800aad995b1a2957ba4f98f9a618a6208f Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 01:28:17 +0200
Subject: [PATCH 13/45] Most working, but once you add the font-css file to the
 web page, there is no font drawn at all

---
 canvas_proxy.js    |  12 +++-
 fonts.js           | 164 +++++++++++++++++++++++++--------------------
 pdf.js             |  34 ++++++++--
 viewer_worker.html |  95 +++++++++++++++++++++++---
 worker.js          |  64 +++++++++---------
 5 files changed, 248 insertions(+), 121 deletions(-)

diff --git a/canvas_proxy.js b/canvas_proxy.js
index 1b100beae..433166aac 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -42,11 +42,17 @@ function CanvasProxy(width, height) {
         "stroke",
         "clip",
         "measureText",
-        "isPointInPath"
+        "isPointInPath",
+
+        "$setCurrentX",
+        "$addCurrentX",
+        "$saveCurrentX",
+        "$restoreCurrentX",
+        "$showText"
     ];
     function buildFuncCall(name) {
         return function() {
-            console.log("funcCall", name)
+            // console.log("funcCall", name)
             stack.push([name, Array.prototype.slice.call(arguments)]);
         }
     }
@@ -103,6 +109,8 @@ function CanvasProxy(width, height) {
 }
 
 CanvasProxy.prototype.flush = function() {
+    // postMessage("log");
+    // postMessage(JSON.stringify([this.$stack.length]));
     postMessage("canvas_proxy_stack");
     postMessage(JSON.stringify(this.$stack));
     this.$stack.length = 0;
diff --git a/fonts.js b/fonts.js
index d5943b7a3..8c0abbcec 100644
--- a/fonts.js
+++ b/fonts.js
@@ -759,91 +759,109 @@ var Font = (function () {
       var data = this.font;
       var fontName = this.name;
 
+      var isWorker = (typeof window == "undefined");
       /** Hack begin */
+      if (!isWorker) {
+
+          // Actually there is not event when a font has finished downloading so
+          // the following code are a dirty hack to 'guess' when a font is ready
+          var canvas = document.createElement("canvas");
+          var style = "border: 1px solid black; position:absolute; top: " +
+                       (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px";
+          canvas.setAttribute("style", style);
+          canvas.setAttribute("width", 340);
+          canvas.setAttribute("heigth", 100);
+          document.body.appendChild(canvas);
+
+          // Get the font size canvas think it will be for 'spaces'
+          var ctx = canvas.getContext("2d");
+          ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
+          var testString = "   ";
+
+          // When debugging use the characters provided by the charsets to visually
+          // see what's happening instead of 'spaces'
+          var debug = false;
+          if (debug) {
+            var name = document.createElement("font");
+            name.setAttribute("style", "position: absolute; left: 20px; top: " +
+                              (100 * fontCount + 60) + "px");
+            name.innerHTML = fontName;
+            document.body.appendChild(name);
+
+            // Retrieve font charset
+            var charset = Fonts[fontName].properties.charset || [];
+
+            // if the charset is too small make it repeat a few times
+            var count = 30;
+            while (count-- && charset.length <= 30)
+              charset = charset.concat(charset.slice());
+
+            for (var i = 0; i < charset.length; i++) {
+              var unicode = GlyphsUnicode[charset[i]];
+              if (!unicode)
+                continue;
+              testString += String.fromCharCode(unicode);
+            }
 
-      // Actually there is not event when a font has finished downloading so
-      // the following code are a dirty hack to 'guess' when a font is ready
-      var canvas = document.createElement("canvas");
-      var style = "border: 1px solid black; position:absolute; top: " +
-                   (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px";
-      canvas.setAttribute("style", style);
-      canvas.setAttribute("width", 340);
-      canvas.setAttribute("heigth", 100);
-      document.body.appendChild(canvas);
-
-      // Get the font size canvas think it will be for 'spaces'
-      var ctx = canvas.getContext("2d");
-      ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
-      var testString = "   ";
-
-      // When debugging use the characters provided by the charsets to visually
-      // see what's happening instead of 'spaces'
-      var debug = false;
-      if (debug) {
-        var name = document.createElement("font");
-        name.setAttribute("style", "position: absolute; left: 20px; top: " +
-                          (100 * fontCount + 60) + "px");
-        name.innerHTML = fontName;
-        document.body.appendChild(name);
-
-        // Retrieve font charset
-        var charset = Fonts[fontName].properties.charset || [];
-
-        // if the charset is too small make it repeat a few times
-        var count = 30;
-        while (count-- && charset.length <= 30)
-          charset = charset.concat(charset.slice());
-
-        for (var i = 0; i < charset.length; i++) {
-          var unicode = GlyphsUnicode[charset[i]];
-          if (!unicode)
-            continue;
-          testString += String.fromCharCode(unicode);
-        }
-
-        ctx.fillText(testString, 20, 20);
-      }
+            ctx.fillText(testString, 20, 20);
+          }
 
-      // Periodicaly check for the width of the testString, it will be
-      // different once the real font has loaded
-      var textWidth = ctx.measureText(testString).width;
-
-      var interval = window.setInterval(function canvasInterval(self) {
-        this.start = this.start || Date.now();
-        ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
-
-        // For some reasons the font has not loaded, so mark it loaded for the
-        // page to proceed but cry
-        if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
-          window.clearInterval(interval);
-          Fonts[fontName].loading = false;
-          warn("Is " + fontName + " for charset: " + charset + " loaded?");
-          this.start = 0;
-        } else if (textWidth != ctx.measureText(testString).width) {
-          window.clearInterval(interval);
-          Fonts[fontName].loading = false;
-          this.start = 0;
-        }
+          // Periodicaly check for the width of the testString, it will be
+          // different once the real font has loaded
+          var textWidth = ctx.measureText(testString).width;
+
+          var interval = window.setInterval(function canvasInterval(self) {
+            this.start = this.start || Date.now();
+            ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
+
+            // For some reasons the font has not loaded, so mark it loaded for the
+            // page to proceed but cry
+            if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
+              window.clearInterval(interval);
+              Fonts[fontName].loading = false;
+              warn("Is " + fontName + " for charset: " + charset + " loaded?");
+              this.start = 0;
+            } else if (textWidth != ctx.measureText(testString).width) {
+              window.clearInterval(interval);
+              Fonts[fontName].loading = false;
+              this.start = 0;
+            }
 
-        if (debug)
-          ctx.fillText(testString, 20, 50);
-      }, 30, this);
+            if (debug)
+              ctx.fillText(testString, 20, 50);
+          }, 30, this);
+    }
 
       /** Hack end */
-
+      //
       // Get the base64 encoding of the binary font data
       var str = "";
       var length = data.length;
       for (var i = 0; i < length; ++i)
         str += String.fromCharCode(data[i]);
 
-      var base64 = window.btoa(str);
-
-      // Add the @font-face rule to the document
-      var url = "url(data:" + this.mimetype + ";base64," + base64 + ");";
-      var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
-      var styleSheet = document.styleSheets[0];
-      styleSheet.insertRule(rule, styleSheet.length);
+      if (isWorker) {
+          postMessage("font");
+          postMessage(JSON.stringify({
+              str: str,
+              mimetype: this.mimetype,
+              fontName: fontName,
+          }));
+
+          setTimeout(function() {
+            Fonts[fontName].loading = false;
+          }, kMaxWaitForFontFace);
+      } else {
+          var base64 = window.btoa(str);
+
+          // Add the @font-face rule to the document
+          var url = "url(data:" + this.mimetype + ";base64," + base64 + ");";
+          var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
+          var styleSheet = document.styleSheets[0];
+          styleSheet.insertRule(rule, styleSheet.length);
+          console.log("added font", fontName);
+          console.log(rule);
+      }
     }
   };
 
diff --git a/pdf.js b/pdf.js
index 09d1c874e..80e9c1930 100644
--- a/pdf.js
+++ b/pdf.js
@@ -2674,12 +2674,18 @@ var CanvasGraphics = (function() {
         },
         save: function() {
             this.ctx.save();
+            if (this.ctx.$saveCurrentX) {
+                this.ctx.$saveCurrentX();
+            }
             this.stateStack.push(this.current);
             this.current = new CanvasExtraState();
         },
         restore: function() {
             var prev = this.stateStack.pop();
             if (prev) {
+                if (this.ctx.$restoreCurrentX) {
+                    this.ctx.$restoreCurrentX();
+                }
                 this.current = prev;
                 this.ctx.restore();
             }
@@ -2760,6 +2766,9 @@ var CanvasGraphics = (function() {
         // Text
         beginText: function() {
             this.current.textMatrix = IDENTITY_MATRIX;
+            if (this.ctx.$setCurrentX) {
+                this.ctx.$setCurrentX(0)
+            }
             this.current.x = this.current.lineX = 0;
             this.current.y = this.current.lineY = 0;
         },
@@ -2814,6 +2823,9 @@ var CanvasGraphics = (function() {
         moveText: function (x, y) {
             this.current.x = this.current.lineX += x;
             this.current.y = this.current.lineY += y;
+            if (this.ctx.$setCurrentX) {
+                this.ctx.$setCurrentX(this.current.x)
+            }
         },
         setLeadingMoveText: function(x, y) {
             this.setLeading(-y);
@@ -2821,6 +2833,10 @@ var CanvasGraphics = (function() {
         },
         setTextMatrix: function(a, b, c, d, e, f) {
             this.current.textMatrix = [ a, b, c, d, e, f ];
+
+            if (this.ctx.$setCurrentX) {
+                this.$setCurrentX(0)
+            }
             this.current.x = this.current.lineX = 0;
             this.current.y = this.current.lineY = 0;
         },
@@ -2831,11 +2847,15 @@ var CanvasGraphics = (function() {
             this.ctx.save();
             this.ctx.transform.apply(this.ctx, this.current.textMatrix);
             this.ctx.scale(1, -1);
-            this.ctx.translate(0, -2 * this.current.y);
 
-            text = Fonts.charsToUnicode(text);
-            this.ctx.fillText(text, this.current.x, this.current.y);
-            this.current.x += this.ctx.measureText(text).width;
+            if (this.ctx.$showText) {
+                this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text));
+            } else {
+                console.log(text, this.current.x);
+                text = Fonts.charsToUnicode(text);
+                this.ctx.fillText(text, 0, 0);
+                this.current.x += this.ctx.measureText(text).width;
+            }
 
             this.ctx.restore();
         },
@@ -2843,7 +2863,11 @@ var CanvasGraphics = (function() {
             for (var i = 0; i < arr.length; ++i) {
                 var e = arr[i];
                 if (IsNum(e)) {
-                    this.current.x -= e * 0.001 * this.current.fontSize;
+                    if (this.ctx.$addCurrentX) {
+                        this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize)
+                    } else {
+                        this.current.x -= e * 0.001 * this.current.fontSize;
+                    }
                 } else if (IsString(e)) {
                     this.showText(e);
                 } else {
diff --git a/viewer_worker.html b/viewer_worker.html
index f9e1f0b32..dde249e55 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -11,11 +11,63 @@ var myWorker = new Worker('worker.js');
 const WAIT = 0;
 const CANVAS_PROXY_STACK = 1;
 const LOG = 2;
+const FONT = 3;
+
+var currentX = 0;
+var currentXStack = [];
+var special = {
+    "$setCurrentX": function(value) {
+        currentX = value;
+    },
+
+    "$addCurrentX": function(value) {
+        currentX += value;
+    },
+
+    "$saveCurrentX": function() {
+        currentXStack.push(currentX);
+    },
+
+    "$restoreCurrentX": function() {
+        currentX = currentXStack.pop();
+    },
+
+    "$showText": function(y, text) {
+        console.log(text, currentX, y, this.measureText(text).width);
+
+        this.translate(currentX, -1 * y);
+        this.fillText(text, 0, 0);
+        currentX += this.measureText(text).width;
+    }
+}
+
+function renderProxyCanvas(stack) {
+    // for (var i = 0; i < stack.length; i++) {
+    for (var i = 0; i < 1000; i++) {
+        var opp = stack[i];
+        if (opp[0] == "$") {
+            // console.log("set property", opp[1], opp[2]);
+            if (opp[1] == "font") {
+                ctx[opp[1]] = opp[2];
+                // console.log("font", opp[2]);
+            } else {
+                ctx[opp[1]] = opp[2];
+            }
+
+        } else if (opp[0] in special) {
+            // console.log("sepcial", opp[0], opp[1])
+            special[opp[0]].apply(ctx, opp[1]);
+        } else {
+            // console.log("execute", opp[0], opp[1]);
+            ctx[opp[0]].apply(ctx, opp[1]);
+        }
+    }
+}
 
 var onMessageState = WAIT;
 myWorker.onmessage = function(event) {
     var data = event.data;
-    console.log("onMessageRaw", data);
+    // console.log("onMessageRaw", data);
     switch (onMessageState) {
         case WAIT:
             if (typeof data != "string") {
@@ -28,11 +80,31 @@ myWorker.onmessage = function(event) {
                 case "canvas_proxy_stack":
                     onMessageState = CANVAS_PROXY_STACK;
                     return;
+                case "font":
+                    onMessageState = FONT;
+                    return;
                 default:
                     throw "unkown state: " + data
             }
             break;
 
+        case FONT:
+            data = JSON.parse(data);
+            var base64 = window.btoa(data.str);
+
+            // Add the @font-face rule to the document
+            var url = "url(data:" + data.mimetype + ";base64," + base64 + ");";
+            var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}";
+            var styleSheet = document.styleSheets[0];
+
+            // ONCE you uncomment this, there is no font painted at all :(
+            // styleSheet.insertRule(rule, styleSheet.length);
+
+            console.log("added font", data.fontName);
+            // console.log(rule);
+            onMessageState = WAIT;
+            break;
+
         case LOG:
             console.log.apply(console, JSON.parse(data));
             onMessageState = WAIT;
@@ -40,17 +112,14 @@ myWorker.onmessage = function(event) {
 
         case CANVAS_PROXY_STACK:
             var stack = JSON.parse(data);
-            for (var i = 0; i < stack.length; i++) {
-                var opp = stack[i];
-                if (opp[0] == "$") {
-                    console.log("set property", opp[1], opp[2]);
-                    ctx[opp[1]] = opp[2];
-                } else {
-                    console.log("execute", opp[0], opp[1]);
-                    ctx[opp[0]].apply(ctx, opp[1]);
-                }
-            }
+            console.log("canvas stack", stack.length)
+            // console.log(stack.length);
             onMessageState = WAIT;
+            // return;
+
+            setTimeout(function() {
+                renderProxyCanvas(stack);
+            }, 2000);
             break;
     }
 }
@@ -75,6 +144,10 @@ function open(url) {
 
 window.onload = function() {
     var ctx = window.ctx = document.getElementById("canvas").getContext("2d");
+    ctx.save();
+    ctx.fillStyle = "rgb(255, 255, 255)";
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+    ctx.restore();
     // for (var name in ctx) {
     //     if (!(ctx[name] instanceof Function)) {
     //         console.log('"' + name + '": "' + ctx[name] + '",');
diff --git a/worker.js b/worker.js
index fdc762afd..9ee9409bd 100644
--- a/worker.js
+++ b/worker.js
@@ -40,6 +40,7 @@ var canvas = new CanvasProxy(1224, 1584);
 // canvas.flush();
 log("test");
 
+var pageInterval;
 onmessage = function(event) {
     var data = event.data;
     var pdfDocument = new PDFDoc(new Stream(data));
@@ -59,36 +60,39 @@ onmessage = function(event) {
 
     //
     var fontsReady = true;
-        // Inspect fonts and translate the missing one
-        var count = fonts.length;
-        for (var i = 0; i < count; i++) {
-          var font = fonts[i];
-          if (Fonts[font.name]) {
-            fontsReady = fontsReady && !Fonts[font.name].loading;
-            continue;
-          }
-
-          new Font(font.name, font.file, font.properties);
-          fontsReady = false;
-        }
-
-        function delayLoadFont() {
-          for (var i = 0; i < count; i++) {
-            if (Fonts[font.name].loading)
-              return;
-          }
-          clearInterval(pageInterval);
-          page.display(gfx);
-
-          canvas.flush();
-        };
-
-        if (fontsReady) {
-          delayLoadFont();
-        } else {
-          pageInterval = setInterval(delayLoadFont, 10);
-        }
-        postMessage(page.code.src);
+    // Inspect fonts and translate the missing one
+    var count = fonts.length;
+    for (var i = 0; i < count; i++) {
+      var font = fonts[i];
+      if (Fonts[font.name]) {
+        fontsReady = fontsReady && !Fonts[font.name].loading;
+        continue;
+      }
+
+      new Font(font.name, font.file, font.properties);
+      fontsReady = false;
+    }
+
+    // function delayLoadFont() {
+    //   for (var i = 0; i < count; i++) {
+    //     if (Fonts[font.name].loading)
+    //       return;
+    //   }
+    //   clearInterval(pageInterval);
+    //   page.display(gfx);
+    //
+    //   log("flush");
+    //   canvas.flush();
+    // };
+
+    // if (fontsReady) {
+    //   delayLoadFont();
+    // } else {
+    //   pageInterval = setInterval(delayLoadFont, 10);
+    // }
+
+    page.display(gfx);
+    canvas.flush();
 }
 
 // function open(url) {

From 61b76c7e87307b5d192712e240fe9b3c8ecfaf46 Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 09:15:55 +0200
Subject: [PATCH 14/45] Make fonts getting loaded by a very nasty hack

---
 fonts.js           |  6 +-----
 pdf.js             |  6 +++---
 viewer_worker.html | 43 ++++++++++++++++++++++++++++---------------
 worker.js          |  2 +-
 4 files changed, 33 insertions(+), 24 deletions(-)

diff --git a/fonts.js b/fonts.js
index 8c0abbcec..c7f54edf9 100644
--- a/fonts.js
+++ b/fonts.js
@@ -844,13 +844,9 @@ var Font = (function () {
           postMessage("font");
           postMessage(JSON.stringify({
               str: str,
-              mimetype: this.mimetype,
               fontName: fontName,
+              mimetype: this.mimetype
           }));
-
-          setTimeout(function() {
-            Fonts[fontName].loading = false;
-          }, kMaxWaitForFontFace);
       } else {
           var base64 = window.btoa(str);
 
diff --git a/pdf.js b/pdf.js
index 80e9c1930..64b99a33e 100644
--- a/pdf.js
+++ b/pdf.js
@@ -2835,7 +2835,7 @@ var CanvasGraphics = (function() {
             this.current.textMatrix = [ a, b, c, d, e, f ];
 
             if (this.ctx.$setCurrentX) {
-                this.$setCurrentX(0)
+                this.ctx.$setCurrentX(0)
             }
             this.current.x = this.current.lineX = 0;
             this.current.y = this.current.lineY = 0;
@@ -2851,9 +2851,9 @@ var CanvasGraphics = (function() {
             if (this.ctx.$showText) {
                 this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text));
             } else {
-                console.log(text, this.current.x);
                 text = Fonts.charsToUnicode(text);
-                this.ctx.fillText(text, 0, 0);
+                this.ctx.translate(this.current.x, -1 * this.current.y);
+                this.ctx.fillText(Fonts.charsToUnicode(text), 0, 0);
                 this.current.x += this.ctx.measureText(text).width;
             }
 
diff --git a/viewer_worker.html b/viewer_worker.html
index dde249e55..930fb6cd5 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -8,10 +8,6 @@ var myWorker = new Worker('worker.js');
 // array[0] = 1;
 // array[1] = 300;
 //
-const WAIT = 0;
-const CANVAS_PROXY_STACK = 1;
-const LOG = 2;
-const FONT = 3;
 
 var currentX = 0;
 var currentXStack = [];
@@ -33,7 +29,7 @@ var special = {
     },
 
     "$showText": function(y, text) {
-        console.log(text, currentX, y, this.measureText(text).width);
+        // console.log(text, currentX, y, this.measureText(text).width);
 
         this.translate(currentX, -1 * y);
         this.fillText(text, 0, 0);
@@ -41,14 +37,16 @@ var special = {
     }
 }
 
+var gStack;
 function renderProxyCanvas(stack) {
-    // for (var i = 0; i < stack.length; i++) {
-    for (var i = 0; i < 1000; i++) {
+    for (var i = 0; i < stack.length; i++) {
+    // for (var i = 0; i < 1000; i++) {
         var opp = stack[i];
         if (opp[0] == "$") {
             // console.log("set property", opp[1], opp[2]);
             if (opp[1] == "font") {
                 ctx[opp[1]] = opp[2];
+                // ctx.font = "10px 'Verdana Bold Italic'";
                 // console.log("font", opp[2]);
             } else {
                 ctx[opp[1]] = opp[2];
@@ -64,7 +62,15 @@ function renderProxyCanvas(stack) {
     }
 }
 
+const WAIT = 0;
+const CANVAS_PROXY_STACK = 1;
+const LOG = 2;
+const FONT = 3;
+
 var onMessageState = WAIT;
+var fontStr = null;
+var first = true;
+var intervals = [];
 myWorker.onmessage = function(event) {
     var data = event.data;
     // console.log("onMessageRaw", data);
@@ -96,12 +102,15 @@ myWorker.onmessage = function(event) {
             var url = "url(data:" + data.mimetype + ";base64," + base64 + ");";
             var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}";
             var styleSheet = document.styleSheets[0];
+            styleSheet.insertRule(rule, styleSheet.length);
 
-            // ONCE you uncomment this, there is no font painted at all :(
-            // styleSheet.insertRule(rule, styleSheet.length);
+            // *HACK*: this makes the font get loaded on the page. WTF? We
+            // really have to set the fonts a few time...
+            var interval = setInterval(function() {
+                ctx.font = "bold italic 20px " + data.fontName + ", Symbol, Arial";
+            }, 10);
+            intervals.push(interval);
 
-            console.log("added font", data.fontName);
-            // console.log(rule);
             onMessageState = WAIT;
             break;
 
@@ -112,14 +121,18 @@ myWorker.onmessage = function(event) {
 
         case CANVAS_PROXY_STACK:
             var stack = JSON.parse(data);
+            gStack = stack;
             console.log("canvas stack", stack.length)
-            // console.log(stack.length);
-            onMessageState = WAIT;
-            // return;
 
+            // Shedule a timeout. Hoping the fonts are loaded after 100ms.
             setTimeout(function() {
+                // Remove all setIntervals to make the font load.
+                intervals.forEach(function(inter) {
+                    clearInterval(inter);
+                });
                 renderProxyCanvas(stack);
-            }, 2000);
+            }, 100);
+            onMessageState = WAIT;
             break;
     }
 }
diff --git a/worker.js b/worker.js
index 9ee9409bd..6d34a9d62 100644
--- a/worker.js
+++ b/worker.js
@@ -48,7 +48,7 @@ onmessage = function(event) {
 
     tic();
     // Let's try to render the first page...
-    var page = pdfDocument.getPage(1);
+    var page = pdfDocument.getPage(8);
 
     // page.compile will collect all fonts for us, once we have loaded them
     // we can trigger the actual page rendering with page.display

From fc007b99d0edf3086445cf5d0751f6b0f003073f Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 09:46:11 +0200
Subject: [PATCH 15/45] Introduce ImageCanvas to handle canvas rendering in
 WebWorker

---
 canvas_proxy.js |  7 ++++---
 fonts.js        |  2 --
 pdf.js          | 41 +++++++++++++++++++++++++++++++++--------
 3 files changed, 37 insertions(+), 13 deletions(-)

diff --git a/canvas_proxy.js b/canvas_proxy.js
index 433166aac..ccc95c74a 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -14,9 +14,9 @@ function CanvasProxy(width, height) {
         "arc",
         "fillText",
         "strokeText",
-        "drawImage",
-        "getImageData",
-        "putImageData",
+        // "drawImage",
+        // "getImageData",
+        // "putImageData",
         "createImageData",
         "drawWindow",
         "save",
@@ -50,6 +50,7 @@ function CanvasProxy(width, height) {
         "$restoreCurrentX",
         "$showText"
     ];
+
     function buildFuncCall(name) {
         return function() {
             // console.log("funcCall", name)
diff --git a/fonts.js b/fonts.js
index c7f54edf9..9c9201b72 100644
--- a/fonts.js
+++ b/fonts.js
@@ -855,8 +855,6 @@ var Font = (function () {
           var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
           var styleSheet = document.styleSheets[0];
           styleSheet.insertRule(rule, styleSheet.length);
-          console.log("added font", fontName);
-          console.log(rule);
       }
     }
   };
diff --git a/pdf.js b/pdf.js
index 64b99a33e..273550084 100644
--- a/pdf.js
+++ b/pdf.js
@@ -2269,14 +2269,32 @@ var Encodings = {
   }
 };
 
+function ImageCanvas(width, height) {
+    var tmpCanvas = this.canvas = document.createElement("canvas");
+    tmpCanvas.width = width;
+    tmpCanvas.height = height;
+
+    this.ctx = tmpCanvas.getContext("2d");
+    this.imgData = this.ctx.getImageData(0, 0, width, height);
+}
+
+ImageCanvas.prototype.putImageData = function(imgData) {
+    this.ctx.putImageData(imgData, 0, 0);
+}
+
+ImageCanvas.prototype.getCanvas = function() {
+    return this.canvas;
+}
+
 var CanvasGraphics = (function() {
-    function constructor(canvasCtx) {
+    function constructor(canvasCtx, imageCanvas) {
         this.ctx = canvasCtx;
         this.current = new CanvasExtraState();
         this.stateStack = [ ];
         this.pendingClip = null;
         this.res = null;
         this.xobjs = null;
+        this.ImageCanvas = imageCanvas || ImageCanvas;
     }
 
     constructor.prototype = {
@@ -3009,6 +3027,7 @@ var CanvasGraphics = (function() {
             var tmpCanvas = document.createElement("canvas");
             tmpCanvas.width = Math.ceil(botRight[0] - topLeft[0]);
             tmpCanvas.height = Math.ceil(botRight[1] - topLeft[1]);
+            console.log("tilingFill", tmpCanvas.width, tmpCanvas.height);
 
             // set the new canvas element context as the graphics context
             var tmpCtx = tmpCanvas.getContext("2d");
@@ -3249,6 +3268,7 @@ var CanvasGraphics = (function() {
                 ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
                               0, -h, w, h);
                 this.restore();
+                console.log("drawImage");
                 return;
             }
 
@@ -3328,11 +3348,15 @@ var CanvasGraphics = (function() {
                 // handle matte object
             }
 
-            var tmpCanvas = document.createElement("canvas");
-            tmpCanvas.width = w;
-            tmpCanvas.height = h;
-            var tmpCtx = tmpCanvas.getContext("2d");
-            var imgData = tmpCtx.getImageData(0, 0, w, h);
+            var tmpCanvas = new this.ImageCanvas(w, h);
+            // var tmpCanvas = document.createElement("canvas");
+            // tmpCanvas.width = w;
+            // tmpCanvas.height = h;
+            //
+            // var tmpCtx = tmpCanvas.getContext("2d");
+            // var imgData = tmpCtx.getImageData(0, 0, w, h);
+            // var pixels = imgData.data;
+            var imgData = tmpCanvas.imgData;
             var pixels = imgData.data;
 
             if (bitsPerComponent != 8)
@@ -3399,8 +3423,9 @@ var CanvasGraphics = (function() {
                     TODO("Images with "+ numComps + " components per pixel");
                 }
             }
-            tmpCtx.putImageData(imgData, 0, 0);
-            ctx.drawImage(tmpCanvas, 0, -h);
+            console.log("paintImageXObject", w, h);
+            tmpCanvas.putImageData(imgData, 0, 0);
+            ctx.drawImage(tmpCanvas.getCanvas(), 0, -h);
             this.restore();
         },
 

From 39ac389a7e556081f6c47f5b17c6a1453f3f680b Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 10:40:51 +0200
Subject: [PATCH 16/45] Get  working for not real images

---
 canvas_proxy.js    | 29 +++++++++++++++++++++++++++++
 viewer_worker.html | 15 ++++++++++-----
 worker.js          | 37 +++----------------------------------
 3 files changed, 42 insertions(+), 39 deletions(-)

diff --git a/canvas_proxy.js b/canvas_proxy.js
index ccc95c74a..610cdcdba 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -1,3 +1,24 @@
+var ImageCanvasProxyCounter = 0;
+function ImageCanvasProxy(width, height) {
+    this.id = ImageCanvasProxyCounter++;
+    this.width = width;
+    this.height = height;
+
+    // Using `Uint8ClampedArray` seems to be the type of ImageData - at least
+    // Firebug tells me so.
+    this.imgData = {
+        data: Uint8ClampedArray(width * height * 4)
+    };
+}
+
+ImageCanvasProxy.prototype.putImageData = function(imgData) {
+    // this.ctx.putImageData(imgData, 0, 0);
+}
+
+ImageCanvasProxy.prototype.getCanvas = function() {
+    return this;
+}
+
 function CanvasProxy(width, height) {
     var stack = this.$stack = [];
 
@@ -51,6 +72,14 @@ function CanvasProxy(width, height) {
         "$showText"
     ];
 
+    this.drawImage = function(canvas, x, y) {
+        if (canvas instanceof ImageCanvasProxy) {
+            stack.push(["$drawCanvas", [canvas.imgData, x, y, canvas.width, canvas.height]]);
+        } else {
+            throw "unkown type to drawImage";
+        }
+    }
+
     function buildFuncCall(name) {
         return function() {
             // console.log("funcCall", name)
diff --git a/viewer_worker.html b/viewer_worker.html
index 930fb6cd5..07623c50c 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -4,11 +4,6 @@
 <script>
 var myWorker = new Worker('worker.js');
 
-// var array = new Uint8Array(2);
-// array[0] = 1;
-// array[1] = 300;
-//
-
 var currentX = 0;
 var currentXStack = [];
 var special = {
@@ -34,6 +29,16 @@ var special = {
         this.translate(currentX, -1 * y);
         this.fillText(text, 0, 0);
         currentX += this.measureText(text).width;
+    },
+
+    "$drawCanvas": function(data, x, y, width, height) {
+        // Ugly: getImageData is called here only to get an object of the right
+        // shape - we are not interessted in the data, as we set it the line
+        // afterwards to something custome.
+        // Can we do better here?
+        var imgData = ctx.getImageData(0, 0, width, height);
+        imgData.data = data;
+        ctx.putImageData(imgData, x, y);
     }
 }
 
diff --git a/worker.js b/worker.js
index 6d34a9d62..33b34f350 100644
--- a/worker.js
+++ b/worker.js
@@ -41,6 +41,7 @@ var canvas = new CanvasProxy(1224, 1584);
 log("test");
 
 var pageInterval;
+
 onmessage = function(event) {
     var data = event.data;
     var pdfDocument = new PDFDoc(new Stream(data));
@@ -48,48 +49,16 @@ onmessage = function(event) {
 
     tic();
     // Let's try to render the first page...
-    var page = pdfDocument.getPage(8);
+    var page = pdfDocument.getPage(2);
 
     // page.compile will collect all fonts for us, once we have loaded them
     // we can trigger the actual page rendering with page.display
     var fonts = [];
 
-    var gfx = new CanvasGraphics(canvas);
+    var gfx = new CanvasGraphics(canvas, ImageCanvasProxy);
     page.compile(gfx, fonts);
     toc("compiled page");
 
-    //
-    var fontsReady = true;
-    // Inspect fonts and translate the missing one
-    var count = fonts.length;
-    for (var i = 0; i < count; i++) {
-      var font = fonts[i];
-      if (Fonts[font.name]) {
-        fontsReady = fontsReady && !Fonts[font.name].loading;
-        continue;
-      }
-
-      new Font(font.name, font.file, font.properties);
-      fontsReady = false;
-    }
-
-    // function delayLoadFont() {
-    //   for (var i = 0; i < count; i++) {
-    //     if (Fonts[font.name].loading)
-    //       return;
-    //   }
-    //   clearInterval(pageInterval);
-    //   page.display(gfx);
-    //
-    //   log("flush");
-    //   canvas.flush();
-    // };
-
-    // if (fontsReady) {
-    //   delayLoadFont();
-    // } else {
-    //   pageInterval = setInterval(delayLoadFont, 10);
-    // }
 
     page.display(gfx);
     canvas.flush();

From 52a117d32a1ff11f9e6810053a2988af90f62e58 Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 11:08:30 +0200
Subject: [PATCH 17/45] Fixing some smaller bugs & do some cleanup.
 viewer_worker.html UI is now

---
 pdf.js             |  3 +-
 viewer_worker.html | 84 +++++++++++++++++++++++++++++++---------------
 worker.js          | 77 ++++++++++++++++++++----------------------
 3 files changed, 94 insertions(+), 70 deletions(-)

diff --git a/pdf.js b/pdf.js
index 273550084..61ceb602a 100644
--- a/pdf.js
+++ b/pdf.js
@@ -2867,7 +2867,7 @@ var CanvasGraphics = (function() {
             this.ctx.scale(1, -1);
 
             if (this.ctx.$showText) {
-                this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text));
+                this.ctx.$showText(this.current.y, text, Fonts.charsToUnicode(text));
             } else {
                 text = Fonts.charsToUnicode(text);
                 this.ctx.translate(this.current.x, -1 * this.current.y);
@@ -3423,7 +3423,6 @@ var CanvasGraphics = (function() {
                     TODO("Images with "+ numComps + " components per pixel");
                 }
             }
-            console.log("paintImageXObject", w, h);
             tmpCanvas.putImageData(imgData, 0, 0);
             ctx.drawImage(tmpCanvas.getCanvas(), 0, -h);
             this.restore();
diff --git a/viewer_worker.html b/viewer_worker.html
index 07623c50c..bba694f21 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -2,6 +2,16 @@
     <head>
         <title>Simple pdf.js page viewer worker</title>
 <script>
+
+var timer = null
+function tic() {
+    timer = Date.now();
+}
+
+function toc(msg) {
+    console.log(msg + ": " + (Date.now() - timer) + "ms");
+}
+
 var myWorker = new Worker('worker.js');
 
 var currentX = 0;
@@ -23,11 +33,9 @@ var special = {
         currentX = currentXStack.pop();
     },
 
-    "$showText": function(y, text) {
-        // console.log(text, currentX, y, this.measureText(text).width);
-
+    "$showText": function(y, text, uniText) {
         this.translate(currentX, -1 * y);
-        this.fillText(text, 0, 0);
+        this.fillText(uniText, 0, 0);
         currentX += this.measureText(text).width;
     },
 
@@ -48,20 +56,10 @@ function renderProxyCanvas(stack) {
     // for (var i = 0; i < 1000; i++) {
         var opp = stack[i];
         if (opp[0] == "$") {
-            // console.log("set property", opp[1], opp[2]);
-            if (opp[1] == "font") {
-                ctx[opp[1]] = opp[2];
-                // ctx.font = "10px 'Verdana Bold Italic'";
-                // console.log("font", opp[2]);
-            } else {
-                ctx[opp[1]] = opp[2];
-            }
-
+            ctx[opp[1]] = opp[2];
         } else if (opp[0] in special) {
-            // console.log("sepcial", opp[0], opp[1])
             special[opp[0]].apply(ctx, opp[1]);
         } else {
-            // console.log("execute", opp[0], opp[1]);
             ctx[opp[0]].apply(ctx, opp[1]);
         }
     }
@@ -71,6 +69,7 @@ const WAIT = 0;
 const CANVAS_PROXY_STACK = 1;
 const LOG = 2;
 const FONT = 3;
+const PDF_NUM_PAGE = 4;
 
 var onMessageState = WAIT;
 var fontStr = null;
@@ -85,6 +84,9 @@ myWorker.onmessage = function(event) {
                 throw "expecting to get an string";
             }
             switch (data) {
+                case "pdf_num_page":
+                    onMessageState = PDF_NUM_PAGE;
+                    return;
                 case "log":
                     onMessageState = LOG;
                     return;
@@ -99,6 +101,13 @@ myWorker.onmessage = function(event) {
             }
             break;
 
+        case PDF_NUM_PAGE:
+            console.log(data);
+            maxPages = parseInt(data);
+            document.getElementById("numPages").innerHTML = "/" + data;
+            onMessageState = WAIT;
+            break;
+
         case FONT:
             data = JSON.parse(data);
             var base64 = window.btoa(data.str);
@@ -115,6 +124,7 @@ myWorker.onmessage = function(event) {
                 ctx.font = "bold italic 20px " + data.fontName + ", Symbol, Arial";
             }, 10);
             intervals.push(interval);
+            console.log("setup font", data.fontName);
 
             onMessageState = WAIT;
             break;
@@ -127,7 +137,7 @@ myWorker.onmessage = function(event) {
         case CANVAS_PROXY_STACK:
             var stack = JSON.parse(data);
             gStack = stack;
-            console.log("canvas stack", stack.length)
+            console.log("canvas stack size", stack.length)
 
             // Shedule a timeout. Hoping the fonts are loaded after 100ms.
             setTimeout(function() {
@@ -135,7 +145,9 @@ myWorker.onmessage = function(event) {
                 intervals.forEach(function(inter) {
                     clearInterval(inter);
                 });
+                tic();
                 renderProxyCanvas(stack);
+                toc("canvas rendering")
             }, 100);
             onMessageState = WAIT;
             break;
@@ -144,6 +156,19 @@ myWorker.onmessage = function(event) {
 //
 // myWorker.postMessage(array);
 
+var currentPage = 1;
+var maxPages = 1;
+function showPage(num) {
+    ctx.save();
+    ctx.fillStyle = "rgb(255, 255, 255)";
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+    ctx.restore();
+    console.log("worker: page=" + num)
+    document.getElementById('pageNumber').value = num;
+    currentPage = parseInt(num);
+    myWorker.postMessage(num);
+}
+
 function open(url) {
     document.title = url;
     var req = new XMLHttpRequest();
@@ -155,22 +180,27 @@ function open(url) {
         var data = req.mozResponseArrayBuffer || req.mozResponse ||
                    req.responseArrayBuffer || req.response;
         myWorker.postMessage(data);
+        showPage("1");
       }
     };
     req.send(null);
 }
 
+function nextPage() {
+    if (currentPage == maxPages) return;
+    currentPage++;
+    showPage(currentPage);
+}
+
+function prevPage() {
+    if (currentPage == 0) return;
+    currentPage--;
+    showPage(currentPage);
+}
+
 window.onload = function() {
-    var ctx = window.ctx = document.getElementById("canvas").getContext("2d");
-    ctx.save();
-    ctx.fillStyle = "rgb(255, 255, 255)";
-    ctx.fillRect(0, 0, canvas.width, canvas.height);
-    ctx.restore();
-    // for (var name in ctx) {
-    //     if (!(ctx[name] instanceof Function)) {
-    //         console.log('"' + name + '": "' + ctx[name] + '",');
-    //     }
-    // }
+    window.canvas = document.getElementById("canvas");
+    window.ctx = canvas.getContext("2d");
     open("compressed.tracemonkey-pldi-09.pdf");
 }
 </script>
@@ -184,7 +214,7 @@ window.onload = function() {
       -- Can we use JSONP to overcome the same-origin restrictions? -->
       <button onclick="prevPage();">Previous</button>
       <button onclick="nextPage();">Next</button>
-      <input type="text" id="pageNumber" onchange="gotoPage(this.value);"
+      <input type="text" id="pageNumber" onchange="showPage(this.value);"
              value="1" size="4"></input>
       <span id="numPages">--</span>
       <span id="info"></span>
diff --git a/worker.js b/worker.js
index 33b34f350..dcb87a811 100644
--- a/worker.js
+++ b/worker.js
@@ -26,7 +26,7 @@ function tic() {
 }
 
 function toc(msg) {
-    log("Took ", (Date.now() - timer));
+    log(msg + ": " + (Date.now() - timer) + "ms");
     timer = null;
 }
 
@@ -41,46 +41,41 @@ var canvas = new CanvasProxy(1224, 1584);
 log("test");
 
 var pageInterval;
-
+var pdfDocument = null;
 onmessage = function(event) {
     var data = event.data;
-    var pdfDocument = new PDFDoc(new Stream(data));
-    var numPages = pdfDocument.numPages;
-
-    tic();
-    // Let's try to render the first page...
-    var page = pdfDocument.getPage(2);
-
-    // page.compile will collect all fonts for us, once we have loaded them
-    // we can trigger the actual page rendering with page.display
-    var fonts = [];
-
-    var gfx = new CanvasGraphics(canvas, ImageCanvasProxy);
-    page.compile(gfx, fonts);
-    toc("compiled page");
-
-
-    page.display(gfx);
-    canvas.flush();
+    if (!pdfDocument) {
+        pdfDocument = new PDFDoc(new Stream(data));
+        postMessage("pdf_num_page");
+        postMessage(pdfDocument.numPages)
+        return;
+    } else {
+        tic();
+
+        // Let's try to render the first page...
+        var page = pdfDocument.getPage(parseInt(data));
+
+        // page.compile will collect all fonts for us, once we have loaded them
+        // we can trigger the actual page rendering with page.display
+        var fonts = [];
+        var gfx = new CanvasGraphics(canvas, ImageCanvasProxy);
+        page.compile(gfx, fonts);
+
+        // Inspect fonts and translate the missing one.
+        var count = fonts.length;
+        for (var i = 0; i < count; i++) {
+          var font = fonts[i];
+          if (Fonts[font.name]) {
+            fontsReady = fontsReady && !Fonts[font.name].loading;
+            continue;
+          }
+
+          // This "builds" the font and sents it over to the main thread.
+          new Font(font.name, font.file, font.properties);
+        }
+        toc("compiled page");
+
+        page.display(gfx);
+        canvas.flush();
+    }
 }
-
-// function open(url) {
-//     var req = new XMLHttpRequest();
-//     req.open("GET", url);
-//     // req.responseType = "arraybuffer";
-//     req.expected = 0;//(document.URL.indexOf("file:") == 0) ? 0 : 200;
-//     req.onreadystatechange = function() {
-//       postMessage("loaded");
-//       if (req.readyState == 4 && req.status == req.expected) {
-//         var data = req.mozResponseArrayBuffer || req.mozResponse ||
-//                    req.responseArrayBuffer || req.response;
-//         pdfDocument = new PDFDoc(new Stream(data));
-//         numPages = pdfDocument.numPages;
-//         // document.getElementById("numPages").innerHTML = numPages.toString();
-//         // goToPage(pageNum);
-//       }
-//     };
-//     req.send(null);
-// }
-//
-// open("compressed.tracemonkey-pldi-09.pdf")
\ No newline at end of file

From e2cd5facbda0bf851126d7782f9b64e200ef8e59 Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 13:19:25 +0200
Subject: [PATCH 18/45] Fix font loading issue by using a hidden DOM font node

---
 viewer_worker.html | 20 ++++++++------------
 1 file changed, 8 insertions(+), 12 deletions(-)

diff --git a/viewer_worker.html b/viewer_worker.html
index bba694f21..ced71679e 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -118,12 +118,10 @@ myWorker.onmessage = function(event) {
             var styleSheet = document.styleSheets[0];
             styleSheet.insertRule(rule, styleSheet.length);
 
-            // *HACK*: this makes the font get loaded on the page. WTF? We
-            // really have to set the fonts a few time...
-            var interval = setInterval(function() {
-                ctx.font = "bold italic 20px " + data.fontName + ", Symbol, Arial";
-            }, 10);
-            intervals.push(interval);
+            // Just adding the font-face to the DOM doesn't make it load. It
+            // seems it's loaded once Gecko notices it's used. Therefore,
+            // add a div on the page using the loaded font.
+            document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>";
             console.log("setup font", data.fontName);
 
             onMessageState = WAIT;
@@ -139,16 +137,13 @@ myWorker.onmessage = function(event) {
             gStack = stack;
             console.log("canvas stack size", stack.length)
 
-            // Shedule a timeout. Hoping the fonts are loaded after 100ms.
+            // There might be fonts that need to get loaded. Shedule the
+            // rendering at the end of the event queue ensures this.
             setTimeout(function() {
-                // Remove all setIntervals to make the font load.
-                intervals.forEach(function(inter) {
-                    clearInterval(inter);
-                });
                 tic();
                 renderProxyCanvas(stack);
                 toc("canvas rendering")
-            }, 100);
+            }, 0);
             onMessageState = WAIT;
             break;
     }
@@ -208,6 +203,7 @@ window.onload = function() {
   </head>
 
   <body>
+    <div id="fonts" style="position: absolute;display:block;z-index:-1;"></div>
     <div id="controls">
     <input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input>
     <!-- This only opens supported PDFs from the source path...

From 2c17af3720dac89b559f6333e99ab0afcd5ce0cf Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 14:02:54 +0200
Subject: [PATCH 19/45] Add JpegStreamProxy - doesnt seem to be used anywhere
 in the example.pdf file

---
 canvas_proxy.js    | 44 ++++++++++++++++++++++++++++++++++++++------
 viewer_worker.html | 25 +++++++++++++++++++++++--
 worker.js          |  3 +++
 3 files changed, 64 insertions(+), 8 deletions(-)

diff --git a/canvas_proxy.js b/canvas_proxy.js
index 610cdcdba..c7234d9c9 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -19,6 +19,38 @@ ImageCanvasProxy.prototype.getCanvas = function() {
     return this;
 }
 
+var JpegStreamProxyCounter = 0;
+// WebWorker Proxy for JpegStream.
+var JpegStreamProxy = (function() {
+    function constructor(bytes, dict) {
+        this.id = JpegStreamProxyCounter++;
+        this.dict = dict;
+
+        // create DOM image.
+        postMessage("jpeg_stream");
+        postMessage({
+            id:  this.id,
+            str: bytesToString(bytes)
+        });
+
+        // var img = new Image();
+        // img.src = "data:image/jpeg;base64," + window.btoa(bytesToString(bytes));
+        // this.domImage = img;
+    }
+
+    constructor.prototype = {
+        getImage: function() {
+            return this;
+            // return this.domImage;
+        },
+        getChar: function() {
+            error("internal error: getChar is not valid on JpegStream");
+        }
+    };
+
+    return constructor;
+})();
+
 function CanvasProxy(width, height) {
     var stack = this.$stack = [];
 
@@ -72,9 +104,11 @@ function CanvasProxy(width, height) {
         "$showText"
     ];
 
-    this.drawImage = function(canvas, x, y) {
-        if (canvas instanceof ImageCanvasProxy) {
-            stack.push(["$drawCanvas", [canvas.imgData, x, y, canvas.width, canvas.height]]);
+    this.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) {
+        if (image instanceof ImageCanvasProxy) {
+            stack.push(["$drawCanvas", [image.imgData, x, y, image.width, image.height]]);
+        } else if(image instanceof JpegStreamProxy) {
+            stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]])
         } else {
             throw "unkown type to drawImage";
         }
@@ -139,9 +173,7 @@ function CanvasProxy(width, height) {
 }
 
 CanvasProxy.prototype.flush = function() {
-    // postMessage("log");
-    // postMessage(JSON.stringify([this.$stack.length]));
     postMessage("canvas_proxy_stack");
-    postMessage(JSON.stringify(this.$stack));
+    postMessage(this.$stack);
     this.$stack.length = 0;
 }
diff --git a/viewer_worker.html b/viewer_worker.html
index ced71679e..fd436db75 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -13,6 +13,7 @@ function toc(msg) {
 }
 
 var myWorker = new Worker('worker.js');
+var images = {};
 
 var currentX = 0;
 var currentXStack = [];
@@ -47,6 +48,15 @@ var special = {
         var imgData = ctx.getImageData(0, 0, width, height);
         imgData.data = data;
         ctx.putImageData(imgData, x, y);
+    },
+
+    "$drawImage": function(id, x, y, sx, sy, swidth, sheight) {
+        var image = images[id];
+        if (!image) {
+            throw "Image not found";
+        }
+        ctx.drawImage(image, x, y, image.width, image.height,
+            sx, sy, swidth, sheight);
     }
 }
 
@@ -70,6 +80,7 @@ const CANVAS_PROXY_STACK = 1;
 const LOG = 2;
 const FONT = 3;
 const PDF_NUM_PAGE = 4;
+const JPEG_STREAM = 5;
 
 var onMessageState = WAIT;
 var fontStr = null;
@@ -96,11 +107,21 @@ myWorker.onmessage = function(event) {
                 case "font":
                     onMessageState = FONT;
                     return;
+                case "jpeg_stream":
+                    onMessageState = JPEG_STREAM;
+                    return;
                 default:
                     throw "unkown state: " + data
             }
             break;
 
+        case JPEG_STREAM:
+            var img = new Image();
+            img.src = "data:image/jpeg;base64," + window.btoa(data.str);
+            images[data.id] = img;
+            console.log("got image", data.id)
+            break;
+
         case PDF_NUM_PAGE:
             console.log(data);
             maxPages = parseInt(data);
@@ -133,7 +154,7 @@ myWorker.onmessage = function(event) {
             break;
 
         case CANVAS_PROXY_STACK:
-            var stack = JSON.parse(data);
+            var stack = data;
             gStack = stack;
             console.log("canvas stack size", stack.length)
 
@@ -188,7 +209,7 @@ function nextPage() {
 }
 
 function prevPage() {
-    if (currentPage == 0) return;
+    if (currentPage == 1) return;
     currentPage--;
     showPage(currentPage);
 }
diff --git a/worker.js b/worker.js
index dcb87a811..6294007b6 100644
--- a/worker.js
+++ b/worker.js
@@ -15,6 +15,9 @@ importScripts("pdf.js");
 importScripts("fonts.js");
 importScripts("glyphlist.js")
 
+// Use the JpegStreamProxy proxy.
+JpegStream = JpegStreamProxy;
+
 // var array = new Uint8Array(2);
 // array[0] = 1;
 // array[1] = 300;

From fe7c8b52f645e60c5569f319fa52e074813d53c7 Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 14:25:21 +0200
Subject: [PATCH 20/45] Add very simple GradientProxy support - makes page 11
 render.

---
 canvas_proxy.js    | 35 +++++++++++++++++++++++++++++++++--
 viewer_worker.html | 19 ++++++++++++++++++-
 2 files changed, 51 insertions(+), 3 deletions(-)

diff --git a/canvas_proxy.js b/canvas_proxy.js
index c7234d9c9..0670762e5 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -51,6 +51,16 @@ var JpegStreamProxy = (function() {
     return constructor;
 })();
 
+// Really simple GradientProxy. There is currently only one active gradient at
+// the time, meaning you can't create a gradient, create a second one and then
+// use the first one again. As this isn't used in pdf.js right now, it's okay.
+function GradientProxy(stack, x0, y0, x1, y1) {
+    stack.push(["$createLinearGradient", [x0, y0, x1, y1]]);
+    this.addColorStop = function(i, rgba) {
+        stack.push(["$addColorStop", [i, rgba]]);
+    }
+}
+
 function CanvasProxy(width, height) {
     var stack = this.$stack = [];
 
@@ -79,7 +89,7 @@ function CanvasProxy(width, height) {
         "translate",
         "transform",
         "setTransform",
-        "createLinearGradient",
+        // "createLinearGradient",
         "createPattern",
         "clearRect",
         "fillRect",
@@ -104,6 +114,10 @@ function CanvasProxy(width, height) {
         "$showText"
     ];
 
+    this.createLinearGradient = function(x0, y0, x1, y1) {
+        return new GradientProxy(stack, x0, y0, x1, y1);
+    }
+
     this.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) {
         if (image instanceof ImageCanvasProxy) {
             stack.push(["$drawCanvas", [image.imgData, x, y, image.width, image.height]]);
@@ -168,7 +182,24 @@ function CanvasProxy(width, height) {
     for (var name in ctxProp) {
         this["$" + name] = ctxProp[name];
         this.__defineGetter__(name, buildGetter(name));
-        this.__defineSetter__(name, buildSetter(name));
+
+        // Special treatment for `fillStyle` and `strokeStyle`: The passed style
+        // might be a gradient. Need to check for that.
+        if (name == "fillStyle" || name == "strokeStyle") {
+            function buildSetterStyle(name) {
+                return function(value) {
+                    if (value instanceof GradientProxy) {
+                        stack.push(["$" + name + "Gradient"]);
+                    } else {
+                        stack.push(["$", name, value]);
+                        return this["$" + name] = value;
+                    }
+                }
+            }
+            this.__defineSetter__(name, buildSetterStyle(name));
+        } else {
+            this.__defineSetter__(name, buildSetter(name));
+        }
     }
 }
 
diff --git a/viewer_worker.html b/viewer_worker.html
index fd436db75..83c41e6e0 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -14,6 +14,7 @@ function toc(msg) {
 
 var myWorker = new Worker('worker.js');
 var images = {};
+var gradient;
 
 var currentX = 0;
 var currentXStack = [];
@@ -57,6 +58,22 @@ var special = {
         }
         ctx.drawImage(image, x, y, image.width, image.height,
             sx, sy, swidth, sheight);
+    },
+
+    "$createLinearGradient": function(x0, y0, x1, y1) {
+        gradient = ctx.createLinearGradient(x0, y0, x1, y1);
+    },
+
+    "$addColorStop": function(i, rgba) {
+        gradient.addColorStop(i, rgba);
+    },
+
+    "$fillStyleGradient": function() {
+        ctx.fillStyle = gradient;
+    },
+
+    "$strokeStyleGradient": function() {
+        ctx.strokeStyle = gradient;
     }
 }
 
@@ -196,7 +213,7 @@ function open(url) {
         var data = req.mozResponseArrayBuffer || req.mozResponse ||
                    req.responseArrayBuffer || req.response;
         myWorker.postMessage(data);
-        showPage("1");
+        showPage("11");
       }
     };
     req.send(null);

From 50d902d9eb41f5aee01bdd36f5ffdaf169dd01c9 Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 20:04:25 +0200
Subject: [PATCH 21/45] Make the ProxyCanvas be more a canvas - provide a ctx
 object to interact through

---
 canvas_proxy.js | 31 ++++++++++++++++++++-----------
 worker.js       | 16 ++--------------
 2 files changed, 22 insertions(+), 25 deletions(-)

diff --git a/canvas_proxy.js b/canvas_proxy.js
index 0670762e5..823757492 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -64,9 +64,18 @@ function GradientProxy(stack, x0, y0, x1, y1) {
 function CanvasProxy(width, height) {
     var stack = this.$stack = [];
 
+    // Dummy context exposed.
+    var ctx = {};
+    this.getContext = function(type) {
+        if (type != "2d") {
+            throw "CanvasProxy can only provide a 2d context.";
+        }
+        return ctx;
+    }
+
     // Expose only the minimum of the canvas object - there is no dom to do
     // more here.
-    this.canvas = {
+    ctx.canvas = {
         width: width,
         height: height
     }
@@ -114,11 +123,11 @@ function CanvasProxy(width, height) {
         "$showText"
     ];
 
-    this.createLinearGradient = function(x0, y0, x1, y1) {
+    ctx.createLinearGradient = function(x0, y0, x1, y1) {
         return new GradientProxy(stack, x0, y0, x1, y1);
     }
 
-    this.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) {
+    ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) {
         if (image instanceof ImageCanvasProxy) {
             stack.push(["$drawCanvas", [image.imgData, x, y, image.width, image.height]]);
         } else if(image instanceof JpegStreamProxy) {
@@ -137,7 +146,7 @@ function CanvasProxy(width, height) {
     var name;
     for (var i = 0; i < ctxFunc.length; i++) {
         name = ctxFunc[i];
-        this[name] = buildFuncCall(name);
+        ctx[name] = buildFuncCall(name);
     }
 
     var ctxProp = {
@@ -168,20 +177,20 @@ function CanvasProxy(width, height) {
 
     function buildGetter(name) {
         return function() {
-            return this["$" + name];
+            return ctx["$" + name];
         }
     }
 
     function buildSetter(name) {
         return function(value) {
             stack.push(["$", name, value]);
-            return this["$" + name] = value;
+            return ctx["$" + name] = value;
         }
     }
 
     for (var name in ctxProp) {
-        this["$" + name] = ctxProp[name];
-        this.__defineGetter__(name, buildGetter(name));
+        ctx["$" + name] = ctxProp[name];
+        ctx.__defineGetter__(name, buildGetter(name));
 
         // Special treatment for `fillStyle` and `strokeStyle`: The passed style
         // might be a gradient. Need to check for that.
@@ -192,13 +201,13 @@ function CanvasProxy(width, height) {
                         stack.push(["$" + name + "Gradient"]);
                     } else {
                         stack.push(["$", name, value]);
-                        return this["$" + name] = value;
+                        return ctx["$" + name] = value;
                     }
                 }
             }
-            this.__defineSetter__(name, buildSetterStyle(name));
+            ctx.__defineSetter__(name, buildSetterStyle(name));
         } else {
-            this.__defineSetter__(name, buildSetter(name));
+            ctx.__defineSetter__(name, buildSetter(name));
         }
     }
 }
diff --git a/worker.js b/worker.js
index 6294007b6..59ad8edea 100644
--- a/worker.js
+++ b/worker.js
@@ -18,11 +18,6 @@ importScripts("glyphlist.js")
 // Use the JpegStreamProxy proxy.
 JpegStream = JpegStreamProxy;
 
-// var array = new Uint8Array(2);
-// array[0] = 1;
-// array[1] = 300;
-// postMessage(array);
-
 var timer = null;
 function tic() {
     timer = Date.now();
@@ -33,15 +28,8 @@ function toc(msg) {
     timer = null;
 }
 
-
+// Create the WebWorkerProxyCanvas.
 var canvas = new CanvasProxy(1224, 1584);
-// canvas.moveTo(0, 10);
-// canvas.lineTo(0, 20);
-// canvas.lineTo(500, 500);
-// canvas.flush();
-// canvas.stroke();
-// canvas.flush();
-log("test");
 
 var pageInterval;
 var pdfDocument = null;
@@ -61,7 +49,7 @@ onmessage = function(event) {
         // page.compile will collect all fonts for us, once we have loaded them
         // we can trigger the actual page rendering with page.display
         var fonts = [];
-        var gfx = new CanvasGraphics(canvas, ImageCanvasProxy);
+        var gfx = new CanvasGraphics(canvas.getContext("2d"), ImageCanvasProxy);
         page.compile(gfx, fonts);
 
         // Inspect fonts and translate the missing one.

From 000d6402580dcd99efdca83031ac2e3b1055bd31 Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 20:16:04 +0200
Subject: [PATCH 22/45] Replace ImageCanvas by ScratchCanvas

---
 pdf.js | 39 +++++++++++++++------------------------
 1 file changed, 15 insertions(+), 24 deletions(-)

diff --git a/pdf.js b/pdf.js
index 61ceb602a..221fd5ece 100644
--- a/pdf.js
+++ b/pdf.js
@@ -2269,21 +2269,18 @@ var Encodings = {
   }
 };
 
-function ImageCanvas(width, height) {
-    var tmpCanvas = this.canvas = document.createElement("canvas");
-    tmpCanvas.width = width;
-    tmpCanvas.height = height;
+function ScratchCanvas(width, height) {
+    var canvas = document.createElement("canvas");
+    canvas.width = width;
+    canvas.height = height;
 
-    this.ctx = tmpCanvas.getContext("2d");
-    this.imgData = this.ctx.getImageData(0, 0, width, height);
-}
-
-ImageCanvas.prototype.putImageData = function(imgData) {
-    this.ctx.putImageData(imgData, 0, 0);
-}
+    this.getContext = function(kind) {
+        return canvas.getContext(kind);
+    }
 
-ImageCanvas.prototype.getCanvas = function() {
-    return this.canvas;
+    this.getCanvas = function() {
+        return canvas;
+    }
 }
 
 var CanvasGraphics = (function() {
@@ -2294,7 +2291,7 @@ var CanvasGraphics = (function() {
         this.pendingClip = null;
         this.res = null;
         this.xobjs = null;
-        this.ImageCanvas = imageCanvas || ImageCanvas;
+        this.ScratchCanvas = imageCanvas || ScratchCanvas;
     }
 
     constructor.prototype = {
@@ -3348,15 +3345,9 @@ var CanvasGraphics = (function() {
                 // handle matte object
             }
 
-            var tmpCanvas = new this.ImageCanvas(w, h);
-            // var tmpCanvas = document.createElement("canvas");
-            // tmpCanvas.width = w;
-            // tmpCanvas.height = h;
-            //
-            // var tmpCtx = tmpCanvas.getContext("2d");
-            // var imgData = tmpCtx.getImageData(0, 0, w, h);
-            // var pixels = imgData.data;
-            var imgData = tmpCanvas.imgData;
+            var tmpCanvas = new this.ScratchCanvas(w, h);
+            var tmpCtx = tmpCanvas.getContext("2d");
+            var imgData = tmpCtx.getImageData(0, 0, w, h);
             var pixels = imgData.data;
 
             if (bitsPerComponent != 8)
@@ -3423,7 +3414,7 @@ var CanvasGraphics = (function() {
                     TODO("Images with "+ numComps + " components per pixel");
                 }
             }
-            tmpCanvas.putImageData(imgData, 0, 0);
+            tmpCtx.putImageData(imgData, 0, 0);
             ctx.drawImage(tmpCanvas.getCanvas(), 0, -h);
             this.restore();
         },

From 22bdd50a98e3097c98288b116618543550a7387b Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 20:49:33 +0200
Subject: [PATCH 23/45] Merge ImageCanvasProxy and CanvasProxy. Add support for
 rendering multiple canvas objects on the worker and assemble them again on
 the main thread.

---
 canvas_proxy.js    | 79 ++++++++++++++++++++++++++++++----------------
 viewer_worker.html | 59 ++++++++++++++++++++++++----------
 worker.js          |  2 +-
 3 files changed, 95 insertions(+), 45 deletions(-)

diff --git a/canvas_proxy.js b/canvas_proxy.js
index 823757492..ed209f126 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -1,23 +1,23 @@
-var ImageCanvasProxyCounter = 0;
-function ImageCanvasProxy(width, height) {
-    this.id = ImageCanvasProxyCounter++;
-    this.width = width;
-    this.height = height;
-
-    // Using `Uint8ClampedArray` seems to be the type of ImageData - at least
-    // Firebug tells me so.
-    this.imgData = {
-        data: Uint8ClampedArray(width * height * 4)
-    };
-}
-
-ImageCanvasProxy.prototype.putImageData = function(imgData) {
-    // this.ctx.putImageData(imgData, 0, 0);
-}
-
-ImageCanvasProxy.prototype.getCanvas = function() {
-    return this;
-}
+// var ImageCanvasProxyCounter = 0;
+// function ImageCanvasProxy(width, height) {
+//     this.id = ImageCanvasProxyCounter++;
+//     this.width = width;
+//     this.height = height;
+//
+//     // Using `Uint8ClampedArray` seems to be the type of ImageData - at least
+//     // Firebug tells me so.
+//     this.imgData = {
+//         data: Uint8ClampedArray(width * height * 4)
+//     };
+// }
+//
+// ImageCanvasProxy.prototype.putImageData = function(imgData) {
+//     // this.ctx.putImageData(imgData, 0, 0);
+// }
+//
+// ImageCanvasProxy.prototype.getCanvas = function() {
+//     return this;
+// }
 
 var JpegStreamProxyCounter = 0;
 // WebWorker Proxy for JpegStream.
@@ -61,7 +61,10 @@ function GradientProxy(stack, x0, y0, x1, y1) {
     }
 }
 
+var canvasProxyCounter = 0;
 function CanvasProxy(width, height) {
+    this.id = canvasProxyCounter++;
+
     var stack = this.$stack = [];
 
     // Dummy context exposed.
@@ -73,12 +76,15 @@ function CanvasProxy(width, height) {
         return ctx;
     }
 
+    this.getCanvas = function() {
+        return this;
+    }
+
     // Expose only the minimum of the canvas object - there is no dom to do
     // more here.
-    ctx.canvas = {
-        width: width,
-        height: height
-    }
+    this.width = width;
+    this.height = height;
+    ctx.canvas = this;
 
     var ctxFunc = [
         "createRadialGradient",
@@ -127,9 +133,23 @@ function CanvasProxy(width, height) {
         return new GradientProxy(stack, x0, y0, x1, y1);
     }
 
+    ctx.getImageData = function(x, y, w, h) {
+        return {
+            width: w,
+            height: h,
+            data: Uint8ClampedArray(w * h * 4)
+        };
+    }
+
+    ctx.putImageData = function(data, x, y, width, height) {
+        stack.push(["$putImageData", [data, x, y, width, height]]);
+    }
+
     ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) {
-        if (image instanceof ImageCanvasProxy) {
-            stack.push(["$drawCanvas", [image.imgData, x, y, image.width, image.height]]);
+        if (image instanceof CanvasProxy) {
+            // Send the image/CanvasProxy to the main thread.
+            image.flush();
+            stack.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]);
         } else if(image instanceof JpegStreamProxy) {
             stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]])
         } else {
@@ -214,6 +234,11 @@ function CanvasProxy(width, height) {
 
 CanvasProxy.prototype.flush = function() {
     postMessage("canvas_proxy_stack");
-    postMessage(this.$stack);
+    postMessage({
+        id:     this.id,
+        stack:  this.$stack,
+        width:  this.width,
+        height: this.height
+    });
     this.$stack.length = 0;
 }
diff --git a/viewer_worker.html b/viewer_worker.html
index 83c41e6e0..c7041bcbd 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -13,7 +13,8 @@ function toc(msg) {
 }
 
 var myWorker = new Worker('worker.js');
-var images = {};
+var imagesList = {};
+var canvasList = {};
 var gradient;
 
 var currentX = 0;
@@ -41,27 +42,40 @@ var special = {
         currentX += this.measureText(text).width;
     },
 
-    "$drawCanvas": function(data, x, y, width, height) {
+    "$putImageData": function(imageData, x, y) {
         // Ugly: getImageData is called here only to get an object of the right
         // shape - we are not interessted in the data, as we set it the line
         // afterwards to something custome.
         // Can we do better here?
-        var imgData = ctx.getImageData(0, 0, width, height);
-        imgData.data = data;
-        ctx.putImageData(imgData, x, y);
+        var imgData = this.getImageData(0, 0, imageData.width, imageData.height);
+        imgData.data = imageData.data;
+        this.putImageData(imgData, x, y);
     },
 
     "$drawImage": function(id, x, y, sx, sy, swidth, sheight) {
-        var image = images[id];
+        var image = imagesList[id];
         if (!image) {
             throw "Image not found";
         }
-        ctx.drawImage(image, x, y, image.width, image.height,
+        this.drawImage(image, x, y, image.width, image.height,
             sx, sy, swidth, sheight);
     },
 
+    "$drawCanvas": function(id, x, y, sx, sy, swidth, sheight) {
+        var canvas = canvasList[id];
+        if (!canvas) {
+            throw "Canvas not found";
+        }
+        if (sheight != null) {
+            this.drawImage(canvas, x, y, canvas.width, canvas.height,
+                sx, sy, swidth, sheight);
+        } else {
+            this.drawImage(canvas, x, y, canvas.width, canvas.height);
+        }
+    },
+
     "$createLinearGradient": function(x0, y0, x1, y1) {
-        gradient = ctx.createLinearGradient(x0, y0, x1, y1);
+        gradient = this.createLinearGradient(x0, y0, x1, y1);
     },
 
     "$addColorStop": function(i, rgba) {
@@ -69,16 +83,17 @@ var special = {
     },
 
     "$fillStyleGradient": function() {
-        ctx.fillStyle = gradient;
+        this.fillStyle = gradient;
     },
 
     "$strokeStyleGradient": function() {
-        ctx.strokeStyle = gradient;
+        this.strokeStyle = gradient;
     }
 }
 
 var gStack;
-function renderProxyCanvas(stack) {
+function renderProxyCanvas(canvas, stack) {
+    var ctx = canvas.getContext("2d");
     for (var i = 0; i < stack.length; i++) {
     // for (var i = 0; i < 1000; i++) {
         var opp = stack[i];
@@ -135,7 +150,7 @@ myWorker.onmessage = function(event) {
         case JPEG_STREAM:
             var img = new Image();
             img.src = "data:image/jpeg;base64," + window.btoa(data.str);
-            images[data.id] = img;
+            imagesList[data.id] = img;
             console.log("got image", data.id)
             break;
 
@@ -171,16 +186,25 @@ myWorker.onmessage = function(event) {
             break;
 
         case CANVAS_PROXY_STACK:
-            var stack = data;
+            var id = data.id;
+            var stack = data.stack;
             gStack = stack;
-            console.log("canvas stack size", stack.length)
+
+            // Check if there is already a canvas with the given id. If not,
+            // create a new canvas.
+            if (!canvasList[id]) {
+                var newCanvas = document.createElement("canvas");
+                newCanvas.width = data.width;
+                newCanvas.height = data.height;
+                canvasList[id] = newCanvas;
+            }
 
             // There might be fonts that need to get loaded. Shedule the
             // rendering at the end of the event queue ensures this.
             setTimeout(function() {
-                tic();
-                renderProxyCanvas(stack);
-                toc("canvas rendering")
+                if (id == 0) tic();
+                renderProxyCanvas(canvasList[id], stack);
+                if (id == 0) toc("canvas rendering")
             }, 0);
             onMessageState = WAIT;
             break;
@@ -234,6 +258,7 @@ function prevPage() {
 window.onload = function() {
     window.canvas = document.getElementById("canvas");
     window.ctx = canvas.getContext("2d");
+    canvasList[0] = window.canvas;
     open("compressed.tracemonkey-pldi-09.pdf");
 }
 </script>
diff --git a/worker.js b/worker.js
index 59ad8edea..e59e37155 100644
--- a/worker.js
+++ b/worker.js
@@ -49,7 +49,7 @@ onmessage = function(event) {
         // page.compile will collect all fonts for us, once we have loaded them
         // we can trigger the actual page rendering with page.display
         var fonts = [];
-        var gfx = new CanvasGraphics(canvas.getContext("2d"), ImageCanvasProxy);
+        var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy);
         page.compile(gfx, fonts);
 
         // Inspect fonts and translate the missing one.

From 0746c7db59d7a4d2c4278573466a98a056e8d79a Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 21:17:32 +0200
Subject: [PATCH 24/45] Add PatternProxy; makes page 13 work

---
 canvas_proxy.js    | 26 +++++++++++++++++++++-----
 pdf.js             | 20 ++++++--------------
 viewer_worker.html | 27 ++++++++++++++++++++++++++-
 3 files changed, 53 insertions(+), 20 deletions(-)

diff --git a/canvas_proxy.js b/canvas_proxy.js
index ed209f126..07ae31a63 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -61,6 +61,20 @@ function GradientProxy(stack, x0, y0, x1, y1) {
     }
 }
 
+var patternProxyCounter = 0;
+function PatternProxy(stack, object, kind) {
+    this.id = patternProxyCounter++;
+
+    if (!(object instanceof CanvasProxy) ) {
+        throw "unkown type to createPattern";
+    }
+    // Flush the object here to ensure it's available on the main thread.
+    // TODO: Make some kind of dependency management, such that the object
+    // gets flushed only if needed.
+    object.flush();
+    stack.push(["$createPatternFromCanvas", [this.id, object.id, kind]]);
+}
+
 var canvasProxyCounter = 0;
 function CanvasProxy(width, height) {
     this.id = canvasProxyCounter++;
@@ -76,10 +90,6 @@ function CanvasProxy(width, height) {
         return ctx;
     }
 
-    this.getCanvas = function() {
-        return this;
-    }
-
     // Expose only the minimum of the canvas object - there is no dom to do
     // more here.
     this.width = width;
@@ -105,7 +115,7 @@ function CanvasProxy(width, height) {
         "transform",
         "setTransform",
         // "createLinearGradient",
-        "createPattern",
+        // "createPattern",
         "clearRect",
         "fillRect",
         "strokeRect",
@@ -129,6 +139,10 @@ function CanvasProxy(width, height) {
         "$showText"
     ];
 
+    ctx.createPattern = function(object, kind) {
+        return new PatternProxy(stack, object, kind);
+    }
+
     ctx.createLinearGradient = function(x0, y0, x1, y1) {
         return new GradientProxy(stack, x0, y0, x1, y1);
     }
@@ -219,6 +233,8 @@ function CanvasProxy(width, height) {
                 return function(value) {
                     if (value instanceof GradientProxy) {
                         stack.push(["$" + name + "Gradient"]);
+                    } else if (value instanceof PatternProxy) {
+                        stack.push(["$" + name + "Pattern", [value.id]]);
                     } else {
                         stack.push(["$", name, value]);
                         return ctx["$" + name] = value;
diff --git a/pdf.js b/pdf.js
index 221fd5ece..1223a2bb6 100644
--- a/pdf.js
+++ b/pdf.js
@@ -2273,14 +2273,7 @@ function ScratchCanvas(width, height) {
     var canvas = document.createElement("canvas");
     canvas.width = width;
     canvas.height = height;
-
-    this.getContext = function(kind) {
-        return canvas.getContext(kind);
-    }
-
-    this.getCanvas = function() {
-        return canvas;
-    }
+    return canvas;
 }
 
 var CanvasGraphics = (function() {
@@ -3021,10 +3014,10 @@ var CanvasGraphics = (function() {
             // we want the canvas to be as large as the step size
             var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix);
 
-            var tmpCanvas = document.createElement("canvas");
-            tmpCanvas.width = Math.ceil(botRight[0] - topLeft[0]);
-            tmpCanvas.height = Math.ceil(botRight[1] - topLeft[1]);
-            console.log("tilingFill", tmpCanvas.width, tmpCanvas.height);
+            var tmpCanvas = new this.ScratchCanvas(
+                Math.ceil(botRight[0] - topLeft[0]),    // WIDTH
+                Math.ceil(botRight[1] - topLeft[1])     // HEIGHT
+            );
 
             // set the new canvas element context as the graphics context
             var tmpCtx = tmpCanvas.getContext("2d");
@@ -3265,7 +3258,6 @@ var CanvasGraphics = (function() {
                 ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
                               0, -h, w, h);
                 this.restore();
-                console.log("drawImage");
                 return;
             }
 
@@ -3415,7 +3407,7 @@ var CanvasGraphics = (function() {
                 }
             }
             tmpCtx.putImageData(imgData, 0, 0);
-            ctx.drawImage(tmpCanvas.getCanvas(), 0, -h);
+            ctx.drawImage(tmpCanvas, 0, -h);
             this.restore();
         },
 
diff --git a/viewer_worker.html b/viewer_worker.html
index c7041bcbd..f6d7f11e5 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -15,6 +15,7 @@ function toc(msg) {
 var myWorker = new Worker('worker.js');
 var imagesList = {};
 var canvasList = {};
+var patternList = {};
 var gradient;
 
 var currentX = 0;
@@ -78,6 +79,14 @@ var special = {
         gradient = this.createLinearGradient(x0, y0, x1, y1);
     },
 
+    "$createPatternFromCanvas": function(patternId, canvasId, kind) {
+        var canvas = canvasList[canvasId];
+        if (!canvas) {
+            throw "Canvas not found";
+        }
+        patternList[patternId] = this.createPattern(canvas, kind);
+    },
+
     "$addColorStop": function(i, rgba) {
         gradient.addColorStop(i, rgba);
     },
@@ -86,8 +95,24 @@ var special = {
         this.fillStyle = gradient;
     },
 
+    "$fillStylePattern": function(id) {
+        var pattern = patternList[id];
+        if (!pattern) {
+            throw "Pattern not found";
+        }
+        this.fillStyle = pattern;
+    },
+
     "$strokeStyleGradient": function() {
         this.strokeStyle = gradient;
+    },
+
+    "$strokeStylePattern": function(id) {
+        var pattern = patternList[id];
+        if (!pattern) {
+            throw "Pattern not found";
+        }
+        this.strokeStyle = pattern;
     }
 }
 
@@ -237,7 +262,7 @@ function open(url) {
         var data = req.mozResponseArrayBuffer || req.mozResponse ||
                    req.responseArrayBuffer || req.response;
         myWorker.postMessage(data);
-        showPage("11");
+        showPage("13");
       }
     };
     req.send(null);

From 08405fa618c55942c1d04b743f3dcbe198f186ce Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 22:00:53 +0200
Subject: [PATCH 25/45] Fix putImageData. Needs to be copied byte by byte.

---
 viewer_worker.html | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/viewer_worker.html b/viewer_worker.html
index f6d7f11e5..92806bc99 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -44,12 +44,17 @@ var special = {
     },
 
     "$putImageData": function(imageData, x, y) {
-        // Ugly: getImageData is called here only to get an object of the right
-        // shape - we are not interessted in the data, as we set it the line
-        // afterwards to something custome.
-        // Can we do better here?
         var imgData = this.getImageData(0, 0, imageData.width, imageData.height);
-        imgData.data = imageData.data;
+
+        // Store the .data property to avaid property lookups.
+        var imageRealData = imageData.data;
+        var imgRealData = imgData.data;
+
+        // Copy over the imageData.
+        var len = imageRealData.length;
+        while (len--)
+            imgRealData[len] = imageRealData[len]
+
         this.putImageData(imgData, x, y);
     },
 
@@ -262,7 +267,7 @@ function open(url) {
         var data = req.mozResponseArrayBuffer || req.mozResponse ||
                    req.responseArrayBuffer || req.response;
         myWorker.postMessage(data);
-        showPage("13");
+        showPage("2");
       }
     };
     req.send(null);

From 4281f799e5926d7087da69809d48ebe2ecfc7425 Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Wed, 22 Jun 2011 22:54:16 +0200
Subject: [PATCH 26/45] Move client code into worker_client.js. Cleanup +
 comments + 2-space-indention

---
 canvas_proxy.js    | 417 +++++++++++++++++++++------------------------
 viewer_worker.html | 299 ++------------------------------
 worker.js          |  96 ++++++-----
 worker_client.js   | 294 ++++++++++++++++++++++++++++++++
 4 files changed, 556 insertions(+), 550 deletions(-)
 create mode 100644 worker_client.js

diff --git a/canvas_proxy.js b/canvas_proxy.js
index 07ae31a63..83b57682f 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -1,260 +1,239 @@
-// var ImageCanvasProxyCounter = 0;
-// function ImageCanvasProxy(width, height) {
-//     this.id = ImageCanvasProxyCounter++;
-//     this.width = width;
-//     this.height = height;
-//
-//     // Using `Uint8ClampedArray` seems to be the type of ImageData - at least
-//     // Firebug tells me so.
-//     this.imgData = {
-//         data: Uint8ClampedArray(width * height * 4)
-//     };
-// }
-//
-// ImageCanvasProxy.prototype.putImageData = function(imgData) {
-//     // this.ctx.putImageData(imgData, 0, 0);
-// }
-//
-// ImageCanvasProxy.prototype.getCanvas = function() {
-//     return this;
-// }
 
 var JpegStreamProxyCounter = 0;
 // WebWorker Proxy for JpegStream.
 var JpegStreamProxy = (function() {
-    function constructor(bytes, dict) {
-        this.id = JpegStreamProxyCounter++;
-        this.dict = dict;
+  function constructor(bytes, dict) {
+    this.id = JpegStreamProxyCounter++;
+    this.dict = dict;
 
-        // create DOM image.
-        postMessage("jpeg_stream");
-        postMessage({
-            id:  this.id,
-            str: bytesToString(bytes)
-        });
-
-        // var img = new Image();
-        // img.src = "data:image/jpeg;base64," + window.btoa(bytesToString(bytes));
-        // this.domImage = img;
+    // Tell the main thread to create an image.
+    postMessage("jpeg_stream");
+    postMessage({
+      id:  this.id,
+      str: bytesToString(bytes)
+    });
+  }
+
+  constructor.prototype = {
+    getImage: function() {
+      return this;
+    },
+    getChar: function() {
+      error("internal error: getChar is not valid on JpegStream");
     }
+  };
 
-    constructor.prototype = {
-        getImage: function() {
-            return this;
-            // return this.domImage;
-        },
-        getChar: function() {
-            error("internal error: getChar is not valid on JpegStream");
-        }
-    };
-
-    return constructor;
+  return constructor;
 })();
 
 // Really simple GradientProxy. There is currently only one active gradient at
 // the time, meaning you can't create a gradient, create a second one and then
 // use the first one again. As this isn't used in pdf.js right now, it's okay.
 function GradientProxy(stack, x0, y0, x1, y1) {
-    stack.push(["$createLinearGradient", [x0, y0, x1, y1]]);
-    this.addColorStop = function(i, rgba) {
-        stack.push(["$addColorStop", [i, rgba]]);
-    }
+  stack.push(["$createLinearGradient", [x0, y0, x1, y1]]);
+  this.addColorStop = function(i, rgba) {
+    stack.push(["$addColorStop", [i, rgba]]);
+  }
 }
 
+// Really simple PatternProxy.
 var patternProxyCounter = 0;
 function PatternProxy(stack, object, kind) {
-    this.id = patternProxyCounter++;
+  this.id = patternProxyCounter++;
 
-    if (!(object instanceof CanvasProxy) ) {
-        throw "unkown type to createPattern";
-    }
-    // Flush the object here to ensure it's available on the main thread.
-    // TODO: Make some kind of dependency management, such that the object
-    // gets flushed only if needed.
-    object.flush();
-    stack.push(["$createPatternFromCanvas", [this.id, object.id, kind]]);
+  if (!(object instanceof CanvasProxy) ) {
+    throw "unkown type to createPattern";
+  }
+
+  // Flush the object here to ensure it's available on the main thread.
+  // TODO: Make some kind of dependency management, such that the object
+  // gets flushed only if needed.
+  object.flush();
+  stack.push(["$createPatternFromCanvas", [this.id, object.id, kind]]);
 }
 
 var canvasProxyCounter = 0;
 function CanvasProxy(width, height) {
-    this.id = canvasProxyCounter++;
-
-    var stack = this.$stack = [];
-
-    // Dummy context exposed.
-    var ctx = {};
-    this.getContext = function(type) {
-        if (type != "2d") {
-            throw "CanvasProxy can only provide a 2d context.";
-        }
-        return ctx;
-    }
-
-    // Expose only the minimum of the canvas object - there is no dom to do
-    // more here.
-    this.width = width;
-    this.height = height;
-    ctx.canvas = this;
-
-    var ctxFunc = [
-        "createRadialGradient",
-        "arcTo",
-        "arc",
-        "fillText",
-        "strokeText",
-        // "drawImage",
-        // "getImageData",
-        // "putImageData",
-        "createImageData",
-        "drawWindow",
-        "save",
-        "restore",
-        "scale",
-        "rotate",
-        "translate",
-        "transform",
-        "setTransform",
-        // "createLinearGradient",
-        // "createPattern",
-        "clearRect",
-        "fillRect",
-        "strokeRect",
-        "beginPath",
-        "closePath",
-        "moveTo",
-        "lineTo",
-        "quadraticCurveTo",
-        "bezierCurveTo",
-        "rect",
-        "fill",
-        "stroke",
-        "clip",
-        "measureText",
-        "isPointInPath",
-
-        "$setCurrentX",
-        "$addCurrentX",
-        "$saveCurrentX",
-        "$restoreCurrentX",
-        "$showText"
-    ];
-
-    ctx.createPattern = function(object, kind) {
-        return new PatternProxy(stack, object, kind);
-    }
+  this.id = canvasProxyCounter++;
 
-    ctx.createLinearGradient = function(x0, y0, x1, y1) {
-        return new GradientProxy(stack, x0, y0, x1, y1);
-    }
+  // The `stack` holds the rendering calls and gets flushed to the main thead.
+  var stack = this.$stack = [];
 
-    ctx.getImageData = function(x, y, w, h) {
-        return {
-            width: w,
-            height: h,
-            data: Uint8ClampedArray(w * h * 4)
-        };
+  // Dummy context that gets exposed.
+  var ctx = {};
+  this.getContext = function(type) {
+    if (type != "2d") {
+      throw "CanvasProxy can only provide a 2d context.";
     }
-
-    ctx.putImageData = function(data, x, y, width, height) {
-        stack.push(["$putImageData", [data, x, y, width, height]]);
+    return ctx;
+  }
+
+  // Expose only the minimum of the canvas object - there is no dom to do
+  // more here.
+  this.width = width;
+  this.height = height;
+  ctx.canvas = this;
+
+  // Setup function calls to `ctx`.
+  var ctxFunc = [
+  "createRadialGradient",
+  "arcTo",
+  "arc",
+  "fillText",
+  "strokeText",
+  "createImageData",
+  "drawWindow",
+  "save",
+  "restore",
+  "scale",
+  "rotate",
+  "translate",
+  "transform",
+  "setTransform",
+  "clearRect",
+  "fillRect",
+  "strokeRect",
+  "beginPath",
+  "closePath",
+  "moveTo",
+  "lineTo",
+  "quadraticCurveTo",
+  "bezierCurveTo",
+  "rect",
+  "fill",
+  "stroke",
+  "clip",
+  "measureText",
+  "isPointInPath",
+
+  // These functions are necessary to track the rendering currentX state.
+  // The exact values can be computed on the main thread only, as the
+  // worker has no idea about text width.
+  "$setCurrentX",
+  "$addCurrentX",
+  "$saveCurrentX",
+  "$restoreCurrentX",
+  "$showText"
+  ];
+
+  function buildFuncCall(name) {
+    return function() {
+      // console.log("funcCall", name)
+      stack.push([name, Array.prototype.slice.call(arguments)]);
     }
-
-    ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) {
-        if (image instanceof CanvasProxy) {
-            // Send the image/CanvasProxy to the main thread.
-            image.flush();
-            stack.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]);
-        } else if(image instanceof JpegStreamProxy) {
-            stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]])
-        } else {
-            throw "unkown type to drawImage";
-        }
-    }
-
-    function buildFuncCall(name) {
-        return function() {
-            // console.log("funcCall", name)
-            stack.push([name, Array.prototype.slice.call(arguments)]);
-        }
+  }
+  var name;
+  for (var i = 0; i < ctxFunc.length; i++) {
+    name = ctxFunc[i];
+    ctx[name] = buildFuncCall(name);
+  }
+
+  // Some function calls that need more work.
+
+  ctx.createPattern = function(object, kind) {
+    return new PatternProxy(stack, object, kind);
+  }
+
+  ctx.createLinearGradient = function(x0, y0, x1, y1) {
+    return new GradientProxy(stack, x0, y0, x1, y1);
+  }
+
+  ctx.getImageData = function(x, y, w, h) {
+    return {
+      width: w,
+      height: h,
+      data: Uint8ClampedArray(w * h * 4)
+    };
+  }
+
+  ctx.putImageData = function(data, x, y, width, height) {
+    stack.push(["$putImageData", [data, x, y, width, height]]);
+  }
+
+  ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) {
+    if (image instanceof CanvasProxy) {
+      // Send the image/CanvasProxy to the main thread.
+      image.flush();
+      stack.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]);
+    } else if(image instanceof JpegStreamProxy) {
+      stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]])
+    } else {
+      throw "unkown type to drawImage";
     }
-    var name;
-    for (var i = 0; i < ctxFunc.length; i++) {
-        name = ctxFunc[i];
-        ctx[name] = buildFuncCall(name);
+  }
+
+  // Setup property access to `ctx`.
+  var ctxProp = {
+    // "canvas"
+    "globalAlpha": "1",
+    "globalCompositeOperation": "source-over",
+    "strokeStyle": "#000000",
+    "fillStyle": "#000000",
+    "lineWidth": "1",
+    "lineCap": "butt",
+    "lineJoin": "miter",
+    "miterLimit": "10",
+    "shadowOffsetX": "0",
+    "shadowOffsetY": "0",
+    "shadowBlur": "0",
+    "shadowColor": "rgba(0, 0, 0, 0)",
+    "font": "10px sans-serif",
+    "textAlign": "start",
+    "textBaseline": "alphabetic",
+    "mozTextStyle": "10px sans-serif",
+    "mozImageSmoothingEnabled": "true"
+  }
+
+  function buildGetter(name) {
+    return function() {
+      return ctx["$" + name];
     }
+  }
 
-    var ctxProp = {
-        // "canvas"
-        "globalAlpha": "1",
-        "globalCompositeOperation": "source-over",
-        "strokeStyle": "#000000",
-        "fillStyle": "#000000",
-        "lineWidth": "1",
-        "lineCap": "butt",
-        "lineJoin": "miter",
-        "miterLimit": "10",
-        "shadowOffsetX": "0",
-        "shadowOffsetY": "0",
-        "shadowBlur": "0",
-        "shadowColor": "rgba(0, 0, 0, 0)",
-        "font": "10px sans-serif",
-        "textAlign": "start",
-        "textBaseline": "alphabetic",
-        "mozTextStyle": "10px sans-serif",
-        "mozImageSmoothingEnabled": "true",
-        "DRAWWINDOW_DRAW_CARET": "1",
-        "DRAWWINDOW_DO_NOT_FLUSH": "2",
-        "DRAWWINDOW_DRAW_VIEW": "4",
-        "DRAWWINDOW_USE_WIDGET_LAYERS": "8",
-        "DRAWWINDOW_ASYNC_DECODE_IMAGES": "16",
+  function buildSetter(name) {
+    return function(value) {
+      stack.push(["$", name, value]);
+      return ctx["$" + name] = value;
     }
+  }
 
-    function buildGetter(name) {
-        return function() {
-            return ctx["$" + name];
-        }
-    }
+  for (var name in ctxProp) {
+    ctx["$" + name] = ctxProp[name];
+    ctx.__defineGetter__(name, buildGetter(name));
 
-    function buildSetter(name) {
+    // Special treatment for `fillStyle` and `strokeStyle`: The passed style
+    // might be a gradient. Need to check for that.
+    if (name == "fillStyle" || name == "strokeStyle") {
+      function buildSetterStyle(name) {
         return function(value) {
+          if (value instanceof GradientProxy) {
+            stack.push(["$" + name + "Gradient"]);
+          } else if (value instanceof PatternProxy) {
+            stack.push(["$" + name + "Pattern", [value.id]]);
+          } else {
             stack.push(["$", name, value]);
             return ctx["$" + name] = value;
+          }
         }
+      }
+      ctx.__defineSetter__(name, buildSetterStyle(name));
+    } else {
+      ctx.__defineSetter__(name, buildSetter(name));
     }
-
-    for (var name in ctxProp) {
-        ctx["$" + name] = ctxProp[name];
-        ctx.__defineGetter__(name, buildGetter(name));
-
-        // Special treatment for `fillStyle` and `strokeStyle`: The passed style
-        // might be a gradient. Need to check for that.
-        if (name == "fillStyle" || name == "strokeStyle") {
-            function buildSetterStyle(name) {
-                return function(value) {
-                    if (value instanceof GradientProxy) {
-                        stack.push(["$" + name + "Gradient"]);
-                    } else if (value instanceof PatternProxy) {
-                        stack.push(["$" + name + "Pattern", [value.id]]);
-                    } else {
-                        stack.push(["$", name, value]);
-                        return ctx["$" + name] = value;
-                    }
-                }
-            }
-            ctx.__defineSetter__(name, buildSetterStyle(name));
-        } else {
-            ctx.__defineSetter__(name, buildSetter(name));
-        }
-    }
+  }
 }
 
+/**
+* Sends the current stack of the CanvasProxy over to the main thread and
+* resets the stack.
+*/
 CanvasProxy.prototype.flush = function() {
-    postMessage("canvas_proxy_stack");
-    postMessage({
-        id:     this.id,
-        stack:  this.$stack,
-        width:  this.width,
-        height: this.height
-    });
-    this.$stack.length = 0;
+  postMessage("canvas_proxy_stack");
+  postMessage({
+    id:     this.id,
+    stack:  this.$stack,
+    width:  this.width,
+    height: this.height
+  });
+  this.$stack.length = 0;
 }
diff --git a/viewer_worker.html b/viewer_worker.html
index 92806bc99..a9f08388f 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -1,295 +1,22 @@
 <html>
     <head>
         <title>Simple pdf.js page viewer worker</title>
+        <script type="text/javascript" src="worker_client.js"></script>
 <script>
 
-var timer = null
-function tic() {
-    timer = Date.now();
-}
-
-function toc(msg) {
-    console.log(msg + ": " + (Date.now() - timer) + "ms");
-}
-
-var myWorker = new Worker('worker.js');
-var imagesList = {};
-var canvasList = {};
-var patternList = {};
-var gradient;
-
-var currentX = 0;
-var currentXStack = [];
-var special = {
-    "$setCurrentX": function(value) {
-        currentX = value;
-    },
-
-    "$addCurrentX": function(value) {
-        currentX += value;
-    },
-
-    "$saveCurrentX": function() {
-        currentXStack.push(currentX);
-    },
-
-    "$restoreCurrentX": function() {
-        currentX = currentXStack.pop();
-    },
-
-    "$showText": function(y, text, uniText) {
-        this.translate(currentX, -1 * y);
-        this.fillText(uniText, 0, 0);
-        currentX += this.measureText(text).width;
-    },
-
-    "$putImageData": function(imageData, x, y) {
-        var imgData = this.getImageData(0, 0, imageData.width, imageData.height);
-
-        // Store the .data property to avaid property lookups.
-        var imageRealData = imageData.data;
-        var imgRealData = imgData.data;
-
-        // Copy over the imageData.
-        var len = imageRealData.length;
-        while (len--)
-            imgRealData[len] = imageRealData[len]
-
-        this.putImageData(imgData, x, y);
-    },
-
-    "$drawImage": function(id, x, y, sx, sy, swidth, sheight) {
-        var image = imagesList[id];
-        if (!image) {
-            throw "Image not found";
-        }
-        this.drawImage(image, x, y, image.width, image.height,
-            sx, sy, swidth, sheight);
-    },
-
-    "$drawCanvas": function(id, x, y, sx, sy, swidth, sheight) {
-        var canvas = canvasList[id];
-        if (!canvas) {
-            throw "Canvas not found";
-        }
-        if (sheight != null) {
-            this.drawImage(canvas, x, y, canvas.width, canvas.height,
-                sx, sy, swidth, sheight);
-        } else {
-            this.drawImage(canvas, x, y, canvas.width, canvas.height);
-        }
-    },
-
-    "$createLinearGradient": function(x0, y0, x1, y1) {
-        gradient = this.createLinearGradient(x0, y0, x1, y1);
-    },
-
-    "$createPatternFromCanvas": function(patternId, canvasId, kind) {
-        var canvas = canvasList[canvasId];
-        if (!canvas) {
-            throw "Canvas not found";
-        }
-        patternList[patternId] = this.createPattern(canvas, kind);
-    },
-
-    "$addColorStop": function(i, rgba) {
-        gradient.addColorStop(i, rgba);
-    },
-
-    "$fillStyleGradient": function() {
-        this.fillStyle = gradient;
-    },
-
-    "$fillStylePattern": function(id) {
-        var pattern = patternList[id];
-        if (!pattern) {
-            throw "Pattern not found";
-        }
-        this.fillStyle = pattern;
-    },
-
-    "$strokeStyleGradient": function() {
-        this.strokeStyle = gradient;
-    },
-
-    "$strokeStylePattern": function(id) {
-        var pattern = patternList[id];
-        if (!pattern) {
-            throw "Pattern not found";
-        }
-        this.strokeStyle = pattern;
-    }
-}
-
-var gStack;
-function renderProxyCanvas(canvas, stack) {
-    var ctx = canvas.getContext("2d");
-    for (var i = 0; i < stack.length; i++) {
-    // for (var i = 0; i < 1000; i++) {
-        var opp = stack[i];
-        if (opp[0] == "$") {
-            ctx[opp[1]] = opp[2];
-        } else if (opp[0] in special) {
-            special[opp[0]].apply(ctx, opp[1]);
-        } else {
-            ctx[opp[0]].apply(ctx, opp[1]);
-        }
-    }
-}
-
-const WAIT = 0;
-const CANVAS_PROXY_STACK = 1;
-const LOG = 2;
-const FONT = 3;
-const PDF_NUM_PAGE = 4;
-const JPEG_STREAM = 5;
-
-var onMessageState = WAIT;
-var fontStr = null;
-var first = true;
-var intervals = [];
-myWorker.onmessage = function(event) {
-    var data = event.data;
-    // console.log("onMessageRaw", data);
-    switch (onMessageState) {
-        case WAIT:
-            if (typeof data != "string") {
-                throw "expecting to get an string";
-            }
-            switch (data) {
-                case "pdf_num_page":
-                    onMessageState = PDF_NUM_PAGE;
-                    return;
-                case "log":
-                    onMessageState = LOG;
-                    return;
-                case "canvas_proxy_stack":
-                    onMessageState = CANVAS_PROXY_STACK;
-                    return;
-                case "font":
-                    onMessageState = FONT;
-                    return;
-                case "jpeg_stream":
-                    onMessageState = JPEG_STREAM;
-                    return;
-                default:
-                    throw "unkown state: " + data
-            }
-            break;
-
-        case JPEG_STREAM:
-            var img = new Image();
-            img.src = "data:image/jpeg;base64," + window.btoa(data.str);
-            imagesList[data.id] = img;
-            console.log("got image", data.id)
-            break;
-
-        case PDF_NUM_PAGE:
-            console.log(data);
-            maxPages = parseInt(data);
-            document.getElementById("numPages").innerHTML = "/" + data;
-            onMessageState = WAIT;
-            break;
-
-        case FONT:
-            data = JSON.parse(data);
-            var base64 = window.btoa(data.str);
-
-            // Add the @font-face rule to the document
-            var url = "url(data:" + data.mimetype + ";base64," + base64 + ");";
-            var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}";
-            var styleSheet = document.styleSheets[0];
-            styleSheet.insertRule(rule, styleSheet.length);
-
-            // Just adding the font-face to the DOM doesn't make it load. It
-            // seems it's loaded once Gecko notices it's used. Therefore,
-            // add a div on the page using the loaded font.
-            document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>";
-            console.log("setup font", data.fontName);
-
-            onMessageState = WAIT;
-            break;
-
-        case LOG:
-            console.log.apply(console, JSON.parse(data));
-            onMessageState = WAIT;
-            break;
-
-        case CANVAS_PROXY_STACK:
-            var id = data.id;
-            var stack = data.stack;
-            gStack = stack;
-
-            // Check if there is already a canvas with the given id. If not,
-            // create a new canvas.
-            if (!canvasList[id]) {
-                var newCanvas = document.createElement("canvas");
-                newCanvas.width = data.width;
-                newCanvas.height = data.height;
-                canvasList[id] = newCanvas;
-            }
-
-            // There might be fonts that need to get loaded. Shedule the
-            // rendering at the end of the event queue ensures this.
-            setTimeout(function() {
-                if (id == 0) tic();
-                renderProxyCanvas(canvasList[id], stack);
-                if (id == 0) toc("canvas rendering")
-            }, 0);
-            onMessageState = WAIT;
-            break;
-    }
-}
-//
-// myWorker.postMessage(array);
-
-var currentPage = 1;
-var maxPages = 1;
-function showPage(num) {
-    ctx.save();
-    ctx.fillStyle = "rgb(255, 255, 255)";
-    ctx.fillRect(0, 0, canvas.width, canvas.height);
-    ctx.restore();
-    console.log("worker: page=" + num)
-    document.getElementById('pageNumber').value = num;
-    currentPage = parseInt(num);
-    myWorker.postMessage(num);
-}
-
-function open(url) {
-    document.title = url;
-    var req = new XMLHttpRequest();
-    req.open("GET", url);
-    req.mozResponseType = req.responseType = "arraybuffer";
-    req.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200;
-    req.onreadystatechange = function() {
-      if (req.readyState == 4 && req.status == req.expected) {
-        var data = req.mozResponseArrayBuffer || req.mozResponse ||
-                   req.responseArrayBuffer || req.response;
-        myWorker.postMessage(data);
-        showPage("2");
-      }
-    };
-    req.send(null);
-}
-
-function nextPage() {
-    if (currentPage == maxPages) return;
-    currentPage++;
-    showPage(currentPage);
-}
-
-function prevPage() {
-    if (currentPage == 1) return;
-    currentPage--;
-    showPage(currentPage);
-}
 
+var pdfDoc;
 window.onload = function() {
     window.canvas = document.getElementById("canvas");
     window.ctx = canvas.getContext("2d");
-    canvasList[0] = window.canvas;
-    open("compressed.tracemonkey-pldi-09.pdf");
+
+    pdfDoc = new WorkerPDFDoc(window.canvas);
+    pdfDoc.onChangePage = function(numPage) {
+        document.getElementById("pageNumber").value = numPage;
+    }
+    pdfDoc.open("compressed.tracemonkey-pldi-09.pdf", function() {
+        document.getElementById("numPages").innerHTML = "/" + pdfDoc.numPages;
+    })
 }
 </script>
         <link rel="stylesheet" href="viewer.css"></link>
@@ -301,9 +28,9 @@ window.onload = function() {
     <input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input>
     <!-- This only opens supported PDFs from the source path...
       -- Can we use JSONP to overcome the same-origin restrictions? -->
-      <button onclick="prevPage();">Previous</button>
-      <button onclick="nextPage();">Next</button>
-      <input type="text" id="pageNumber" onchange="showPage(this.value);"
+      <button onclick="pdfDoc.prevPage();">Previous</button>
+      <button onclick="pdfDoc.nextPage();">Next</button>
+      <input type="text" id="pageNumber" onchange="pdfDoc.showPage(this.value);"
              value="1" size="4"></input>
       <span id="numPages">--</span>
       <span id="info"></span>
diff --git a/worker.js b/worker.js
index e59e37155..09e2b8145 100644
--- a/worker.js
+++ b/worker.js
@@ -1,15 +1,26 @@
 "use strict";
 
+var timer = null;
+function tic() {
+  timer = Date.now();
+}
+
+function toc(msg) {
+  log(msg + ": " + (Date.now() - timer) + "ms");
+  timer = null;
+}
+
 function log() {
-    var args = Array.prototype.slice.call(arguments);
-    postMessage("log");
-    postMessage(JSON.stringify(args))
+  var args = Array.prototype.slice.call(arguments);
+  postMessage("log");
+  postMessage(JSON.stringify(args))
 }
 
 var console = {
-    log: log
+  log: log
 }
 
+//
 importScripts("canvas_proxy.js");
 importScripts("pdf.js");
 importScripts("fonts.js");
@@ -18,55 +29,50 @@ importScripts("glyphlist.js")
 // Use the JpegStreamProxy proxy.
 JpegStream = JpegStreamProxy;
 
-var timer = null;
-function tic() {
-    timer = Date.now();
-}
-
-function toc(msg) {
-    log(msg + ": " + (Date.now() - timer) + "ms");
-    timer = null;
-}
-
 // Create the WebWorkerProxyCanvas.
 var canvas = new CanvasProxy(1224, 1584);
 
-var pageInterval;
+// Listen for messages from the main thread.
 var pdfDocument = null;
 onmessage = function(event) {
-    var data = event.data;
-    if (!pdfDocument) {
-        pdfDocument = new PDFDoc(new Stream(data));
-        postMessage("pdf_num_page");
-        postMessage(pdfDocument.numPages)
-        return;
-    } else {
-        tic();
+  var data = event.data;
+  // If there is no pdfDocument yet, then the sent data is the PDFDocument.
+  if (!pdfDocument) {
+    pdfDocument = new PDFDoc(new Stream(data));
+    postMessage("pdf_num_page");
+    postMessage(pdfDocument.numPages)
+    return;
+  }
+  // User requested to render a certain page.
+  else {
+    tic();
 
-        // Let's try to render the first page...
-        var page = pdfDocument.getPage(parseInt(data));
+    // Let's try to render the first page...
+    var page = pdfDocument.getPage(parseInt(data));
 
-        // page.compile will collect all fonts for us, once we have loaded them
-        // we can trigger the actual page rendering with page.display
-        var fonts = [];
-        var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy);
-        page.compile(gfx, fonts);
+    // page.compile will collect all fonts for us, once we have loaded them
+    // we can trigger the actual page rendering with page.display
+    var fonts = [];
+    var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy);
+    page.compile(gfx, fonts);
 
-        // Inspect fonts and translate the missing one.
-        var count = fonts.length;
-        for (var i = 0; i < count; i++) {
-          var font = fonts[i];
-          if (Fonts[font.name]) {
-            fontsReady = fontsReady && !Fonts[font.name].loading;
-            continue;
-          }
+    // Inspect fonts and translate the missing one.
+    var count = fonts.length;
+    for (var i = 0; i < count; i++) {
+      var font = fonts[i];
+      if (Fonts[font.name]) {
+        fontsReady = fontsReady && !Fonts[font.name].loading;
+        continue;
+      }
 
-          // This "builds" the font and sents it over to the main thread.
-          new Font(font.name, font.file, font.properties);
-        }
-        toc("compiled page");
-
-        page.display(gfx);
-        canvas.flush();
+      // This "builds" the font and sents it over to the main thread.
+      new Font(font.name, font.file, font.properties);
     }
+    toc("compiled page");
+
+    tic()
+    page.display(gfx);
+    canvas.flush();
+    toc("displayed page");
+  }
 }
diff --git a/worker_client.js b/worker_client.js
new file mode 100644
index 000000000..316ef1fc0
--- /dev/null
+++ b/worker_client.js
@@ -0,0 +1,294 @@
+"use strict";
+
+function WorkerPDFDoc(canvas) {
+  var timer = null
+  function tic() {
+    timer = Date.now();
+  }
+
+  function toc(msg) {
+    console.log(msg + ": " + (Date.now() - timer) + "ms");
+  }
+
+  this.ctx = canvas.getContext("2d");
+  this.canvas = canvas;
+  this.worker = new Worker('worker.js');
+
+  this.numPage = 1;
+  this.numPages = null;
+
+  var imagesList = {};
+  var canvasList = {
+    0: canvas
+  };
+  var patternList = {};
+  var gradient;
+
+  var currentX = 0;
+  var currentXStack = [];
+
+  var ctxSpecial = {
+    "$setCurrentX": function(value) {
+      currentX = value;
+    },
+
+    "$addCurrentX": function(value) {
+      currentX += value;
+    },
+
+    "$saveCurrentX": function() {
+      currentXStack.push(currentX);
+    },
+
+    "$restoreCurrentX": function() {
+      currentX = currentXStack.pop();
+    },
+
+    "$showText": function(y, text, uniText) {
+      this.translate(currentX, -1 * y);
+      this.fillText(uniText, 0, 0);
+      currentX += this.measureText(text).width;
+    },
+
+    "$putImageData": function(imageData, x, y) {
+      var imgData = this.getImageData(0, 0, imageData.width, imageData.height);
+
+      // Store the .data property to avaid property lookups.
+      var imageRealData = imageData.data;
+      var imgRealData = imgData.data;
+
+      // Copy over the imageData.
+      var len = imageRealData.length;
+      while (len--)
+      imgRealData[len] = imageRealData[len]
+
+      this.putImageData(imgData, x, y);
+    },
+
+    "$drawImage": function(id, x, y, sx, sy, swidth, sheight) {
+      var image = imagesList[id];
+      if (!image) {
+        throw "Image not found";
+      }
+      this.drawImage(image, x, y, image.width, image.height,
+        sx, sy, swidth, sheight);
+    },
+
+    "$drawCanvas": function(id, x, y, sx, sy, swidth, sheight) {
+      var canvas = canvasList[id];
+      if (!canvas) {
+        throw "Canvas not found";
+      }
+      if (sheight != null) {
+        this.drawImage(canvas, x, y, canvas.width, canvas.height,
+          sx, sy, swidth, sheight);
+      } else {
+        this.drawImage(canvas, x, y, canvas.width, canvas.height);
+      }
+    },
+
+    "$createLinearGradient": function(x0, y0, x1, y1) {
+      gradient = this.createLinearGradient(x0, y0, x1, y1);
+    },
+
+    "$createPatternFromCanvas": function(patternId, canvasId, kind) {
+      var canvas = canvasList[canvasId];
+      if (!canvas) {
+        throw "Canvas not found";
+      }
+      patternList[patternId] = this.createPattern(canvas, kind);
+    },
+
+    "$addColorStop": function(i, rgba) {
+      gradient.addColorStop(i, rgba);
+    },
+
+    "$fillStyleGradient": function() {
+      this.fillStyle = gradient;
+    },
+
+    "$fillStylePattern": function(id) {
+      var pattern = patternList[id];
+      if (!pattern) {
+        throw "Pattern not found";
+      }
+      this.fillStyle = pattern;
+    },
+
+    "$strokeStyleGradient": function() {
+      this.strokeStyle = gradient;
+    },
+
+    "$strokeStylePattern": function(id) {
+      var pattern = patternList[id];
+      if (!pattern) {
+        throw "Pattern not found";
+      }
+      this.strokeStyle = pattern;
+    }
+  }
+
+  function renderProxyCanvas(canvas, stack) {
+    var ctx = canvas.getContext("2d");
+    for (var i = 0; i < stack.length; i++) {
+      var opp = stack[i];
+      if (opp[0] == "$") {
+        ctx[opp[1]] = opp[2];
+      } else if (opp[0] in ctxSpecial) {
+        ctxSpecial[opp[0]].apply(ctx, opp[1]);
+      } else {
+        ctx[opp[0]].apply(ctx, opp[1]);
+      }
+    }
+  }
+
+  /**
+  * onMessage state machine.
+  */
+  const WAIT = 0;
+  const CANVAS_PROXY_STACK = 1;
+  const LOG = 2;
+  const FONT = 3;
+  const PDF_NUM_PAGE = 4;
+  const JPEG_STREAM = 5;
+
+  var onMessageState = WAIT;
+  this.worker.onmessage = function(event) {
+    var data = event.data;
+    // console.log("onMessageRaw", data);
+    switch (onMessageState) {
+      case WAIT:
+        if (typeof data != "string") {
+          throw "expecting to get an string";
+        }
+        switch (data) {
+          case "pdf_num_page":
+            onMessageState = PDF_NUM_PAGE;
+            return;
+
+          case "log":
+            onMessageState = LOG;
+            return;
+
+          case "canvas_proxy_stack":
+            onMessageState = CANVAS_PROXY_STACK;
+            return;
+
+          case "font":
+            onMessageState = FONT;
+            return;
+
+          case "jpeg_stream":
+            onMessageState = JPEG_STREAM;
+            return;
+
+          default:
+            throw "unkown state: " + data
+        }
+      break;
+
+      case JPEG_STREAM:
+        var img = new Image();
+        img.src = "data:image/jpeg;base64," + window.btoa(data.str);
+        imagesList[data.id] = img;
+        console.log("got image", data.id)
+      break;
+
+      case PDF_NUM_PAGE:
+        this.numPages = parseInt(data);
+        if (this.loadCallback) {
+          this.loadCallback();
+        }
+        onMessageState = WAIT;
+      break;
+
+      case FONT:
+        data = JSON.parse(data);
+        var base64 = window.btoa(data.str);
+
+        // Add the @font-face rule to the document
+        var url = "url(data:" + data.mimetype + ";base64," + base64 + ");";
+        var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}";
+        var styleSheet = document.styleSheets[0];
+        styleSheet.insertRule(rule, styleSheet.length);
+
+        // Just adding the font-face to the DOM doesn't make it load. It
+        // seems it's loaded once Gecko notices it's used. Therefore,
+        // add a div on the page using the loaded font.
+        document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>";
+
+        onMessageState = WAIT;
+      break;
+
+      case LOG:
+        console.log.apply(console, JSON.parse(data));
+        onMessageState = WAIT;
+      break;
+
+      case CANVAS_PROXY_STACK:
+        var id = data.id;
+        var stack = data.stack;
+
+        // Check if there is already a canvas with the given id. If not,
+        // create a new canvas.
+        if (!canvasList[id]) {
+          var newCanvas = document.createElement("canvas");
+          newCanvas.width = data.width;
+          newCanvas.height = data.height;
+          canvasList[id] = newCanvas;
+        }
+
+        // There might be fonts that need to get loaded. Shedule the
+        // rendering at the end of the event queue ensures this.
+        setTimeout(function() {
+          if (id == 0) tic();
+          renderProxyCanvas(canvasList[id], stack);
+          if (id == 0) toc("canvas rendering")
+        }, 0);
+        onMessageState = WAIT;
+      break;
+    }
+  }.bind(this);
+}
+
+  WorkerPDFDoc.prototype.open = function(url, callback) {
+  var req = new XMLHttpRequest();
+  req.open("GET", url);
+  req.mozResponseType = req.responseType = "arraybuffer";
+  req.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200;
+  req.onreadystatechange = function() {
+    if (req.readyState == 4 && req.status == req.expected) {
+      var data = req.mozResponseArrayBuffer || req.mozResponse ||
+      req.responseArrayBuffer || req.response;
+
+      this.loadCallback = callback;
+      this.worker.postMessage(data);
+      this.showPage(this.numPage);
+    }
+  }.bind(this);
+  req.send(null);
+}
+
+WorkerPDFDoc.prototype.showPage = function(numPage) {
+  var ctx = this.ctx;
+  ctx.save();
+  ctx.fillStyle = "rgb(255, 255, 255)";
+  ctx.fillRect(0, 0, canvas.width, canvas.height);
+  ctx.restore();
+
+  this.numPage = parseInt(numPage);
+  this.worker.postMessage(numPage);
+  if (this.onChangePage) {
+    this.onChangePage(numPage);
+  }
+}
+
+WorkerPDFDoc.prototype.nextPage = function() {
+  if (this.numPage == this.numPages) return;
+  this.showPage(++this.numPage);
+}
+
+WorkerPDFDoc.prototype.prevPage = function() {
+  if (this.numPage == 1) return;
+  this.showPage(--this.numPage);
+}

From a3d815074dd9d186c26fb6ecf9eed9cd0fb6a60d Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Thu, 23 Jun 2011 13:09:36 +0200
Subject: [PATCH 27/45] First pass on review: worker.js -> pdf_worker.js,
 Font.bind cleanup + other stuff

---
 canvas_proxy.js            | 73 ++++++++++++++++--------------
 fonts.js                   | 92 +++++++-------------------------------
 pdf.js                     |  8 ++--
 worker.js => pdf_worker.js |  3 ++
 viewer_worker.html         |  1 +
 worker_client.js           | 23 ++++++----
 6 files changed, 77 insertions(+), 123 deletions(-)
 rename worker.js => pdf_worker.js (92%)

diff --git a/canvas_proxy.js b/canvas_proxy.js
index 83b57682f..0b7681bfe 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -1,3 +1,7 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+"use strict";
 
 var JpegStreamProxyCounter = 0;
 // WebWorker Proxy for JpegStream.
@@ -29,16 +33,16 @@ var JpegStreamProxy = (function() {
 // Really simple GradientProxy. There is currently only one active gradient at
 // the time, meaning you can't create a gradient, create a second one and then
 // use the first one again. As this isn't used in pdf.js right now, it's okay.
-function GradientProxy(stack, x0, y0, x1, y1) {
-  stack.push(["$createLinearGradient", [x0, y0, x1, y1]]);
+function GradientProxy(cmdQueue, x0, y0, x1, y1) {
+  cmdQueue.push(["$createLinearGradient", [x0, y0, x1, y1]]);
   this.addColorStop = function(i, rgba) {
-    stack.push(["$addColorStop", [i, rgba]]);
+    cmdQueue.push(["$addColorStop", [i, rgba]]);
   }
 }
 
 // Really simple PatternProxy.
 var patternProxyCounter = 0;
-function PatternProxy(stack, object, kind) {
+function PatternProxy(cmdQueue, object, kind) {
   this.id = patternProxyCounter++;
 
   if (!(object instanceof CanvasProxy) ) {
@@ -49,7 +53,7 @@ function PatternProxy(stack, object, kind) {
   // TODO: Make some kind of dependency management, such that the object
   // gets flushed only if needed.
   object.flush();
-  stack.push(["$createPatternFromCanvas", [this.id, object.id, kind]]);
+  cmdQueue.push(["$createPatternFromCanvas", [this.id, object.id, kind]]);
 }
 
 var canvasProxyCounter = 0;
@@ -57,7 +61,7 @@ function CanvasProxy(width, height) {
   this.id = canvasProxyCounter++;
 
   // The `stack` holds the rendering calls and gets flushed to the main thead.
-  var stack = this.$stack = [];
+  var cmdQueue = this.cmdQueue = [];
 
   // Dummy context that gets exposed.
   var ctx = {};
@@ -119,7 +123,7 @@ function CanvasProxy(width, height) {
   function buildFuncCall(name) {
     return function() {
       // console.log("funcCall", name)
-      stack.push([name, Array.prototype.slice.call(arguments)]);
+      cmdQueue.push([name, Array.prototype.slice.call(arguments)]);
     }
   }
   var name;
@@ -131,11 +135,11 @@ function CanvasProxy(width, height) {
   // Some function calls that need more work.
 
   ctx.createPattern = function(object, kind) {
-    return new PatternProxy(stack, object, kind);
+    return new PatternProxy(cmdQueue, object, kind);
   }
 
   ctx.createLinearGradient = function(x0, y0, x1, y1) {
-    return new GradientProxy(stack, x0, y0, x1, y1);
+    return new GradientProxy(cmdQueue, x0, y0, x1, y1);
   }
 
   ctx.getImageData = function(x, y, w, h) {
@@ -147,16 +151,16 @@ function CanvasProxy(width, height) {
   }
 
   ctx.putImageData = function(data, x, y, width, height) {
-    stack.push(["$putImageData", [data, x, y, width, height]]);
+    cmdQueue.push(["$putImageData", [data, x, y, width, height]]);
   }
 
   ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) {
     if (image instanceof CanvasProxy) {
       // Send the image/CanvasProxy to the main thread.
       image.flush();
-      stack.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]);
+      cmdQueue.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]);
     } else if(image instanceof JpegStreamProxy) {
-      stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]])
+      cmdQueue.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]])
     } else {
       throw "unkown type to drawImage";
     }
@@ -192,11 +196,26 @@ function CanvasProxy(width, height) {
 
   function buildSetter(name) {
     return function(value) {
-      stack.push(["$", name, value]);
+      cmdQueue.push(["$", name, value]);
       return ctx["$" + name] = value;
     }
   }
 
+  // Setting the value to `stroke|fillStyle` needs special handling, as it
+  // might gets an gradient/pattern.
+  function buildSetterStyle(name) {
+    return function(value) {
+      if (value instanceof GradientProxy) {
+        cmdQueue.push(["$" + name + "Gradient"]);
+      } else if (value instanceof PatternProxy) {
+        cmdQueue.push(["$" + name + "Pattern", [value.id]]);
+      } else {
+        cmdQueue.push(["$", name, value]);
+        return ctx["$" + name] = value;
+      }
+    }
+  }
+
   for (var name in ctxProp) {
     ctx["$" + name] = ctxProp[name];
     ctx.__defineGetter__(name, buildGetter(name));
@@ -204,18 +223,6 @@ function CanvasProxy(width, height) {
     // Special treatment for `fillStyle` and `strokeStyle`: The passed style
     // might be a gradient. Need to check for that.
     if (name == "fillStyle" || name == "strokeStyle") {
-      function buildSetterStyle(name) {
-        return function(value) {
-          if (value instanceof GradientProxy) {
-            stack.push(["$" + name + "Gradient"]);
-          } else if (value instanceof PatternProxy) {
-            stack.push(["$" + name + "Pattern", [value.id]]);
-          } else {
-            stack.push(["$", name, value]);
-            return ctx["$" + name] = value;
-          }
-        }
-      }
       ctx.__defineSetter__(name, buildSetterStyle(name));
     } else {
       ctx.__defineSetter__(name, buildSetter(name));
@@ -224,16 +231,16 @@ function CanvasProxy(width, height) {
 }
 
 /**
-* Sends the current stack of the CanvasProxy over to the main thread and
-* resets the stack.
+* Sends the current cmdQueue of the CanvasProxy over to the main thread and
+* resets the cmdQueue.
 */
 CanvasProxy.prototype.flush = function() {
-  postMessage("canvas_proxy_stack");
+  postMessage("canvas_proxy_cmd_queue");
   postMessage({
-    id:     this.id,
-    stack:  this.$stack,
-    width:  this.width,
-    height: this.height
+    id:         this.id,
+    cmdQueue:   this.cmdQueue,
+    width:      this.width,
+    height:     this.height
   });
-  this.$stack.length = 0;
+  this.cmdQueue.length = 0;
 }
diff --git a/fonts.js b/fonts.js
index 9c9201b72..a3604c6b9 100644
--- a/fonts.js
+++ b/fonts.js
@@ -759,88 +759,15 @@ var Font = (function () {
       var data = this.font;
       var fontName = this.name;
 
-      var isWorker = (typeof window == "undefined");
-      /** Hack begin */
-      if (!isWorker) {
-
-          // Actually there is not event when a font has finished downloading so
-          // the following code are a dirty hack to 'guess' when a font is ready
-          var canvas = document.createElement("canvas");
-          var style = "border: 1px solid black; position:absolute; top: " +
-                       (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px";
-          canvas.setAttribute("style", style);
-          canvas.setAttribute("width", 340);
-          canvas.setAttribute("heigth", 100);
-          document.body.appendChild(canvas);
-
-          // Get the font size canvas think it will be for 'spaces'
-          var ctx = canvas.getContext("2d");
-          ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
-          var testString = "   ";
-
-          // When debugging use the characters provided by the charsets to visually
-          // see what's happening instead of 'spaces'
-          var debug = false;
-          if (debug) {
-            var name = document.createElement("font");
-            name.setAttribute("style", "position: absolute; left: 20px; top: " +
-                              (100 * fontCount + 60) + "px");
-            name.innerHTML = fontName;
-            document.body.appendChild(name);
-
-            // Retrieve font charset
-            var charset = Fonts[fontName].properties.charset || [];
-
-            // if the charset is too small make it repeat a few times
-            var count = 30;
-            while (count-- && charset.length <= 30)
-              charset = charset.concat(charset.slice());
-
-            for (var i = 0; i < charset.length; i++) {
-              var unicode = GlyphsUnicode[charset[i]];
-              if (!unicode)
-                continue;
-              testString += String.fromCharCode(unicode);
-            }
-
-            ctx.fillText(testString, 20, 20);
-          }
-
-          // Periodicaly check for the width of the testString, it will be
-          // different once the real font has loaded
-          var textWidth = ctx.measureText(testString).width;
-
-          var interval = window.setInterval(function canvasInterval(self) {
-            this.start = this.start || Date.now();
-            ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
-
-            // For some reasons the font has not loaded, so mark it loaded for the
-            // page to proceed but cry
-            if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
-              window.clearInterval(interval);
-              Fonts[fontName].loading = false;
-              warn("Is " + fontName + " for charset: " + charset + " loaded?");
-              this.start = 0;
-            } else if (textWidth != ctx.measureText(testString).width) {
-              window.clearInterval(interval);
-              Fonts[fontName].loading = false;
-              this.start = 0;
-            }
-
-            if (debug)
-              ctx.fillText(testString, 20, 50);
-          }, 30, this);
-    }
-
-      /** Hack end */
-      //
       // Get the base64 encoding of the binary font data
       var str = "";
       var length = data.length;
       for (var i = 0; i < length; ++i)
         str += String.fromCharCode(data[i]);
 
-      if (isWorker) {
+      // Insert the font-face css on the page. In a web worker, this needs to
+      // be forwareded on the main thread.
+      if (typeof window == "undefined") {
           postMessage("font");
           postMessage(JSON.stringify({
               str: str,
@@ -855,6 +782,19 @@ var Font = (function () {
           var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
           var styleSheet = document.styleSheets[0];
           styleSheet.insertRule(rule, styleSheet.length);
+
+          var div = document.createElement("div");
+          div.innerHTML += "<div style='font-family:" +
+            fontName +
+            ";'>j</div>";
+          document.body.appendChild(div);
+
+          Fonts[fontName].loading = true;
+          window.setTimeout(function() {
+            Fonts[fontName].loading = false;
+          // Timeout of just `0`, `10` doesn't work here, but for me all values
+          // above work. Setting value to 50ms.
+          }, 50);
       }
     }
   };
diff --git a/pdf.js b/pdf.js
index 1223a2bb6..847067946 100644
--- a/pdf.js
+++ b/pdf.js
@@ -2645,9 +2645,7 @@ var CanvasGraphics = (function() {
             }
 
             var fn = Function("objpool", src);
-            var ret = function (gfx) { fn.call(gfx, objpool); };
-            ret.src = src;
-            return ret;
+            return function (gfx) { fn.call(gfx, objpool); };
         },
 
         endDrawing: function() {
@@ -3015,8 +3013,8 @@ var CanvasGraphics = (function() {
             var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix);
 
             var tmpCanvas = new this.ScratchCanvas(
-                Math.ceil(botRight[0] - topLeft[0]),    // WIDTH
-                Math.ceil(botRight[1] - topLeft[1])     // HEIGHT
+                Math.ceil(botRight[0] - topLeft[0]),    // width
+                Math.ceil(botRight[1] - topLeft[1])     // height
             );
 
             // set the new canvas element context as the graphics context
diff --git a/worker.js b/pdf_worker.js
similarity index 92%
rename from worker.js
rename to pdf_worker.js
index 09e2b8145..91245aedb 100644
--- a/worker.js
+++ b/pdf_worker.js
@@ -1,3 +1,6 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
 "use strict";
 
 var timer = null;
diff --git a/viewer_worker.html b/viewer_worker.html
index a9f08388f..a5ffc6a6e 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -14,6 +14,7 @@ window.onload = function() {
     pdfDoc.onChangePage = function(numPage) {
         document.getElementById("pageNumber").value = numPage;
     }
+    // pdfDoc.open("canvas.pdf", function() {
     pdfDoc.open("compressed.tracemonkey-pldi-09.pdf", function() {
         document.getElementById("numPages").innerHTML = "/" + pdfDoc.numPages;
     })
diff --git a/worker_client.js b/worker_client.js
index 316ef1fc0..f69f4f682 100644
--- a/worker_client.js
+++ b/worker_client.js
@@ -1,3 +1,6 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
 "use strict";
 
 function WorkerPDFDoc(canvas) {
@@ -128,10 +131,11 @@ function WorkerPDFDoc(canvas) {
     }
   }
 
-  function renderProxyCanvas(canvas, stack) {
+  function renderProxyCanvas(canvas, cmdQueue) {
     var ctx = canvas.getContext("2d");
-    for (var i = 0; i < stack.length; i++) {
-      var opp = stack[i];
+    var cmdQueueLength = cmdQueue.length;
+    for (var i = 0; i < cmdQueueLength; i++) {
+      var opp = cmdQueue[i];
       if (opp[0] == "$") {
         ctx[opp[1]] = opp[2];
       } else if (opp[0] in ctxSpecial) {
@@ -146,7 +150,7 @@ function WorkerPDFDoc(canvas) {
   * onMessage state machine.
   */
   const WAIT = 0;
-  const CANVAS_PROXY_STACK = 1;
+  const CANVAS_PROXY_CMD_QUEUE = 1;
   const LOG = 2;
   const FONT = 3;
   const PDF_NUM_PAGE = 4;
@@ -170,8 +174,8 @@ function WorkerPDFDoc(canvas) {
             onMessageState = LOG;
             return;
 
-          case "canvas_proxy_stack":
-            onMessageState = CANVAS_PROXY_STACK;
+          case "canvas_proxy_cmd_queue":
+            onMessageState = CANVAS_PROXY_CMD_QUEUE;
             return;
 
           case "font":
@@ -215,6 +219,7 @@ function WorkerPDFDoc(canvas) {
         // Just adding the font-face to the DOM doesn't make it load. It
         // seems it's loaded once Gecko notices it's used. Therefore,
         // add a div on the page using the loaded font.
+        var div = document.createElement("div");
         document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>";
 
         onMessageState = WAIT;
@@ -225,9 +230,9 @@ function WorkerPDFDoc(canvas) {
         onMessageState = WAIT;
       break;
 
-      case CANVAS_PROXY_STACK:
+      case CANVAS_PROXY_CMD_QUEUE:
         var id = data.id;
-        var stack = data.stack;
+        var cmdQueue = data.cmdQueue;
 
         // Check if there is already a canvas with the given id. If not,
         // create a new canvas.
@@ -242,7 +247,7 @@ function WorkerPDFDoc(canvas) {
         // rendering at the end of the event queue ensures this.
         setTimeout(function() {
           if (id == 0) tic();
-          renderProxyCanvas(canvasList[id], stack);
+          renderProxyCanvas(canvasList[id], cmdQueue);
           if (id == 0) toc("canvas rendering")
         }, 0);
         onMessageState = WAIT;

From da7f555fd7cccfb8cef331438fec7e905059373d Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Thu, 23 Jun 2011 13:25:59 +0200
Subject: [PATCH 28/45] Change postMessage to send only one object that holds
 the action and data.

---
 canvas_proxy.js  |  17 +++---
 fonts.js         |  10 ++--
 pdf_worker.js    |  12 +++--
 worker_client.js | 136 +++++++++++++++++------------------------------
 4 files changed, 72 insertions(+), 103 deletions(-)

diff --git a/canvas_proxy.js b/canvas_proxy.js
index 0b7681bfe..e2795bd00 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -11,10 +11,9 @@ var JpegStreamProxy = (function() {
     this.dict = dict;
 
     // Tell the main thread to create an image.
-    postMessage("jpeg_stream");
     postMessage({
-      id:  this.id,
-      str: bytesToString(bytes)
+      action: jpeg_stream,
+      data:   bytesToString(bytes)
     });
   }
 
@@ -235,12 +234,14 @@ function CanvasProxy(width, height) {
 * resets the cmdQueue.
 */
 CanvasProxy.prototype.flush = function() {
-  postMessage("canvas_proxy_cmd_queue");
   postMessage({
-    id:         this.id,
-    cmdQueue:   this.cmdQueue,
-    width:      this.width,
-    height:     this.height
+    action: "canvas_proxy_cmd_queue",
+    data: {
+      id:         this.id,
+      cmdQueue:   this.cmdQueue,
+      width:      this.width,
+      height:     this.height
+    }
   });
   this.cmdQueue.length = 0;
 }
diff --git a/fonts.js b/fonts.js
index a3604c6b9..7f4958caf 100644
--- a/fonts.js
+++ b/fonts.js
@@ -768,12 +768,14 @@ var Font = (function () {
       // Insert the font-face css on the page. In a web worker, this needs to
       // be forwareded on the main thread.
       if (typeof window == "undefined") {
-          postMessage("font");
-          postMessage(JSON.stringify({
-              str: str,
+          postMessage({
+            action: "font",
+            data: {
+              raw: str,
               fontName: fontName,
               mimetype: this.mimetype
-          }));
+            }
+          });
       } else {
           var base64 = window.btoa(str);
 
diff --git a/pdf_worker.js b/pdf_worker.js
index 91245aedb..13a1f3f28 100644
--- a/pdf_worker.js
+++ b/pdf_worker.js
@@ -15,8 +15,10 @@ function toc(msg) {
 
 function log() {
   var args = Array.prototype.slice.call(arguments);
-  postMessage("log");
-  postMessage(JSON.stringify(args))
+  postMessage({
+    action: "log",
+    args: args
+  });
 }
 
 var console = {
@@ -42,8 +44,10 @@ onmessage = function(event) {
   // If there is no pdfDocument yet, then the sent data is the PDFDocument.
   if (!pdfDocument) {
     pdfDocument = new PDFDoc(new Stream(data));
-    postMessage("pdf_num_page");
-    postMessage(pdfDocument.numPages)
+    postMessage({
+      action: "pdf_num_pages",
+      data: pdfDocument.numPages
+    });
     return;
   }
   // User requested to render a certain page.
diff --git a/worker_client.js b/worker_client.js
index f69f4f682..4af0d9764 100644
--- a/worker_client.js
+++ b/worker_client.js
@@ -15,7 +15,7 @@ function WorkerPDFDoc(canvas) {
 
   this.ctx = canvas.getContext("2d");
   this.canvas = canvas;
-  this.worker = new Worker('worker.js');
+  this.worker = new Worker('pdf_worker.js');
 
   this.numPage = 1;
   this.numPages = null;
@@ -147,90 +147,44 @@ function WorkerPDFDoc(canvas) {
   }
 
   /**
-  * onMessage state machine.
+  * Functions to handle data sent by the WebWorker.
   */
-  const WAIT = 0;
-  const CANVAS_PROXY_CMD_QUEUE = 1;
-  const LOG = 2;
-  const FONT = 3;
-  const PDF_NUM_PAGE = 4;
-  const JPEG_STREAM = 5;
-
-  var onMessageState = WAIT;
-  this.worker.onmessage = function(event) {
-    var data = event.data;
-    // console.log("onMessageRaw", data);
-    switch (onMessageState) {
-      case WAIT:
-        if (typeof data != "string") {
-          throw "expecting to get an string";
-        }
-        switch (data) {
-          case "pdf_num_page":
-            onMessageState = PDF_NUM_PAGE;
-            return;
-
-          case "log":
-            onMessageState = LOG;
-            return;
-
-          case "canvas_proxy_cmd_queue":
-            onMessageState = CANVAS_PROXY_CMD_QUEUE;
-            return;
-
-          case "font":
-            onMessageState = FONT;
-            return;
-
-          case "jpeg_stream":
-            onMessageState = JPEG_STREAM;
-            return;
+  var actionHandler = {
+    "log": function(data) {
+      console.log.apply(console, data);
+    },
+    
+    "pdf_num_pages": function(data) {
+      this.numPages = parseInt(data);
+      if (this.loadCallback) {
+        this.loadCallback();
+      }
+    },
+    
+    "font": function(data) {
+      var base64 = window.btoa(data.raw);
+
+      // Add the @font-face rule to the document
+      var url = "url(data:" + data.mimetype + ";base64," + base64 + ");";
+      var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}";
+      var styleSheet = document.styleSheets[0];
+      styleSheet.insertRule(rule, styleSheet.length);
+
+      // Just adding the font-face to the DOM doesn't make it load. It
+      // seems it's loaded once Gecko notices it's used. Therefore,
+      // add a div on the page using the loaded font.
+      var div = document.createElement("div");
+      document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>";
+    },
 
-          default:
-            throw "unkown state: " + data
-        }
-      break;
-
-      case JPEG_STREAM:
-        var img = new Image();
-        img.src = "data:image/jpeg;base64," + window.btoa(data.str);
-        imagesList[data.id] = img;
-        console.log("got image", data.id)
-      break;
-
-      case PDF_NUM_PAGE:
-        this.numPages = parseInt(data);
-        if (this.loadCallback) {
-          this.loadCallback();
-        }
-        onMessageState = WAIT;
-      break;
-
-      case FONT:
-        data = JSON.parse(data);
-        var base64 = window.btoa(data.str);
-
-        // Add the @font-face rule to the document
-        var url = "url(data:" + data.mimetype + ";base64," + base64 + ");";
-        var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}";
-        var styleSheet = document.styleSheets[0];
-        styleSheet.insertRule(rule, styleSheet.length);
-
-        // Just adding the font-face to the DOM doesn't make it load. It
-        // seems it's loaded once Gecko notices it's used. Therefore,
-        // add a div on the page using the loaded font.
-        var div = document.createElement("div");
-        document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>";
-
-        onMessageState = WAIT;
-      break;
-
-      case LOG:
-        console.log.apply(console, JSON.parse(data));
-        onMessageState = WAIT;
-      break;
-
-      case CANVAS_PROXY_CMD_QUEUE:
+    "jpeg_stream": function(data) {
+      var img = new Image();
+      img.src = "data:image/jpeg;base64," + window.btoa(data);
+      imagesList[data.id] = img;
+      console.log("got image", data.id)
+    },
+    
+    "canvas_proxy_cmd_queue": function(data) {
         var id = data.id;
         var cmdQueue = data.cmdQueue;
 
@@ -250,13 +204,21 @@ function WorkerPDFDoc(canvas) {
           renderProxyCanvas(canvasList[id], cmdQueue);
           if (id == 0) toc("canvas rendering")
         }, 0);
-        onMessageState = WAIT;
-      break;
     }
-  }.bind(this);
+  }
+
+  // List to the WebWorker for data and call actionHandler on it.
+  this.worker.onmessage = function(event) {
+    var data = event.data;
+    if (data.action in actionHandler) {
+      actionHandler[data.action].call(this, data.data);
+    } else {
+      throw "Unkown action from worker: " + data.action;
+    }
+  }
 }
 
-  WorkerPDFDoc.prototype.open = function(url, callback) {
+WorkerPDFDoc.prototype.open = function(url, callback) {
   var req = new XMLHttpRequest();
   req.open("GET", url);
   req.mozResponseType = req.responseType = "arraybuffer";

From f32dfa9414eef8bdaa5b5e65f0d1b1fcb02de3ce Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Thu, 23 Jun 2011 14:36:45 +0200
Subject: [PATCH 29/45] Fix sending image data to main thread

---
 canvas_proxy.js  | 7 +++++--
 viewer.js        | 2 +-
 worker_client.js | 5 ++---
 3 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/canvas_proxy.js b/canvas_proxy.js
index e2795bd00..d6f5a0a25 100644
--- a/canvas_proxy.js
+++ b/canvas_proxy.js
@@ -12,8 +12,11 @@ var JpegStreamProxy = (function() {
 
     // Tell the main thread to create an image.
     postMessage({
-      action: jpeg_stream,
-      data:   bytesToString(bytes)
+      action: "jpeg_stream",
+      data: {
+        id: this.id,
+        raw: bytesToString(bytes)
+      }
     });
   }
 
diff --git a/viewer.js b/viewer.js
index 41aaf354c..d0aeb0b2d 100644
--- a/viewer.js
+++ b/viewer.js
@@ -10,7 +10,7 @@ function load(userInput) {
     pageNum = parseInt(queryParams().page) || 1;
     var fileName = userInput;
     if (!userInput) {
-      fileName = queryParams().file || "compressed.tracemonkey-pldi-09.pdf";
+      fileName = "canvas.pdf";
     }
     open(fileName);
 }
diff --git a/worker_client.js b/worker_client.js
index 4af0d9764..385103c30 100644
--- a/worker_client.js
+++ b/worker_client.js
@@ -71,7 +71,7 @@ function WorkerPDFDoc(canvas) {
     "$drawImage": function(id, x, y, sx, sy, swidth, sheight) {
       var image = imagesList[id];
       if (!image) {
-        throw "Image not found";
+        throw "Image not found: " + id;
       }
       this.drawImage(image, x, y, image.width, image.height,
         sx, sy, swidth, sheight);
@@ -179,9 +179,8 @@ function WorkerPDFDoc(canvas) {
 
     "jpeg_stream": function(data) {
       var img = new Image();
-      img.src = "data:image/jpeg;base64," + window.btoa(data);
+      img.src = "data:image/jpeg;base64," + window.btoa(data.raw);
       imagesList[data.id] = img;
-      console.log("got image", data.id)
     },
     
     "canvas_proxy_cmd_queue": function(data) {

From f256716022c86a216ec6bc15e6c12644d43d9e80 Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Thu, 23 Jun 2011 15:24:55 +0200
Subject: [PATCH 30/45] Fix WebWorker logging and add separate timing for
 `fonts`.

---
 pdf_worker.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/pdf_worker.js b/pdf_worker.js
index 13a1f3f28..86dfec2dd 100644
--- a/pdf_worker.js
+++ b/pdf_worker.js
@@ -17,7 +17,7 @@ function log() {
   var args = Array.prototype.slice.call(arguments);
   postMessage({
     action: "log",
-    args: args
+    data: args
   });
 }
 
@@ -62,7 +62,9 @@ onmessage = function(event) {
     var fonts = [];
     var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy);
     page.compile(gfx, fonts);
+    toc("compiled page");
 
+    tic()
     // Inspect fonts and translate the missing one.
     var count = fonts.length;
     for (var i = 0; i < count; i++) {
@@ -75,7 +77,7 @@ onmessage = function(event) {
       // This "builds" the font and sents it over to the main thread.
       new Font(font.name, font.file, font.properties);
     }
-    toc("compiled page");
+    toc("fonts");
 
     tic()
     page.display(gfx);

From 4b27045d2e45e9aa370a0ed2299cd83fef7f78aa Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Thu, 23 Jun 2011 19:43:01 +0200
Subject: [PATCH 31/45] Ensure divs used to make fonts load are not visible

---
 fonts.js           | 6 +++---
 viewer.js          | 2 +-
 viewer_worker.html | 2 --
 worker_client.js   | 5 ++++-
 4 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/fonts.js b/fonts.js
index 7f4958caf..ba40ef8e1 100644
--- a/fonts.js
+++ b/fonts.js
@@ -786,9 +786,9 @@ var Font = (function () {
           styleSheet.insertRule(rule, styleSheet.length);
 
           var div = document.createElement("div");
-          div.innerHTML += "<div style='font-family:" +
-            fontName +
-            ";'>j</div>";
+          var style = 'font-family:"' + fontName + 
+            '";position: absolute;top:-99999;left:-99999;z-index:-99999';
+          div.setAttribute("style", style);
           document.body.appendChild(div);
 
           Fonts[fontName].loading = true;
diff --git a/viewer.js b/viewer.js
index d0aeb0b2d..41aaf354c 100644
--- a/viewer.js
+++ b/viewer.js
@@ -10,7 +10,7 @@ function load(userInput) {
     pageNum = parseInt(queryParams().page) || 1;
     var fileName = userInput;
     if (!userInput) {
-      fileName = "canvas.pdf";
+      fileName = queryParams().file || "compressed.tracemonkey-pldi-09.pdf";
     }
     open(fileName);
 }
diff --git a/viewer_worker.html b/viewer_worker.html
index a5ffc6a6e..d13935f13 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -22,9 +22,7 @@ window.onload = function() {
 </script>
         <link rel="stylesheet" href="viewer.css"></link>
   </head>
-
   <body>
-    <div id="fonts" style="position: absolute;display:block;z-index:-1;"></div>
     <div id="controls">
     <input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input>
     <!-- This only opens supported PDFs from the source path...
diff --git a/worker_client.js b/worker_client.js
index 385103c30..62a7c1377 100644
--- a/worker_client.js
+++ b/worker_client.js
@@ -174,7 +174,10 @@ function WorkerPDFDoc(canvas) {
       // seems it's loaded once Gecko notices it's used. Therefore,
       // add a div on the page using the loaded font.
       var div = document.createElement("div");
-      document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>";
+      var style = 'font-family:"' + data.fontName + 
+        '";position: absolute;top:-99999;left:-99999;z-index:-99999';
+      div.setAttribute("style", style);
+      document.body.appendChild(div);
     },
 
     "jpeg_stream": function(data) {

From d6239571e945c9008ea1156ad755651773f4118a Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Thu, 23 Jun 2011 22:24:41 +0200
Subject: [PATCH 32/45] Clear the main canvas right before the next rendering
 begins. Keeps the canvas from beeing blank for a few ms

---
 worker_client.js | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/worker_client.js b/worker_client.js
index 62a7c1377..c1e124693 100644
--- a/worker_client.js
+++ b/worker_client.js
@@ -202,10 +202,17 @@ function WorkerPDFDoc(canvas) {
         // There might be fonts that need to get loaded. Shedule the
         // rendering at the end of the event queue ensures this.
         setTimeout(function() {
-          if (id == 0) tic();
+          if (id == 0) {
+            tic();
+            var ctx = this.ctx;
+            ctx.save();
+            ctx.fillStyle = "rgb(255, 255, 255)";
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+            ctx.restore();
+          }
           renderProxyCanvas(canvasList[id], cmdQueue);
           if (id == 0) toc("canvas rendering")
-        }, 0);
+        }, 0, this);
     }
   }
 
@@ -239,12 +246,6 @@ WorkerPDFDoc.prototype.open = function(url, callback) {
 }
 
 WorkerPDFDoc.prototype.showPage = function(numPage) {
-  var ctx = this.ctx;
-  ctx.save();
-  ctx.fillStyle = "rgb(255, 255, 255)";
-  ctx.fillRect(0, 0, canvas.width, canvas.height);
-  ctx.restore();
-
   this.numPage = parseInt(numPage);
   this.worker.postMessage(numPage);
   if (this.onChangePage) {

From 3bef1534b4fe8d659c35b890fcf07bcb053d049f Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Thu, 23 Jun 2011 22:55:26 +0200
Subject: [PATCH 33/45] Reverts parts of 60f4d16360: Use old font-is-loaded
 mechanism + some code refactoring to add bindDOM and bindWorker.

---
 fonts.js | 149 +++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 106 insertions(+), 43 deletions(-)

diff --git a/fonts.js b/fonts.js
index ba40ef8e1..f00f5c75f 100644
--- a/fonts.js
+++ b/fonts.js
@@ -135,15 +135,28 @@ var Font = (function () {
         break;
     }
 
+    var data = this.font;
     Fonts[name] = {
-      data: this.font,
+      data: data,
       properties: properties,
       loading: true,
       cache: Object.create(null)
     }
 
-    // Attach the font to the document
-    this.bind();
+    // Convert data to a string.
+    var dataStr = "";
+    var length = data.length;
+    for (var i = 0; i < length; ++i)
+      dataStr += String.fromCharCode(data[i]);
+
+    // Attach the font to the document. If this script is runnig in a worker,
+    // call `bindWorker`, which sends stuff over to the main thread.
+    if (typeof window != "undefined") {
+      this.bindDOM(dataStr);
+    } else {
+      this.bindWorker(dataStr);
+    }
+
   };
 
   function stringToArray(str) {
@@ -755,49 +768,99 @@ var Font = (function () {
       return fontData;
     },
 
-    bind: function font_bind() {
-      var data = this.font;
+    bindWorker: function font_bind_worker(dataStr) {
+      postMessage({
+        action: "font",
+        data: {
+          raw:      dataStr,
+          fontName: this.name,
+          mimetype: this.mimetype
+        }
+      });
+    },
+
+    bindDOM: function font_bind_dom(dataStr) {
       var fontName = this.name;
 
-      // Get the base64 encoding of the binary font data
-      var str = "";
-      var length = data.length;
-      for (var i = 0; i < length; ++i)
-        str += String.fromCharCode(data[i]);
-
-      // Insert the font-face css on the page. In a web worker, this needs to
-      // be forwareded on the main thread.
-      if (typeof window == "undefined") {
-          postMessage({
-            action: "font",
-            data: {
-              raw: str,
-              fontName: fontName,
-              mimetype: this.mimetype
-            }
-          });
-      } else {
-          var base64 = window.btoa(str);
-
-          // Add the @font-face rule to the document
-          var url = "url(data:" + this.mimetype + ";base64," + base64 + ");";
-          var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
-          var styleSheet = document.styleSheets[0];
-          styleSheet.insertRule(rule, styleSheet.length);
-
-          var div = document.createElement("div");
-          var style = 'font-family:"' + fontName + 
-            '";position: absolute;top:-99999;left:-99999;z-index:-99999';
-          div.setAttribute("style", style);
-          document.body.appendChild(div);
-
-          Fonts[fontName].loading = true;
-          window.setTimeout(function() {
-            Fonts[fontName].loading = false;
-          // Timeout of just `0`, `10` doesn't work here, but for me all values
-          // above work. Setting value to 50ms.
-          }, 50);
+      /** Hack begin */
+      // Actually there is not event when a font has finished downloading so
+      // the following code are a dirty hack to 'guess' when a font is ready
+      var canvas = document.createElement("canvas");
+      var style = "border: 1px solid black; position:absolute; top: " +
+                   (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px";
+      canvas.setAttribute("style", style);
+      canvas.setAttribute("width", 340);
+      canvas.setAttribute("heigth", 100);
+      document.body.appendChild(canvas);
+
+      // Get the font size canvas think it will be for 'spaces'
+      var ctx = canvas.getContext("2d");
+      ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
+      var testString = " ";
+
+      // When debugging use the characters provided by the charsets to visually
+      // see what's happening instead of 'spaces'
+      var debug = false;
+      if (debug) {
+        var name = document.createElement("font");
+        name.setAttribute("style", "position: absolute; left: 20px; top: " +
+                          (100 * fontCount + 60) + "px");
+        name.innerHTML = fontName;
+        document.body.appendChild(name);
+
+        // Retrieve font charset
+        var charset = Fonts[fontName].properties.charset || [];
+
+        // if the charset is too small make it repeat a few times
+        var count = 30;
+        while (count-- && charset.length <= 30)
+          charset = charset.concat(charset.slice());
+
+        for (var i = 0; i < charset.length; i++) {
+          var unicode = GlyphsUnicode[charset[i]];
+          if (!unicode)
+            continue;
+          testString += String.fromCharCode(unicode);
+        }
+
+        ctx.fillText(testString, 20, 20);
       }
+
+      // Periodicaly check for the width of the testString, it will be
+      // different once the real font has loaded
+      var textWidth = ctx.measureText(testString).width;
+
+      var interval = window.setInterval(function canvasInterval(self) {
+        this.start = this.start || Date.now();
+        ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
+
+        // For some reasons the font has not loaded, so mark it loaded for the
+        // page to proceed but cry
+        if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
+          window.clearInterval(interval);
+          Fonts[fontName].loading = false;
+          warn("Is " + fontName + " for charset: " + charset + " loaded?");
+          this.start = 0;
+        } else if (textWidth != ctx.measureText(testString).width) {
+          window.clearInterval(interval);
+          Fonts[fontName].loading = false;
+          this.start = 0;
+        }
+
+        if (debug)
+          ctx.fillText(testString, 20, 50);
+      }, 30, this);
+
+      /** Hack end */
+      
+      // Convert the data string and add it to the page.
+      var base64 = window.btoa(dataStr);
+
+      // Add the @font-face rule to the document
+      var url = "url(data:" + this.mimetype + ";base64," + base64 + ");";
+      var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
+      var styleSheet = document.styleSheets[0];
+      styleSheet.insertRule(rule, styleSheet.length);
     }
   };
 

From d1b75dd6331461b0def4e5fdecb95b111561870b Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Thu, 23 Jun 2011 23:06:33 +0200
Subject: [PATCH 34/45] Replace tic & toc by console.time/timeEnd

---
 pdf_worker.js    | 51 ++++++++++++++++++++++++------------------------
 worker_client.js | 26 +++++++++++++++---------
 2 files changed, 43 insertions(+), 34 deletions(-)

diff --git a/pdf_worker.js b/pdf_worker.js
index 86dfec2dd..fa29428c7 100644
--- a/pdf_worker.js
+++ b/pdf_worker.js
@@ -3,26 +3,27 @@
 
 "use strict";
 
-var timer = null;
-function tic() {
-  timer = Date.now();
-}
-
-function toc(msg) {
-  log(msg + ": " + (Date.now() - timer) + "ms");
-  timer = null;
-}
-
-function log() {
-  var args = Array.prototype.slice.call(arguments);
-  postMessage({
-    action: "log",
-    data: args
-  });
-}
-
+var consoleTimer = {};
 var console = {
-  log: log
+  log: function log() {
+    var args = Array.prototype.slice.call(arguments);
+    postMessage({
+      action: "log",
+      data: args
+    });
+  },
+  
+  time: function(name) {
+    consoleTimer[name] = Date.now();
+  },
+  
+  timeEnd: function(name) {
+    var time = consoleTimer[name];
+    if (time == null) {
+      throw "Unkown timer name " + name;
+    }
+    this.log("Timer:", name, Date.now() - time);
+  }
 }
 
 //
@@ -52,7 +53,7 @@ onmessage = function(event) {
   }
   // User requested to render a certain page.
   else {
-    tic();
+    console.time("compile");
 
     // Let's try to render the first page...
     var page = pdfDocument.getPage(parseInt(data));
@@ -62,9 +63,9 @@ onmessage = function(event) {
     var fonts = [];
     var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy);
     page.compile(gfx, fonts);
-    toc("compiled page");
+    console.timeEnd("compile");
 
-    tic()
+    console.time("fonts");
     // Inspect fonts and translate the missing one.
     var count = fonts.length;
     for (var i = 0; i < count; i++) {
@@ -77,11 +78,11 @@ onmessage = function(event) {
       // This "builds" the font and sents it over to the main thread.
       new Font(font.name, font.file, font.properties);
     }
-    toc("fonts");
+    console.timeEnd("fonts");
 
-    tic()
+    console.time("display");
     page.display(gfx);
     canvas.flush();
-    toc("displayed page");
+    console.timeEnd("display");
   }
 }
diff --git a/worker_client.js b/worker_client.js
index c1e124693..b39374af1 100644
--- a/worker_client.js
+++ b/worker_client.js
@@ -3,15 +3,23 @@
 
 "use strict";
 
+if (typeof console.time == "undefined") {
+  var consoleTimer = {};
+  console.time = function(name) {
+    consoleTimer[name] = Date.now();
+  };
+  
+  console.timeEnd = function(name) {
+    var time = consoleTimer[name];
+    if (time == null) {
+      throw "Unkown timer name " + name;
+    }
+    this.log("Timer:", name, Date.now() - time);
+  };
+}
+
 function WorkerPDFDoc(canvas) {
   var timer = null
-  function tic() {
-    timer = Date.now();
-  }
-
-  function toc(msg) {
-    console.log(msg + ": " + (Date.now() - timer) + "ms");
-  }
 
   this.ctx = canvas.getContext("2d");
   this.canvas = canvas;
@@ -203,7 +211,7 @@ function WorkerPDFDoc(canvas) {
         // rendering at the end of the event queue ensures this.
         setTimeout(function() {
           if (id == 0) {
-            tic();
+            console.time("canvas rendering");
             var ctx = this.ctx;
             ctx.save();
             ctx.fillStyle = "rgb(255, 255, 255)";
@@ -211,7 +219,7 @@ function WorkerPDFDoc(canvas) {
             ctx.restore();
           }
           renderProxyCanvas(canvasList[id], cmdQueue);
-          if (id == 0) toc("canvas rendering")
+          if (id == 0) console.timeEnd("canvas rendering")
         }, 0, this);
     }
   }

From 8cd986f15aa4a676554bb78b8f5fc2e886af5046 Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Thu, 23 Jun 2011 23:55:14 +0200
Subject: [PATCH 35/45] Small rebase tidyup + cleanup

---
 pdf.js             | 24 +++++++++++-------------
 viewer_worker.html |  2 +-
 worker_client.js   |  4 ++--
 3 files changed, 14 insertions(+), 16 deletions(-)

diff --git a/pdf.js b/pdf.js
index 847067946..da5926c09 100644
--- a/pdf.js
+++ b/pdf.js
@@ -2287,6 +2287,14 @@ var CanvasGraphics = (function() {
         this.ScratchCanvas = imageCanvas || ScratchCanvas;
     }
 
+    var LINE_CAP_STYLES = [ "butt", "round", "square" ];
+    var LINE_JOIN_STYLES = [ "miter", "round", "bevel" ];
+    var NORMAL_CLIP = {};
+    var EO_CLIP = {};
+
+    // Used for tiling patterns
+    var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
+
     constructor.prototype = {
         map: {
             // Graphics state
@@ -2381,18 +2389,8 @@ var CanvasGraphics = (function() {
             // Compatibility
             BX: "beginCompat",
             EX: "endCompat",
-        };
-    }
-
-    var LINE_CAP_STYLES = [ "butt", "round", "square" ];
-    var LINE_JOIN_STYLES = [ "miter", "round", "bevel" ];
-    var NORMAL_CLIP = {};
-    var EO_CLIP = {};
-
-    // Used for tiling patterns
-    var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
-
-    constructor.prototype = {
+        },
+      
         translateFont: function(fontDict, xref, resources) {
             var fd = fontDict.get("FontDescriptor");
             if (!fd)
@@ -2855,7 +2853,7 @@ var CanvasGraphics = (function() {
             this.ctx.scale(1, -1);
 
             if (this.ctx.$showText) {
-                this.ctx.$showText(this.current.y, text, Fonts.charsToUnicode(text));
+                this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text));
             } else {
                 text = Fonts.charsToUnicode(text);
                 this.ctx.translate(this.current.x, -1 * this.current.y);
diff --git a/viewer_worker.html b/viewer_worker.html
index d13935f13..51f2b9d8a 100644
--- a/viewer_worker.html
+++ b/viewer_worker.html
@@ -1,6 +1,6 @@
 <html>
     <head>
-        <title>Simple pdf.js page viewer worker</title>
+        <title>Simple pdf.js page worker viewer</title>
         <script type="text/javascript" src="worker_client.js"></script>
 <script>
 
diff --git a/worker_client.js b/worker_client.js
index b39374af1..359a5f7c1 100644
--- a/worker_client.js
+++ b/worker_client.js
@@ -55,9 +55,9 @@ function WorkerPDFDoc(canvas) {
       currentX = currentXStack.pop();
     },
 
-    "$showText": function(y, text, uniText) {
+    "$showText": function(y, text) {
       this.translate(currentX, -1 * y);
-      this.fillText(uniText, 0, 0);
+      this.fillText(text, 0, 0);
       currentX += this.measureText(text).width;
     },
 

From f2126374334ec82e8d2473fc13b56d70ba6cd3f8 Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Thu, 23 Jun 2011 15:09:17 -0700
Subject: [PATCH 36/45] Remove invalid trailing commas.

---
 test/resources/browser_manifests/browser_manifest.json.mac | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/resources/browser_manifests/browser_manifest.json.mac b/test/resources/browser_manifests/browser_manifest.json.mac
index c287ab32a..7c9dda943 100644
--- a/test/resources/browser_manifests/browser_manifest.json.mac
+++ b/test/resources/browser_manifests/browser_manifest.json.mac
@@ -1,10 +1,10 @@
 [
    {
     "name":"firefox5",
-    "path":"/Applications/Firefox.app",
+    "path":"/Applications/Firefox.app"
    },
    {
     "name":"firefox6",
-    "path":"/Users/sayrer/firefoxen/Aurora.app",
+    "path":"/Users/sayrer/firefoxen/Aurora.app"
    }
 ]

From d97f9086bdfca9dc24f39dab79b1faa672c7cd1e Mon Sep 17 00:00:00 2001
From: Chris Jones <jones.chris.g@gmail.com>
Date: Thu, 23 Jun 2011 15:36:53 -0700
Subject: [PATCH 37/45] fix rendering of some symbols.  Patch by Vivien
 Nicolas.

---
 pdf.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pdf.js b/pdf.js
index da5926c09..ffefc61a1 100644
--- a/pdf.js
+++ b/pdf.js
@@ -2857,7 +2857,7 @@ var CanvasGraphics = (function() {
             } else {
                 text = Fonts.charsToUnicode(text);
                 this.ctx.translate(this.current.x, -1 * this.current.y);
-                this.ctx.fillText(Fonts.charsToUnicode(text), 0, 0);
+                this.ctx.fillText(text, 0, 0);
                 this.current.x += this.ctx.measureText(text).width;
             }
 

From ebb75998a27a0de67f00e98c8a4236129f7cdd23 Mon Sep 17 00:00:00 2001
From: Rob Sayre <sayrer@gmail.com>
Date: Thu, 23 Jun 2011 15:47:25 -0700
Subject: [PATCH 38/45] only supply a -foreground argument on Mac.

---
 test/test.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/test/test.py b/test/test.py
index d7a0fa674..53f65f78b 100644
--- a/test/test.py
+++ b/test/test.py
@@ -159,7 +159,10 @@ class BrowserCommand():
         shutil.rmtree(self.tempDir)
 
     def start(self, url):
-        cmds = [self.path, "-foreground", "-no-remote", "-profile", self.profileDir, url]
+        cmds = [self.path]
+        if platform.system() == "Darwin":
+            cmds.append("-foreground")
+        cmds.extend(["-no-remote", "-profile", self.profileDir, url])
         subprocess.call(cmds)
 
 def makeBrowserCommands(browserManifestFile):

From ae2e637044e5abe5d4c119de133e65e0aef4a9b5 Mon Sep 17 00:00:00 2001
From: Chris Jones <jones.chris.g@gmail.com>
Date: Thu, 23 Jun 2011 17:09:42 -0700
Subject: [PATCH 39/45] TODO for bpc!=8

---
 pdf.js | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/pdf.js b/pdf.js
index ffefc61a1..b2b6401fd 100644
--- a/pdf.js
+++ b/pdf.js
@@ -3275,8 +3275,11 @@ var CanvasGraphics = (function() {
                 }
             }
 
-            if (bitsPerComponent !== 8)
-                error("Unsupported bpc");
+            if (bitsPerComponent !== 8) {
+                TODO("Support bpc="+ bitsPerComponent);
+                this.restore();
+                return;
+            }
 
             var xref = this.xref;
             var colorSpaces = this.colorSpaces;

From 791947ff748bf819636bf58cc43ca0e2051ebc64 Mon Sep 17 00:00:00 2001
From: Justin D'Arcangelo <justindarc@gmail.com>
Date: Thu, 23 Jun 2011 20:50:01 -0400
Subject: [PATCH 40/45] Refactored the multi-page viewer to adhere to the
 coding style guidelines.

---
 multi-page-viewer.css  | 197 -----------------
 multi-page-viewer.html |  51 -----
 multi-page-viewer.js   | 466 -----------------------------------------
 multi_page_viewer.css  | 197 +++++++++++++++++
 multi_page_viewer.html |  51 +++++
 multi_page_viewer.js   | 458 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 706 insertions(+), 714 deletions(-)
 delete mode 100644 multi-page-viewer.css
 delete mode 100644 multi-page-viewer.html
 delete mode 100644 multi-page-viewer.js
 create mode 100644 multi_page_viewer.css
 create mode 100644 multi_page_viewer.html
 create mode 100644 multi_page_viewer.js

diff --git a/multi-page-viewer.css b/multi-page-viewer.css
deleted file mode 100644
index 7f4701022..000000000
--- a/multi-page-viewer.css
+++ /dev/null
@@ -1,197 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
-/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
-
-body {
-    background-color: #929292;
-    font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
-    margin: 0px;
-    padding: 0px;
-}
-
-canvas {
-    box-shadow: 0px 4px 10px #000;
-    -moz-box-shadow: 0px 4px 10px #000;
-    -webkit-box-shadow: 0px 4px 10px #000;
-}
-
-span {
-    font-size: 0.8em;
-}
-
-.control {
-    display: inline-block;
-    float: left;
-    margin: 0px 20px 0px 0px;
-    padding: 0px 4px 0px 0px;
-}
-
-.control > input {
-    float: left;
-    border: 1px solid #4d4d4d;
-    height: 20px;
-    padding: 0px;
-    margin: 0px 2px 0px 0px;
-    border-radius: 4px;
-    -moz-border-radius: 4px;
-    -webkit-border-radius: 4px;
-    box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-}
-
-.control > select {
-    float: left;
-    border: 1px solid #4d4d4d;
-    height: 22px;
-    padding: 2px 0px 0px;
-    margin: 0px 0px 1px;
-    border-radius: 4px;
-    -moz-border-radius: 4px;
-    -webkit-border-radius: 4px;
-    box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-}
-
-.control > span {
-    cursor: default;
-    float: left;
-    height: 18px;
-    margin: 5px 2px 0px;
-    padding: 0px;
-    user-select: none;
-    -moz-user-select: none;
-    -webkit-user-select: none;
-}
-
-.control .label {
-    clear: both;
-    float: left;
-    font-size: 0.65em;
-    margin: 2px 0px 0px;
-    position: relative;
-    text-align: center;
-    width: 100%;
-}
-
-.page {
-    width: 816px;
-    height: 1056px;
-    margin: 10px auto;
-}
-
-#controls {
-    background-color: #eee;
-    border-bottom: 1px solid #666;
-    padding: 4px 0px 0px 8px;
-    position: fixed;
-    left: 0px;
-    top: 0px;
-    height: 40px;
-    width: 100%;
-    box-shadow: 0px 2px 8px #000;
-    -moz-box-shadow: 0px 2px 8px #000;
-    -webkit-box-shadow: 0px 2px 8px #000;
-}
-
-#controls input {
-    user-select: text;
-    -moz-user-select: text;
-    -webkit-user-select: text;
-}
-
-#previousPageButton {
-    background: url('images/buttons.png') no-repeat 0px -23px;
-    cursor: default;
-    display: inline-block;
-    float: left;
-    margin: 0px;
-    width: 28px;
-    height: 23px;
-}
-
-#previousPageButton.down {
-    background: url('images/buttons.png') no-repeat 0px -46px;
-}
-
-#previousPageButton.disabled {
-    background: url('images/buttons.png') no-repeat 0px 0px;
-}
-
-#nextPageButton {
-    background: url('images/buttons.png') no-repeat -28px -23px;
-    cursor: default;
-    display: inline-block;
-    float: left;
-    margin: 0px;
-    width: 28px;
-    height: 23px;
-}
-
-#nextPageButton.down {
-    background: url('images/buttons.png') no-repeat -28px -46px;
-}
-
-#nextPageButton.disabled {
-    background: url('images/buttons.png') no-repeat -28px 0px;
-}
-
-#openFileButton {
-    background: url('images/buttons.png') no-repeat -56px -23px;
-    cursor: default;
-    display: inline-block;
-    float: left;
-    margin: 0px 0px 0px 3px;
-    width: 29px;
-    height: 23px;
-}
-
-#openFileButton.down {
-    background: url('images/buttons.png') no-repeat -56px -46px;
-}
-
-#openFileButton.disabled {
-    background: url('images/buttons.png') no-repeat -56px 0px;
-}
-
-#fileInput {
-    display: none;
-}
-
-#pageNumber {
-    text-align: right;
-}
-
-#sidebar {
-    background-color: rgba(0, 0, 0, 0.8);
-    position: fixed;
-    width: 150px;
-    top: 62px;
-    bottom: 18px;
-    border-top-right-radius: 8px;
-    border-bottom-right-radius: 8px;
-    -moz-border-radius-topright: 8px;
-    -moz-border-radius-bottomright: 8px;
-    -webkit-border-top-right-radius: 8px;
-    -webkit-border-bottom-right-radius: 8px;
-}
-
-#sidebarScrollView {
-    position: absolute;
-    overflow: hidden;
-    overflow-y: auto;
-    top: 40px;
-    right: 10px;
-    bottom: 10px;
-    left: 10px;
-}
-
-#sidebarContentView {
-    height: auto;
-    width: 100px;
-}
-
-#viewer {
-    margin: 44px 0px 0px;
-    padding: 8px 0px;
-}
diff --git a/multi-page-viewer.html b/multi-page-viewer.html
deleted file mode 100644
index ffbdfe707..000000000
--- a/multi-page-viewer.html
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>pdf.js Multi-Page Viewer</title>
-<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>
-<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen"/>
-<script type="text/javascript" src="pdf.js"></script>
-<script type="text/javascript" src="fonts.js"></script>
-<script type="text/javascript" src="glyphlist.js"></script>
-<script type="text/javascript" src="multi-page-viewer.js"></script>
-</head>
-<body>
-    <div id="controls">
-        <span class="control">
-            <span id="previousPageButton" class="disabled"></span>
-            <span id="nextPageButton" class="disabled"></span>
-            <span class="label">Previous/Next</span>
-        </span>
-        <span class="control">
-            <input type="text" id="pageNumber" value="1" size="2"/>
-            <span>/</span>
-            <span id="numPages">--</span>
-            <span class="label">Page Number</span>
-        </span>
-        <span class="control">
-            <select id="scaleSelect">
-                <option value="50">50%</option>
-                <option value="75">75%</option>
-                <option value="100" selected="selected">100%</option>
-                <option value="125">125%</option>
-                <option value="150">150%</option>
-                <option value="200">200%</option>
-            </select>
-            <span class="label">Zoom</span>
-        </span>
-        <span class="control">
-            <span id="openFileButton"></span>
-            <input type="file" id="fileInput"/>
-            <span class="label">Open File</span>
-        </span>
-    </div>
-    <!--<div id="sidebar">
-        <div id="sidebarScrollView">
-            <div id="sidebarContentView">
-                
-            </div>
-        </div>
-    </div>-->
-    <div id="viewer"></div>
-</body>
-</html>
diff --git a/multi-page-viewer.js b/multi-page-viewer.js
deleted file mode 100644
index baad7809e..000000000
--- a/multi-page-viewer.js
+++ /dev/null
@@ -1,466 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
-/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
-
-"use strict";
-
-var PDFViewer = {
-    queryParams: {},
-
-    element: null,
-
-    previousPageButton: null,
-    nextPageButton: null,
-    pageNumberInput: null,
-    scaleSelect: null,
-    fileInput: null,
-    
-    willJumpToPage: false,
-
-    pdf: null,
-
-    url: 'compressed.tracemonkey-pldi-09.pdf',
-    pageNumber: 1,
-    numberOfPages: 1,
-
-    scale: 1.0,
-
-    pageWidth: function() {
-        return 816 * PDFViewer.scale;
-    },
-
-    pageHeight: function() {
-        return 1056 * PDFViewer.scale;
-    },
-    
-    lastPagesDrawn: [],
-    
-    visiblePages: function() {  
-        var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.      
-        var windowTop = window.pageYOffset;
-        var windowBottom = window.pageYOffset + window.innerHeight;
-        var pageStartIndex = Math.floor(windowTop / pageHeight);
-        var pageStopIndex = Math.ceil(windowBottom / pageHeight);
-
-        var pages = [];  
-
-        for (var i = pageStartIndex; i <= pageStopIndex; i++) {
-            pages.push(i + 1);
-        }
-
-        return pages;
-    },
-  
-    createPage: function(num) {
-        var anchor = document.createElement('a');
-        anchor.name = '' + num;
-    
-        var div = document.createElement('div');
-        div.id = 'pageContainer' + num;
-        div.className = 'page';
-        div.style.width = PDFViewer.pageWidth() + 'px';
-        div.style.height = PDFViewer.pageHeight() + 'px';
-        
-        PDFViewer.element.appendChild(anchor);
-        PDFViewer.element.appendChild(div);
-    },
-    
-    removePage: function(num) {
-        var div = document.getElementById('pageContainer' + num);
-        
-        if (div) {
-            while (div.hasChildNodes()) {
-                div.removeChild(div.firstChild);
-            }
-        }
-    },
-
-    drawPage: function(num) {
-        if (!PDFViewer.pdf) {
-            return;
-        }
-        
-        var div = document.getElementById('pageContainer' + num);
-        var canvas = document.createElement('canvas');
-        
-        if (div && !div.hasChildNodes()) {
-            div.appendChild(canvas);
-            
-            var page = PDFViewer.pdf.getPage(num);
-
-            canvas.id = 'page' + num;
-            canvas.mozOpaque = true;
-
-            // Canvas dimensions must be specified in CSS pixels. CSS pixels
-            // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
-            canvas.width = PDFViewer.pageWidth();
-            canvas.height = PDFViewer.pageHeight();
-
-            var ctx = canvas.getContext('2d');
-            ctx.save();
-            ctx.fillStyle = 'rgb(255, 255, 255)';
-            ctx.fillRect(0, 0, canvas.width, canvas.height);
-            ctx.restore();
-
-            var gfx = new CanvasGraphics(ctx);
-            var fonts = [];
-        
-            // page.compile will collect all fonts for us, once we have loaded them
-            // we can trigger the actual page rendering with page.display
-            page.compile(gfx, fonts);
-        
-            var areFontsReady = true;
-        
-            // Inspect fonts and translate the missing one
-            var fontCount = fonts.length;
-        
-            for (var i = 0; i < fontCount; i++) {
-                var font = fonts[i];
-            
-                if (Fonts[font.name]) {
-                    areFontsReady = areFontsReady && !Fonts[font.name].loading;
-                    continue;
-                }
-
-                new Font(font.name, font.file, font.properties);
-            
-                areFontsReady = false;
-            }
-
-            var pageInterval;
-            
-            var delayLoadFont = function() {
-                for (var i = 0; i < fontCount; i++) {
-                    if (Fonts[font.name].loading) {
-                        return;
-                    }
-                }
-
-                clearInterval(pageInterval);
-                
-                while (div.hasChildNodes()) {
-                    div.removeChild(div.firstChild);
-                }
-                
-                PDFViewer.drawPage(num);
-            }
-
-            if (!areFontsReady) {
-                pageInterval = setInterval(delayLoadFont, 10);
-                return;
-            }
-
-            page.display(gfx);
-        }
-    },
-
-    changeScale: function(num) {
-        while (PDFViewer.element.hasChildNodes()) {
-            PDFViewer.element.removeChild(PDFViewer.element.firstChild);
-        }
-
-        PDFViewer.scale = num / 100;
-
-        var i;
-
-        if (PDFViewer.pdf) {
-            for (i = 1; i <= PDFViewer.numberOfPages; i++) {
-                PDFViewer.createPage(i);
-            }
-
-            if (PDFViewer.numberOfPages > 0) {
-                PDFViewer.drawPage(1);
-            }
-        }
-
-        for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
-            var option = PDFViewer.scaleSelect.childNodes[i];
-            
-            if (option.value == num) {
-                if (!option.selected) {
-                    option.selected = 'selected';
-                }
-            } else {
-                if (option.selected) {
-                    option.removeAttribute('selected');
-                }
-            }
-        }
-        
-        PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
-    },
-
-    goToPage: function(num) {
-        if (1 <= num && num <= PDFViewer.numberOfPages) {
-            PDFViewer.pageNumber = num;
-            PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
-            PDFViewer.willJumpToPage = true;
-
-            document.location.hash = PDFViewer.pageNumber;
-
-            PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
-                'disabled' : '';
-            PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
-                'disabled' : '';
-        }
-    },
-  
-    goToPreviousPage: function() {
-        if (PDFViewer.pageNumber > 1) {
-            PDFViewer.goToPage(--PDFViewer.pageNumber);
-        }
-    },
-  
-    goToNextPage: function() {
-        if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
-            PDFViewer.goToPage(++PDFViewer.pageNumber);
-        }
-    },
-  
-    openURL: function(url) {
-        PDFViewer.url = url;
-        document.title = url;
-    
-        var req = new XMLHttpRequest();
-        req.open('GET', url);
-        req.mozResponseType = req.responseType = 'arraybuffer';
-        req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
-    
-        req.onreadystatechange = function() {
-            if (req.readyState === 4 && req.status === req.expected) {
-                var data = req.mozResponseArrayBuffer ||
-                    req.mozResponse ||
-                    req.responseArrayBuffer ||
-                    req.response;
-
-                PDFViewer.readPDF(data);
-            }
-        };
-    
-        req.send(null);
-    },
-    
-    readPDF: function(data) {
-        while (PDFViewer.element.hasChildNodes()) {
-            PDFViewer.element.removeChild(PDFViewer.element.firstChild);
-        }
-        
-        PDFViewer.pdf = new PDFDoc(new Stream(data));
-        PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
-        document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
-  
-        for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
-            PDFViewer.createPage(i);
-        }
-
-        if (PDFViewer.numberOfPages > 0) {
-            PDFViewer.drawPage(1);
-            document.location.hash = 1;
-        }
-        
-        PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
-             'disabled' : '';
-         PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
-             'disabled' : '';
-    }
-};
-
-window.onload = function() {
-
-    // Parse the URL query parameters into a cached object.
-    PDFViewer.queryParams = function() {
-        var qs = window.location.search.substring(1);
-        var kvs = qs.split('&');
-        var params = {};
-        for (var i = 0; i < kvs.length; ++i) {
-            var kv = kvs[i].split('=');
-            params[unescape(kv[0])] = unescape(kv[1]);
-        }
-        
-        return params;
-    }();
-
-    PDFViewer.element = document.getElementById('viewer');
-
-    PDFViewer.pageNumberInput = document.getElementById('pageNumber');
-    PDFViewer.pageNumberInput.onkeydown = function(evt) {
-        var charCode = evt.charCode || evt.keyCode;
-
-        // Up arrow key.
-        if (charCode === 38) {
-            PDFViewer.goToNextPage();
-            this.select();
-        }
-        
-        // Down arrow key.
-        else if (charCode === 40) {
-            PDFViewer.goToPreviousPage();
-            this.select();
-        }
-
-        // All other non-numeric keys (excluding Left arrow, Right arrow,
-        // Backspace, and Delete keys).
-        else if ((charCode < 48 || charCode > 57) &&
-            charCode !== 8 &&   // Backspace
-            charCode !== 46 &&  // Delete
-            charCode !== 37 &&  // Left arrow
-            charCode !== 39     // Right arrow
-        ) {
-            return false;
-        }
-
-        return true;
-    };
-    PDFViewer.pageNumberInput.onkeyup = function(evt) {
-        var charCode = evt.charCode || evt.keyCode;
-
-        // All numeric keys, Backspace, and Delete.
-        if ((charCode >= 48 && charCode <= 57) ||
-            charCode === 8 ||   // Backspace
-            charCode === 46     // Delete
-        ) {
-            PDFViewer.goToPage(this.value);
-        }
-        
-        this.focus();
-    };
-
-    PDFViewer.previousPageButton = document.getElementById('previousPageButton');
-    PDFViewer.previousPageButton.onclick = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-            PDFViewer.goToPreviousPage();
-        }
-    };
-    PDFViewer.previousPageButton.onmousedown = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-             this.className = 'down';
-        }
-    };
-    PDFViewer.previousPageButton.onmouseup = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-    };
-    PDFViewer.previousPageButton.onmouseout = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-    };
-    
-    PDFViewer.nextPageButton = document.getElementById('nextPageButton');
-    PDFViewer.nextPageButton.onclick = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-            PDFViewer.goToNextPage();
-        }
-    };
-    PDFViewer.nextPageButton.onmousedown = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-            this.className = 'down';
-        }
-    };
-    PDFViewer.nextPageButton.onmouseup = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-    };
-    PDFViewer.nextPageButton.onmouseout = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-    };
-
-    PDFViewer.scaleSelect = document.getElementById('scaleSelect');
-    PDFViewer.scaleSelect.onchange = function(evt) {
-        PDFViewer.changeScale(parseInt(this.value));
-    };
-    
-    if (window.File && window.FileReader && window.FileList && window.Blob) {
-        var openFileButton = document.getElementById('openFileButton');
-        openFileButton.onclick = function(evt) {
-            if (this.className.indexOf('disabled') === -1) {
-                PDFViewer.fileInput.click();
-            }
-        };
-        openFileButton.onmousedown = function(evt) {
-            if (this.className.indexOf('disabled') === -1) {
-                this.className = 'down';
-            }
-        };
-        openFileButton.onmouseup = function(evt) {
-            this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-        };
-        openFileButton.onmouseout = function(evt) {
-            this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-        };
-        
-        PDFViewer.fileInput = document.getElementById('fileInput');
-        PDFViewer.fileInput.onchange = function(evt) {
-            var files = evt.target.files;
-            
-            if (files.length > 0) {
-                var file = files[0];
-                var fileReader = new FileReader();
-                
-                document.title = file.name;
-                
-                // Read the local file into a Uint8Array.
-                fileReader.onload = function(evt) {
-                    var data = evt.target.result;
-                    var buffer = new ArrayBuffer(data.length);
-                    var uint8Array = new Uint8Array(buffer);
-                    
-                    for (var i = 0; i < data.length; i++) {
-                        uint8Array[i] = data.charCodeAt(i);
-                    }
-                    
-                    PDFViewer.readPDF(uint8Array);
-                };
-                
-                // Read as a binary string since "readAsArrayBuffer" is not yet
-                // implemented in Firefox.
-                fileReader.readAsBinaryString(file);
-            }
-        };
-        PDFViewer.fileInput.value = null;
-    } else {
-        document.getElementById('fileWrapper').style.display = 'none';
-    }
-
-    PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
-    PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
-    
-    PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
-
-    window.onscroll = function(evt) {        
-        var lastPagesDrawn = PDFViewer.lastPagesDrawn;
-        var visiblePages = PDFViewer.visiblePages();
-        
-        var pagesToDraw = [];
-        var pagesToKeep = [];
-        var pagesToRemove = [];
-        
-        var i;
-
-        // Determine which visible pages were not previously drawn.
-        for (i = 0; i < visiblePages.length; i++) {
-            if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
-                pagesToDraw.push(visiblePages[i]);
-                PDFViewer.drawPage(visiblePages[i]);
-            } else {
-                pagesToKeep.push(visiblePages[i]);
-            }
-        }
-
-        // Determine which previously drawn pages are no longer visible.
-        for (i = 0; i < lastPagesDrawn.length; i++) {
-            if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
-                pagesToRemove.push(lastPagesDrawn[i]);
-                PDFViewer.removePage(lastPagesDrawn[i]);
-            }
-        }
-        
-        PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
-        
-        // Update the page number input with the current page number.
-        if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
-            PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
-            PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
-                'disabled' : '';
-            PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
-                'disabled' : '';
-        } else {
-            PDFViewer.willJumpToPage = false;
-        }
-    };
-};
diff --git a/multi_page_viewer.css b/multi_page_viewer.css
new file mode 100644
index 000000000..fce7d7b32
--- /dev/null
+++ b/multi_page_viewer.css
@@ -0,0 +1,197 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+
+body {
+  background-color: #929292;
+  font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
+  margin: 0px;
+  padding: 0px;
+}
+
+canvas {
+  box-shadow: 0px 4px 10px #000;
+  -moz-box-shadow: 0px 4px 10px #000;
+  -webkit-box-shadow: 0px 4px 10px #000;
+}
+
+span {
+  font-size: 0.8em;
+}
+
+.control {
+  display: inline-block;
+  float: left;
+  margin: 0px 20px 0px 0px;
+  padding: 0px 4px 0px 0px;
+}
+
+.control > input {
+  float: left;
+  border: 1px solid #4d4d4d;
+  height: 20px;
+  padding: 0px;
+  margin: 0px 2px 0px 0px;
+  border-radius: 4px;
+  -moz-border-radius: 4px;
+  -webkit-border-radius: 4px;
+  box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+  -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+  -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+}
+
+.control > select {
+  float: left;
+  border: 1px solid #4d4d4d;
+  height: 22px;
+  padding: 2px 0px 0px;
+  margin: 0px 0px 1px;
+  border-radius: 4px;
+  -moz-border-radius: 4px;
+  -webkit-border-radius: 4px;
+  box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+  -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+  -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+}
+
+.control > span {
+  cursor: default;
+  float: left;
+  height: 18px;
+  margin: 5px 2px 0px;
+  padding: 0px;
+  user-select: none;
+  -moz-user-select: none;
+  -webkit-user-select: none;
+}
+
+.control .label {
+  clear: both;
+  float: left;
+  font-size: 0.65em;
+  margin: 2px 0px 0px;
+  position: relative;
+  text-align: center;
+  width: 100%;
+}
+
+.page {
+  width: 816px;
+  height: 1056px;
+  margin: 10px auto;
+}
+
+#controls {
+  background-color: #eee;
+  border-bottom: 1px solid #666;
+  padding: 4px 0px 0px 8px;
+  position: fixed;
+  left: 0px;
+  top: 0px;
+  height: 40px;
+  width: 100%;
+  box-shadow: 0px 2px 8px #000;
+  -moz-box-shadow: 0px 2px 8px #000;
+  -webkit-box-shadow: 0px 2px 8px #000;
+}
+
+#controls input {
+  user-select: text;
+  -moz-user-select: text;
+  -webkit-user-select: text;
+}
+
+#previousPageButton {
+  background: url('images/buttons.png') no-repeat 0px -23px;
+  cursor: default;
+  display: inline-block;
+  float: left;
+  margin: 0px;
+  width: 28px;
+  height: 23px;
+}
+
+#previousPageButton.down {
+  background: url('images/buttons.png') no-repeat 0px -46px;
+}
+
+#previousPageButton.disabled {
+  background: url('images/buttons.png') no-repeat 0px 0px;
+}
+
+#nextPageButton {
+  background: url('images/buttons.png') no-repeat -28px -23px;
+  cursor: default;
+  display: inline-block;
+  float: left;
+  margin: 0px;
+  width: 28px;
+  height: 23px;
+}
+
+#nextPageButton.down {
+  background: url('images/buttons.png') no-repeat -28px -46px;
+}
+
+#nextPageButton.disabled {
+  background: url('images/buttons.png') no-repeat -28px 0px;
+}
+
+#openFileButton {
+  background: url('images/buttons.png') no-repeat -56px -23px;
+  cursor: default;
+  display: inline-block;
+  float: left;
+  margin: 0px 0px 0px 3px;
+  width: 29px;
+  height: 23px;
+}
+
+#openFileButton.down {
+  background: url('images/buttons.png') no-repeat -56px -46px;
+}
+
+#openFileButton.disabled {
+  background: url('images/buttons.png') no-repeat -56px 0px;
+}
+
+#fileInput {
+  display: none;
+}
+
+#pageNumber {
+  text-align: right;
+}
+
+#sidebar {
+  background-color: rgba(0, 0, 0, 0.8);
+  position: fixed;
+  width: 150px;
+  top: 62px;
+  bottom: 18px;
+  border-top-right-radius: 8px;
+  border-bottom-right-radius: 8px;
+  -moz-border-radius-topright: 8px;
+  -moz-border-radius-bottomright: 8px;
+  -webkit-border-top-right-radius: 8px;
+  -webkit-border-bottom-right-radius: 8px;
+}
+
+#sidebarScrollView {
+  position: absolute;
+  overflow: hidden;
+  overflow-y: auto;
+  top: 40px;
+  right: 10px;
+  bottom: 10px;
+  left: 10px;
+}
+
+#sidebarContentView {
+  height: auto;
+  width: 100px;
+}
+
+#viewer {
+  margin: 44px 0px 0px;
+  padding: 8px 0px;
+}
diff --git a/multi_page_viewer.html b/multi_page_viewer.html
new file mode 100644
index 000000000..47234686d
--- /dev/null
+++ b/multi_page_viewer.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>pdf.js Multi-Page Viewer</title>
+<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>
+<link rel="stylesheet" href="multi_page_viewer.css" type="text/css" media="screen"/>
+<script type="text/javascript" src="pdf.js"></script>
+<script type="text/javascript" src="fonts.js"></script>
+<script type="text/javascript" src="glyphlist.js"></script>
+<script type="text/javascript" src="multi_page_viewer.js"></script>
+</head>
+<body>
+  <div id="controls">
+    <span class="control">
+      <span id="previousPageButton" class="disabled"></span>
+      <span id="nextPageButton" class="disabled"></span>
+      <span class="label">Previous/Next</span>
+    </span>
+    <span class="control">
+      <input type="text" id="pageNumber" value="1" size="2"/>
+      <span>/</span>
+      <span id="numPages">--</span>
+      <span class="label">Page Number</span>
+    </span>
+    <span class="control">
+      <select id="scaleSelect">
+        <option value="50">50%</option>
+        <option value="75">75%</option>
+        <option value="100" selected="selected">100%</option>
+        <option value="125">125%</option>
+        <option value="150">150%</option>
+        <option value="200">200%</option>
+      </select>
+      <span class="label">Zoom</span>
+    </span>
+    <span class="control">
+      <span id="openFileButton"></span>
+      <input type="file" id="fileInput"/>
+      <span class="label">Open File</span>
+    </span>
+  </div>
+  <!--<div id="sidebar">
+    <div id="sidebarScrollView">
+      <div id="sidebarContentView">
+        
+      </div>
+    </div>
+  </div>-->
+  <div id="viewer"></div>
+</body>
+</html>
diff --git a/multi_page_viewer.js b/multi_page_viewer.js
new file mode 100644
index 000000000..ddb541175
--- /dev/null
+++ b/multi_page_viewer.js
@@ -0,0 +1,458 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+
+"use strict";
+
+var PDFViewer = {
+  queryParams: {},
+  
+  element: null,
+  
+  previousPageButton: null,
+  nextPageButton: null,
+  pageNumberInput: null,
+  scaleSelect: null,
+  fileInput: null,
+  
+  willJumpToPage: false,
+  
+  pdf: null,
+  
+  url: 'compressed.tracemonkey-pldi-09.pdf',
+  pageNumber: 1,
+  numberOfPages: 1,
+  
+  scale: 1.0,
+  
+  pageWidth: function() {
+    return 816 * PDFViewer.scale;
+  },
+  
+  pageHeight: function() {
+    return 1056 * PDFViewer.scale;
+  },
+  
+  lastPagesDrawn: [],
+  
+  visiblePages: function() {  
+    var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.      
+    var windowTop = window.pageYOffset;
+    var windowBottom = window.pageYOffset + window.innerHeight;
+    var pageStartIndex = Math.floor(windowTop / pageHeight);
+    var pageStopIndex = Math.ceil(windowBottom / pageHeight);
+    
+    var pages = [];  
+    
+    for (var i = pageStartIndex; i <= pageStopIndex; i++) {
+      pages.push(i + 1);
+    }
+    
+    return pages;
+  },
+  
+  createPage: function(num) {
+    var anchor = document.createElement('a');
+    anchor.name = '' + num;
+    
+    var div = document.createElement('div');
+    div.id = 'pageContainer' + num;
+    div.className = 'page';
+    div.style.width = PDFViewer.pageWidth() + 'px';
+    div.style.height = PDFViewer.pageHeight() + 'px';
+    
+    PDFViewer.element.appendChild(anchor);
+    PDFViewer.element.appendChild(div);
+  },
+  
+  removePage: function(num) {
+    var div = document.getElementById('pageContainer' + num);
+    
+    if (div) {
+      while (div.hasChildNodes()) {
+        div.removeChild(div.firstChild);
+      }
+    }
+  },
+  
+  drawPage: function(num) {
+    if (!PDFViewer.pdf) {
+      return;
+    }
+    
+    var div = document.getElementById('pageContainer' + num);
+    var canvas = document.createElement('canvas');
+    
+    if (div && !div.hasChildNodes()) {
+      div.appendChild(canvas);
+      
+      var page = PDFViewer.pdf.getPage(num);
+      
+      canvas.id = 'page' + num;
+      canvas.mozOpaque = true;
+      
+      // Canvas dimensions must be specified in CSS pixels. CSS pixels
+      // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
+      canvas.width = PDFViewer.pageWidth();
+      canvas.height = PDFViewer.pageHeight();
+      
+      var ctx = canvas.getContext('2d');
+      ctx.save();
+      ctx.fillStyle = 'rgb(255, 255, 255)';
+      ctx.fillRect(0, 0, canvas.width, canvas.height);
+      ctx.restore();
+      
+      var gfx = new CanvasGraphics(ctx);
+      var fonts = [];
+      
+      // page.compile will collect all fonts for us, once we have loaded them
+      // we can trigger the actual page rendering with page.display
+      page.compile(gfx, fonts);
+      
+      var areFontsReady = true;
+      
+      // Inspect fonts and translate the missing one
+      var fontCount = fonts.length;
+      
+      for (var i = 0; i < fontCount; i++) {
+        var font = fonts[i];
+        
+        if (Fonts[font.name]) {
+          areFontsReady = areFontsReady && !Fonts[font.name].loading;
+          continue;
+        }
+        
+        new Font(font.name, font.file, font.properties);
+        
+        areFontsReady = false;
+      }
+      
+      var pageInterval;
+      
+      var delayLoadFont = function() {
+        for (var i = 0; i < fontCount; i++) {
+          if (Fonts[font.name].loading) {
+            return;
+          }
+        }
+        
+        clearInterval(pageInterval);
+        
+        while (div.hasChildNodes()) {
+          div.removeChild(div.firstChild);
+        }
+        
+        PDFViewer.drawPage(num);
+      }
+      
+      if (!areFontsReady) {
+        pageInterval = setInterval(delayLoadFont, 10);
+        return;
+      }
+      
+      page.display(gfx);
+    }
+  },
+  
+  changeScale: function(num) {
+    while (PDFViewer.element.hasChildNodes()) {
+      PDFViewer.element.removeChild(PDFViewer.element.firstChild);
+    }
+    
+    PDFViewer.scale = num / 100;
+    
+    var i;
+    
+    if (PDFViewer.pdf) {
+      for (i = 1; i <= PDFViewer.numberOfPages; i++) {
+        PDFViewer.createPage(i);
+      }
+      
+      if (PDFViewer.numberOfPages > 0) {
+        PDFViewer.drawPage(1);
+      }
+    }
+    
+    for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
+      var option = PDFViewer.scaleSelect.childNodes[i];
+      
+      if (option.value == num) {
+        if (!option.selected) {
+          option.selected = 'selected';
+        }
+      } else {
+        if (option.selected) {
+          option.removeAttribute('selected');
+        }
+      }
+    }
+    
+    PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
+  },
+  
+  goToPage: function(num) {
+    if (1 <= num && num <= PDFViewer.numberOfPages) {
+      PDFViewer.pageNumber = num;
+      PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
+      PDFViewer.willJumpToPage = true;
+      
+      document.location.hash = PDFViewer.pageNumber;
+      
+      PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
+      PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
+    }
+  },
+  
+  goToPreviousPage: function() {
+    if (PDFViewer.pageNumber > 1) {
+      PDFViewer.goToPage(--PDFViewer.pageNumber);
+    }
+  },
+  
+  goToNextPage: function() {
+    if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
+      PDFViewer.goToPage(++PDFViewer.pageNumber);
+    }
+  },
+  
+  openURL: function(url) {
+    PDFViewer.url = url;
+    document.title = url;
+    
+    var req = new XMLHttpRequest();
+    req.open('GET', url);
+    req.mozResponseType = req.responseType = 'arraybuffer';
+    req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
+    
+    req.onreadystatechange = function() {
+      if (req.readyState === 4 && req.status === req.expected) {
+        var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response;
+        
+        PDFViewer.readPDF(data);
+      }
+    };
+    
+    req.send(null);
+  },
+  
+  readPDF: function(data) {
+    while (PDFViewer.element.hasChildNodes()) {
+      PDFViewer.element.removeChild(PDFViewer.element.firstChild);
+    }
+    
+    PDFViewer.pdf = new PDFDoc(new Stream(data));
+    PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
+    document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
+    
+    for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
+      PDFViewer.createPage(i);
+    }
+    
+    if (PDFViewer.numberOfPages > 0) {
+      PDFViewer.drawPage(1);
+      document.location.hash = 1;
+    }
+    
+    PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
+    PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
+  }
+};
+
+window.onload = function() {
+  
+  // Parse the URL query parameters into a cached object.
+  PDFViewer.queryParams = function() {
+    var qs = window.location.search.substring(1);
+    var kvs = qs.split('&');
+    var params = {};
+    
+    for (var i = 0; i < kvs.length; ++i) {
+      var kv = kvs[i].split('=');
+      params[unescape(kv[0])] = unescape(kv[1]);
+    }
+    
+    return params;
+  }();
+
+  PDFViewer.element = document.getElementById('viewer');
+  
+  PDFViewer.pageNumberInput = document.getElementById('pageNumber');
+  PDFViewer.pageNumberInput.onkeydown = function(evt) {
+    var charCode = evt.charCode || evt.keyCode;
+    
+    // Up arrow key.
+    if (charCode === 38) {
+      PDFViewer.goToNextPage();
+      this.select();
+    }
+    
+    // Down arrow key.
+    else if (charCode === 40) {
+      PDFViewer.goToPreviousPage();
+      this.select();
+    }
+    
+    // All other non-numeric keys (excluding Left arrow, Right arrow,
+    // Backspace, and Delete keys).
+    else if ((charCode < 48 || charCode > 57) &&
+      charCode !== 8 &&   // Backspace
+      charCode !== 46 &&  // Delete
+      charCode !== 37 &&  // Left arrow
+      charCode !== 39     // Right arrow
+    ) {
+      return false;
+    }
+    
+    return true;
+  };
+  PDFViewer.pageNumberInput.onkeyup = function(evt) {
+    var charCode = evt.charCode || evt.keyCode;
+    
+    // All numeric keys, Backspace, and Delete.
+    if ((charCode >= 48 && charCode <= 57) ||
+      charCode === 8 ||   // Backspace
+      charCode === 46     // Delete
+    ) {
+      PDFViewer.goToPage(this.value);
+    }
+    
+    this.focus();
+  };
+  
+  PDFViewer.previousPageButton = document.getElementById('previousPageButton');
+  PDFViewer.previousPageButton.onclick = function(evt) {
+    if (this.className.indexOf('disabled') === -1) {
+      PDFViewer.goToPreviousPage();
+    }
+  };
+  PDFViewer.previousPageButton.onmousedown = function(evt) {
+    if (this.className.indexOf('disabled') === -1) {
+      this.className = 'down';
+    }
+  };
+  PDFViewer.previousPageButton.onmouseup = function(evt) {
+    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+  };
+  PDFViewer.previousPageButton.onmouseout = function(evt) {
+    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+  };
+  
+  PDFViewer.nextPageButton = document.getElementById('nextPageButton');
+  PDFViewer.nextPageButton.onclick = function(evt) {
+    if (this.className.indexOf('disabled') === -1) {
+      PDFViewer.goToNextPage();
+    }
+  };
+  PDFViewer.nextPageButton.onmousedown = function(evt) {
+    if (this.className.indexOf('disabled') === -1) {
+      this.className = 'down';
+    }
+  };
+  PDFViewer.nextPageButton.onmouseup = function(evt) {
+    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+  };
+  PDFViewer.nextPageButton.onmouseout = function(evt) {
+    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+  };
+  
+  PDFViewer.scaleSelect = document.getElementById('scaleSelect');
+  PDFViewer.scaleSelect.onchange = function(evt) {
+    PDFViewer.changeScale(parseInt(this.value));
+  };
+  
+  if (window.File && window.FileReader && window.FileList && window.Blob) {
+    var openFileButton = document.getElementById('openFileButton');
+    openFileButton.onclick = function(evt) {
+      if (this.className.indexOf('disabled') === -1) {
+        PDFViewer.fileInput.click();
+      }
+    };
+    openFileButton.onmousedown = function(evt) {
+      if (this.className.indexOf('disabled') === -1) {
+        this.className = 'down';
+      }
+    };
+    openFileButton.onmouseup = function(evt) {
+      this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+    };
+    openFileButton.onmouseout = function(evt) {
+      this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+    };
+    
+    PDFViewer.fileInput = document.getElementById('fileInput');
+    PDFViewer.fileInput.onchange = function(evt) {
+      var files = evt.target.files;
+      
+      if (files.length > 0) {
+        var file = files[0];
+        var fileReader = new FileReader();
+        
+        document.title = file.name;
+        
+        // Read the local file into a Uint8Array.
+        fileReader.onload = function(evt) {
+          var data = evt.target.result;
+          var buffer = new ArrayBuffer(data.length);
+          var uint8Array = new Uint8Array(buffer);
+          
+          for (var i = 0; i < data.length; i++) {
+            uint8Array[i] = data.charCodeAt(i);
+          }
+          
+          PDFViewer.readPDF(uint8Array);
+        };
+        
+        // Read as a binary string since "readAsArrayBuffer" is not yet
+        // implemented in Firefox.
+        fileReader.readAsBinaryString(file);
+      }
+    };
+    PDFViewer.fileInput.value = null;
+  } else {
+    document.getElementById('fileWrapper').style.display = 'none';
+  }
+  
+  PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
+  PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
+  
+  PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
+  
+  window.onscroll = function(evt) {        
+    var lastPagesDrawn = PDFViewer.lastPagesDrawn;
+    var visiblePages = PDFViewer.visiblePages();
+    
+    var pagesToDraw = [];
+    var pagesToKeep = [];
+    var pagesToRemove = [];
+    
+    var i;
+    
+    // Determine which visible pages were not previously drawn.
+    for (i = 0; i < visiblePages.length; i++) {
+      if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
+        pagesToDraw.push(visiblePages[i]);
+        PDFViewer.drawPage(visiblePages[i]);
+      } else {
+        pagesToKeep.push(visiblePages[i]);
+      }
+    }
+    
+    // Determine which previously drawn pages are no longer visible.
+    for (i = 0; i < lastPagesDrawn.length; i++) {
+      if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
+        pagesToRemove.push(lastPagesDrawn[i]);
+        PDFViewer.removePage(lastPagesDrawn[i]);
+      }
+    }
+    
+    PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
+    
+    // Update the page number input with the current page number.
+    if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
+      PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
+      PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
+      PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
+    } else {
+      PDFViewer.willJumpToPage = false;
+    }
+  };
+};

From 8584daf7380bc9ef6f885a1de81dbcabd261bb5b Mon Sep 17 00:00:00 2001
From: Justin D'Arcangelo <justindarc@gmail.com>
Date: Thu, 23 Jun 2011 21:11:50 -0400
Subject: [PATCH 41/45] Revert ae2e637044e5abe5d4c119de133e65e0aef4a9b5^..HEAD

---
 multi-page-viewer.css  | 197 ++++++++++
 multi-page-viewer.html |  51 +++
 multi-page-viewer.js   | 466 +++++++++++++++++++++++
 multi_page_viewer.css  | 234 ++++++------
 multi_page_viewer.html |  76 ++--
 multi_page_viewer.js   | 842 +++++++++++++++++++++--------------------
 pdf.js                 |   7 +-
 7 files changed, 1296 insertions(+), 577 deletions(-)
 create mode 100644 multi-page-viewer.css
 create mode 100644 multi-page-viewer.html
 create mode 100644 multi-page-viewer.js

diff --git a/multi-page-viewer.css b/multi-page-viewer.css
new file mode 100644
index 000000000..7f4701022
--- /dev/null
+++ b/multi-page-viewer.css
@@ -0,0 +1,197 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+
+body {
+    background-color: #929292;
+    font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
+    margin: 0px;
+    padding: 0px;
+}
+
+canvas {
+    box-shadow: 0px 4px 10px #000;
+    -moz-box-shadow: 0px 4px 10px #000;
+    -webkit-box-shadow: 0px 4px 10px #000;
+}
+
+span {
+    font-size: 0.8em;
+}
+
+.control {
+    display: inline-block;
+    float: left;
+    margin: 0px 20px 0px 0px;
+    padding: 0px 4px 0px 0px;
+}
+
+.control > input {
+    float: left;
+    border: 1px solid #4d4d4d;
+    height: 20px;
+    padding: 0px;
+    margin: 0px 2px 0px 0px;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+    box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+    -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+    -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+}
+
+.control > select {
+    float: left;
+    border: 1px solid #4d4d4d;
+    height: 22px;
+    padding: 2px 0px 0px;
+    margin: 0px 0px 1px;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+    box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+    -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+    -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+}
+
+.control > span {
+    cursor: default;
+    float: left;
+    height: 18px;
+    margin: 5px 2px 0px;
+    padding: 0px;
+    user-select: none;
+    -moz-user-select: none;
+    -webkit-user-select: none;
+}
+
+.control .label {
+    clear: both;
+    float: left;
+    font-size: 0.65em;
+    margin: 2px 0px 0px;
+    position: relative;
+    text-align: center;
+    width: 100%;
+}
+
+.page {
+    width: 816px;
+    height: 1056px;
+    margin: 10px auto;
+}
+
+#controls {
+    background-color: #eee;
+    border-bottom: 1px solid #666;
+    padding: 4px 0px 0px 8px;
+    position: fixed;
+    left: 0px;
+    top: 0px;
+    height: 40px;
+    width: 100%;
+    box-shadow: 0px 2px 8px #000;
+    -moz-box-shadow: 0px 2px 8px #000;
+    -webkit-box-shadow: 0px 2px 8px #000;
+}
+
+#controls input {
+    user-select: text;
+    -moz-user-select: text;
+    -webkit-user-select: text;
+}
+
+#previousPageButton {
+    background: url('images/buttons.png') no-repeat 0px -23px;
+    cursor: default;
+    display: inline-block;
+    float: left;
+    margin: 0px;
+    width: 28px;
+    height: 23px;
+}
+
+#previousPageButton.down {
+    background: url('images/buttons.png') no-repeat 0px -46px;
+}
+
+#previousPageButton.disabled {
+    background: url('images/buttons.png') no-repeat 0px 0px;
+}
+
+#nextPageButton {
+    background: url('images/buttons.png') no-repeat -28px -23px;
+    cursor: default;
+    display: inline-block;
+    float: left;
+    margin: 0px;
+    width: 28px;
+    height: 23px;
+}
+
+#nextPageButton.down {
+    background: url('images/buttons.png') no-repeat -28px -46px;
+}
+
+#nextPageButton.disabled {
+    background: url('images/buttons.png') no-repeat -28px 0px;
+}
+
+#openFileButton {
+    background: url('images/buttons.png') no-repeat -56px -23px;
+    cursor: default;
+    display: inline-block;
+    float: left;
+    margin: 0px 0px 0px 3px;
+    width: 29px;
+    height: 23px;
+}
+
+#openFileButton.down {
+    background: url('images/buttons.png') no-repeat -56px -46px;
+}
+
+#openFileButton.disabled {
+    background: url('images/buttons.png') no-repeat -56px 0px;
+}
+
+#fileInput {
+    display: none;
+}
+
+#pageNumber {
+    text-align: right;
+}
+
+#sidebar {
+    background-color: rgba(0, 0, 0, 0.8);
+    position: fixed;
+    width: 150px;
+    top: 62px;
+    bottom: 18px;
+    border-top-right-radius: 8px;
+    border-bottom-right-radius: 8px;
+    -moz-border-radius-topright: 8px;
+    -moz-border-radius-bottomright: 8px;
+    -webkit-border-top-right-radius: 8px;
+    -webkit-border-bottom-right-radius: 8px;
+}
+
+#sidebarScrollView {
+    position: absolute;
+    overflow: hidden;
+    overflow-y: auto;
+    top: 40px;
+    right: 10px;
+    bottom: 10px;
+    left: 10px;
+}
+
+#sidebarContentView {
+    height: auto;
+    width: 100px;
+}
+
+#viewer {
+    margin: 44px 0px 0px;
+    padding: 8px 0px;
+}
diff --git a/multi-page-viewer.html b/multi-page-viewer.html
new file mode 100644
index 000000000..ffbdfe707
--- /dev/null
+++ b/multi-page-viewer.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>pdf.js Multi-Page Viewer</title>
+<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>
+<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen"/>
+<script type="text/javascript" src="pdf.js"></script>
+<script type="text/javascript" src="fonts.js"></script>
+<script type="text/javascript" src="glyphlist.js"></script>
+<script type="text/javascript" src="multi-page-viewer.js"></script>
+</head>
+<body>
+    <div id="controls">
+        <span class="control">
+            <span id="previousPageButton" class="disabled"></span>
+            <span id="nextPageButton" class="disabled"></span>
+            <span class="label">Previous/Next</span>
+        </span>
+        <span class="control">
+            <input type="text" id="pageNumber" value="1" size="2"/>
+            <span>/</span>
+            <span id="numPages">--</span>
+            <span class="label">Page Number</span>
+        </span>
+        <span class="control">
+            <select id="scaleSelect">
+                <option value="50">50%</option>
+                <option value="75">75%</option>
+                <option value="100" selected="selected">100%</option>
+                <option value="125">125%</option>
+                <option value="150">150%</option>
+                <option value="200">200%</option>
+            </select>
+            <span class="label">Zoom</span>
+        </span>
+        <span class="control">
+            <span id="openFileButton"></span>
+            <input type="file" id="fileInput"/>
+            <span class="label">Open File</span>
+        </span>
+    </div>
+    <!--<div id="sidebar">
+        <div id="sidebarScrollView">
+            <div id="sidebarContentView">
+                
+            </div>
+        </div>
+    </div>-->
+    <div id="viewer"></div>
+</body>
+</html>
diff --git a/multi-page-viewer.js b/multi-page-viewer.js
new file mode 100644
index 000000000..baad7809e
--- /dev/null
+++ b/multi-page-viewer.js
@@ -0,0 +1,466 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+
+"use strict";
+
+var PDFViewer = {
+    queryParams: {},
+
+    element: null,
+
+    previousPageButton: null,
+    nextPageButton: null,
+    pageNumberInput: null,
+    scaleSelect: null,
+    fileInput: null,
+    
+    willJumpToPage: false,
+
+    pdf: null,
+
+    url: 'compressed.tracemonkey-pldi-09.pdf',
+    pageNumber: 1,
+    numberOfPages: 1,
+
+    scale: 1.0,
+
+    pageWidth: function() {
+        return 816 * PDFViewer.scale;
+    },
+
+    pageHeight: function() {
+        return 1056 * PDFViewer.scale;
+    },
+    
+    lastPagesDrawn: [],
+    
+    visiblePages: function() {  
+        var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.      
+        var windowTop = window.pageYOffset;
+        var windowBottom = window.pageYOffset + window.innerHeight;
+        var pageStartIndex = Math.floor(windowTop / pageHeight);
+        var pageStopIndex = Math.ceil(windowBottom / pageHeight);
+
+        var pages = [];  
+
+        for (var i = pageStartIndex; i <= pageStopIndex; i++) {
+            pages.push(i + 1);
+        }
+
+        return pages;
+    },
+  
+    createPage: function(num) {
+        var anchor = document.createElement('a');
+        anchor.name = '' + num;
+    
+        var div = document.createElement('div');
+        div.id = 'pageContainer' + num;
+        div.className = 'page';
+        div.style.width = PDFViewer.pageWidth() + 'px';
+        div.style.height = PDFViewer.pageHeight() + 'px';
+        
+        PDFViewer.element.appendChild(anchor);
+        PDFViewer.element.appendChild(div);
+    },
+    
+    removePage: function(num) {
+        var div = document.getElementById('pageContainer' + num);
+        
+        if (div) {
+            while (div.hasChildNodes()) {
+                div.removeChild(div.firstChild);
+            }
+        }
+    },
+
+    drawPage: function(num) {
+        if (!PDFViewer.pdf) {
+            return;
+        }
+        
+        var div = document.getElementById('pageContainer' + num);
+        var canvas = document.createElement('canvas');
+        
+        if (div && !div.hasChildNodes()) {
+            div.appendChild(canvas);
+            
+            var page = PDFViewer.pdf.getPage(num);
+
+            canvas.id = 'page' + num;
+            canvas.mozOpaque = true;
+
+            // Canvas dimensions must be specified in CSS pixels. CSS pixels
+            // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
+            canvas.width = PDFViewer.pageWidth();
+            canvas.height = PDFViewer.pageHeight();
+
+            var ctx = canvas.getContext('2d');
+            ctx.save();
+            ctx.fillStyle = 'rgb(255, 255, 255)';
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+            ctx.restore();
+
+            var gfx = new CanvasGraphics(ctx);
+            var fonts = [];
+        
+            // page.compile will collect all fonts for us, once we have loaded them
+            // we can trigger the actual page rendering with page.display
+            page.compile(gfx, fonts);
+        
+            var areFontsReady = true;
+        
+            // Inspect fonts and translate the missing one
+            var fontCount = fonts.length;
+        
+            for (var i = 0; i < fontCount; i++) {
+                var font = fonts[i];
+            
+                if (Fonts[font.name]) {
+                    areFontsReady = areFontsReady && !Fonts[font.name].loading;
+                    continue;
+                }
+
+                new Font(font.name, font.file, font.properties);
+            
+                areFontsReady = false;
+            }
+
+            var pageInterval;
+            
+            var delayLoadFont = function() {
+                for (var i = 0; i < fontCount; i++) {
+                    if (Fonts[font.name].loading) {
+                        return;
+                    }
+                }
+
+                clearInterval(pageInterval);
+                
+                while (div.hasChildNodes()) {
+                    div.removeChild(div.firstChild);
+                }
+                
+                PDFViewer.drawPage(num);
+            }
+
+            if (!areFontsReady) {
+                pageInterval = setInterval(delayLoadFont, 10);
+                return;
+            }
+
+            page.display(gfx);
+        }
+    },
+
+    changeScale: function(num) {
+        while (PDFViewer.element.hasChildNodes()) {
+            PDFViewer.element.removeChild(PDFViewer.element.firstChild);
+        }
+
+        PDFViewer.scale = num / 100;
+
+        var i;
+
+        if (PDFViewer.pdf) {
+            for (i = 1; i <= PDFViewer.numberOfPages; i++) {
+                PDFViewer.createPage(i);
+            }
+
+            if (PDFViewer.numberOfPages > 0) {
+                PDFViewer.drawPage(1);
+            }
+        }
+
+        for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
+            var option = PDFViewer.scaleSelect.childNodes[i];
+            
+            if (option.value == num) {
+                if (!option.selected) {
+                    option.selected = 'selected';
+                }
+            } else {
+                if (option.selected) {
+                    option.removeAttribute('selected');
+                }
+            }
+        }
+        
+        PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
+    },
+
+    goToPage: function(num) {
+        if (1 <= num && num <= PDFViewer.numberOfPages) {
+            PDFViewer.pageNumber = num;
+            PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
+            PDFViewer.willJumpToPage = true;
+
+            document.location.hash = PDFViewer.pageNumber;
+
+            PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
+                'disabled' : '';
+            PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
+                'disabled' : '';
+        }
+    },
+  
+    goToPreviousPage: function() {
+        if (PDFViewer.pageNumber > 1) {
+            PDFViewer.goToPage(--PDFViewer.pageNumber);
+        }
+    },
+  
+    goToNextPage: function() {
+        if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
+            PDFViewer.goToPage(++PDFViewer.pageNumber);
+        }
+    },
+  
+    openURL: function(url) {
+        PDFViewer.url = url;
+        document.title = url;
+    
+        var req = new XMLHttpRequest();
+        req.open('GET', url);
+        req.mozResponseType = req.responseType = 'arraybuffer';
+        req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
+    
+        req.onreadystatechange = function() {
+            if (req.readyState === 4 && req.status === req.expected) {
+                var data = req.mozResponseArrayBuffer ||
+                    req.mozResponse ||
+                    req.responseArrayBuffer ||
+                    req.response;
+
+                PDFViewer.readPDF(data);
+            }
+        };
+    
+        req.send(null);
+    },
+    
+    readPDF: function(data) {
+        while (PDFViewer.element.hasChildNodes()) {
+            PDFViewer.element.removeChild(PDFViewer.element.firstChild);
+        }
+        
+        PDFViewer.pdf = new PDFDoc(new Stream(data));
+        PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
+        document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
+  
+        for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
+            PDFViewer.createPage(i);
+        }
+
+        if (PDFViewer.numberOfPages > 0) {
+            PDFViewer.drawPage(1);
+            document.location.hash = 1;
+        }
+        
+        PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
+             'disabled' : '';
+         PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
+             'disabled' : '';
+    }
+};
+
+window.onload = function() {
+
+    // Parse the URL query parameters into a cached object.
+    PDFViewer.queryParams = function() {
+        var qs = window.location.search.substring(1);
+        var kvs = qs.split('&');
+        var params = {};
+        for (var i = 0; i < kvs.length; ++i) {
+            var kv = kvs[i].split('=');
+            params[unescape(kv[0])] = unescape(kv[1]);
+        }
+        
+        return params;
+    }();
+
+    PDFViewer.element = document.getElementById('viewer');
+
+    PDFViewer.pageNumberInput = document.getElementById('pageNumber');
+    PDFViewer.pageNumberInput.onkeydown = function(evt) {
+        var charCode = evt.charCode || evt.keyCode;
+
+        // Up arrow key.
+        if (charCode === 38) {
+            PDFViewer.goToNextPage();
+            this.select();
+        }
+        
+        // Down arrow key.
+        else if (charCode === 40) {
+            PDFViewer.goToPreviousPage();
+            this.select();
+        }
+
+        // All other non-numeric keys (excluding Left arrow, Right arrow,
+        // Backspace, and Delete keys).
+        else if ((charCode < 48 || charCode > 57) &&
+            charCode !== 8 &&   // Backspace
+            charCode !== 46 &&  // Delete
+            charCode !== 37 &&  // Left arrow
+            charCode !== 39     // Right arrow
+        ) {
+            return false;
+        }
+
+        return true;
+    };
+    PDFViewer.pageNumberInput.onkeyup = function(evt) {
+        var charCode = evt.charCode || evt.keyCode;
+
+        // All numeric keys, Backspace, and Delete.
+        if ((charCode >= 48 && charCode <= 57) ||
+            charCode === 8 ||   // Backspace
+            charCode === 46     // Delete
+        ) {
+            PDFViewer.goToPage(this.value);
+        }
+        
+        this.focus();
+    };
+
+    PDFViewer.previousPageButton = document.getElementById('previousPageButton');
+    PDFViewer.previousPageButton.onclick = function(evt) {
+        if (this.className.indexOf('disabled') === -1) {
+            PDFViewer.goToPreviousPage();
+        }
+    };
+    PDFViewer.previousPageButton.onmousedown = function(evt) {
+        if (this.className.indexOf('disabled') === -1) {
+             this.className = 'down';
+        }
+    };
+    PDFViewer.previousPageButton.onmouseup = function(evt) {
+        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+    };
+    PDFViewer.previousPageButton.onmouseout = function(evt) {
+        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+    };
+    
+    PDFViewer.nextPageButton = document.getElementById('nextPageButton');
+    PDFViewer.nextPageButton.onclick = function(evt) {
+        if (this.className.indexOf('disabled') === -1) {
+            PDFViewer.goToNextPage();
+        }
+    };
+    PDFViewer.nextPageButton.onmousedown = function(evt) {
+        if (this.className.indexOf('disabled') === -1) {
+            this.className = 'down';
+        }
+    };
+    PDFViewer.nextPageButton.onmouseup = function(evt) {
+        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+    };
+    PDFViewer.nextPageButton.onmouseout = function(evt) {
+        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+    };
+
+    PDFViewer.scaleSelect = document.getElementById('scaleSelect');
+    PDFViewer.scaleSelect.onchange = function(evt) {
+        PDFViewer.changeScale(parseInt(this.value));
+    };
+    
+    if (window.File && window.FileReader && window.FileList && window.Blob) {
+        var openFileButton = document.getElementById('openFileButton');
+        openFileButton.onclick = function(evt) {
+            if (this.className.indexOf('disabled') === -1) {
+                PDFViewer.fileInput.click();
+            }
+        };
+        openFileButton.onmousedown = function(evt) {
+            if (this.className.indexOf('disabled') === -1) {
+                this.className = 'down';
+            }
+        };
+        openFileButton.onmouseup = function(evt) {
+            this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+        };
+        openFileButton.onmouseout = function(evt) {
+            this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+        };
+        
+        PDFViewer.fileInput = document.getElementById('fileInput');
+        PDFViewer.fileInput.onchange = function(evt) {
+            var files = evt.target.files;
+            
+            if (files.length > 0) {
+                var file = files[0];
+                var fileReader = new FileReader();
+                
+                document.title = file.name;
+                
+                // Read the local file into a Uint8Array.
+                fileReader.onload = function(evt) {
+                    var data = evt.target.result;
+                    var buffer = new ArrayBuffer(data.length);
+                    var uint8Array = new Uint8Array(buffer);
+                    
+                    for (var i = 0; i < data.length; i++) {
+                        uint8Array[i] = data.charCodeAt(i);
+                    }
+                    
+                    PDFViewer.readPDF(uint8Array);
+                };
+                
+                // Read as a binary string since "readAsArrayBuffer" is not yet
+                // implemented in Firefox.
+                fileReader.readAsBinaryString(file);
+            }
+        };
+        PDFViewer.fileInput.value = null;
+    } else {
+        document.getElementById('fileWrapper').style.display = 'none';
+    }
+
+    PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
+    PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
+    
+    PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
+
+    window.onscroll = function(evt) {        
+        var lastPagesDrawn = PDFViewer.lastPagesDrawn;
+        var visiblePages = PDFViewer.visiblePages();
+        
+        var pagesToDraw = [];
+        var pagesToKeep = [];
+        var pagesToRemove = [];
+        
+        var i;
+
+        // Determine which visible pages were not previously drawn.
+        for (i = 0; i < visiblePages.length; i++) {
+            if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
+                pagesToDraw.push(visiblePages[i]);
+                PDFViewer.drawPage(visiblePages[i]);
+            } else {
+                pagesToKeep.push(visiblePages[i]);
+            }
+        }
+
+        // Determine which previously drawn pages are no longer visible.
+        for (i = 0; i < lastPagesDrawn.length; i++) {
+            if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
+                pagesToRemove.push(lastPagesDrawn[i]);
+                PDFViewer.removePage(lastPagesDrawn[i]);
+            }
+        }
+        
+        PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
+        
+        // Update the page number input with the current page number.
+        if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
+            PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
+            PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
+                'disabled' : '';
+            PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
+                'disabled' : '';
+        } else {
+            PDFViewer.willJumpToPage = false;
+        }
+    };
+};
diff --git a/multi_page_viewer.css b/multi_page_viewer.css
index fce7d7b32..7f4701022 100644
--- a/multi_page_viewer.css
+++ b/multi_page_viewer.css
@@ -2,196 +2,196 @@
 /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
 
 body {
-  background-color: #929292;
-  font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
-  margin: 0px;
-  padding: 0px;
+    background-color: #929292;
+    font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
+    margin: 0px;
+    padding: 0px;
 }
 
 canvas {
-  box-shadow: 0px 4px 10px #000;
-  -moz-box-shadow: 0px 4px 10px #000;
-  -webkit-box-shadow: 0px 4px 10px #000;
+    box-shadow: 0px 4px 10px #000;
+    -moz-box-shadow: 0px 4px 10px #000;
+    -webkit-box-shadow: 0px 4px 10px #000;
 }
 
 span {
-  font-size: 0.8em;
+    font-size: 0.8em;
 }
 
 .control {
-  display: inline-block;
-  float: left;
-  margin: 0px 20px 0px 0px;
-  padding: 0px 4px 0px 0px;
+    display: inline-block;
+    float: left;
+    margin: 0px 20px 0px 0px;
+    padding: 0px 4px 0px 0px;
 }
 
 .control > input {
-  float: left;
-  border: 1px solid #4d4d4d;
-  height: 20px;
-  padding: 0px;
-  margin: 0px 2px 0px 0px;
-  border-radius: 4px;
-  -moz-border-radius: 4px;
-  -webkit-border-radius: 4px;
-  box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-  -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-  -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+    float: left;
+    border: 1px solid #4d4d4d;
+    height: 20px;
+    padding: 0px;
+    margin: 0px 2px 0px 0px;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+    box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+    -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+    -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
 }
 
 .control > select {
-  float: left;
-  border: 1px solid #4d4d4d;
-  height: 22px;
-  padding: 2px 0px 0px;
-  margin: 0px 0px 1px;
-  border-radius: 4px;
-  -moz-border-radius: 4px;
-  -webkit-border-radius: 4px;
-  box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-  -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-  -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+    float: left;
+    border: 1px solid #4d4d4d;
+    height: 22px;
+    padding: 2px 0px 0px;
+    margin: 0px 0px 1px;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+    box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+    -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+    -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
 }
 
 .control > span {
-  cursor: default;
-  float: left;
-  height: 18px;
-  margin: 5px 2px 0px;
-  padding: 0px;
-  user-select: none;
-  -moz-user-select: none;
-  -webkit-user-select: none;
+    cursor: default;
+    float: left;
+    height: 18px;
+    margin: 5px 2px 0px;
+    padding: 0px;
+    user-select: none;
+    -moz-user-select: none;
+    -webkit-user-select: none;
 }
 
 .control .label {
-  clear: both;
-  float: left;
-  font-size: 0.65em;
-  margin: 2px 0px 0px;
-  position: relative;
-  text-align: center;
-  width: 100%;
+    clear: both;
+    float: left;
+    font-size: 0.65em;
+    margin: 2px 0px 0px;
+    position: relative;
+    text-align: center;
+    width: 100%;
 }
 
 .page {
-  width: 816px;
-  height: 1056px;
-  margin: 10px auto;
+    width: 816px;
+    height: 1056px;
+    margin: 10px auto;
 }
 
 #controls {
-  background-color: #eee;
-  border-bottom: 1px solid #666;
-  padding: 4px 0px 0px 8px;
-  position: fixed;
-  left: 0px;
-  top: 0px;
-  height: 40px;
-  width: 100%;
-  box-shadow: 0px 2px 8px #000;
-  -moz-box-shadow: 0px 2px 8px #000;
-  -webkit-box-shadow: 0px 2px 8px #000;
+    background-color: #eee;
+    border-bottom: 1px solid #666;
+    padding: 4px 0px 0px 8px;
+    position: fixed;
+    left: 0px;
+    top: 0px;
+    height: 40px;
+    width: 100%;
+    box-shadow: 0px 2px 8px #000;
+    -moz-box-shadow: 0px 2px 8px #000;
+    -webkit-box-shadow: 0px 2px 8px #000;
 }
 
 #controls input {
-  user-select: text;
-  -moz-user-select: text;
-  -webkit-user-select: text;
+    user-select: text;
+    -moz-user-select: text;
+    -webkit-user-select: text;
 }
 
 #previousPageButton {
-  background: url('images/buttons.png') no-repeat 0px -23px;
-  cursor: default;
-  display: inline-block;
-  float: left;
-  margin: 0px;
-  width: 28px;
-  height: 23px;
+    background: url('images/buttons.png') no-repeat 0px -23px;
+    cursor: default;
+    display: inline-block;
+    float: left;
+    margin: 0px;
+    width: 28px;
+    height: 23px;
 }
 
 #previousPageButton.down {
-  background: url('images/buttons.png') no-repeat 0px -46px;
+    background: url('images/buttons.png') no-repeat 0px -46px;
 }
 
 #previousPageButton.disabled {
-  background: url('images/buttons.png') no-repeat 0px 0px;
+    background: url('images/buttons.png') no-repeat 0px 0px;
 }
 
 #nextPageButton {
-  background: url('images/buttons.png') no-repeat -28px -23px;
-  cursor: default;
-  display: inline-block;
-  float: left;
-  margin: 0px;
-  width: 28px;
-  height: 23px;
+    background: url('images/buttons.png') no-repeat -28px -23px;
+    cursor: default;
+    display: inline-block;
+    float: left;
+    margin: 0px;
+    width: 28px;
+    height: 23px;
 }
 
 #nextPageButton.down {
-  background: url('images/buttons.png') no-repeat -28px -46px;
+    background: url('images/buttons.png') no-repeat -28px -46px;
 }
 
 #nextPageButton.disabled {
-  background: url('images/buttons.png') no-repeat -28px 0px;
+    background: url('images/buttons.png') no-repeat -28px 0px;
 }
 
 #openFileButton {
-  background: url('images/buttons.png') no-repeat -56px -23px;
-  cursor: default;
-  display: inline-block;
-  float: left;
-  margin: 0px 0px 0px 3px;
-  width: 29px;
-  height: 23px;
+    background: url('images/buttons.png') no-repeat -56px -23px;
+    cursor: default;
+    display: inline-block;
+    float: left;
+    margin: 0px 0px 0px 3px;
+    width: 29px;
+    height: 23px;
 }
 
 #openFileButton.down {
-  background: url('images/buttons.png') no-repeat -56px -46px;
+    background: url('images/buttons.png') no-repeat -56px -46px;
 }
 
 #openFileButton.disabled {
-  background: url('images/buttons.png') no-repeat -56px 0px;
+    background: url('images/buttons.png') no-repeat -56px 0px;
 }
 
 #fileInput {
-  display: none;
+    display: none;
 }
 
 #pageNumber {
-  text-align: right;
+    text-align: right;
 }
 
 #sidebar {
-  background-color: rgba(0, 0, 0, 0.8);
-  position: fixed;
-  width: 150px;
-  top: 62px;
-  bottom: 18px;
-  border-top-right-radius: 8px;
-  border-bottom-right-radius: 8px;
-  -moz-border-radius-topright: 8px;
-  -moz-border-radius-bottomright: 8px;
-  -webkit-border-top-right-radius: 8px;
-  -webkit-border-bottom-right-radius: 8px;
+    background-color: rgba(0, 0, 0, 0.8);
+    position: fixed;
+    width: 150px;
+    top: 62px;
+    bottom: 18px;
+    border-top-right-radius: 8px;
+    border-bottom-right-radius: 8px;
+    -moz-border-radius-topright: 8px;
+    -moz-border-radius-bottomright: 8px;
+    -webkit-border-top-right-radius: 8px;
+    -webkit-border-bottom-right-radius: 8px;
 }
 
 #sidebarScrollView {
-  position: absolute;
-  overflow: hidden;
-  overflow-y: auto;
-  top: 40px;
-  right: 10px;
-  bottom: 10px;
-  left: 10px;
+    position: absolute;
+    overflow: hidden;
+    overflow-y: auto;
+    top: 40px;
+    right: 10px;
+    bottom: 10px;
+    left: 10px;
 }
 
 #sidebarContentView {
-  height: auto;
-  width: 100px;
+    height: auto;
+    width: 100px;
 }
 
 #viewer {
-  margin: 44px 0px 0px;
-  padding: 8px 0px;
+    margin: 44px 0px 0px;
+    padding: 8px 0px;
 }
diff --git a/multi_page_viewer.html b/multi_page_viewer.html
index 47234686d..ffbdfe707 100644
--- a/multi_page_viewer.html
+++ b/multi_page_viewer.html
@@ -3,49 +3,49 @@
 <head>
 <title>pdf.js Multi-Page Viewer</title>
 <meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>
-<link rel="stylesheet" href="multi_page_viewer.css" type="text/css" media="screen"/>
+<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen"/>
 <script type="text/javascript" src="pdf.js"></script>
 <script type="text/javascript" src="fonts.js"></script>
 <script type="text/javascript" src="glyphlist.js"></script>
-<script type="text/javascript" src="multi_page_viewer.js"></script>
+<script type="text/javascript" src="multi-page-viewer.js"></script>
 </head>
 <body>
-  <div id="controls">
-    <span class="control">
-      <span id="previousPageButton" class="disabled"></span>
-      <span id="nextPageButton" class="disabled"></span>
-      <span class="label">Previous/Next</span>
-    </span>
-    <span class="control">
-      <input type="text" id="pageNumber" value="1" size="2"/>
-      <span>/</span>
-      <span id="numPages">--</span>
-      <span class="label">Page Number</span>
-    </span>
-    <span class="control">
-      <select id="scaleSelect">
-        <option value="50">50%</option>
-        <option value="75">75%</option>
-        <option value="100" selected="selected">100%</option>
-        <option value="125">125%</option>
-        <option value="150">150%</option>
-        <option value="200">200%</option>
-      </select>
-      <span class="label">Zoom</span>
-    </span>
-    <span class="control">
-      <span id="openFileButton"></span>
-      <input type="file" id="fileInput"/>
-      <span class="label">Open File</span>
-    </span>
-  </div>
-  <!--<div id="sidebar">
-    <div id="sidebarScrollView">
-      <div id="sidebarContentView">
-        
-      </div>
+    <div id="controls">
+        <span class="control">
+            <span id="previousPageButton" class="disabled"></span>
+            <span id="nextPageButton" class="disabled"></span>
+            <span class="label">Previous/Next</span>
+        </span>
+        <span class="control">
+            <input type="text" id="pageNumber" value="1" size="2"/>
+            <span>/</span>
+            <span id="numPages">--</span>
+            <span class="label">Page Number</span>
+        </span>
+        <span class="control">
+            <select id="scaleSelect">
+                <option value="50">50%</option>
+                <option value="75">75%</option>
+                <option value="100" selected="selected">100%</option>
+                <option value="125">125%</option>
+                <option value="150">150%</option>
+                <option value="200">200%</option>
+            </select>
+            <span class="label">Zoom</span>
+        </span>
+        <span class="control">
+            <span id="openFileButton"></span>
+            <input type="file" id="fileInput"/>
+            <span class="label">Open File</span>
+        </span>
     </div>
-  </div>-->
-  <div id="viewer"></div>
+    <!--<div id="sidebar">
+        <div id="sidebarScrollView">
+            <div id="sidebarContentView">
+                
+            </div>
+        </div>
+    </div>-->
+    <div id="viewer"></div>
 </body>
 </html>
diff --git a/multi_page_viewer.js b/multi_page_viewer.js
index ddb541175..baad7809e 100644
--- a/multi_page_viewer.js
+++ b/multi_page_viewer.js
@@ -4,455 +4,463 @@
 "use strict";
 
 var PDFViewer = {
-  queryParams: {},
-  
-  element: null,
-  
-  previousPageButton: null,
-  nextPageButton: null,
-  pageNumberInput: null,
-  scaleSelect: null,
-  fileInput: null,
-  
-  willJumpToPage: false,
-  
-  pdf: null,
-  
-  url: 'compressed.tracemonkey-pldi-09.pdf',
-  pageNumber: 1,
-  numberOfPages: 1,
-  
-  scale: 1.0,
-  
-  pageWidth: function() {
-    return 816 * PDFViewer.scale;
-  },
-  
-  pageHeight: function() {
-    return 1056 * PDFViewer.scale;
-  },
-  
-  lastPagesDrawn: [],
-  
-  visiblePages: function() {  
-    var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.      
-    var windowTop = window.pageYOffset;
-    var windowBottom = window.pageYOffset + window.innerHeight;
-    var pageStartIndex = Math.floor(windowTop / pageHeight);
-    var pageStopIndex = Math.ceil(windowBottom / pageHeight);
-    
-    var pages = [];  
-    
-    for (var i = pageStartIndex; i <= pageStopIndex; i++) {
-      pages.push(i + 1);
-    }
-    
-    return pages;
-  },
-  
-  createPage: function(num) {
-    var anchor = document.createElement('a');
-    anchor.name = '' + num;
-    
-    var div = document.createElement('div');
-    div.id = 'pageContainer' + num;
-    div.className = 'page';
-    div.style.width = PDFViewer.pageWidth() + 'px';
-    div.style.height = PDFViewer.pageHeight() + 'px';
-    
-    PDFViewer.element.appendChild(anchor);
-    PDFViewer.element.appendChild(div);
-  },
-  
-  removePage: function(num) {
-    var div = document.getElementById('pageContainer' + num);
+    queryParams: {},
+
+    element: null,
+
+    previousPageButton: null,
+    nextPageButton: null,
+    pageNumberInput: null,
+    scaleSelect: null,
+    fileInput: null,
     
-    if (div) {
-      while (div.hasChildNodes()) {
-        div.removeChild(div.firstChild);
-      }
-    }
-  },
+    willJumpToPage: false,
+
+    pdf: null,
+
+    url: 'compressed.tracemonkey-pldi-09.pdf',
+    pageNumber: 1,
+    numberOfPages: 1,
+
+    scale: 1.0,
+
+    pageWidth: function() {
+        return 816 * PDFViewer.scale;
+    },
+
+    pageHeight: function() {
+        return 1056 * PDFViewer.scale;
+    },
+    
+    lastPagesDrawn: [],
+    
+    visiblePages: function() {  
+        var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.      
+        var windowTop = window.pageYOffset;
+        var windowBottom = window.pageYOffset + window.innerHeight;
+        var pageStartIndex = Math.floor(windowTop / pageHeight);
+        var pageStopIndex = Math.ceil(windowBottom / pageHeight);
+
+        var pages = [];  
+
+        for (var i = pageStartIndex; i <= pageStopIndex; i++) {
+            pages.push(i + 1);
+        }
+
+        return pages;
+    },
   
-  drawPage: function(num) {
-    if (!PDFViewer.pdf) {
-      return;
-    }
-    
-    var div = document.getElementById('pageContainer' + num);
-    var canvas = document.createElement('canvas');
+    createPage: function(num) {
+        var anchor = document.createElement('a');
+        anchor.name = '' + num;
+    
+        var div = document.createElement('div');
+        div.id = 'pageContainer' + num;
+        div.className = 'page';
+        div.style.width = PDFViewer.pageWidth() + 'px';
+        div.style.height = PDFViewer.pageHeight() + 'px';
+        
+        PDFViewer.element.appendChild(anchor);
+        PDFViewer.element.appendChild(div);
+    },
     
-    if (div && !div.hasChildNodes()) {
-      div.appendChild(canvas);
-      
-      var page = PDFViewer.pdf.getPage(num);
-      
-      canvas.id = 'page' + num;
-      canvas.mozOpaque = true;
-      
-      // Canvas dimensions must be specified in CSS pixels. CSS pixels
-      // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
-      canvas.width = PDFViewer.pageWidth();
-      canvas.height = PDFViewer.pageHeight();
-      
-      var ctx = canvas.getContext('2d');
-      ctx.save();
-      ctx.fillStyle = 'rgb(255, 255, 255)';
-      ctx.fillRect(0, 0, canvas.width, canvas.height);
-      ctx.restore();
-      
-      var gfx = new CanvasGraphics(ctx);
-      var fonts = [];
-      
-      // page.compile will collect all fonts for us, once we have loaded them
-      // we can trigger the actual page rendering with page.display
-      page.compile(gfx, fonts);
-      
-      var areFontsReady = true;
-      
-      // Inspect fonts and translate the missing one
-      var fontCount = fonts.length;
-      
-      for (var i = 0; i < fontCount; i++) {
-        var font = fonts[i];
+    removePage: function(num) {
+        var div = document.getElementById('pageContainer' + num);
         
-        if (Fonts[font.name]) {
-          areFontsReady = areFontsReady && !Fonts[font.name].loading;
-          continue;
+        if (div) {
+            while (div.hasChildNodes()) {
+                div.removeChild(div.firstChild);
+            }
         }
-        
-        new Font(font.name, font.file, font.properties);
-        
-        areFontsReady = false;
-      }
-      
-      var pageInterval;
-      
-      var delayLoadFont = function() {
-        for (var i = 0; i < fontCount; i++) {
-          if (Fonts[font.name].loading) {
+    },
+
+    drawPage: function(num) {
+        if (!PDFViewer.pdf) {
             return;
-          }
         }
         
-        clearInterval(pageInterval);
+        var div = document.getElementById('pageContainer' + num);
+        var canvas = document.createElement('canvas');
         
-        while (div.hasChildNodes()) {
-          div.removeChild(div.firstChild);
-        }
+        if (div && !div.hasChildNodes()) {
+            div.appendChild(canvas);
+            
+            var page = PDFViewer.pdf.getPage(num);
+
+            canvas.id = 'page' + num;
+            canvas.mozOpaque = true;
+
+            // Canvas dimensions must be specified in CSS pixels. CSS pixels
+            // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
+            canvas.width = PDFViewer.pageWidth();
+            canvas.height = PDFViewer.pageHeight();
+
+            var ctx = canvas.getContext('2d');
+            ctx.save();
+            ctx.fillStyle = 'rgb(255, 255, 255)';
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+            ctx.restore();
+
+            var gfx = new CanvasGraphics(ctx);
+            var fonts = [];
         
-        PDFViewer.drawPage(num);
-      }
-      
-      if (!areFontsReady) {
-        pageInterval = setInterval(delayLoadFont, 10);
-        return;
-      }
-      
-      page.display(gfx);
-    }
-  },
-  
-  changeScale: function(num) {
-    while (PDFViewer.element.hasChildNodes()) {
-      PDFViewer.element.removeChild(PDFViewer.element.firstChild);
-    }
-    
-    PDFViewer.scale = num / 100;
-    
-    var i;
-    
-    if (PDFViewer.pdf) {
-      for (i = 1; i <= PDFViewer.numberOfPages; i++) {
-        PDFViewer.createPage(i);
-      }
-      
-      if (PDFViewer.numberOfPages > 0) {
-        PDFViewer.drawPage(1);
-      }
-    }
-    
-    for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
-      var option = PDFViewer.scaleSelect.childNodes[i];
-      
-      if (option.value == num) {
-        if (!option.selected) {
-          option.selected = 'selected';
+            // page.compile will collect all fonts for us, once we have loaded them
+            // we can trigger the actual page rendering with page.display
+            page.compile(gfx, fonts);
+        
+            var areFontsReady = true;
+        
+            // Inspect fonts and translate the missing one
+            var fontCount = fonts.length;
+        
+            for (var i = 0; i < fontCount; i++) {
+                var font = fonts[i];
+            
+                if (Fonts[font.name]) {
+                    areFontsReady = areFontsReady && !Fonts[font.name].loading;
+                    continue;
+                }
+
+                new Font(font.name, font.file, font.properties);
+            
+                areFontsReady = false;
+            }
+
+            var pageInterval;
+            
+            var delayLoadFont = function() {
+                for (var i = 0; i < fontCount; i++) {
+                    if (Fonts[font.name].loading) {
+                        return;
+                    }
+                }
+
+                clearInterval(pageInterval);
+                
+                while (div.hasChildNodes()) {
+                    div.removeChild(div.firstChild);
+                }
+                
+                PDFViewer.drawPage(num);
+            }
+
+            if (!areFontsReady) {
+                pageInterval = setInterval(delayLoadFont, 10);
+                return;
+            }
+
+            page.display(gfx);
         }
-      } else {
-        if (option.selected) {
-          option.removeAttribute('selected');
+    },
+
+    changeScale: function(num) {
+        while (PDFViewer.element.hasChildNodes()) {
+            PDFViewer.element.removeChild(PDFViewer.element.firstChild);
         }
-      }
-    }
-    
-    PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
-  },
-  
-  goToPage: function(num) {
-    if (1 <= num && num <= PDFViewer.numberOfPages) {
-      PDFViewer.pageNumber = num;
-      PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
-      PDFViewer.willJumpToPage = true;
-      
-      document.location.hash = PDFViewer.pageNumber;
-      
-      PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
-      PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
-    }
-  },
+
+        PDFViewer.scale = num / 100;
+
+        var i;
+
+        if (PDFViewer.pdf) {
+            for (i = 1; i <= PDFViewer.numberOfPages; i++) {
+                PDFViewer.createPage(i);
+            }
+
+            if (PDFViewer.numberOfPages > 0) {
+                PDFViewer.drawPage(1);
+            }
+        }
+
+        for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
+            var option = PDFViewer.scaleSelect.childNodes[i];
+            
+            if (option.value == num) {
+                if (!option.selected) {
+                    option.selected = 'selected';
+                }
+            } else {
+                if (option.selected) {
+                    option.removeAttribute('selected');
+                }
+            }
+        }
+        
+        PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
+    },
+
+    goToPage: function(num) {
+        if (1 <= num && num <= PDFViewer.numberOfPages) {
+            PDFViewer.pageNumber = num;
+            PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
+            PDFViewer.willJumpToPage = true;
+
+            document.location.hash = PDFViewer.pageNumber;
+
+            PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
+                'disabled' : '';
+            PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
+                'disabled' : '';
+        }
+    },
   
-  goToPreviousPage: function() {
-    if (PDFViewer.pageNumber > 1) {
-      PDFViewer.goToPage(--PDFViewer.pageNumber);
-    }
-  },
+    goToPreviousPage: function() {
+        if (PDFViewer.pageNumber > 1) {
+            PDFViewer.goToPage(--PDFViewer.pageNumber);
+        }
+    },
   
-  goToNextPage: function() {
-    if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
-      PDFViewer.goToPage(++PDFViewer.pageNumber);
-    }
-  },
+    goToNextPage: function() {
+        if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
+            PDFViewer.goToPage(++PDFViewer.pageNumber);
+        }
+    },
   
-  openURL: function(url) {
-    PDFViewer.url = url;
-    document.title = url;
+    openURL: function(url) {
+        PDFViewer.url = url;
+        document.title = url;
+    
+        var req = new XMLHttpRequest();
+        req.open('GET', url);
+        req.mozResponseType = req.responseType = 'arraybuffer';
+        req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
+    
+        req.onreadystatechange = function() {
+            if (req.readyState === 4 && req.status === req.expected) {
+                var data = req.mozResponseArrayBuffer ||
+                    req.mozResponse ||
+                    req.responseArrayBuffer ||
+                    req.response;
+
+                PDFViewer.readPDF(data);
+            }
+        };
     
-    var req = new XMLHttpRequest();
-    req.open('GET', url);
-    req.mozResponseType = req.responseType = 'arraybuffer';
-    req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
+        req.send(null);
+    },
     
-    req.onreadystatechange = function() {
-      if (req.readyState === 4 && req.status === req.expected) {
-        var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response;
+    readPDF: function(data) {
+        while (PDFViewer.element.hasChildNodes()) {
+            PDFViewer.element.removeChild(PDFViewer.element.firstChild);
+        }
         
-        PDFViewer.readPDF(data);
-      }
-    };
-    
-    req.send(null);
-  },
+        PDFViewer.pdf = new PDFDoc(new Stream(data));
+        PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
+        document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
   
-  readPDF: function(data) {
-    while (PDFViewer.element.hasChildNodes()) {
-      PDFViewer.element.removeChild(PDFViewer.element.firstChild);
-    }
-    
-    PDFViewer.pdf = new PDFDoc(new Stream(data));
-    PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
-    document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
-    
-    for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
-      PDFViewer.createPage(i);
-    }
-    
-    if (PDFViewer.numberOfPages > 0) {
-      PDFViewer.drawPage(1);
-      document.location.hash = 1;
+        for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
+            PDFViewer.createPage(i);
+        }
+
+        if (PDFViewer.numberOfPages > 0) {
+            PDFViewer.drawPage(1);
+            document.location.hash = 1;
+        }
+        
+        PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
+             'disabled' : '';
+         PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
+             'disabled' : '';
     }
-    
-    PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
-    PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
-  }
 };
 
 window.onload = function() {
-  
-  // Parse the URL query parameters into a cached object.
-  PDFViewer.queryParams = function() {
-    var qs = window.location.search.substring(1);
-    var kvs = qs.split('&');
-    var params = {};
-    
-    for (var i = 0; i < kvs.length; ++i) {
-      var kv = kvs[i].split('=');
-      params[unescape(kv[0])] = unescape(kv[1]);
-    }
-    
-    return params;
-  }();
 
-  PDFViewer.element = document.getElementById('viewer');
-  
-  PDFViewer.pageNumberInput = document.getElementById('pageNumber');
-  PDFViewer.pageNumberInput.onkeydown = function(evt) {
-    var charCode = evt.charCode || evt.keyCode;
-    
-    // Up arrow key.
-    if (charCode === 38) {
-      PDFViewer.goToNextPage();
-      this.select();
-    }
-    
-    // Down arrow key.
-    else if (charCode === 40) {
-      PDFViewer.goToPreviousPage();
-      this.select();
-    }
-    
-    // All other non-numeric keys (excluding Left arrow, Right arrow,
-    // Backspace, and Delete keys).
-    else if ((charCode < 48 || charCode > 57) &&
-      charCode !== 8 &&   // Backspace
-      charCode !== 46 &&  // Delete
-      charCode !== 37 &&  // Left arrow
-      charCode !== 39     // Right arrow
-    ) {
-      return false;
-    }
-    
-    return true;
-  };
-  PDFViewer.pageNumberInput.onkeyup = function(evt) {
-    var charCode = evt.charCode || evt.keyCode;
-    
-    // All numeric keys, Backspace, and Delete.
-    if ((charCode >= 48 && charCode <= 57) ||
-      charCode === 8 ||   // Backspace
-      charCode === 46     // Delete
-    ) {
-      PDFViewer.goToPage(this.value);
-    }
+    // Parse the URL query parameters into a cached object.
+    PDFViewer.queryParams = function() {
+        var qs = window.location.search.substring(1);
+        var kvs = qs.split('&');
+        var params = {};
+        for (var i = 0; i < kvs.length; ++i) {
+            var kv = kvs[i].split('=');
+            params[unescape(kv[0])] = unescape(kv[1]);
+        }
+        
+        return params;
+    }();
+
+    PDFViewer.element = document.getElementById('viewer');
+
+    PDFViewer.pageNumberInput = document.getElementById('pageNumber');
+    PDFViewer.pageNumberInput.onkeydown = function(evt) {
+        var charCode = evt.charCode || evt.keyCode;
+
+        // Up arrow key.
+        if (charCode === 38) {
+            PDFViewer.goToNextPage();
+            this.select();
+        }
+        
+        // Down arrow key.
+        else if (charCode === 40) {
+            PDFViewer.goToPreviousPage();
+            this.select();
+        }
+
+        // All other non-numeric keys (excluding Left arrow, Right arrow,
+        // Backspace, and Delete keys).
+        else if ((charCode < 48 || charCode > 57) &&
+            charCode !== 8 &&   // Backspace
+            charCode !== 46 &&  // Delete
+            charCode !== 37 &&  // Left arrow
+            charCode !== 39     // Right arrow
+        ) {
+            return false;
+        }
+
+        return true;
+    };
+    PDFViewer.pageNumberInput.onkeyup = function(evt) {
+        var charCode = evt.charCode || evt.keyCode;
+
+        // All numeric keys, Backspace, and Delete.
+        if ((charCode >= 48 && charCode <= 57) ||
+            charCode === 8 ||   // Backspace
+            charCode === 46     // Delete
+        ) {
+            PDFViewer.goToPage(this.value);
+        }
+        
+        this.focus();
+    };
+
+    PDFViewer.previousPageButton = document.getElementById('previousPageButton');
+    PDFViewer.previousPageButton.onclick = function(evt) {
+        if (this.className.indexOf('disabled') === -1) {
+            PDFViewer.goToPreviousPage();
+        }
+    };
+    PDFViewer.previousPageButton.onmousedown = function(evt) {
+        if (this.className.indexOf('disabled') === -1) {
+             this.className = 'down';
+        }
+    };
+    PDFViewer.previousPageButton.onmouseup = function(evt) {
+        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+    };
+    PDFViewer.previousPageButton.onmouseout = function(evt) {
+        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+    };
     
-    this.focus();
-  };
-  
-  PDFViewer.previousPageButton = document.getElementById('previousPageButton');
-  PDFViewer.previousPageButton.onclick = function(evt) {
-    if (this.className.indexOf('disabled') === -1) {
-      PDFViewer.goToPreviousPage();
-    }
-  };
-  PDFViewer.previousPageButton.onmousedown = function(evt) {
-    if (this.className.indexOf('disabled') === -1) {
-      this.className = 'down';
-    }
-  };
-  PDFViewer.previousPageButton.onmouseup = function(evt) {
-    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-  };
-  PDFViewer.previousPageButton.onmouseout = function(evt) {
-    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-  };
-  
-  PDFViewer.nextPageButton = document.getElementById('nextPageButton');
-  PDFViewer.nextPageButton.onclick = function(evt) {
-    if (this.className.indexOf('disabled') === -1) {
-      PDFViewer.goToNextPage();
-    }
-  };
-  PDFViewer.nextPageButton.onmousedown = function(evt) {
-    if (this.className.indexOf('disabled') === -1) {
-      this.className = 'down';
-    }
-  };
-  PDFViewer.nextPageButton.onmouseup = function(evt) {
-    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-  };
-  PDFViewer.nextPageButton.onmouseout = function(evt) {
-    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-  };
-  
-  PDFViewer.scaleSelect = document.getElementById('scaleSelect');
-  PDFViewer.scaleSelect.onchange = function(evt) {
-    PDFViewer.changeScale(parseInt(this.value));
-  };
-  
-  if (window.File && window.FileReader && window.FileList && window.Blob) {
-    var openFileButton = document.getElementById('openFileButton');
-    openFileButton.onclick = function(evt) {
-      if (this.className.indexOf('disabled') === -1) {
-        PDFViewer.fileInput.click();
-      }
+    PDFViewer.nextPageButton = document.getElementById('nextPageButton');
+    PDFViewer.nextPageButton.onclick = function(evt) {
+        if (this.className.indexOf('disabled') === -1) {
+            PDFViewer.goToNextPage();
+        }
+    };
+    PDFViewer.nextPageButton.onmousedown = function(evt) {
+        if (this.className.indexOf('disabled') === -1) {
+            this.className = 'down';
+        }
     };
-    openFileButton.onmousedown = function(evt) {
-      if (this.className.indexOf('disabled') === -1) {
-        this.className = 'down';
-      }
+    PDFViewer.nextPageButton.onmouseup = function(evt) {
+        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
     };
-    openFileButton.onmouseup = function(evt) {
-      this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+    PDFViewer.nextPageButton.onmouseout = function(evt) {
+        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
     };
-    openFileButton.onmouseout = function(evt) {
-      this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+
+    PDFViewer.scaleSelect = document.getElementById('scaleSelect');
+    PDFViewer.scaleSelect.onchange = function(evt) {
+        PDFViewer.changeScale(parseInt(this.value));
     };
     
-    PDFViewer.fileInput = document.getElementById('fileInput');
-    PDFViewer.fileInput.onchange = function(evt) {
-      var files = evt.target.files;
-      
-      if (files.length > 0) {
-        var file = files[0];
-        var fileReader = new FileReader();
-        
-        document.title = file.name;
-        
-        // Read the local file into a Uint8Array.
-        fileReader.onload = function(evt) {
-          var data = evt.target.result;
-          var buffer = new ArrayBuffer(data.length);
-          var uint8Array = new Uint8Array(buffer);
-          
-          for (var i = 0; i < data.length; i++) {
-            uint8Array[i] = data.charCodeAt(i);
-          }
-          
-          PDFViewer.readPDF(uint8Array);
+    if (window.File && window.FileReader && window.FileList && window.Blob) {
+        var openFileButton = document.getElementById('openFileButton');
+        openFileButton.onclick = function(evt) {
+            if (this.className.indexOf('disabled') === -1) {
+                PDFViewer.fileInput.click();
+            }
+        };
+        openFileButton.onmousedown = function(evt) {
+            if (this.className.indexOf('disabled') === -1) {
+                this.className = 'down';
+            }
+        };
+        openFileButton.onmouseup = function(evt) {
+            this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+        };
+        openFileButton.onmouseout = function(evt) {
+            this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
         };
         
-        // Read as a binary string since "readAsArrayBuffer" is not yet
-        // implemented in Firefox.
-        fileReader.readAsBinaryString(file);
-      }
-    };
-    PDFViewer.fileInput.value = null;
-  } else {
-    document.getElementById('fileWrapper').style.display = 'none';
-  }
-  
-  PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
-  PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
-  
-  PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
-  
-  window.onscroll = function(evt) {        
-    var lastPagesDrawn = PDFViewer.lastPagesDrawn;
-    var visiblePages = PDFViewer.visiblePages();
-    
-    var pagesToDraw = [];
-    var pagesToKeep = [];
-    var pagesToRemove = [];
-    
-    var i;
-    
-    // Determine which visible pages were not previously drawn.
-    for (i = 0; i < visiblePages.length; i++) {
-      if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
-        pagesToDraw.push(visiblePages[i]);
-        PDFViewer.drawPage(visiblePages[i]);
-      } else {
-        pagesToKeep.push(visiblePages[i]);
-      }
-    }
-    
-    // Determine which previously drawn pages are no longer visible.
-    for (i = 0; i < lastPagesDrawn.length; i++) {
-      if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
-        pagesToRemove.push(lastPagesDrawn[i]);
-        PDFViewer.removePage(lastPagesDrawn[i]);
-      }
-    }
-    
-    PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
-    
-    // Update the page number input with the current page number.
-    if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
-      PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
-      PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
-      PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
+        PDFViewer.fileInput = document.getElementById('fileInput');
+        PDFViewer.fileInput.onchange = function(evt) {
+            var files = evt.target.files;
+            
+            if (files.length > 0) {
+                var file = files[0];
+                var fileReader = new FileReader();
+                
+                document.title = file.name;
+                
+                // Read the local file into a Uint8Array.
+                fileReader.onload = function(evt) {
+                    var data = evt.target.result;
+                    var buffer = new ArrayBuffer(data.length);
+                    var uint8Array = new Uint8Array(buffer);
+                    
+                    for (var i = 0; i < data.length; i++) {
+                        uint8Array[i] = data.charCodeAt(i);
+                    }
+                    
+                    PDFViewer.readPDF(uint8Array);
+                };
+                
+                // Read as a binary string since "readAsArrayBuffer" is not yet
+                // implemented in Firefox.
+                fileReader.readAsBinaryString(file);
+            }
+        };
+        PDFViewer.fileInput.value = null;
     } else {
-      PDFViewer.willJumpToPage = false;
+        document.getElementById('fileWrapper').style.display = 'none';
     }
-  };
+
+    PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
+    PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
+    
+    PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
+
+    window.onscroll = function(evt) {        
+        var lastPagesDrawn = PDFViewer.lastPagesDrawn;
+        var visiblePages = PDFViewer.visiblePages();
+        
+        var pagesToDraw = [];
+        var pagesToKeep = [];
+        var pagesToRemove = [];
+        
+        var i;
+
+        // Determine which visible pages were not previously drawn.
+        for (i = 0; i < visiblePages.length; i++) {
+            if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
+                pagesToDraw.push(visiblePages[i]);
+                PDFViewer.drawPage(visiblePages[i]);
+            } else {
+                pagesToKeep.push(visiblePages[i]);
+            }
+        }
+
+        // Determine which previously drawn pages are no longer visible.
+        for (i = 0; i < lastPagesDrawn.length; i++) {
+            if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
+                pagesToRemove.push(lastPagesDrawn[i]);
+                PDFViewer.removePage(lastPagesDrawn[i]);
+            }
+        }
+        
+        PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
+        
+        // Update the page number input with the current page number.
+        if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
+            PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
+            PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
+                'disabled' : '';
+            PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
+                'disabled' : '';
+        } else {
+            PDFViewer.willJumpToPage = false;
+        }
+    };
 };
diff --git a/pdf.js b/pdf.js
index b2b6401fd..ffefc61a1 100644
--- a/pdf.js
+++ b/pdf.js
@@ -3275,11 +3275,8 @@ var CanvasGraphics = (function() {
                 }
             }
 
-            if (bitsPerComponent !== 8) {
-                TODO("Support bpc="+ bitsPerComponent);
-                this.restore();
-                return;
-            }
+            if (bitsPerComponent !== 8)
+                error("Unsupported bpc");
 
             var xref = this.xref;
             var colorSpaces = this.colorSpaces;

From f9687a1707adc61d17aff2533b8498bfbfedff82 Mon Sep 17 00:00:00 2001
From: Justin D'Arcangelo <justindarc@gmail.com>
Date: Thu, 23 Jun 2011 21:12:39 -0400
Subject: [PATCH 42/45] Fixed file renaming issues.

---
 multi_page_viewer.css  | 197 -----------------
 multi_page_viewer.html |  51 -----
 multi_page_viewer.js   | 466 -----------------------------------------
 3 files changed, 714 deletions(-)
 delete mode 100644 multi_page_viewer.css
 delete mode 100644 multi_page_viewer.html
 delete mode 100644 multi_page_viewer.js

diff --git a/multi_page_viewer.css b/multi_page_viewer.css
deleted file mode 100644
index 7f4701022..000000000
--- a/multi_page_viewer.css
+++ /dev/null
@@ -1,197 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
-/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
-
-body {
-    background-color: #929292;
-    font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
-    margin: 0px;
-    padding: 0px;
-}
-
-canvas {
-    box-shadow: 0px 4px 10px #000;
-    -moz-box-shadow: 0px 4px 10px #000;
-    -webkit-box-shadow: 0px 4px 10px #000;
-}
-
-span {
-    font-size: 0.8em;
-}
-
-.control {
-    display: inline-block;
-    float: left;
-    margin: 0px 20px 0px 0px;
-    padding: 0px 4px 0px 0px;
-}
-
-.control > input {
-    float: left;
-    border: 1px solid #4d4d4d;
-    height: 20px;
-    padding: 0px;
-    margin: 0px 2px 0px 0px;
-    border-radius: 4px;
-    -moz-border-radius: 4px;
-    -webkit-border-radius: 4px;
-    box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-}
-
-.control > select {
-    float: left;
-    border: 1px solid #4d4d4d;
-    height: 22px;
-    padding: 2px 0px 0px;
-    margin: 0px 0px 1px;
-    border-radius: 4px;
-    -moz-border-radius: 4px;
-    -webkit-border-radius: 4px;
-    box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-}
-
-.control > span {
-    cursor: default;
-    float: left;
-    height: 18px;
-    margin: 5px 2px 0px;
-    padding: 0px;
-    user-select: none;
-    -moz-user-select: none;
-    -webkit-user-select: none;
-}
-
-.control .label {
-    clear: both;
-    float: left;
-    font-size: 0.65em;
-    margin: 2px 0px 0px;
-    position: relative;
-    text-align: center;
-    width: 100%;
-}
-
-.page {
-    width: 816px;
-    height: 1056px;
-    margin: 10px auto;
-}
-
-#controls {
-    background-color: #eee;
-    border-bottom: 1px solid #666;
-    padding: 4px 0px 0px 8px;
-    position: fixed;
-    left: 0px;
-    top: 0px;
-    height: 40px;
-    width: 100%;
-    box-shadow: 0px 2px 8px #000;
-    -moz-box-shadow: 0px 2px 8px #000;
-    -webkit-box-shadow: 0px 2px 8px #000;
-}
-
-#controls input {
-    user-select: text;
-    -moz-user-select: text;
-    -webkit-user-select: text;
-}
-
-#previousPageButton {
-    background: url('images/buttons.png') no-repeat 0px -23px;
-    cursor: default;
-    display: inline-block;
-    float: left;
-    margin: 0px;
-    width: 28px;
-    height: 23px;
-}
-
-#previousPageButton.down {
-    background: url('images/buttons.png') no-repeat 0px -46px;
-}
-
-#previousPageButton.disabled {
-    background: url('images/buttons.png') no-repeat 0px 0px;
-}
-
-#nextPageButton {
-    background: url('images/buttons.png') no-repeat -28px -23px;
-    cursor: default;
-    display: inline-block;
-    float: left;
-    margin: 0px;
-    width: 28px;
-    height: 23px;
-}
-
-#nextPageButton.down {
-    background: url('images/buttons.png') no-repeat -28px -46px;
-}
-
-#nextPageButton.disabled {
-    background: url('images/buttons.png') no-repeat -28px 0px;
-}
-
-#openFileButton {
-    background: url('images/buttons.png') no-repeat -56px -23px;
-    cursor: default;
-    display: inline-block;
-    float: left;
-    margin: 0px 0px 0px 3px;
-    width: 29px;
-    height: 23px;
-}
-
-#openFileButton.down {
-    background: url('images/buttons.png') no-repeat -56px -46px;
-}
-
-#openFileButton.disabled {
-    background: url('images/buttons.png') no-repeat -56px 0px;
-}
-
-#fileInput {
-    display: none;
-}
-
-#pageNumber {
-    text-align: right;
-}
-
-#sidebar {
-    background-color: rgba(0, 0, 0, 0.8);
-    position: fixed;
-    width: 150px;
-    top: 62px;
-    bottom: 18px;
-    border-top-right-radius: 8px;
-    border-bottom-right-radius: 8px;
-    -moz-border-radius-topright: 8px;
-    -moz-border-radius-bottomright: 8px;
-    -webkit-border-top-right-radius: 8px;
-    -webkit-border-bottom-right-radius: 8px;
-}
-
-#sidebarScrollView {
-    position: absolute;
-    overflow: hidden;
-    overflow-y: auto;
-    top: 40px;
-    right: 10px;
-    bottom: 10px;
-    left: 10px;
-}
-
-#sidebarContentView {
-    height: auto;
-    width: 100px;
-}
-
-#viewer {
-    margin: 44px 0px 0px;
-    padding: 8px 0px;
-}
diff --git a/multi_page_viewer.html b/multi_page_viewer.html
deleted file mode 100644
index ffbdfe707..000000000
--- a/multi_page_viewer.html
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>pdf.js Multi-Page Viewer</title>
-<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>
-<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen"/>
-<script type="text/javascript" src="pdf.js"></script>
-<script type="text/javascript" src="fonts.js"></script>
-<script type="text/javascript" src="glyphlist.js"></script>
-<script type="text/javascript" src="multi-page-viewer.js"></script>
-</head>
-<body>
-    <div id="controls">
-        <span class="control">
-            <span id="previousPageButton" class="disabled"></span>
-            <span id="nextPageButton" class="disabled"></span>
-            <span class="label">Previous/Next</span>
-        </span>
-        <span class="control">
-            <input type="text" id="pageNumber" value="1" size="2"/>
-            <span>/</span>
-            <span id="numPages">--</span>
-            <span class="label">Page Number</span>
-        </span>
-        <span class="control">
-            <select id="scaleSelect">
-                <option value="50">50%</option>
-                <option value="75">75%</option>
-                <option value="100" selected="selected">100%</option>
-                <option value="125">125%</option>
-                <option value="150">150%</option>
-                <option value="200">200%</option>
-            </select>
-            <span class="label">Zoom</span>
-        </span>
-        <span class="control">
-            <span id="openFileButton"></span>
-            <input type="file" id="fileInput"/>
-            <span class="label">Open File</span>
-        </span>
-    </div>
-    <!--<div id="sidebar">
-        <div id="sidebarScrollView">
-            <div id="sidebarContentView">
-                
-            </div>
-        </div>
-    </div>-->
-    <div id="viewer"></div>
-</body>
-</html>
diff --git a/multi_page_viewer.js b/multi_page_viewer.js
deleted file mode 100644
index baad7809e..000000000
--- a/multi_page_viewer.js
+++ /dev/null
@@ -1,466 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
-/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
-
-"use strict";
-
-var PDFViewer = {
-    queryParams: {},
-
-    element: null,
-
-    previousPageButton: null,
-    nextPageButton: null,
-    pageNumberInput: null,
-    scaleSelect: null,
-    fileInput: null,
-    
-    willJumpToPage: false,
-
-    pdf: null,
-
-    url: 'compressed.tracemonkey-pldi-09.pdf',
-    pageNumber: 1,
-    numberOfPages: 1,
-
-    scale: 1.0,
-
-    pageWidth: function() {
-        return 816 * PDFViewer.scale;
-    },
-
-    pageHeight: function() {
-        return 1056 * PDFViewer.scale;
-    },
-    
-    lastPagesDrawn: [],
-    
-    visiblePages: function() {  
-        var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.      
-        var windowTop = window.pageYOffset;
-        var windowBottom = window.pageYOffset + window.innerHeight;
-        var pageStartIndex = Math.floor(windowTop / pageHeight);
-        var pageStopIndex = Math.ceil(windowBottom / pageHeight);
-
-        var pages = [];  
-
-        for (var i = pageStartIndex; i <= pageStopIndex; i++) {
-            pages.push(i + 1);
-        }
-
-        return pages;
-    },
-  
-    createPage: function(num) {
-        var anchor = document.createElement('a');
-        anchor.name = '' + num;
-    
-        var div = document.createElement('div');
-        div.id = 'pageContainer' + num;
-        div.className = 'page';
-        div.style.width = PDFViewer.pageWidth() + 'px';
-        div.style.height = PDFViewer.pageHeight() + 'px';
-        
-        PDFViewer.element.appendChild(anchor);
-        PDFViewer.element.appendChild(div);
-    },
-    
-    removePage: function(num) {
-        var div = document.getElementById('pageContainer' + num);
-        
-        if (div) {
-            while (div.hasChildNodes()) {
-                div.removeChild(div.firstChild);
-            }
-        }
-    },
-
-    drawPage: function(num) {
-        if (!PDFViewer.pdf) {
-            return;
-        }
-        
-        var div = document.getElementById('pageContainer' + num);
-        var canvas = document.createElement('canvas');
-        
-        if (div && !div.hasChildNodes()) {
-            div.appendChild(canvas);
-            
-            var page = PDFViewer.pdf.getPage(num);
-
-            canvas.id = 'page' + num;
-            canvas.mozOpaque = true;
-
-            // Canvas dimensions must be specified in CSS pixels. CSS pixels
-            // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
-            canvas.width = PDFViewer.pageWidth();
-            canvas.height = PDFViewer.pageHeight();
-
-            var ctx = canvas.getContext('2d');
-            ctx.save();
-            ctx.fillStyle = 'rgb(255, 255, 255)';
-            ctx.fillRect(0, 0, canvas.width, canvas.height);
-            ctx.restore();
-
-            var gfx = new CanvasGraphics(ctx);
-            var fonts = [];
-        
-            // page.compile will collect all fonts for us, once we have loaded them
-            // we can trigger the actual page rendering with page.display
-            page.compile(gfx, fonts);
-        
-            var areFontsReady = true;
-        
-            // Inspect fonts and translate the missing one
-            var fontCount = fonts.length;
-        
-            for (var i = 0; i < fontCount; i++) {
-                var font = fonts[i];
-            
-                if (Fonts[font.name]) {
-                    areFontsReady = areFontsReady && !Fonts[font.name].loading;
-                    continue;
-                }
-
-                new Font(font.name, font.file, font.properties);
-            
-                areFontsReady = false;
-            }
-
-            var pageInterval;
-            
-            var delayLoadFont = function() {
-                for (var i = 0; i < fontCount; i++) {
-                    if (Fonts[font.name].loading) {
-                        return;
-                    }
-                }
-
-                clearInterval(pageInterval);
-                
-                while (div.hasChildNodes()) {
-                    div.removeChild(div.firstChild);
-                }
-                
-                PDFViewer.drawPage(num);
-            }
-
-            if (!areFontsReady) {
-                pageInterval = setInterval(delayLoadFont, 10);
-                return;
-            }
-
-            page.display(gfx);
-        }
-    },
-
-    changeScale: function(num) {
-        while (PDFViewer.element.hasChildNodes()) {
-            PDFViewer.element.removeChild(PDFViewer.element.firstChild);
-        }
-
-        PDFViewer.scale = num / 100;
-
-        var i;
-
-        if (PDFViewer.pdf) {
-            for (i = 1; i <= PDFViewer.numberOfPages; i++) {
-                PDFViewer.createPage(i);
-            }
-
-            if (PDFViewer.numberOfPages > 0) {
-                PDFViewer.drawPage(1);
-            }
-        }
-
-        for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
-            var option = PDFViewer.scaleSelect.childNodes[i];
-            
-            if (option.value == num) {
-                if (!option.selected) {
-                    option.selected = 'selected';
-                }
-            } else {
-                if (option.selected) {
-                    option.removeAttribute('selected');
-                }
-            }
-        }
-        
-        PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
-    },
-
-    goToPage: function(num) {
-        if (1 <= num && num <= PDFViewer.numberOfPages) {
-            PDFViewer.pageNumber = num;
-            PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
-            PDFViewer.willJumpToPage = true;
-
-            document.location.hash = PDFViewer.pageNumber;
-
-            PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
-                'disabled' : '';
-            PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
-                'disabled' : '';
-        }
-    },
-  
-    goToPreviousPage: function() {
-        if (PDFViewer.pageNumber > 1) {
-            PDFViewer.goToPage(--PDFViewer.pageNumber);
-        }
-    },
-  
-    goToNextPage: function() {
-        if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
-            PDFViewer.goToPage(++PDFViewer.pageNumber);
-        }
-    },
-  
-    openURL: function(url) {
-        PDFViewer.url = url;
-        document.title = url;
-    
-        var req = new XMLHttpRequest();
-        req.open('GET', url);
-        req.mozResponseType = req.responseType = 'arraybuffer';
-        req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
-    
-        req.onreadystatechange = function() {
-            if (req.readyState === 4 && req.status === req.expected) {
-                var data = req.mozResponseArrayBuffer ||
-                    req.mozResponse ||
-                    req.responseArrayBuffer ||
-                    req.response;
-
-                PDFViewer.readPDF(data);
-            }
-        };
-    
-        req.send(null);
-    },
-    
-    readPDF: function(data) {
-        while (PDFViewer.element.hasChildNodes()) {
-            PDFViewer.element.removeChild(PDFViewer.element.firstChild);
-        }
-        
-        PDFViewer.pdf = new PDFDoc(new Stream(data));
-        PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
-        document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
-  
-        for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
-            PDFViewer.createPage(i);
-        }
-
-        if (PDFViewer.numberOfPages > 0) {
-            PDFViewer.drawPage(1);
-            document.location.hash = 1;
-        }
-        
-        PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
-             'disabled' : '';
-         PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
-             'disabled' : '';
-    }
-};
-
-window.onload = function() {
-
-    // Parse the URL query parameters into a cached object.
-    PDFViewer.queryParams = function() {
-        var qs = window.location.search.substring(1);
-        var kvs = qs.split('&');
-        var params = {};
-        for (var i = 0; i < kvs.length; ++i) {
-            var kv = kvs[i].split('=');
-            params[unescape(kv[0])] = unescape(kv[1]);
-        }
-        
-        return params;
-    }();
-
-    PDFViewer.element = document.getElementById('viewer');
-
-    PDFViewer.pageNumberInput = document.getElementById('pageNumber');
-    PDFViewer.pageNumberInput.onkeydown = function(evt) {
-        var charCode = evt.charCode || evt.keyCode;
-
-        // Up arrow key.
-        if (charCode === 38) {
-            PDFViewer.goToNextPage();
-            this.select();
-        }
-        
-        // Down arrow key.
-        else if (charCode === 40) {
-            PDFViewer.goToPreviousPage();
-            this.select();
-        }
-
-        // All other non-numeric keys (excluding Left arrow, Right arrow,
-        // Backspace, and Delete keys).
-        else if ((charCode < 48 || charCode > 57) &&
-            charCode !== 8 &&   // Backspace
-            charCode !== 46 &&  // Delete
-            charCode !== 37 &&  // Left arrow
-            charCode !== 39     // Right arrow
-        ) {
-            return false;
-        }
-
-        return true;
-    };
-    PDFViewer.pageNumberInput.onkeyup = function(evt) {
-        var charCode = evt.charCode || evt.keyCode;
-
-        // All numeric keys, Backspace, and Delete.
-        if ((charCode >= 48 && charCode <= 57) ||
-            charCode === 8 ||   // Backspace
-            charCode === 46     // Delete
-        ) {
-            PDFViewer.goToPage(this.value);
-        }
-        
-        this.focus();
-    };
-
-    PDFViewer.previousPageButton = document.getElementById('previousPageButton');
-    PDFViewer.previousPageButton.onclick = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-            PDFViewer.goToPreviousPage();
-        }
-    };
-    PDFViewer.previousPageButton.onmousedown = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-             this.className = 'down';
-        }
-    };
-    PDFViewer.previousPageButton.onmouseup = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-    };
-    PDFViewer.previousPageButton.onmouseout = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-    };
-    
-    PDFViewer.nextPageButton = document.getElementById('nextPageButton');
-    PDFViewer.nextPageButton.onclick = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-            PDFViewer.goToNextPage();
-        }
-    };
-    PDFViewer.nextPageButton.onmousedown = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-            this.className = 'down';
-        }
-    };
-    PDFViewer.nextPageButton.onmouseup = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-    };
-    PDFViewer.nextPageButton.onmouseout = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-    };
-
-    PDFViewer.scaleSelect = document.getElementById('scaleSelect');
-    PDFViewer.scaleSelect.onchange = function(evt) {
-        PDFViewer.changeScale(parseInt(this.value));
-    };
-    
-    if (window.File && window.FileReader && window.FileList && window.Blob) {
-        var openFileButton = document.getElementById('openFileButton');
-        openFileButton.onclick = function(evt) {
-            if (this.className.indexOf('disabled') === -1) {
-                PDFViewer.fileInput.click();
-            }
-        };
-        openFileButton.onmousedown = function(evt) {
-            if (this.className.indexOf('disabled') === -1) {
-                this.className = 'down';
-            }
-        };
-        openFileButton.onmouseup = function(evt) {
-            this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-        };
-        openFileButton.onmouseout = function(evt) {
-            this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-        };
-        
-        PDFViewer.fileInput = document.getElementById('fileInput');
-        PDFViewer.fileInput.onchange = function(evt) {
-            var files = evt.target.files;
-            
-            if (files.length > 0) {
-                var file = files[0];
-                var fileReader = new FileReader();
-                
-                document.title = file.name;
-                
-                // Read the local file into a Uint8Array.
-                fileReader.onload = function(evt) {
-                    var data = evt.target.result;
-                    var buffer = new ArrayBuffer(data.length);
-                    var uint8Array = new Uint8Array(buffer);
-                    
-                    for (var i = 0; i < data.length; i++) {
-                        uint8Array[i] = data.charCodeAt(i);
-                    }
-                    
-                    PDFViewer.readPDF(uint8Array);
-                };
-                
-                // Read as a binary string since "readAsArrayBuffer" is not yet
-                // implemented in Firefox.
-                fileReader.readAsBinaryString(file);
-            }
-        };
-        PDFViewer.fileInput.value = null;
-    } else {
-        document.getElementById('fileWrapper').style.display = 'none';
-    }
-
-    PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
-    PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
-    
-    PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
-
-    window.onscroll = function(evt) {        
-        var lastPagesDrawn = PDFViewer.lastPagesDrawn;
-        var visiblePages = PDFViewer.visiblePages();
-        
-        var pagesToDraw = [];
-        var pagesToKeep = [];
-        var pagesToRemove = [];
-        
-        var i;
-
-        // Determine which visible pages were not previously drawn.
-        for (i = 0; i < visiblePages.length; i++) {
-            if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
-                pagesToDraw.push(visiblePages[i]);
-                PDFViewer.drawPage(visiblePages[i]);
-            } else {
-                pagesToKeep.push(visiblePages[i]);
-            }
-        }
-
-        // Determine which previously drawn pages are no longer visible.
-        for (i = 0; i < lastPagesDrawn.length; i++) {
-            if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
-                pagesToRemove.push(lastPagesDrawn[i]);
-                PDFViewer.removePage(lastPagesDrawn[i]);
-            }
-        }
-        
-        PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
-        
-        // Update the page number input with the current page number.
-        if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
-            PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
-            PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
-                'disabled' : '';
-            PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
-                'disabled' : '';
-        } else {
-            PDFViewer.willJumpToPage = false;
-        }
-    };
-};

From b18006a055b07eefc6b1495c7a581c1fa55681b5 Mon Sep 17 00:00:00 2001
From: Justin D'Arcangelo <justindarc@gmail.com>
Date: Thu, 23 Jun 2011 21:17:31 -0400
Subject: [PATCH 43/45] Fixed file renaming issues.

---
 multi-page-viewer.css => multi_page_viewer.css   | 0
 multi-page-viewer.html => multi_page_viewer.html | 0
 multi-page-viewer.js => multi_page_viewer.js     | 0
 3 files changed, 0 insertions(+), 0 deletions(-)
 rename multi-page-viewer.css => multi_page_viewer.css (100%)
 rename multi-page-viewer.html => multi_page_viewer.html (100%)
 rename multi-page-viewer.js => multi_page_viewer.js (100%)

diff --git a/multi-page-viewer.css b/multi_page_viewer.css
similarity index 100%
rename from multi-page-viewer.css
rename to multi_page_viewer.css
diff --git a/multi-page-viewer.html b/multi_page_viewer.html
similarity index 100%
rename from multi-page-viewer.html
rename to multi_page_viewer.html
diff --git a/multi-page-viewer.js b/multi_page_viewer.js
similarity index 100%
rename from multi-page-viewer.js
rename to multi_page_viewer.js

From b9af0da8f8bd59874b1141649b5b84dc30b7ca71 Mon Sep 17 00:00:00 2001
From: Justin D'Arcangelo <justindarc@gmail.com>
Date: Thu, 23 Jun 2011 21:18:07 -0400
Subject: [PATCH 44/45] Fixed vim indentation rules.

---
 multi_page_viewer.css  | 238 ++++++------
 multi_page_viewer.html |  76 ++--
 multi_page_viewer.js   | 846 ++++++++++++++++++++---------------------
 3 files changed, 576 insertions(+), 584 deletions(-)

diff --git a/multi_page_viewer.css b/multi_page_viewer.css
index 7f4701022..b3eaab792 100644
--- a/multi_page_viewer.css
+++ b/multi_page_viewer.css
@@ -1,197 +1,197 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
-/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
 body {
-    background-color: #929292;
-    font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
-    margin: 0px;
-    padding: 0px;
+  background-color: #929292;
+  font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
+  margin: 0px;
+  padding: 0px;
 }
 
 canvas {
-    box-shadow: 0px 4px 10px #000;
-    -moz-box-shadow: 0px 4px 10px #000;
-    -webkit-box-shadow: 0px 4px 10px #000;
+  box-shadow: 0px 4px 10px #000;
+  -moz-box-shadow: 0px 4px 10px #000;
+  -webkit-box-shadow: 0px 4px 10px #000;
 }
 
 span {
-    font-size: 0.8em;
+  font-size: 0.8em;
 }
 
 .control {
-    display: inline-block;
-    float: left;
-    margin: 0px 20px 0px 0px;
-    padding: 0px 4px 0px 0px;
+  display: inline-block;
+  float: left;
+  margin: 0px 20px 0px 0px;
+  padding: 0px 4px 0px 0px;
 }
 
 .control > input {
-    float: left;
-    border: 1px solid #4d4d4d;
-    height: 20px;
-    padding: 0px;
-    margin: 0px 2px 0px 0px;
-    border-radius: 4px;
-    -moz-border-radius: 4px;
-    -webkit-border-radius: 4px;
-    box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+  float: left;
+  border: 1px solid #4d4d4d;
+  height: 20px;
+  padding: 0px;
+  margin: 0px 2px 0px 0px;
+  border-radius: 4px;
+  -moz-border-radius: 4px;
+  -webkit-border-radius: 4px;
+  box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+  -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+  -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
 }
 
 .control > select {
-    float: left;
-    border: 1px solid #4d4d4d;
-    height: 22px;
-    padding: 2px 0px 0px;
-    margin: 0px 0px 1px;
-    border-radius: 4px;
-    -moz-border-radius: 4px;
-    -webkit-border-radius: 4px;
-    box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-    -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+  float: left;
+  border: 1px solid #4d4d4d;
+  height: 22px;
+  padding: 2px 0px 0px;
+  margin: 0px 0px 1px;
+  border-radius: 4px;
+  -moz-border-radius: 4px;
+  -webkit-border-radius: 4px;
+  box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+  -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+  -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
 }
 
 .control > span {
-    cursor: default;
-    float: left;
-    height: 18px;
-    margin: 5px 2px 0px;
-    padding: 0px;
-    user-select: none;
-    -moz-user-select: none;
-    -webkit-user-select: none;
+  cursor: default;
+  float: left;
+  height: 18px;
+  margin: 5px 2px 0px;
+  padding: 0px;
+  user-select: none;
+  -moz-user-select: none;
+  -webkit-user-select: none;
 }
 
 .control .label {
-    clear: both;
-    float: left;
-    font-size: 0.65em;
-    margin: 2px 0px 0px;
-    position: relative;
-    text-align: center;
-    width: 100%;
+  clear: both;
+  float: left;
+  font-size: 0.65em;
+  margin: 2px 0px 0px;
+  position: relative;
+  text-align: center;
+  width: 100%;
 }
 
 .page {
-    width: 816px;
-    height: 1056px;
-    margin: 10px auto;
+  width: 816px;
+  height: 1056px;
+  margin: 10px auto;
 }
 
 #controls {
-    background-color: #eee;
-    border-bottom: 1px solid #666;
-    padding: 4px 0px 0px 8px;
-    position: fixed;
-    left: 0px;
-    top: 0px;
-    height: 40px;
-    width: 100%;
-    box-shadow: 0px 2px 8px #000;
-    -moz-box-shadow: 0px 2px 8px #000;
-    -webkit-box-shadow: 0px 2px 8px #000;
+  background-color: #eee;
+  border-bottom: 1px solid #666;
+  padding: 4px 0px 0px 8px;
+  position: fixed;
+  left: 0px;
+  top: 0px;
+  height: 40px;
+  width: 100%;
+  box-shadow: 0px 2px 8px #000;
+  -moz-box-shadow: 0px 2px 8px #000;
+  -webkit-box-shadow: 0px 2px 8px #000;
 }
 
 #controls input {
-    user-select: text;
-    -moz-user-select: text;
-    -webkit-user-select: text;
+  user-select: text;
+  -moz-user-select: text;
+  -webkit-user-select: text;
 }
 
 #previousPageButton {
-    background: url('images/buttons.png') no-repeat 0px -23px;
-    cursor: default;
-    display: inline-block;
-    float: left;
-    margin: 0px;
-    width: 28px;
-    height: 23px;
+  background: url('images/buttons.png') no-repeat 0px -23px;
+  cursor: default;
+  display: inline-block;
+  float: left;
+  margin: 0px;
+  width: 28px;
+  height: 23px;
 }
 
 #previousPageButton.down {
-    background: url('images/buttons.png') no-repeat 0px -46px;
+  background: url('images/buttons.png') no-repeat 0px -46px;
 }
 
 #previousPageButton.disabled {
-    background: url('images/buttons.png') no-repeat 0px 0px;
+  background: url('images/buttons.png') no-repeat 0px 0px;
 }
 
 #nextPageButton {
-    background: url('images/buttons.png') no-repeat -28px -23px;
-    cursor: default;
-    display: inline-block;
-    float: left;
-    margin: 0px;
-    width: 28px;
-    height: 23px;
+  background: url('images/buttons.png') no-repeat -28px -23px;
+  cursor: default;
+  display: inline-block;
+  float: left;
+  margin: 0px;
+  width: 28px;
+  height: 23px;
 }
 
 #nextPageButton.down {
-    background: url('images/buttons.png') no-repeat -28px -46px;
+  background: url('images/buttons.png') no-repeat -28px -46px;
 }
 
 #nextPageButton.disabled {
-    background: url('images/buttons.png') no-repeat -28px 0px;
+  background: url('images/buttons.png') no-repeat -28px 0px;
 }
 
 #openFileButton {
-    background: url('images/buttons.png') no-repeat -56px -23px;
-    cursor: default;
-    display: inline-block;
-    float: left;
-    margin: 0px 0px 0px 3px;
-    width: 29px;
-    height: 23px;
+  background: url('images/buttons.png') no-repeat -56px -23px;
+  cursor: default;
+  display: inline-block;
+  float: left;
+  margin: 0px 0px 0px 3px;
+  width: 29px;
+  height: 23px;
 }
 
 #openFileButton.down {
-    background: url('images/buttons.png') no-repeat -56px -46px;
+  background: url('images/buttons.png') no-repeat -56px -46px;
 }
 
 #openFileButton.disabled {
-    background: url('images/buttons.png') no-repeat -56px 0px;
+  background: url('images/buttons.png') no-repeat -56px 0px;
 }
 
 #fileInput {
-    display: none;
+  display: none;
 }
 
 #pageNumber {
-    text-align: right;
+  text-align: right;
 }
 
 #sidebar {
-    background-color: rgba(0, 0, 0, 0.8);
-    position: fixed;
-    width: 150px;
-    top: 62px;
-    bottom: 18px;
-    border-top-right-radius: 8px;
-    border-bottom-right-radius: 8px;
-    -moz-border-radius-topright: 8px;
-    -moz-border-radius-bottomright: 8px;
-    -webkit-border-top-right-radius: 8px;
-    -webkit-border-bottom-right-radius: 8px;
+  background-color: rgba(0, 0, 0, 0.8);
+  position: fixed;
+  width: 150px;
+  top: 62px;
+  bottom: 18px;
+  border-top-right-radius: 8px;
+  border-bottom-right-radius: 8px;
+  -moz-border-radius-topright: 8px;
+  -moz-border-radius-bottomright: 8px;
+  -webkit-border-top-right-radius: 8px;
+  -webkit-border-bottom-right-radius: 8px;
 }
 
 #sidebarScrollView {
-    position: absolute;
-    overflow: hidden;
-    overflow-y: auto;
-    top: 40px;
-    right: 10px;
-    bottom: 10px;
-    left: 10px;
+  position: absolute;
+  overflow: hidden;
+  overflow-y: auto;
+  top: 40px;
+  right: 10px;
+  bottom: 10px;
+  left: 10px;
 }
 
 #sidebarContentView {
-    height: auto;
-    width: 100px;
+  height: auto;
+  width: 100px;
 }
 
 #viewer {
-    margin: 44px 0px 0px;
-    padding: 8px 0px;
+  margin: 44px 0px 0px;
+  padding: 8px 0px;
 }
diff --git a/multi_page_viewer.html b/multi_page_viewer.html
index ffbdfe707..47234686d 100644
--- a/multi_page_viewer.html
+++ b/multi_page_viewer.html
@@ -3,49 +3,49 @@
 <head>
 <title>pdf.js Multi-Page Viewer</title>
 <meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>
-<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen"/>
+<link rel="stylesheet" href="multi_page_viewer.css" type="text/css" media="screen"/>
 <script type="text/javascript" src="pdf.js"></script>
 <script type="text/javascript" src="fonts.js"></script>
 <script type="text/javascript" src="glyphlist.js"></script>
-<script type="text/javascript" src="multi-page-viewer.js"></script>
+<script type="text/javascript" src="multi_page_viewer.js"></script>
 </head>
 <body>
-    <div id="controls">
-        <span class="control">
-            <span id="previousPageButton" class="disabled"></span>
-            <span id="nextPageButton" class="disabled"></span>
-            <span class="label">Previous/Next</span>
-        </span>
-        <span class="control">
-            <input type="text" id="pageNumber" value="1" size="2"/>
-            <span>/</span>
-            <span id="numPages">--</span>
-            <span class="label">Page Number</span>
-        </span>
-        <span class="control">
-            <select id="scaleSelect">
-                <option value="50">50%</option>
-                <option value="75">75%</option>
-                <option value="100" selected="selected">100%</option>
-                <option value="125">125%</option>
-                <option value="150">150%</option>
-                <option value="200">200%</option>
-            </select>
-            <span class="label">Zoom</span>
-        </span>
-        <span class="control">
-            <span id="openFileButton"></span>
-            <input type="file" id="fileInput"/>
-            <span class="label">Open File</span>
-        </span>
+  <div id="controls">
+    <span class="control">
+      <span id="previousPageButton" class="disabled"></span>
+      <span id="nextPageButton" class="disabled"></span>
+      <span class="label">Previous/Next</span>
+    </span>
+    <span class="control">
+      <input type="text" id="pageNumber" value="1" size="2"/>
+      <span>/</span>
+      <span id="numPages">--</span>
+      <span class="label">Page Number</span>
+    </span>
+    <span class="control">
+      <select id="scaleSelect">
+        <option value="50">50%</option>
+        <option value="75">75%</option>
+        <option value="100" selected="selected">100%</option>
+        <option value="125">125%</option>
+        <option value="150">150%</option>
+        <option value="200">200%</option>
+      </select>
+      <span class="label">Zoom</span>
+    </span>
+    <span class="control">
+      <span id="openFileButton"></span>
+      <input type="file" id="fileInput"/>
+      <span class="label">Open File</span>
+    </span>
+  </div>
+  <!--<div id="sidebar">
+    <div id="sidebarScrollView">
+      <div id="sidebarContentView">
+        
+      </div>
     </div>
-    <!--<div id="sidebar">
-        <div id="sidebarScrollView">
-            <div id="sidebarContentView">
-                
-            </div>
-        </div>
-    </div>-->
-    <div id="viewer"></div>
+  </div>-->
+  <div id="viewer"></div>
 </body>
 </html>
diff --git a/multi_page_viewer.js b/multi_page_viewer.js
index baad7809e..3a02ea332 100644
--- a/multi_page_viewer.js
+++ b/multi_page_viewer.js
@@ -1,466 +1,458 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
-/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
 "use strict";
 
 var PDFViewer = {
-    queryParams: {},
-
-    element: null,
-
-    previousPageButton: null,
-    nextPageButton: null,
-    pageNumberInput: null,
-    scaleSelect: null,
-    fileInput: null,
+  queryParams: {},
+  
+  element: null,
+  
+  previousPageButton: null,
+  nextPageButton: null,
+  pageNumberInput: null,
+  scaleSelect: null,
+  fileInput: null,
+  
+  willJumpToPage: false,
+  
+  pdf: null,
+  
+  url: 'compressed.tracemonkey-pldi-09.pdf',
+  pageNumber: 1,
+  numberOfPages: 1,
+  
+  scale: 1.0,
+  
+  pageWidth: function() {
+    return 816 * PDFViewer.scale;
+  },
+  
+  pageHeight: function() {
+    return 1056 * PDFViewer.scale;
+  },
+  
+  lastPagesDrawn: [],
+  
+  visiblePages: function() {  
+    var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.      
+    var windowTop = window.pageYOffset;
+    var windowBottom = window.pageYOffset + window.innerHeight;
+    var pageStartIndex = Math.floor(windowTop / pageHeight);
+    var pageStopIndex = Math.ceil(windowBottom / pageHeight);
     
-    willJumpToPage: false,
-
-    pdf: null,
-
-    url: 'compressed.tracemonkey-pldi-09.pdf',
-    pageNumber: 1,
-    numberOfPages: 1,
-
-    scale: 1.0,
-
-    pageWidth: function() {
-        return 816 * PDFViewer.scale;
-    },
-
-    pageHeight: function() {
-        return 1056 * PDFViewer.scale;
-    },
-    
-    lastPagesDrawn: [],
-    
-    visiblePages: function() {  
-        var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.      
-        var windowTop = window.pageYOffset;
-        var windowBottom = window.pageYOffset + window.innerHeight;
-        var pageStartIndex = Math.floor(windowTop / pageHeight);
-        var pageStopIndex = Math.ceil(windowBottom / pageHeight);
-
-        var pages = [];  
-
-        for (var i = pageStartIndex; i <= pageStopIndex; i++) {
-            pages.push(i + 1);
-        }
-
-        return pages;
-    },
+    var pages = [];  
+    
+    for (var i = pageStartIndex; i <= pageStopIndex; i++) {
+      pages.push(i + 1);
+    }
+    
+    return pages;
+  },
   
-    createPage: function(num) {
-        var anchor = document.createElement('a');
-        anchor.name = '' + num;
-    
-        var div = document.createElement('div');
-        div.id = 'pageContainer' + num;
-        div.className = 'page';
-        div.style.width = PDFViewer.pageWidth() + 'px';
-        div.style.height = PDFViewer.pageHeight() + 'px';
-        
-        PDFViewer.element.appendChild(anchor);
-        PDFViewer.element.appendChild(div);
-    },
+  createPage: function(num) {
+    var anchor = document.createElement('a');
+    anchor.name = '' + num;
+    
+    var div = document.createElement('div');
+    div.id = 'pageContainer' + num;
+    div.className = 'page';
+    div.style.width = PDFViewer.pageWidth() + 'px';
+    div.style.height = PDFViewer.pageHeight() + 'px';
+    
+    PDFViewer.element.appendChild(anchor);
+    PDFViewer.element.appendChild(div);
+  },
+  
+  removePage: function(num) {
+    var div = document.getElementById('pageContainer' + num);
+    
+    if (div) {
+      while (div.hasChildNodes()) {
+        div.removeChild(div.firstChild);
+      }
+    }
+  },
+  
+  drawPage: function(num) {
+    if (!PDFViewer.pdf) {
+      return;
+    }
+    
+    var div = document.getElementById('pageContainer' + num);
+    var canvas = document.createElement('canvas');
     
-    removePage: function(num) {
-        var div = document.getElementById('pageContainer' + num);
+    if (div && !div.hasChildNodes()) {
+      div.appendChild(canvas);
+      
+      var page = PDFViewer.pdf.getPage(num);
+      
+      canvas.id = 'page' + num;
+      canvas.mozOpaque = true;
+      
+      // Canvas dimensions must be specified in CSS pixels. CSS pixels
+      // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
+      canvas.width = PDFViewer.pageWidth();
+      canvas.height = PDFViewer.pageHeight();
+      
+      var ctx = canvas.getContext('2d');
+      ctx.save();
+      ctx.fillStyle = 'rgb(255, 255, 255)';
+      ctx.fillRect(0, 0, canvas.width, canvas.height);
+      ctx.restore();
+      
+      var gfx = new CanvasGraphics(ctx);
+      var fonts = [];
+      
+      // page.compile will collect all fonts for us, once we have loaded them
+      // we can trigger the actual page rendering with page.display
+      page.compile(gfx, fonts);
+      
+      var areFontsReady = true;
+      
+      // Inspect fonts and translate the missing one
+      var fontCount = fonts.length;
+      
+      for (var i = 0; i < fontCount; i++) {
+        var font = fonts[i];
         
-        if (div) {
-            while (div.hasChildNodes()) {
-                div.removeChild(div.firstChild);
-            }
-        }
-    },
-
-    drawPage: function(num) {
-        if (!PDFViewer.pdf) {
-            return;
+        if (Fonts[font.name]) {
+          areFontsReady = areFontsReady && !Fonts[font.name].loading;
+          continue;
         }
         
-        var div = document.getElementById('pageContainer' + num);
-        var canvas = document.createElement('canvas');
+        new Font(font.name, font.file, font.properties);
         
-        if (div && !div.hasChildNodes()) {
-            div.appendChild(canvas);
-            
-            var page = PDFViewer.pdf.getPage(num);
-
-            canvas.id = 'page' + num;
-            canvas.mozOpaque = true;
-
-            // Canvas dimensions must be specified in CSS pixels. CSS pixels
-            // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
-            canvas.width = PDFViewer.pageWidth();
-            canvas.height = PDFViewer.pageHeight();
-
-            var ctx = canvas.getContext('2d');
-            ctx.save();
-            ctx.fillStyle = 'rgb(255, 255, 255)';
-            ctx.fillRect(0, 0, canvas.width, canvas.height);
-            ctx.restore();
-
-            var gfx = new CanvasGraphics(ctx);
-            var fonts = [];
-        
-            // page.compile will collect all fonts for us, once we have loaded them
-            // we can trigger the actual page rendering with page.display
-            page.compile(gfx, fonts);
-        
-            var areFontsReady = true;
+        areFontsReady = false;
+      }
+      
+      var pageInterval;
+      
+      var delayLoadFont = function() {
+        for (var i = 0; i < fontCount; i++) {
+          if (Fonts[font.name].loading) {
+            return;
+          }
+        }
         
-            // Inspect fonts and translate the missing one
-            var fontCount = fonts.length;
+        clearInterval(pageInterval);
         
-            for (var i = 0; i < fontCount; i++) {
-                var font = fonts[i];
-            
-                if (Fonts[font.name]) {
-                    areFontsReady = areFontsReady && !Fonts[font.name].loading;
-                    continue;
-                }
-
-                new Font(font.name, font.file, font.properties);
-            
-                areFontsReady = false;
-            }
-
-            var pageInterval;
-            
-            var delayLoadFont = function() {
-                for (var i = 0; i < fontCount; i++) {
-                    if (Fonts[font.name].loading) {
-                        return;
-                    }
-                }
-
-                clearInterval(pageInterval);
-                
-                while (div.hasChildNodes()) {
-                    div.removeChild(div.firstChild);
-                }
-                
-                PDFViewer.drawPage(num);
-            }
-
-            if (!areFontsReady) {
-                pageInterval = setInterval(delayLoadFont, 10);
-                return;
-            }
-
-            page.display(gfx);
-        }
-    },
-
-    changeScale: function(num) {
-        while (PDFViewer.element.hasChildNodes()) {
-            PDFViewer.element.removeChild(PDFViewer.element.firstChild);
-        }
-
-        PDFViewer.scale = num / 100;
-
-        var i;
-
-        if (PDFViewer.pdf) {
-            for (i = 1; i <= PDFViewer.numberOfPages; i++) {
-                PDFViewer.createPage(i);
-            }
-
-            if (PDFViewer.numberOfPages > 0) {
-                PDFViewer.drawPage(1);
-            }
-        }
-
-        for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
-            var option = PDFViewer.scaleSelect.childNodes[i];
-            
-            if (option.value == num) {
-                if (!option.selected) {
-                    option.selected = 'selected';
-                }
-            } else {
-                if (option.selected) {
-                    option.removeAttribute('selected');
-                }
-            }
+        while (div.hasChildNodes()) {
+          div.removeChild(div.firstChild);
         }
         
-        PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
-    },
-
-    goToPage: function(num) {
-        if (1 <= num && num <= PDFViewer.numberOfPages) {
-            PDFViewer.pageNumber = num;
-            PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
-            PDFViewer.willJumpToPage = true;
-
-            document.location.hash = PDFViewer.pageNumber;
-
-            PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
-                'disabled' : '';
-            PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
-                'disabled' : '';
-        }
-    },
+        PDFViewer.drawPage(num);
+      }
+      
+      if (!areFontsReady) {
+        pageInterval = setInterval(delayLoadFont, 10);
+        return;
+      }
+      
+      page.display(gfx);
+    }
+  },
   
-    goToPreviousPage: function() {
-        if (PDFViewer.pageNumber > 1) {
-            PDFViewer.goToPage(--PDFViewer.pageNumber);
+  changeScale: function(num) {
+    while (PDFViewer.element.hasChildNodes()) {
+      PDFViewer.element.removeChild(PDFViewer.element.firstChild);
+    }
+    
+    PDFViewer.scale = num / 100;
+    
+    var i;
+    
+    if (PDFViewer.pdf) {
+      for (i = 1; i <= PDFViewer.numberOfPages; i++) {
+        PDFViewer.createPage(i);
+      }
+      
+      if (PDFViewer.numberOfPages > 0) {
+        PDFViewer.drawPage(1);
+      }
+    }
+    
+    for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
+      var option = PDFViewer.scaleSelect.childNodes[i];
+      
+      if (option.value == num) {
+        if (!option.selected) {
+          option.selected = 'selected';
         }
-    },
-  
-    goToNextPage: function() {
-        if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
-            PDFViewer.goToPage(++PDFViewer.pageNumber);
+      } else {
+        if (option.selected) {
+          option.removeAttribute('selected');
         }
-    },
+      }
+    }
+    
+    PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
+  },
   
-    openURL: function(url) {
-        PDFViewer.url = url;
-        document.title = url;
-    
-        var req = new XMLHttpRequest();
-        req.open('GET', url);
-        req.mozResponseType = req.responseType = 'arraybuffer';
-        req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
-    
-        req.onreadystatechange = function() {
-            if (req.readyState === 4 && req.status === req.expected) {
-                var data = req.mozResponseArrayBuffer ||
-                    req.mozResponse ||
-                    req.responseArrayBuffer ||
-                    req.response;
-
-                PDFViewer.readPDF(data);
-            }
-        };
+  goToPage: function(num) {
+    if (1 <= num && num <= PDFViewer.numberOfPages) {
+      PDFViewer.pageNumber = num;
+      PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
+      PDFViewer.willJumpToPage = true;
+      
+      document.location.hash = PDFViewer.pageNumber;
+      
+      PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
+      PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
+    }
+  },
+  
+  goToPreviousPage: function() {
+    if (PDFViewer.pageNumber > 1) {
+      PDFViewer.goToPage(--PDFViewer.pageNumber);
+    }
+  },
+  
+  goToNextPage: function() {
+    if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
+      PDFViewer.goToPage(++PDFViewer.pageNumber);
+    }
+  },
+  
+  openURL: function(url) {
+    PDFViewer.url = url;
+    document.title = url;
     
-        req.send(null);
-    },
+    var req = new XMLHttpRequest();
+    req.open('GET', url);
+    req.mozResponseType = req.responseType = 'arraybuffer';
+    req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
     
-    readPDF: function(data) {
-        while (PDFViewer.element.hasChildNodes()) {
-            PDFViewer.element.removeChild(PDFViewer.element.firstChild);
-        }
+    req.onreadystatechange = function() {
+      if (req.readyState === 4 && req.status === req.expected) {
+        var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response;
         
-        PDFViewer.pdf = new PDFDoc(new Stream(data));
-        PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
-        document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
+        PDFViewer.readPDF(data);
+      }
+    };
+    
+    req.send(null);
+  },
   
-        for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
-            PDFViewer.createPage(i);
-        }
-
-        if (PDFViewer.numberOfPages > 0) {
-            PDFViewer.drawPage(1);
-            document.location.hash = 1;
-        }
-        
-        PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
-             'disabled' : '';
-         PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
-             'disabled' : '';
+  readPDF: function(data) {
+    while (PDFViewer.element.hasChildNodes()) {
+      PDFViewer.element.removeChild(PDFViewer.element.firstChild);
     }
+    
+    PDFViewer.pdf = new PDFDoc(new Stream(data));
+    PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
+    document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
+    
+    for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
+      PDFViewer.createPage(i);
+    }
+    
+    if (PDFViewer.numberOfPages > 0) {
+      PDFViewer.drawPage(1);
+      document.location.hash = 1;
+    }
+    
+    PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
+    PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
+  }
 };
 
 window.onload = function() {
+  
+  // Parse the URL query parameters into a cached object.
+  PDFViewer.queryParams = function() {
+    var qs = window.location.search.substring(1);
+    var kvs = qs.split('&');
+    var params = {};
+    
+    for (var i = 0; i < kvs.length; ++i) {
+      var kv = kvs[i].split('=');
+      params[unescape(kv[0])] = unescape(kv[1]);
+    }
+    
+    return params;
+  }();
 
-    // Parse the URL query parameters into a cached object.
-    PDFViewer.queryParams = function() {
-        var qs = window.location.search.substring(1);
-        var kvs = qs.split('&');
-        var params = {};
-        for (var i = 0; i < kvs.length; ++i) {
-            var kv = kvs[i].split('=');
-            params[unescape(kv[0])] = unescape(kv[1]);
-        }
-        
-        return params;
-    }();
-
-    PDFViewer.element = document.getElementById('viewer');
-
-    PDFViewer.pageNumberInput = document.getElementById('pageNumber');
-    PDFViewer.pageNumberInput.onkeydown = function(evt) {
-        var charCode = evt.charCode || evt.keyCode;
-
-        // Up arrow key.
-        if (charCode === 38) {
-            PDFViewer.goToNextPage();
-            this.select();
-        }
-        
-        // Down arrow key.
-        else if (charCode === 40) {
-            PDFViewer.goToPreviousPage();
-            this.select();
-        }
-
-        // All other non-numeric keys (excluding Left arrow, Right arrow,
-        // Backspace, and Delete keys).
-        else if ((charCode < 48 || charCode > 57) &&
-            charCode !== 8 &&   // Backspace
-            charCode !== 46 &&  // Delete
-            charCode !== 37 &&  // Left arrow
-            charCode !== 39     // Right arrow
-        ) {
-            return false;
-        }
-
-        return true;
-    };
-    PDFViewer.pageNumberInput.onkeyup = function(evt) {
-        var charCode = evt.charCode || evt.keyCode;
-
-        // All numeric keys, Backspace, and Delete.
-        if ((charCode >= 48 && charCode <= 57) ||
-            charCode === 8 ||   // Backspace
-            charCode === 46     // Delete
-        ) {
-            PDFViewer.goToPage(this.value);
-        }
-        
-        this.focus();
-    };
-
-    PDFViewer.previousPageButton = document.getElementById('previousPageButton');
-    PDFViewer.previousPageButton.onclick = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-            PDFViewer.goToPreviousPage();
-        }
-    };
-    PDFViewer.previousPageButton.onmousedown = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-             this.className = 'down';
-        }
-    };
-    PDFViewer.previousPageButton.onmouseup = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-    };
-    PDFViewer.previousPageButton.onmouseout = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-    };
+  PDFViewer.element = document.getElementById('viewer');
+  
+  PDFViewer.pageNumberInput = document.getElementById('pageNumber');
+  PDFViewer.pageNumberInput.onkeydown = function(evt) {
+    var charCode = evt.charCode || evt.keyCode;
     
-    PDFViewer.nextPageButton = document.getElementById('nextPageButton');
-    PDFViewer.nextPageButton.onclick = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-            PDFViewer.goToNextPage();
-        }
-    };
-    PDFViewer.nextPageButton.onmousedown = function(evt) {
-        if (this.className.indexOf('disabled') === -1) {
-            this.className = 'down';
-        }
+    // Up arrow key.
+    if (charCode === 38) {
+      PDFViewer.goToNextPage();
+      this.select();
+    }
+    
+    // Down arrow key.
+    else if (charCode === 40) {
+      PDFViewer.goToPreviousPage();
+      this.select();
+    }
+    
+    // All other non-numeric keys (excluding Left arrow, Right arrow,
+    // Backspace, and Delete keys).
+    else if ((charCode < 48 || charCode > 57) &&
+      charCode !== 8 &&   // Backspace
+      charCode !== 46 &&  // Delete
+      charCode !== 37 &&  // Left arrow
+      charCode !== 39     // Right arrow
+    ) {
+      return false;
+    }
+    
+    return true;
+  };
+  PDFViewer.pageNumberInput.onkeyup = function(evt) {
+    var charCode = evt.charCode || evt.keyCode;
+    
+    // All numeric keys, Backspace, and Delete.
+    if ((charCode >= 48 && charCode <= 57) ||
+      charCode === 8 ||   // Backspace
+      charCode === 46     // Delete
+    ) {
+      PDFViewer.goToPage(this.value);
+    }
+    
+    this.focus();
+  };
+  
+  PDFViewer.previousPageButton = document.getElementById('previousPageButton');
+  PDFViewer.previousPageButton.onclick = function(evt) {
+    if (this.className.indexOf('disabled') === -1) {
+      PDFViewer.goToPreviousPage();
+    }
+  };
+  PDFViewer.previousPageButton.onmousedown = function(evt) {
+    if (this.className.indexOf('disabled') === -1) {
+      this.className = 'down';
+    }
+  };
+  PDFViewer.previousPageButton.onmouseup = function(evt) {
+    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+  };
+  PDFViewer.previousPageButton.onmouseout = function(evt) {
+    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+  };
+  
+  PDFViewer.nextPageButton = document.getElementById('nextPageButton');
+  PDFViewer.nextPageButton.onclick = function(evt) {
+    if (this.className.indexOf('disabled') === -1) {
+      PDFViewer.goToNextPage();
+    }
+  };
+  PDFViewer.nextPageButton.onmousedown = function(evt) {
+    if (this.className.indexOf('disabled') === -1) {
+      this.className = 'down';
+    }
+  };
+  PDFViewer.nextPageButton.onmouseup = function(evt) {
+    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+  };
+  PDFViewer.nextPageButton.onmouseout = function(evt) {
+    this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+  };
+  
+  PDFViewer.scaleSelect = document.getElementById('scaleSelect');
+  PDFViewer.scaleSelect.onchange = function(evt) {
+    PDFViewer.changeScale(parseInt(this.value));
+  };
+  
+  if (window.File && window.FileReader && window.FileList && window.Blob) {
+    var openFileButton = document.getElementById('openFileButton');
+    openFileButton.onclick = function(evt) {
+      if (this.className.indexOf('disabled') === -1) {
+        PDFViewer.fileInput.click();
+      }
     };
-    PDFViewer.nextPageButton.onmouseup = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+    openFileButton.onmousedown = function(evt) {
+      if (this.className.indexOf('disabled') === -1) {
+        this.className = 'down';
+      }
     };
-    PDFViewer.nextPageButton.onmouseout = function(evt) {
-        this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+    openFileButton.onmouseup = function(evt) {
+      this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
     };
-
-    PDFViewer.scaleSelect = document.getElementById('scaleSelect');
-    PDFViewer.scaleSelect.onchange = function(evt) {
-        PDFViewer.changeScale(parseInt(this.value));
+    openFileButton.onmouseout = function(evt) {
+      this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
     };
     
-    if (window.File && window.FileReader && window.FileList && window.Blob) {
-        var openFileButton = document.getElementById('openFileButton');
-        openFileButton.onclick = function(evt) {
-            if (this.className.indexOf('disabled') === -1) {
-                PDFViewer.fileInput.click();
-            }
-        };
-        openFileButton.onmousedown = function(evt) {
-            if (this.className.indexOf('disabled') === -1) {
-                this.className = 'down';
-            }
-        };
-        openFileButton.onmouseup = function(evt) {
-            this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-        };
-        openFileButton.onmouseout = function(evt) {
-            this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
-        };
+    PDFViewer.fileInput = document.getElementById('fileInput');
+    PDFViewer.fileInput.onchange = function(evt) {
+      var files = evt.target.files;
+      
+      if (files.length > 0) {
+        var file = files[0];
+        var fileReader = new FileReader();
         
-        PDFViewer.fileInput = document.getElementById('fileInput');
-        PDFViewer.fileInput.onchange = function(evt) {
-            var files = evt.target.files;
-            
-            if (files.length > 0) {
-                var file = files[0];
-                var fileReader = new FileReader();
-                
-                document.title = file.name;
-                
-                // Read the local file into a Uint8Array.
-                fileReader.onload = function(evt) {
-                    var data = evt.target.result;
-                    var buffer = new ArrayBuffer(data.length);
-                    var uint8Array = new Uint8Array(buffer);
-                    
-                    for (var i = 0; i < data.length; i++) {
-                        uint8Array[i] = data.charCodeAt(i);
-                    }
-                    
-                    PDFViewer.readPDF(uint8Array);
-                };
-                
-                // Read as a binary string since "readAsArrayBuffer" is not yet
-                // implemented in Firefox.
-                fileReader.readAsBinaryString(file);
-            }
-        };
-        PDFViewer.fileInput.value = null;
-    } else {
-        document.getElementById('fileWrapper').style.display = 'none';
-    }
-
-    PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
-    PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
-    
-    PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
-
-    window.onscroll = function(evt) {        
-        var lastPagesDrawn = PDFViewer.lastPagesDrawn;
-        var visiblePages = PDFViewer.visiblePages();
+        document.title = file.name;
         
-        var pagesToDraw = [];
-        var pagesToKeep = [];
-        var pagesToRemove = [];
-        
-        var i;
-
-        // Determine which visible pages were not previously drawn.
-        for (i = 0; i < visiblePages.length; i++) {
-            if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
-                pagesToDraw.push(visiblePages[i]);
-                PDFViewer.drawPage(visiblePages[i]);
-            } else {
-                pagesToKeep.push(visiblePages[i]);
-            }
-        }
-
-        // Determine which previously drawn pages are no longer visible.
-        for (i = 0; i < lastPagesDrawn.length; i++) {
-            if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
-                pagesToRemove.push(lastPagesDrawn[i]);
-                PDFViewer.removePage(lastPagesDrawn[i]);
-            }
-        }
-        
-        PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
+        // Read the local file into a Uint8Array.
+        fileReader.onload = function(evt) {
+          var data = evt.target.result;
+          var buffer = new ArrayBuffer(data.length);
+          var uint8Array = new Uint8Array(buffer);
+          
+          for (var i = 0; i < data.length; i++) {
+            uint8Array[i] = data.charCodeAt(i);
+          }
+          
+          PDFViewer.readPDF(uint8Array);
+        };
         
-        // Update the page number input with the current page number.
-        if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
-            PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
-            PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
-                'disabled' : '';
-            PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
-                'disabled' : '';
-        } else {
-            PDFViewer.willJumpToPage = false;
-        }
+        // Read as a binary string since "readAsArrayBuffer" is not yet
+        // implemented in Firefox.
+        fileReader.readAsBinaryString(file);
+      }
     };
+    PDFViewer.fileInput.value = null;
+  } else {
+    document.getElementById('fileWrapper').style.display = 'none';
+  }
+  
+  PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
+  PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
+  
+  PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
+  
+  window.onscroll = function(evt) {        
+    var lastPagesDrawn = PDFViewer.lastPagesDrawn;
+    var visiblePages = PDFViewer.visiblePages();
+    
+    var pagesToDraw = [];
+    var pagesToKeep = [];
+    var pagesToRemove = [];
+    
+    var i;
+    
+    // Determine which visible pages were not previously drawn.
+    for (i = 0; i < visiblePages.length; i++) {
+      if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
+        pagesToDraw.push(visiblePages[i]);
+        PDFViewer.drawPage(visiblePages[i]);
+      } else {
+        pagesToKeep.push(visiblePages[i]);
+      }
+    }
+    
+    // Determine which previously drawn pages are no longer visible.
+    for (i = 0; i < lastPagesDrawn.length; i++) {
+      if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
+        pagesToRemove.push(lastPagesDrawn[i]);
+        PDFViewer.removePage(lastPagesDrawn[i]);
+      }
+    }
+    
+    PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
+    
+    // Update the page number input with the current page number.
+    if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
+      PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
+      PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
+      PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
+    } else {
+      PDFViewer.willJumpToPage = false;
+    }
+  };
 };

From c5b889a47baad3a581cda3861c4137a1f63f8943 Mon Sep 17 00:00:00 2001
From: Justin D'Arcangelo <justindarc@gmail.com>
Date: Thu, 23 Jun 2011 21:37:40 -0400
Subject: [PATCH 45/45] Brought pdf.js back up to the latest revision.

---
 pdf.js | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/pdf.js b/pdf.js
index ffefc61a1..b2b6401fd 100644
--- a/pdf.js
+++ b/pdf.js
@@ -3275,8 +3275,11 @@ var CanvasGraphics = (function() {
                 }
             }
 
-            if (bitsPerComponent !== 8)
-                error("Unsupported bpc");
+            if (bitsPerComponent !== 8) {
+                TODO("Support bpc="+ bitsPerComponent);
+                this.restore();
+                return;
+            }
 
             var xref = this.xref;
             var colorSpaces = this.colorSpaces;