Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 17 Mar 2014 17:34:10 -0400
changeset 192458 dc2225034769b79699a0349aeb594dd4f9a0b157
parent 192457 3b1a35bd4e3f8b95a9ee166042042608d50f03d1 (current diff)
parent 192436 c318f34d4bda508ca22c107640a218c9e21a2cc5 (diff)
child 192459 5b26312bcf46a40562a2ae1600f652969ece4958
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound.
--- a/.hgtags
+++ b/.hgtags
@@ -100,8 +100,9 @@ 05025f4889a0bf4dc99ce0c244c750adc002f015
 ba2cc1eda988a1614d8986ae145d28e1268409b9 FIREFOX_AURORA_29_BASE-m
 ba2cc1eda988a1614d8986ae145d28e1268409b9 Tagging for mozilla-central version bumps CLOSED TREE DONTBUILD
 ba2cc1eda988a1614d8986ae145d28e1268409b9 Tagging for mozilla-central version bumps CLOSED TREE DONTBUILD
 0000000000000000000000000000000000000000 Tagging for mozilla-central version bumps CLOSED TREE DONTBUILD
 ba2cc1eda988a1614d8986ae145d28e1268409b9 FIREFOX_AURORA_29_BASE-m
 0000000000000000000000000000000000000000 FIREFOX_AURORA_29_BASE-m
 ba2cc1eda988a1614d8986ae145d28e1268409b9 FIREFOX_AURORA_29_BASE
 9f12a9fab080f2d363d7424e25b9ffe85ebc3414 FIREFOX_AURORA_28_BASE
+83c9853e136451474dfa6d1aaa60a7fca7d2d83a FIREFOX_AURORA_30_BASE
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -671,16 +671,29 @@ pref("hal.processPriorityManager.gonk.BA
 
 pref("hal.processPriorityManager.gonk.BACKGROUND.OomScoreAdjust", 667);
 pref("hal.processPriorityManager.gonk.BACKGROUND.KillUnderKB", 20480);
 pref("hal.processPriorityManager.gonk.BACKGROUND.Nice", 18);
 
 // Processes get this niceness when they have low CPU priority.
 pref("hal.processPriorityManager.gonk.LowCPUNice", 18);
 
+// By default the compositor thread on gonk runs without real-time priority.  RT
+// priority can be enabled by setting this pref to a value between 1 and 99.
+// Note that audio processing currently runs at RT priority 2 or 3 at most.
+//
+// If RT priority is disabled, then the compositor nice value is used.  The
+// code will default to ANDROID_PRIORITY_URGENT_DISPLAY which is -8.  Per gfx
+// request we are keeping the compositor at nice level 0 until we can complete
+// the investigation in bug 982972.
+//
+// Do not change these values without gfx team review.
+pref("hal.gonk.compositor.rt_priority", 0);
+pref("hal.gonk.compositor.nice", 0);
+
 // Fire a memory pressure event when the system has less than Xmb of memory
 // remaining.  You should probably set this just above Y.KillUnderKB for
 // the highest priority class Y that you want to make an effort to keep alive.
 // (For example, we want BACKGROUND_PERCEIVABLE to stay alive.)  If you set
 // this too high, then we'll send out a memory pressure event every Z seconds
 // (see below), even while we have processes that we would happily kill in
 // order to free up memory.
 pref("hal.processPriorityManager.gonk.notifyLowMemUnderKB", 14336);
--- a/b2g/chrome/content/devtools.js
+++ b/b2g/chrome/content/devtools.js
@@ -39,19 +39,20 @@ let developerHUD = {
   _frames: new Map(),
   _client: null,
   _webappsActor: null,
   _watchers: [],
   _logging: true,
 
   /**
    * This method registers a metric watcher that will watch one or more metrics
-   * of apps that are being tracked. A watcher must implement the trackApp(app)
-   * and untrackApp(app) methods, add entries to the app.metrics map, keep them
-   * up-to-date, and call app.display() when values were changed.
+   * on app frames that are being tracked. A watcher must implement the
+   * `trackTarget(target)` and `untrackTarget(target)` methods, register
+   * observed metrics with `target.register(metric)`, and keep them up-to-date
+   * with `target.update(metric, value, message)` when necessary.
    */
   registerWatcher: function dwp_registerWatcher(watcher) {
     this._watchers.unshift(watcher);
   },
 
   init: function dwp_init() {
     if (this._client)
       return;
@@ -82,17 +83,17 @@ let developerHUD = {
 
         let frames = systemapp.contentWindow.document.querySelectorAll('iframe[mozapp]');
         for (let frame of frames) {
           this.trackFrame(frame);
         }
       });
     });
 
-    SettingsListener.observe('hud.logging', enabled => {
+    SettingsListener.observe('hud.logging', this._logging, enabled => {
       this._logging = enabled;
     });
   },
 
   uninit: function dwp_uninit() {
     if (!this._client)
       return;
 
@@ -189,19 +190,19 @@ let developerHUD = {
       dump(DEVELOPER_HUD_LOG_PREFIX + ': ' + message + '\n');
     }
   }
 
 };
 
 
 /**
- * An App object represents all there is to know about a Firefox OS app that is
- * being tracked, e.g. its manifest information, current values of watched
- * metrics, and how to update these values on the front-end.
+ * A Target object represents all there is to know about a Firefox OS app frame
+ * that is being tracked, e.g. a pointer to the frame, current values of watched
+ * metrics, and how to notify the front-end when metrics have changed.
  */
 function Target(frame, actor) {
   this.frame = frame;
   this.actor = actor;
   this.metrics = new Map();
 }
 
 Target.prototype = {
@@ -271,37 +272,47 @@ Target.prototype = {
     shell.sendEvent(this.frame, 'developer-hud-update', Cu.cloneInto(data, this.frame));
   }
 
 };
 
 
 /**
  * The Console Watcher tracks the following metrics in apps: reflows, warnings,
- * and errors.
+ * and errors, with security errors reported separately.
  */
 let consoleWatcher = {
 
+  _client: null,
   _targets: new Map(),
   _watching: {
     reflows: false,
     warnings: false,
-    errors: false
+    errors: false,
+    security: false
   },
-  _client: null,
+  _security: [
+    'Mixed Content Blocker',
+    'Mixed Content Message',
+    'CSP',
+    'Invalid HSTS Headers',
+    'Insecure Password Field',
+    'SSL',
+    'CORS'
+  ],
 
   init: function cw_init(client) {
     this._client = client;
     this.consoleListener = this.consoleListener.bind(this);
 
     let watching = this._watching;
 
     for (let key in watching) {
       let metric = key;
-      SettingsListener.observe('hud.' + metric, false, watch => {
+      SettingsListener.observe('hud.' + metric, watching[metric], watch => {
         // Watch or unwatch the metric.
         if (watching[metric] = watch) {
           return;
         }
 
         // If unwatched, remove any existing widgets for that metric.
         for (let target of this._targets.values()) {
           target.clear(metric);
@@ -314,16 +325,17 @@ let consoleWatcher = {
     client.addListener('consoleAPICall', this.consoleListener);
     client.addListener('reflowActivity', this.consoleListener);
   },
 
   trackTarget: function cw_trackTarget(target) {
     target.register('reflows');
     target.register('warnings');
     target.register('errors');
+    target.register('security');
 
     this._client.request({
       to: target.actor.consoleActor,
       type: 'startListeners',
       listeners: ['LogMessage', 'PageError', 'ConsoleAPI', 'ReflowActivity']
     }, (res) => {
       this._targets.set(target.actor.consoleActor, target);
     });
@@ -352,23 +364,27 @@ let consoleWatcher = {
         if (pageError.warning || pageError.strict) {
           metric = 'warnings';
           output += 'warning (';
         } else {
           metric = 'errors';
           output += 'error (';
         }
 
+        if (this._security.indexOf(pageError.category) > -1) {
+          metric = 'security';
+        }
+
         let {errorMessage, sourceName, category, lineNumber, columnNumber} = pageError;
         output += category + '): "' + (errorMessage.initial || errorMessage) +
           '" in ' + sourceName + ':' + lineNumber + ':' + columnNumber;
         break;
 
       case 'consoleAPICall':
-        switch (packet.output.level) {
+        switch (packet.message.level) {
 
           case 'error':
             metric = 'errors';
             output += 'error (console)';
             break;
 
           case 'warn':
             metric = 'warnings';
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0701e08a025787481a5d23443c75be7473c866f5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c03a6af9028c4b74a84b5a98085bbb0c07261175"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="15d69a6789c638709911507f74d25c0425963636">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0701e08a025787481a5d23443c75be7473c866f5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c03a6af9028c4b74a84b5a98085bbb0c07261175"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="a9e08b91e9cd1f0930f16cfc49ec72f63575d5fe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0701e08a025787481a5d23443c75be7473c866f5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c03a6af9028c4b74a84b5a98085bbb0c07261175"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0701e08a025787481a5d23443c75be7473c866f5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c03a6af9028c4b74a84b5a98085bbb0c07261175"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "remote": "", 
         "branch": "", 
         "revision": ""
     }, 
-    "revision": "fb54fbd4849b9adfdb4fc236c2cf8161545d736d", 
+    "revision": "f5280df8a66c88c8bafc0062e19a4770ce18e08d", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0701e08a025787481a5d23443c75be7473c866f5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c03a6af9028c4b74a84b5a98085bbb0c07261175"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0701e08a025787481a5d23443c75be7473c866f5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c03a6af9028c4b74a84b5a98085bbb0c07261175"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0701e08a025787481a5d23443c75be7473c866f5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c03a6af9028c4b74a84b5a98085bbb0c07261175"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0701e08a025787481a5d23443c75be7473c866f5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c03a6af9028c4b74a84b5a98085bbb0c07261175"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="15d69a6789c638709911507f74d25c0425963636">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0701e08a025787481a5d23443c75be7473c866f5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c03a6af9028c4b74a84b5a98085bbb0c07261175"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0701e08a025787481a5d23443c75be7473c866f5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c03a6af9028c4b74a84b5a98085bbb0c07261175"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -1,16 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 MOZ_APP_BASENAME=B2G
 MOZ_APP_VENDOR=Mozilla
 
-MOZ_APP_VERSION=30.0a1
+MOZ_APP_VERSION=31.0a1
 MOZ_APP_UA_NAME=Firefox
 
 MOZ_UA_OS_AGNOSTIC=1
 
 MOZ_B2G_VERSION=1.4.0.0-prerelease
 MOZ_B2G_OS_NAME=Boot2Gecko
 
 MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
--- a/browser/config/version.txt
+++ b/browser/config/version.txt
@@ -1,1 +1,1 @@
-30.0a1
+31.0a1
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -2,18 +2,19 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // Used to detect minification for automatic pretty printing
-const SAMPLE_SIZE = 30; // no of lines
-const INDENT_COUNT_THRESHOLD = 20; // percentage
+const SAMPLE_SIZE = 50; // no of lines
+const INDENT_COUNT_THRESHOLD = 5; // percentage
+const CHARACTER_LIMIT = 250; // line character limit
 
 /**
  * Functions handling the sources UI.
  */
 function SourcesView() {
   dumpn("SourcesView was instantiated");
 
   this.togglePrettyPrint = this.togglePrettyPrint.bind(this);
@@ -1495,31 +1496,38 @@ let SourceUtils = {
       return this._minifiedCache.get(sourceClient);
     }
 
     let isMinified;
     let lineEndIndex = 0;
     let lineStartIndex = 0;
     let lines = 0;
     let indentCount = 0;
+    let overCharLimit = false;
 
     // Strip comments.
     aText = aText.replace(/\/\*[\S\s]*?\*\/|\/\/(.+|\n)/g, "");
 
     while (lines++ < SAMPLE_SIZE) {
       lineEndIndex = aText.indexOf("\n", lineStartIndex);
       if (lineEndIndex == -1) {
          break;
       }
       if (/^\s+/.test(aText.slice(lineStartIndex, lineEndIndex))) {
         indentCount++;
       }
+      // For files with no indents but are not minified.
+      if ((lineEndIndex - lineStartIndex) > CHARACTER_LIMIT) {
+        overCharLimit = true;
+        break;
+      }
       lineStartIndex = lineEndIndex + 1;
     }
-    isMinified = ((indentCount / lines ) * 100) < INDENT_COUNT_THRESHOLD;
+    isMinified = ((indentCount / lines ) * 100) < INDENT_COUNT_THRESHOLD ||
+                 overCharLimit;
 
     this._minifiedCache.set(sourceClient, isMinified);
     return isMinified;
   },
 
   /**
    * Clears the labels, groups and minify cache, populated by methods like
    * SourceUtils.getSourceLabel or Source Utils.getSourceGroup.
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 0.8.1114
+Current extension version is: 0.8.1181
 
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -56,16 +56,37 @@ function getBoolPref(aPref, aDefaultValu
 function getIntPref(aPref, aDefaultValue) {
   try {
     return Services.prefs.getIntPref(aPref);
   } catch (ex) {
     return aDefaultValue;
   }
 }
 
+function initializeDefaultPreferences() {
+  Cu.import('resource://pdf.js/default_preferences.js');
+
+  var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
+  var defaultValue;
+  for (var key in DEFAULT_PREFERENCES) {
+    defaultValue = DEFAULT_PREFERENCES[key];
+    switch (typeof defaultValue) {
+      case 'boolean':
+        defaultBranch.setBoolPref(key, defaultValue);
+        break;
+      case 'number':
+        defaultBranch.setIntPref(key, defaultValue);
+        break;
+      case 'string':
+        defaultBranch.setCharPref(key, defaultValue);
+        break;
+    }
+  }
+}
+
 // Register/unregister a constructor as a factory.
 function Factory() {}
 Factory.prototype = {
   register: function register(targetConstructor) {
     var proto = targetConstructor.prototype;
     this._classID = proto.classID;
 
     var factory = XPCOMUtils._getFactory(targetConstructor);
@@ -99,16 +120,18 @@ let PdfJs = {
 
     // Listen for when pdf.js is completely disabled or a different pdf handler
     // is chosen.
     Services.prefs.addObserver(PREF_DISABLED, this, false);
     Services.prefs.addObserver(PREF_DISABLED_PLUGIN_TYPES, this, false);
     Services.obs.addObserver(this, TOPIC_PDFJS_HANDLER_CHANGED, false);
     Services.obs.addObserver(this, TOPIC_PLUGINS_LIST_UPDATED, false);
     Services.obs.addObserver(this, TOPIC_PLUGIN_INFO_UPDATED, false);
+
+    initializeDefaultPreferences();
   },
 
   _migrate: function migrate() {
     const VERSION = 1;
     var currentVersion = getIntPref(PREF_MIGRATION_VERSION, 0);
     if (currentVersion >= VERSION) {
       return;
     }
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -11,43 +11,41 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 /* jshint esnext:true */
 /* globals Components, Services, XPCOMUtils, NetUtil, PrivateBrowsingUtils,
-           dump, NetworkManager, PdfJsTelemetry, DEFAULT_PREFERENCES */
+           dump, NetworkManager, PdfJsTelemetry */
 
 'use strict';
 
 var EXPORTED_SYMBOLS = ['PdfStreamConverter'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 // True only if this is the version of pdf.js that is included with firefox.
 const MOZ_CENTRAL = JSON.parse('true');
 const PDFJS_EVENT_ID = 'pdf.js.message';
 const PDF_CONTENT_TYPE = 'application/pdf';
 const PREF_PREFIX = 'pdfjs';
 const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html';
 const MAX_DATABASE_LENGTH = 4096;
-const MAX_STRING_PREF_LENGTH = 4096;
+const MAX_NUMBER_OF_PREFS = 50;
+const MAX_STRING_PREF_LENGTH = 128;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/NetUtil.jsm');
 Cu.import('resource://pdf.js/network.js');
 
-// Load the default preferences.
-Cu.import('resource://pdf.js/default_preferences.js');
-
 XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
   'resource://gre/modules/PrivateBrowsingUtils.jsm');
 
 XPCOMUtils.defineLazyModuleGetter(this, 'PdfJsTelemetry',
   'resource://pdf.js/PdfJsTelemetry.jsm');
 
 var Svc = {};
 XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
@@ -448,66 +446,70 @@ ChromeActions.prototype = {
     if ((typeof result !== 'number' || result < 0 || result > 3) ||
         (findPreviousType !== 'undefined' && findPreviousType !== 'boolean')) {
       return;
     }
     getChromeWindow(this.domWindow).gFindBar
                                    .updateControlState(result, findPrevious);
   },
   setPreferences: function(prefs) {
-    var prefValue, defaultValue, prefName, prefType, defaultType;
-
-    for (var key in DEFAULT_PREFERENCES) {
+    var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
+    var numberOfPrefs = 0;
+    var prefValue, prefName;
+    for (var key in prefs) {
+      if (++numberOfPrefs > MAX_NUMBER_OF_PREFS) {
+        log('setPreferences - Exceeded the maximum number of preferences ' +
+            'that is allowed to be set at once.');
+        break;
+      } else if (!defaultBranch.getPrefType(key)) {
+        continue;
+      }
       prefValue = prefs[key];
-      defaultValue = DEFAULT_PREFERENCES[key];
       prefName = (PREF_PREFIX + '.' + key);
-
-      if (prefValue === undefined || prefValue === defaultValue) {
-        Services.prefs.clearUserPref(prefName);
-      } else {
-        prefType = typeof prefValue;
-        defaultType = typeof defaultValue;
-
-        if (prefType !== defaultType) {
-          continue;
-        }
-        switch (defaultType) {
-          case 'boolean':
-            setBoolPref(prefName, prefValue);
-            break;
-          case 'number':
-            setIntPref(prefName, prefValue);
-            break;
-          case 'string':
-            // Protect against adding arbitrarily long strings in about:config.
-            if (prefValue.length <= MAX_STRING_PREF_LENGTH) {
-              setStringPref(prefName, prefValue);
-            }
-            break;
-        }
+      switch (typeof prefValue) {
+        case 'boolean':
+          setBoolPref(prefName, prefValue);
+          break;
+        case 'number':
+          setIntPref(prefName, prefValue);
+          break;
+        case 'string':
+          if (prefValue.length > MAX_STRING_PREF_LENGTH) {
+            log('setPreferences - Exceeded the maximum allowed length ' +
+                'for a string preference.');
+          } else {
+            setStringPref(prefName, prefValue);
+          }
+          break;
       }
     }
   },
-  getPreferences: function() {
-    var currentPrefs = {};
-    var defaultValue, prefName;
-
-    for (var key in DEFAULT_PREFERENCES) {
-      defaultValue = DEFAULT_PREFERENCES[key];
+  getPreferences: function(prefs) {
+    var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
+    var currentPrefs = {}, numberOfPrefs = 0;
+    var prefValue, prefName;
+    for (var key in prefs) {
+      if (++numberOfPrefs > MAX_NUMBER_OF_PREFS) {
+        log('getPreferences - Exceeded the maximum number of preferences ' +
+            'that is allowed to be fetched at once.');
+        break;
+      } else if (!defaultBranch.getPrefType(key)) {
+        continue;
+      }
+      prefValue = prefs[key];
       prefName = (PREF_PREFIX + '.' + key);
-
-      switch (typeof defaultValue) {
+      switch (typeof prefValue) {
         case 'boolean':
-          currentPrefs[key] = getBoolPref(prefName, defaultValue);
+          currentPrefs[key] = getBoolPref(prefName, prefValue);
           break;
         case 'number':
-          currentPrefs[key] = getIntPref(prefName, defaultValue);
+          currentPrefs[key] = getIntPref(prefName, prefValue);
           break;
         case 'string':
-          currentPrefs[key] = getStringPref(prefName, defaultValue);
+          currentPrefs[key] = getStringPref(prefName, prefValue);
           break;
       }
     }
     return JSON.stringify(currentPrefs);
   }
 };
 
 var RangedChromeActions = (function RangedChromeActionsClosure() {
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -16,18 +16,18 @@
  */
 /*jshint globalstrict: false */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.1114';
-PDFJS.build = 'ea9c8f8';
+PDFJS.version = '0.8.1181';
+PDFJS.build = '31ea4e0';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -220,18 +220,19 @@ function backtrace() {
   try {
     throw new Error();
   } catch (e) {
     return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
   }
 }
 
 function assert(cond, msg) {
-  if (!cond)
+  if (!cond) {
     error(msg);
+  }
 }
 
 var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = {
   unknown: 'unknown',
   forms: 'forms',
   javaScript: 'javaScript',
   smask: 'smask',
   shadingPattern: 'shadingPattern',
@@ -252,20 +253,22 @@ var UnsupportedManager = PDFJS.Unsupport
       }
     }
   };
 })();
 
 // Combines two URLs. The baseUrl shall be absolute URL. If the url is an
 // absolute URL, it will be returned as is.
 function combineUrl(baseUrl, url) {
-  if (!url)
+  if (!url) {
     return baseUrl;
-  if (/^[a-z][a-z0-9+\-.]*:/i.test(url))
+  }
+  if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) {
     return url;
+  }
   if (url.charAt(0) == '/') {
     // absolute path
     var i = baseUrl.indexOf('://');
     if (url.charAt(1) === '/') {
       ++i;
     } else {
       i = baseUrl.indexOf('/', i + 3);
     }
@@ -304,18 +307,19 @@ function isValidUrl(url, allowRelative) 
       return false;
   }
 }
 PDFJS.isValidUrl = isValidUrl;
 
 // In a well-formed PDF, |cond| holds.  If it doesn't, subsequent
 // behavior is undefined.
 function assertWellFormed(cond, msg) {
-  if (!cond)
+  if (!cond) {
     error(msg);
+  }
 }
 
 function shadow(obj, prop, value) {
   Object.defineProperty(obj, prop, { value: value,
                                      enumerable: true,
                                      configurable: true,
                                      writable: false });
   return value;
@@ -422,18 +426,19 @@ function bytesToString(bytes) {
     strBuf.push(String.fromCharCode(bytes[n]));
   }
   return strBuf.join('');
 }
 
 function stringToBytes(str) {
   var length = str.length;
   var bytes = new Uint8Array(length);
-  for (var n = 0; n < length; ++n)
+  for (var n = 0; n < length; ++n) {
     bytes[n] = str.charCodeAt(n) & 0xFF;
+  }
   return bytes;
 }
 
 var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
 
 var Util = PDFJS.Util = (function UtilClosure() {
   function Util() {}
 
@@ -827,24 +832,25 @@ function isArrayBuffer(v) {
 }
 
 function isRef(v) {
   return v instanceof Ref;
 }
 
 function isPDFFunction(v) {
   var fnDict;
-  if (typeof v != 'object')
+  if (typeof v != 'object') {
     return false;
-  else if (isDict(v))
+  } else if (isDict(v)) {
     fnDict = v;
-  else if (isStream(v))
+  } else if (isStream(v)) {
     fnDict = v.dict;
-  else
+  } else {
     return false;
+  }
   return fnDict.has('FunctionType');
 }
 
 /**
  * Legacy support for PDFJS Promise implementation.
  * TODO remove eventually
  */
 var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() {
@@ -903,70 +909,77 @@ var LegacyPromise = PDFJS.LegacyPromise 
     }
     return;
   }
   throw new Error('DOM Promise is not present');
 })();
 
 var StatTimer = (function StatTimerClosure() {
   function rpad(str, pad, length) {
-    while (str.length < length)
+    while (str.length < length) {
       str += pad;
+    }
     return str;
   }
   function StatTimer() {
     this.started = {};
     this.times = [];
     this.enabled = true;
   }
   StatTimer.prototype = {
     time: function StatTimer_time(name) {
-      if (!this.enabled)
+      if (!this.enabled) {
         return;
-      if (name in this.started)
+      }
+      if (name in this.started) {
         warn('Timer is already running for ' + name);
+      }
       this.started[name] = Date.now();
     },
     timeEnd: function StatTimer_timeEnd(name) {
-      if (!this.enabled)
+      if (!this.enabled) {
         return;
-      if (!(name in this.started))
+      }
+      if (!(name in this.started)) {
         warn('Timer has not been started for ' + name);
+      }
       this.times.push({
         'name': name,
         'start': this.started[name],
         'end': Date.now()
       });
       // Remove timer from started so it can be called again.
       delete this.started[name];
     },
     toString: function StatTimer_toString() {
       var times = this.times;
       var out = '';
       // Find the longest name for padding purposes.
       var longest = 0;
       for (var i = 0, ii = times.length; i < ii; ++i) {
         var name = times[i]['name'];
-        if (name.length > longest)
+        if (name.length > longest) {
           longest = name.length;
+        }
       }
       for (var i = 0, ii = times.length; i < ii; ++i) {
         var span = times[i];
         var duration = span.end - span.start;
         out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
       }
       return out;
     }
   };
   return StatTimer;
 })();
 
 PDFJS.createBlob = function createBlob(data, contentType) {
-  if (typeof Blob !== 'undefined')
+  if (typeof Blob !== 'undefined') {
     return new Blob([data], { type: contentType });
+  }
   // Blob builder is deprecated in FF14 and removed in FF18.
   var bb = new MozBlobBuilder();
   bb.append(data);
   return bb.getBlob(contentType);
 };
 
 PDFJS.createObjectURL = (function createObjectURLClosure() {
   // Blob/createObjectURL is not available, falling back to data schema.
@@ -1233,19 +1246,19 @@ var ColorSpace = (function ColorSpaceClo
      * This should be true for all colorspaces except for lab color spaces
      * which are [0,100], [-128, 127], [-128, 127].
      */
     usesZeroToOneRange: true
   };
 
   ColorSpace.parse = function ColorSpace_parse(cs, xref, res) {
     var IR = ColorSpace.parseToIR(cs, xref, res);
-    if (IR instanceof AlternateCS)
+    if (IR instanceof AlternateCS) {
       return IR;
-
+    }
     return ColorSpace.fromIR(IR);
   };
 
   ColorSpace.fromIR = function ColorSpace_fromIR(IR) {
     var name = isArray(IR) ? IR[0] : IR;
 
     switch (name) {
       case 'DeviceGrayCS':
@@ -1256,18 +1269,19 @@ var ColorSpace = (function ColorSpaceClo
         return this.singletons.cmyk;
       case 'CalGrayCS':
         var whitePoint = IR[1].WhitePoint;
         var blackPoint = IR[1].BlackPoint;
         var gamma = IR[1].Gamma;
         return new CalGrayCS(whitePoint, blackPoint, gamma);
       case 'PatternCS':
         var basePatternCS = IR[1];
-        if (basePatternCS)
+        if (basePatternCS) {
           basePatternCS = ColorSpace.fromIR(basePatternCS);
+        }
         return new PatternCS(basePatternCS);
       case 'IndexedCS':
         var baseIndexedCS = IR[1];
         var hiVal = IR[2];
         var lookup = IR[3];
         return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup);
       case 'AlternateCS':
         var numComps = IR[1];
@@ -1287,18 +1301,19 @@ var ColorSpace = (function ColorSpaceClo
     return null;
   };
 
   ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) {
     if (isName(cs)) {
       var colorSpaces = res.get('ColorSpace');
       if (isDict(colorSpaces)) {
         var refcs = colorSpaces.get(cs.name);
-        if (refcs)
+        if (refcs) {
           cs = refcs;
+        }
       }
     }
 
     cs = xref.fetchIfRef(cs);
     var mode;
 
     if (isName(cs)) {
       mode = cs.name;
@@ -1337,45 +1352,48 @@ var ColorSpace = (function ColorSpaceClo
           var params = cs[1].getAll();
           return ['CalGrayCS', params];
         case 'CalRGB':
           return 'DeviceRgbCS';
         case 'ICCBased':
           var stream = xref.fetchIfRef(cs[1]);
           var dict = stream.dict;
           var numComps = dict.get('N');
-          if (numComps == 1)
+          if (numComps == 1) {
             return 'DeviceGrayCS';
-          if (numComps == 3)
+          } else if (numComps == 3) {
             return 'DeviceRgbCS';
-          if (numComps == 4)
+          } else if (numComps == 4) {
             return 'DeviceCmykCS';
+          }
           break;
         case 'Pattern':
           var basePatternCS = cs[1];
-          if (basePatternCS)
+          if (basePatternCS) {
             basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res);
+          }
           return ['PatternCS', basePatternCS];
         case 'Indexed':
         case 'I':
           var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res);
           var hiVal = cs[2] + 1;
           var lookup = xref.fetchIfRef(cs[3]);
           if (isStream(lookup)) {
             lookup = lookup.getBytes();
           }
           return ['IndexedCS', baseIndexedCS, hiVal, lookup];
         case 'Separation':
         case 'DeviceN':
           var name = cs[1];
           var numComps = 1;
-          if (isName(name))
+          if (isName(name)) {
             numComps = 1;
-          else if (isArray(name))
+          } else if (isArray(name)) {
             numComps = name.length;
+          }
           var alt = ColorSpace.parseToIR(cs[2], xref, res);
           var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
           return ['AlternateCS', numComps, alt, tintFnIR];
         case 'Lab':
           var params = cs[1].getAll();
           return ['LabCS', params];
         default:
           error('unimplemented color space object "' + mode + '"');
@@ -1390,26 +1408,28 @@ var ColorSpace = (function ColorSpaceClo
    * This handles the general decode maps where there are two values per
    * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color.
    * This does not handle Lab, Indexed, or Pattern decode maps since they are
    * slightly different.
    * @param {Array} decode Decode map (usually from an image).
    * @param {Number} n Number of components the color space has.
    */
   ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) {
-    if (!decode)
+    if (!decode) {
       return true;
+    }
 
     if (n * 2 !== decode.length) {
       warn('The decode map is not the correct length');
       return true;
     }
     for (var i = 0, ii = decode.length; i < ii; i += 2) {
-      if (decode[i] !== 0 || decode[i + 1] != 1)
+      if (decode[i] !== 0 || decode[i + 1] != 1) {
         return false;
+      }
     }
     return true;
   };
 
   ColorSpace.singletons = {
     get gray() {
       return shadow(this, 'gray', new DeviceGrayCS());
     },
@@ -1526,18 +1546,19 @@ var IndexedCS = (function IndexedCSClosu
     var lookupArray;
 
     if (isStream(lookup)) {
       lookupArray = new Uint8Array(length);
       var bytes = lookup.getBytes(length);
       lookupArray.set(bytes);
     } else if (isString(lookup)) {
       lookupArray = new Uint8Array(length);
-      for (var i = 0; i < length; ++i)
+      for (var i = 0; i < length; ++i) {
         lookupArray[i] = lookup.charCodeAt(i);
+      }
     } else if (lookup instanceof Uint8Array || lookup instanceof Array) {
       lookupArray = lookup;
     } else {
       error('Unrecognized lookup table: ' + lookup);
     }
     this.lookup = lookupArray;
   }
 
@@ -1860,18 +1881,19 @@ var CalGrayCS = (function CalGrayCSClosu
 // LabCS: Based on "PDF Reference, Sixth Ed", p.250
 //
 var LabCS = (function LabCSClosure() {
   function LabCS(whitePoint, blackPoint, range) {
     this.name = 'Lab';
     this.numComps = 3;
     this.defaultColor = new Float32Array([0, 0, 0]);
 
-    if (!whitePoint)
+    if (!whitePoint) {
       error('WhitePoint missing - required for color space Lab');
+    }
     blackPoint = blackPoint || [0, 0, 0];
     range = range || [-100, 100, -100, 100];
 
     // Translate args to spec variables
     this.XW = whitePoint[0];
     this.YW = whitePoint[1];
     this.ZW = whitePoint[2];
     this.amin = range[0];
@@ -1881,18 +1903,19 @@ var LabCS = (function LabCSClosure() {
 
     // These are here just for completeness - the spec doesn't offer any
     // formulas that use BlackPoint in Lab
     this.XB = blackPoint[0];
     this.YB = blackPoint[1];
     this.ZB = blackPoint[2];
 
     // Validate vars as per spec
-    if (this.XW < 0 || this.ZW < 0 || this.YW !== 1)
+    if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
       error('Invalid WhitePoint components, no fallback available');
+    }
 
     if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
       info('Invalid BlackPoint, falling back to default');
       this.XB = this.YB = this.ZB = 0;
     }
 
     if (this.amin > this.amax || this.bmin > this.bmax) {
       info('Invalid Range, falling back to defaults');
@@ -1900,20 +1923,21 @@ var LabCS = (function LabCSClosure() {
       this.amax = 100;
       this.bmin = -100;
       this.bmax = 100;
     }
   }
 
   // Function g(x) from spec
   function fn_g(x) {
-    if (x >= 6 / 29)
+    if (x >= 6 / 29) {
       return x * x * x;
-    else
+    } else {
       return (108 / 841) * (x - 4 / 29);
+    }
   }
 
   function decode(value, high1, low2, high2) {
     return low2 + (value) * (high2 - low2) / (high1);
   }
 
   // If decoding is needed maxVal should be 2^bits per component - 1.
   function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) {
@@ -2001,18 +2025,19 @@ var PDFFunction = (function PDFFunctionC
   var CONSTRUCT_INTERPOLATED = 2;
   var CONSTRUCT_STICHED = 3;
   var CONSTRUCT_POSTSCRIPT = 4;
 
   return {
     getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps,
                                                        str) {
       var length = 1;
-      for (var i = 0, ii = size.length; i < ii; i++)
+      for (var i = 0, ii = size.length; i < ii; i++) {
         length *= size[i];
+      }
       length *= outputSize;
 
       var array = [];
       var codeSize = 0;
       var codeBuf = 0;
       // 32 is a valid bps so shifting won't work
       var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1);
 
@@ -2028,29 +2053,31 @@ var PDFFunction = (function PDFFunctionC
         array.push((codeBuf >> codeSize) * sampleMul);
         codeBuf &= (1 << codeSize) - 1;
       }
       return array;
     },
 
     getIR: function PDFFunction_getIR(xref, fn) {
       var dict = fn.dict;
-      if (!dict)
+      if (!dict) {
         dict = fn;
+      }
 
       var types = [this.constructSampled,
                    null,
                    this.constructInterpolated,
                    this.constructStiched,
                    this.constructPostScript];
 
       var typeNum = dict.get('FunctionType');
       var typeFn = types[typeNum];
-      if (!typeFn)
+      if (!typeFn) {
         error('Unknown type of function');
+      }
 
       return typeFn.call(this, fn, dict, xref);
     },
 
     fromIR: function PDFFunction_fromIR(IR) {
       var type = IR[0];
       switch (type) {
         case CONSTRUCT_SAMPLED:
@@ -2080,18 +2107,19 @@ var PDFFunction = (function PDFFunctionC
           out[index] = [arr[i], arr[i + 1]];
           ++index;
         }
         return out;
       }
       var domain = dict.get('Domain');
       var range = dict.get('Range');
 
-      if (!domain || !range)
+      if (!domain || !range) {
         error('No domain or range');
+      }
 
       var inputSize = domain.length / 2;
       var outputSize = range.length / 2;
 
       domain = toMultiArray(domain);
       range = toMultiArray(range);
 
       var size = dict.get('Size');
@@ -2109,20 +2137,21 @@ var PDFFunction = (function PDFFunctionC
         for (var i = 0; i < inputSize; ++i) {
           encode.push(0);
           encode.push(size[i] - 1);
         }
       }
       encode = toMultiArray(encode);
 
       var decode = dict.get('Decode');
-      if (!decode)
+      if (!decode) {
         decode = range;
-      else
+      } else {
         decode = toMultiArray(decode);
+      }
 
       var samples = this.getSampleArray(size, outputSize, bps, str);
 
       return [
         CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size,
         outputSize, Math.pow(2, bps) - 1, range
       ];
     },
@@ -2140,29 +2169,31 @@ var PDFFunction = (function PDFFunctionC
         var encode = IR[3];
         var decode = IR[4];
         var samples = IR[5];
         var size = IR[6];
         var n = IR[7];
         var mask = IR[8];
         var range = IR[9];
 
-        if (m != args.length)
+        if (m != args.length) {
           error('Incorrect number of arguments: ' + m + ' != ' +
                 args.length);
+        }
 
         var x = args;
 
         // Building the cube vertices: its part and sample index
         // http://rjwagner49.com/Mathematics/Interpolation.pdf
         var cubeVertices = 1 << m;
         var cubeN = new Float64Array(cubeVertices);
         var cubeVertex = new Uint32Array(cubeVertices);
-        for (var j = 0; j < cubeVertices; j++)
+        for (var j = 0; j < cubeVertices; j++) {
           cubeN[j] = 1;
+        }
 
         var k = n, pos = 1;
         // Map x_i to y_j for 0 <= i < m using the sampled function.
         for (var i = 0; i < m; ++i) {
           // x_i' = min(max(x_i, Domain_2i), Domain_2i+1)
           var domain_2i = domain[i][0];
           var domain_2i_1 = domain[i][1];
           var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1);
@@ -2195,18 +2226,19 @@ var PDFFunction = (function PDFFunctionC
           k *= size_i;
           pos <<= 1;
         }
 
         var y = new Float64Array(n);
         for (var j = 0; j < n; ++j) {
           // Sum all cube vertices' samples portions
           var rj = 0;
-          for (var i = 0; i < cubeVertices; i++)
+          for (var i = 0; i < cubeVertices; i++) {
             rj += samples[cubeVertex[i] + j] * cubeN[i];
+          }
 
           // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1,
           //                    Decode_2j, Decode_2j+1)
           rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
 
           // y_j = min(max(r_j, range_2j), range_2j+1)
           y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]);
         }
@@ -2216,61 +2248,67 @@ var PDFFunction = (function PDFFunctionC
     },
 
     constructInterpolated: function PDFFunction_constructInterpolated(str,
                                                                       dict) {
       var c0 = dict.get('C0') || [0];
       var c1 = dict.get('C1') || [1];
       var n = dict.get('N');
 
-      if (!isArray(c0) || !isArray(c1))
+      if (!isArray(c0) || !isArray(c1)) {
         error('Illegal dictionary for interpolated function');
+      }
 
       var length = c0.length;
       var diff = [];
-      for (var i = 0; i < length; ++i)
+      for (var i = 0; i < length; ++i) {
         diff.push(c1[i] - c0[i]);
+      }
 
       return [CONSTRUCT_INTERPOLATED, c0, diff, n];
     },
 
     constructInterpolatedFromIR:
       function PDFFunction_constructInterpolatedFromIR(IR) {
       var c0 = IR[1];
       var diff = IR[2];
       var n = IR[3];
 
       var length = diff.length;
 
       return function constructInterpolatedFromIRResult(args) {
         var x = n == 1 ? args[0] : Math.pow(args[0], n);
 
         var out = [];
-        for (var j = 0; j < length; ++j)
+        for (var j = 0; j < length; ++j) {
           out.push(c0[j] + (x * diff[j]));
+        }
 
         return out;
 
       };
     },
 
     constructStiched: function PDFFunction_constructStiched(fn, dict, xref) {
       var domain = dict.get('Domain');
 
-      if (!domain)
+      if (!domain) {
         error('No domain');
+      }
 
       var inputSize = domain.length / 2;
-      if (inputSize != 1)
+      if (inputSize != 1) {
         error('Bad domain for stiched function');
+      }
 
       var fnRefs = dict.get('Functions');
       var fns = [];
-      for (var i = 0, ii = fnRefs.length; i < ii; ++i)
+      for (var i = 0, ii = fnRefs.length; i < ii; ++i) {
         fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i])));
+      }
 
       var bounds = dict.get('Bounds');
       var encode = dict.get('Encode');
 
       return [CONSTRUCT_STICHED, domain, bounds, encode, fns];
     },
 
     constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) {
@@ -2281,59 +2319,65 @@ var PDFFunction = (function PDFFunctionC
       var fns = [];
 
       for (var i = 0, ii = fnsIR.length; i < ii; i++) {
         fns.push(PDFFunction.fromIR(fnsIR[i]));
       }
 
       return function constructStichedFromIRResult(args) {
         var clip = function constructStichedFromIRClip(v, min, max) {
-          if (v > max)
+          if (v > max) {
             v = max;
-          else if (v < min)
+          } else if (v < min) {
             v = min;
+          }
           return v;
         };
 
         // clip to domain
         var v = clip(args[0], domain[0], domain[1]);
         // calulate which bound the value is in
         for (var i = 0, ii = bounds.length; i < ii; ++i) {
-          if (v < bounds[i])
+          if (v < bounds[i]) {
             break;
+          }
         }
 
         // encode value into domain of function
         var dmin = domain[0];
-        if (i > 0)
+        if (i > 0) {
           dmin = bounds[i - 1];
+        }
         var dmax = domain[1];
-        if (i < bounds.length)
+        if (i < bounds.length) {
           dmax = bounds[i];
+        }
 
         var rmin = encode[2 * i];
         var rmax = encode[2 * i + 1];
 
         var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
 
-        // call the appropropriate function
+        // call the appropriate function
         return fns[i]([v2]);
       };
     },
 
     constructPostScript: function PDFFunction_constructPostScript(fn, dict,
                                                                   xref) {
       var domain = dict.get('Domain');
       var range = dict.get('Range');
 
-      if (!domain)
+      if (!domain) {
         error('No domain.');
-
-      if (!range)
+      }
+
+      if (!range) {
         error('No range.');
+      }
 
       var lexer = new PostScriptLexer(fn);
       var parser = new PostScriptParser(lexer);
       var code = parser.parse();
 
       return [CONSTRUCT_POSTSCRIPT, domain, range, code];
     },
 
@@ -2349,28 +2393,30 @@ var PDFFunction = (function PDFFunctionC
       var cache = new FunctionCache();
       return function constructPostScriptFromIRResult(args) {
         var initialStack = [];
         for (var i = 0, ii = (domain.length / 2); i < ii; ++i) {
           initialStack.push(args[i]);
         }
 
         var key = initialStack.join('_');
-        if (cache.has(key))
+        if (cache.has(key)) {
           return cache.get(key);
+        }
 
         var stack = evaluator.execute(initialStack);
         var transformed = [];
         for (i = numOutputs - 1; i >= 0; --i) {
           var out = stack.pop();
           var rangeIndex = 2 * i;
-          if (out < range[rangeIndex])
+          if (out < range[rangeIndex]) {
             out = range[rangeIndex];
-          else if (out > range[rangeIndex + 1])
+          } else if (out > range[rangeIndex + 1]) {
             out = range[rangeIndex + 1];
+          }
           transformed[i] = out;
         }
         cache.set(key, transformed);
         return transformed;
       };
     }
   };
 })();
@@ -2403,31 +2449,35 @@ var FunctionCache = (function FunctionCa
 var PostScriptStack = (function PostScriptStackClosure() {
   var MAX_STACK_SIZE = 100;
   function PostScriptStack(initialStack) {
     this.stack = initialStack || [];
   }
 
   PostScriptStack.prototype = {
     push: function PostScriptStack_push(value) {
-      if (this.stack.length >= MAX_STACK_SIZE)
+      if (this.stack.length >= MAX_STACK_SIZE) {
         error('PostScript function stack overflow.');
+      }
       this.stack.push(value);
     },
     pop: function PostScriptStack_pop() {
-      if (this.stack.length <= 0)
+      if (this.stack.length <= 0) {
         error('PostScript function stack underflow.');
+      }
       return this.stack.pop();
     },
     copy: function PostScriptStack_copy(n) {
-      if (this.stack.length + n >= MAX_STACK_SIZE)
+      if (this.stack.length + n >= MAX_STACK_SIZE) {
         error('PostScript function stack overflow.');
+      }
       var stack = this.stack;
-      for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++)
+      for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) {
         stack.push(stack[i]);
+      }
     },
     index: function PostScriptStack_index(n) {
       this.push(this.stack[this.stack.length - n - 1]);
     },
     // rotate the last n stack elements p times
     roll: function PostScriptStack_roll(n, p) {
       var stack = this.stack;
       var l = stack.length - n;
@@ -2463,18 +2513,19 @@ var PostScriptEvaluator = (function Post
           stack.push(operator);
           continue;
         }
         switch (operator) {
           // non standard ps operators
           case 'jz': // jump if false
             b = stack.pop();
             a = stack.pop();
-            if (!a)
+            if (!a) {
               counter = b;
+            }
             break;
           case 'j': // jump
             a = stack.pop();
             counter = a;
             break;
 
           // all ps operators in alphabetical order (excluding if/ifelse)
           case 'abs':
@@ -2484,32 +2535,34 @@ var PostScriptEvaluator = (function Post
           case 'add':
             b = stack.pop();
             a = stack.pop();
             stack.push(a + b);
             break;
           case 'and':
             b = stack.pop();
             a = stack.pop();
-            if (isBool(a) && isBool(b))
+            if (isBool(a) && isBool(b)) {
               stack.push(a && b);
-            else
+            } else {
               stack.push(a & b);
+            }
             break;
           case 'atan':
             a = stack.pop();
             stack.push(Math.atan(a));
             break;
           case 'bitshift':
             b = stack.pop();
             a = stack.pop();
-            if (a > 0)
+            if (a > 0) {
               stack.push(a << b);
-            else
+            } else {
               stack.push(a >> b);
+            }
             break;
           case 'ceiling':
             a = stack.pop();
             stack.push(Math.ceil(a));
             break;
           case 'copy':
             a = stack.pop();
             stack.copy(a);
@@ -2606,28 +2659,30 @@ var PostScriptEvaluator = (function Post
             stack.push(a != b);
             break;
           case 'neg':
             a = stack.pop();
             stack.push(-b);
             break;
           case 'not':
             a = stack.pop();
-            if (isBool(a) && isBool(b))
+            if (isBool(a) && isBool(b)) {
               stack.push(a && b);
-            else
+            } else {
               stack.push(a & b);
+            }
             break;
           case 'or':
             b = stack.pop();
             a = stack.pop();
-            if (isBool(a) && isBool(b))
+            if (isBool(a) && isBool(b)) {
               stack.push(a || b);
-            else
+            } else {
               stack.push(a | b);
+            }
             break;
           case 'pop':
             stack.pop();
             break;
           case 'roll':
             b = stack.pop();
             a = stack.pop();
             stack.roll(a, b);
@@ -2655,33 +2710,37 @@ var PostScriptEvaluator = (function Post
           case 'truncate':
             a = stack.pop();
             a = a < 0 ? Math.ceil(a) : Math.floor(a);
             stack.push(a);
             break;
           case 'xor':
             b = stack.pop();
             a = stack.pop();
-            if (isBool(a) && isBool(b))
+            if (isBool(a) && isBool(b)) {
               stack.push(a != b);
-            else
+            } else {
               stack.push(a ^ b);
+            }
             break;
           default:
             error('Unknown operator ' + operator);
             break;
         }
       }
       return stack.stack;
     }
   };
   return PostScriptEvaluator;
 })();
 
 
+var HIGHLIGHT_OFFSET = 4; // px
+var SUPPORTED_TYPES = ['Link', 'Text', 'Widget'];
+
 var Annotation = (function AnnotationClosure() {
   // 12.5.5: Algorithm: Appearance streams
   function getTransformMatrix(rect, bbox, matrix) {
     var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
     var minX = bounds[0];
     var minY = bounds[1];
     var maxX = bounds[2];
     var maxY = bounds[3];
@@ -2794,35 +2853,60 @@ var Annotation = (function AnnotationClo
     },
 
     getHtmlElement: function Annotation_getHtmlElement(commonObjs) {
       throw new NotImplementedException(
         'getHtmlElement() should be implemented in subclass');
     },
 
     // TODO(mack): Remove this, it's not really that helpful.
-    getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect) {
+    getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect,
+                                                             borderWidth) {
       assert(!isWorker,
         'getEmptyContainer() should be called from main thread');
 
+      var bWidth = borderWidth || 0;
+
       rect = rect || this.data.rect;
       var element = document.createElement(tagName);
-      element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
-      element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
+      element.style.borderWidth = bWidth + 'px';
+      var width = rect[2] - rect[0] - 2 * bWidth;
+      var height = rect[3] - rect[1] - 2 * bWidth;
+      element.style.width = width + 'px';
+      element.style.height = height + 'px';
       return element;
     },
 
+    isInvisible: function Annotation_isInvisible() {
+      var data = this.data;
+      if (data && SUPPORTED_TYPES.indexOf(data.subtype) !== -1) {
+        return false;
+      } else {
+        return !!(data &&
+                  data.annotationFlags &&            // Default: not invisible
+                  data.annotationFlags & 0x1);       // Invisible
+      }
+    },
+
     isViewable: function Annotation_isViewable() {
       var data = this.data;
-      return !!(
-        data &&
-        (!data.annotationFlags ||
-         !(data.annotationFlags & 0x22)) && // Hidden or NoView
-        data.rect                            // rectangle is nessessary
-      );
+      return !!(!this.isInvisible() &&
+                data &&
+                (!data.annotationFlags ||
+                 !(data.annotationFlags & 0x22)) &&  // Hidden or NoView
+                data.rect);                          // rectangle is nessessary
+    },
+
+    isPrintable: function Annotation_isPrintable() {
+      var data = this.data;
+      return !!(!this.isInvisible() &&
+                data &&
+                data.annotationFlags &&              // Default: not printable
+                data.annotationFlags & 0x4 &&        // Print
+                data.rect);                          // rectangle is nessessary
     },
 
     loadResources: function(keys) {
       var promise = new LegacyPromise();
       this.appearance.dict.getAsync('Resources').then(function(resources) {
         if (!resources) {
           promise.resolve();
           return;
@@ -2833,17 +2917,17 @@ var Annotation = (function AnnotationClo
         objectLoader.load().then(function() {
           promise.resolve(resources);
         });
       }.bind(this));
 
       return promise;
     },
 
-    getOperatorList: function Annotation_getToOperatorList(evaluator) {
+    getOperatorList: function Annotation_getOperatorList(evaluator) {
 
       var promise = new LegacyPromise();
 
       if (!this.appearance) {
         promise.resolve(new OperatorList());
         return promise;
       }
 
@@ -2940,35 +3024,39 @@ var Annotation = (function AnnotationClo
 
     var params = {
       dict: dict,
       ref: ref,
     };
 
     var annotation = new Constructor(params);
 
-    if (annotation.isViewable()) {
+    if (annotation.isViewable() || annotation.isPrintable()) {
       return annotation;
     } else {
       warn('unimplemented annotation type: ' + subtype);
     }
   };
 
   Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
-      annotations, opList, pdfManager, partialEvaluator) {
+      annotations, opList, pdfManager, partialEvaluator, intent) {
 
     function reject(e) {
       annotationsReadyPromise.reject(e);
     }
 
     var annotationsReadyPromise = new LegacyPromise();
 
     var annotationPromises = [];
     for (var i = 0, n = annotations.length; i < n; ++i) {
-      annotationPromises.push(annotations[i].getOperatorList(partialEvaluator));
+      if (intent === 'display' && annotations[i].isViewable() ||
+          intent === 'print' && annotations[i].isPrintable()) {
+        annotationPromises.push(
+          annotations[i].getOperatorList(partialEvaluator));
+      }
     }
     Promise.all(annotationPromises).then(function(datas) {
       opList.addOp(OPS.beginAnnotations, []);
       for (var i = 0, n = datas.length; i < n; ++i) {
         var annotOpList = datas[i];
         opList.addOpList(annotOpList);
       }
       opList.addOp(OPS.endAnnotations, []);
@@ -3020,18 +3108,19 @@ var WidgetAnnotation = (function WidgetA
         // with the same name may exist. Replacing the empty name
         // with the '`' plus index in the parent's 'Kids' array.
         // This is not in the PDF spec but necessary to id the
         // the input controls.
         var kids = parent.get('Kids');
         var j, jj;
         for (j = 0, jj = kids.length; j < jj; j++) {
           var kidRef = kids[j];
-          if (kidRef.num == ref.num && kidRef.gen == ref.gen)
+          if (kidRef.num == ref.num && kidRef.gen == ref.gen) {
             break;
+          }
         }
         fieldName.unshift('`' + j);
       }
       namedItem = parent;
       ref = parentRef;
     }
     data.fullName = fieldName.join('.');
   }
@@ -3170,127 +3259,228 @@ var TextWidgetAnnotation = (function Tex
       promise.resolve(opList);
       return promise;
     }
   });
 
   return TextWidgetAnnotation;
 })();
 
+var InteractiveAnnotation = (function InteractiveAnnotationClosure() {
+  function InteractiveAnnotation(params) {
+    Annotation.call(this, params);
+  }
+
+  Util.inherit(InteractiveAnnotation, Annotation, {
+    hasHtml: function InteractiveAnnotation_hasHtml() {
+      return true;
+    },
+
+    highlight: function InteractiveAnnotation_highlight() {
+      if (this.highlightElement &&
+         this.highlightElement.hasAttribute('hidden')) {
+        this.highlightElement.removeAttribute('hidden');
+      }
+    },
+
+    unhighlight: function InteractiveAnnotation_unhighlight() {
+      if (this.highlightElement &&
+         !this.highlightElement.hasAttribute('hidden')) {
+        this.highlightElement.setAttribute('hidden', true);
+      }
+    },
+
+    initContainer: function InteractiveAnnotation_initContainer() {
+
+      var item = this.data;
+      var rect = item.rect;
+
+      var container = this.getEmptyContainer('section', rect, item.borderWidth);
+      container.style.backgroundColor = item.color;
+
+      var color = item.color;
+      var rgb = [];
+      for (var i = 0; i < 3; ++i) {
+        rgb[i] = Math.round(color[i] * 255);
+      }
+      item.colorCssRgb = Util.makeCssRgb(rgb);
+
+      var highlight = document.createElement('div');
+      highlight.className = 'annotationHighlight';
+      highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px';
+      highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px';
+      highlight.setAttribute('hidden', true);
+
+      this.highlightElement = highlight;
+      container.appendChild(this.highlightElement);
+
+      return container;
+    }
+  });
+
+  return InteractiveAnnotation;
+})();
+
 var TextAnnotation = (function TextAnnotationClosure() {
   function TextAnnotation(params) {
-    Annotation.call(this, params);
+    InteractiveAnnotation.call(this, params);
 
     if (params.data) {
       return;
     }
 
     var dict = params.dict;
     var data = this.data;
 
     var content = dict.get('Contents');
     var title = dict.get('T');
     data.content = stringToPDFString(content || '');
     data.title = stringToPDFString(title || '');
-    data.name = !dict.has('Name') ? 'Note' : dict.get('Name').name;
+
+    if (data.hasAppearance) {
+      data.name = 'NoIcon';
+    } else {
+      data.name = dict.has('Name') ? dict.get('Name').name : 'Note';
+    }
+
+    if (dict.has('C')) {
+      data.hasBgColor = true;
+    }
   }
 
   var ANNOT_MIN_SIZE = 10;
 
-  Util.inherit(TextAnnotation, Annotation, {
-
-    getOperatorList: function TextAnnotation_getOperatorList(evaluator) {
-      var promise = new LegacyPromise();
-      promise.resolve(new OperatorList());
-      return promise;
-    },
-
-    hasHtml: function TextAnnotation_hasHtml() {
-      return true;
-    },
+  Util.inherit(TextAnnotation, InteractiveAnnotation, {
 
     getHtmlElement: function TextAnnotation_getHtmlElement(commonObjs) {
       assert(!isWorker, 'getHtmlElement() shall be called from main thread');
 
       var item = this.data;
       var rect = item.rect;
 
       // sanity check because of OOo-generated PDFs
       if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) {
         rect[3] = rect[1] + ANNOT_MIN_SIZE;
       }
       if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
         rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
       }
 
-      var container = this.getEmptyContainer('section', rect);
+      var container = this.initContainer();
       container.className = 'annotText';
 
-      var image = document.createElement('img');
+      var image  = document.createElement('img');
       image.style.height = container.style.height;
+      image.style.width = container.style.width;
       var iconName = item.name;
       image.src = PDFJS.imageResourcesPath + 'annotation-' +
         iconName.toLowerCase() + '.svg';
       image.alt = '[{{type}} Annotation]';
       image.dataset.l10nId = 'text_annotation_type';
       image.dataset.l10nArgs = JSON.stringify({type: iconName});
+
+      var contentWrapper = document.createElement('div');
+      contentWrapper.className = 'annotTextContentWrapper';
+      contentWrapper.style.left = Math.floor(rect[2] - rect[0] + 5) + 'px';
+      contentWrapper.style.top = '-10px';
+
       var content = document.createElement('div');
+      content.className = 'annotTextContent';
       content.setAttribute('hidden', true);
+      if (item.hasBgColor) {
+        var color = item.color;
+        var rgb = [];
+        for (var i = 0; i < 3; ++i) {
+          // Enlighten the color (70%)
+          var c = Math.round(color[i] * 255);
+          rgb[i] = Math.round((255 - c) * 0.7) + c;
+        }
+        content.style.backgroundColor = Util.makeCssRgb(rgb);
+      }
+
       var title = document.createElement('h1');
       var text = document.createElement('p');
-      content.style.left = Math.floor(rect[2] - rect[0]) + 'px';
-      content.style.top = '0px';
       title.textContent = item.title;
 
       if (!item.content && !item.title) {
         content.setAttribute('hidden', true);
       } else {
         var e = document.createElement('span');
         var lines = item.content.split(/(?:\r\n?|\n)/);
         for (var i = 0, ii = lines.length; i < ii; ++i) {
           var line = lines[i];
           e.appendChild(document.createTextNode(line));
-          if (i < (ii - 1))
+          if (i < (ii - 1)) {
             e.appendChild(document.createElement('br'));
+          }
         }
         text.appendChild(e);
 
-        var showAnnotation = function showAnnotation() {
-          container.style.zIndex += 1;
-          content.removeAttribute('hidden');
+        var pinned = false;
+
+        var showAnnotation = function showAnnotation(pin) {
+          if (pin) {
+            pinned = true;
+          }
+          if (content.hasAttribute('hidden')) {
+            container.style.zIndex += 1;
+            content.removeAttribute('hidden');
+          }
         };
 
-        var hideAnnotation = function hideAnnotation(e) {
-          if (e.toElement || e.relatedTarget) { // No context menu is used
+        var hideAnnotation = function hideAnnotation(unpin) {
+          if (unpin) {
+            pinned = false;
+          }
+          if (!content.hasAttribute('hidden') && !pinned) {
             container.style.zIndex -= 1;
             content.setAttribute('hidden', true);
           }
         };
 
-        content.addEventListener('mouseover', showAnnotation, false);
-        content.addEventListener('mouseout', hideAnnotation, false);
-        image.addEventListener('mouseover', showAnnotation, false);
-        image.addEventListener('mouseout', hideAnnotation, false);
+        var toggleAnnotation = function toggleAnnotation() {
+          if (pinned) {
+            hideAnnotation(true);
+          } else {
+            showAnnotation(true);
+          }
+        };
+
+        var self = this;
+        image.addEventListener('click', function image_clickHandler() {
+          toggleAnnotation();
+        }, false);
+        image.addEventListener('mouseover', function image_mouseOverHandler() {
+          showAnnotation();
+        }, false);
+        image.addEventListener('mouseout', function image_mouseOutHandler() {
+          hideAnnotation();
+        }, false);
+
+        content.addEventListener('click', function content_clickHandler() {
+          hideAnnotation(true);
+        }, false);
       }
 
       content.appendChild(title);
       content.appendChild(text);
+      contentWrapper.appendChild(content);
       container.appendChild(image);
-      container.appendChild(content);
+      container.appendChild(contentWrapper);
 
       return container;
     }
   });
 
   return TextAnnotation;
 })();
 
 var LinkAnnotation = (function LinkAnnotationClosure() {
   function LinkAnnotation(params) {
-    Annotation.call(this, params);
+    InteractiveAnnotation.call(this, params);
 
     if (params.data) {
       return;
     }
 
     var dict = params.dict;
     var data = this.data;
 
@@ -3343,46 +3533,38 @@ var LinkAnnotation = (function LinkAnnot
   // Lets URLs beginning with 'www.' default to using the 'http://' protocol.
   function addDefaultProtocolToUrl(url) {
     if (url && url.indexOf('www.') === 0) {
       return ('http://' + url);
     }
     return url;
   }
 
-  Util.inherit(LinkAnnotation, Annotation, {
+  Util.inherit(LinkAnnotation, InteractiveAnnotation, {
     hasOperatorList: function LinkAnnotation_hasOperatorList() {
       return false;
     },
 
-    hasHtml: function LinkAnnotation_hasHtml() {
-      return true;
-    },
-
     getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) {
-      var rect = this.data.rect;
-      var element = document.createElement('a');
-      var borderWidth = this.data.borderWidth;
-
-      element.style.borderWidth = borderWidth + 'px';
-      var color = this.data.color;
-      var rgb = [];
-      for (var i = 0; i < 3; ++i) {
-        rgb[i] = Math.round(color[i] * 255);
-      }
-      element.style.borderColor = Util.makeCssRgb(rgb);
-      element.style.borderStyle = 'solid';
-
-      var width = rect[2] - rect[0] - 2 * borderWidth;
-      var height = rect[3] - rect[1] - 2 * borderWidth;
-      element.style.width = width + 'px';
-      element.style.height = height + 'px';
-
-      element.href = this.data.url || '';
-      return element;
+
+      var container = this.initContainer();
+      container.className = 'annotLink';
+
+      var item = this.data;
+      var rect = item.rect;
+
+      container.style.borderColor = item.colorCssRgb;
+      container.style.borderStyle = 'solid';
+
+      var link = document.createElement('a');
+      link.href = link.title = this.data.url || '';
+
+      container.appendChild(link);
+
+      return container;
     }
   });
 
   return LinkAnnotation;
 })();
 
 
 /**
@@ -3699,20 +3881,19 @@ var PDFDocumentProxy = (function PDFDocu
 var PDFPageProxy = (function PDFPageProxyClosure() {
   function PDFPageProxy(pageInfo, transport) {
     this.pageInfo = pageInfo;
     this.transport = transport;
     this.stats = new StatTimer();
     this.stats.enabled = !!globalScope.PDFJS.enableStats;
     this.commonObjs = transport.commonObjs;
     this.objs = new PDFObjects();
-    this.receivingOperatorList  = false;
     this.cleanupAfterRender = false;
     this.pendingDestroy = false;
-    this.renderTasks = [];
+    this.intentStates = {};
   }
   PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ {
     /**
      * @return {number} Page number of the page. First page is 1.
      */
     get pageNumber() {
       return this.pageInfo.pageIndex + 1;
     },
@@ -3781,59 +3962,73 @@ var PDFPageProxy = (function PDFPageProx
     render: function PDFPageProxy_render(params) {
       var stats = this.stats;
       stats.time('Overall');
 
       // If there was a pending destroy cancel it so no cleanup happens during
       // this call to render.
       this.pendingDestroy = false;
 
+      var renderingIntent = 'intent' in params ?
+        (params.intent == 'print' ? 'print' : 'display') :
+        'display';
+
+      if (!this.intentStates[renderingIntent]) {
+        this.intentStates[renderingIntent] = {};
+      }
+      var intentState = this.intentStates[renderingIntent];
+
       // If there is no displayReadyPromise yet, then the operatorList was never
       // requested before. Make the request and create the promise.
-      if (!this.displayReadyPromise) {
-        this.receivingOperatorList = true;
-        this.displayReadyPromise = new LegacyPromise();
-        this.operatorList = {
+      if (!intentState.displayReadyPromise) {
+        intentState.receivingOperatorList = true;
+        intentState.displayReadyPromise = new LegacyPromise();
+        intentState.operatorList = {
           fnArray: [],
           argsArray: [],
           lastChunk: false
         };
 
         this.stats.time('Page Request');
         this.transport.messageHandler.send('RenderPageRequest', {
-          pageIndex: this.pageNumber - 1
+          pageIndex: this.pageNumber - 1,
+          intent: renderingIntent
         });
       }
 
       var internalRenderTask = new InternalRenderTask(complete, params,
                                        this.objs, this.commonObjs,
-                                       this.operatorList, this.pageNumber);
-      this.renderTasks.push(internalRenderTask);
+                                       intentState.operatorList,
+                                       this.pageNumber);
+      if (!intentState.renderTasks) {
+        intentState.renderTasks = [];
+      }
+      intentState.renderTasks.push(internalRenderTask);
       var renderTask = new RenderTask(internalRenderTask);
 
       var self = this;
-      this.displayReadyPromise.then(
+      intentState.displayReadyPromise.then(
         function pageDisplayReadyPromise(transparency) {
           if (self.pendingDestroy) {
             complete();
             return;
           }
           stats.time('Rendering');
           internalRenderTask.initalizeGraphics(transparency);
           internalRenderTask.operatorListChanged();
         },
         function pageDisplayReadPromiseError(reason) {
           complete(reason);
         }
       );
 
       function complete(error) {
-        var i = self.renderTasks.indexOf(internalRenderTask);
+        var i = intentState.renderTasks.indexOf(internalRenderTask);
         if (i >= 0) {
-          self.renderTasks.splice(i, 1);
+          intentState.renderTasks.splice(i, 1);
         }
 
         if (self.cleanupAfterRender) {
           self.pendingDestroy = true;
         }
         self._tryDestroy();
 
         if (error) {
@@ -3871,53 +4066,61 @@ var PDFPageProxy = (function PDFPageProx
     },
     /**
      * For internal use only. Attempts to clean up if rendering is in a state
      * where that's possible.
      * @ignore
      */
     _tryDestroy: function PDFPageProxy__destroy() {
       if (!this.pendingDestroy ||
-          this.renderTasks.length !== 0 ||
-          this.receivingOperatorList) {
+          Object.keys(this.intentStates).some(function(intent) {
+            var intentState = this.intentStates[intent];
+            return intentState.renderTasks.length !== 0 ||
+                   intentState.receivingOperatorList;
+          }, this)) {
         return;
       }
 
-      delete this.operatorList;
-      delete this.displayReadyPromise;
-      delete this.annotationsPromise;
+      Object.keys(this.intentStates).forEach(function(intent) {
+        delete this.intentStates[intent];
+      }, this);
       this.objs.clear();
       this.pendingDestroy = false;
     },
     /**
      * For internal use only.
      * @ignore
      */
-    _startRenderPage: function PDFPageProxy_startRenderPage(transparency) {
-      this.displayReadyPromise.resolve(transparency);
+    _startRenderPage: function PDFPageProxy_startRenderPage(transparency,
+                                                            intent) {
+      var intentState = this.intentStates[intent];
+      intentState.displayReadyPromise.resolve(transparency);
     },
     /**
      * For internal use only.
      * @ignore
      */
-    _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk) {
+    _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk,
+                                                            intent) {
+      var intentState = this.intentStates[intent];
       // Add the new chunk to the current operator list.
       for (var i = 0, ii = operatorListChunk.length; i < ii; i++) {
-        this.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
-        this.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
-      }
-      this.operatorList.lastChunk = operatorListChunk.lastChunk;
+        intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
+        intentState.operatorList.argsArray.push(
+          operatorListChunk.argsArray[i]);
+      }
+      intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
 
       // Notify all the rendering tasks there are more operators to be consumed.
-      for (var i = 0; i < this.renderTasks.length; i++) {
-        this.renderTasks[i].operatorListChanged();
+      for (var i = 0; i < intentState.renderTasks.length; i++) {
+        intentState.renderTasks[i].operatorListChanged();
       }
 
       if (operatorListChunk.lastChunk) {
-        this.receivingOperatorList = false;
+        intentState.receivingOperatorList = false;
         this._tryDestroy();
       }
     }
   };
   return PDFPageProxy;
 })();
 
 /**
@@ -4123,23 +4326,23 @@ var WorkerTransport = (function WorkerTr
         var promise = this.pageCache[data.pageIndex].annotationsPromise;
         promise.resolve(annotations);
       }, this);
 
       messageHandler.on('StartRenderPage', function transportRender(data) {
         var page = this.pageCache[data.pageIndex];
 
         page.stats.timeEnd('Page Request');
-        page._startRenderPage(data.transparency);
+        page._startRenderPage(data.transparency, data.intent);
       }, this);
 
       messageHandler.on('RenderPageChunk', function transportRender(data) {
         var page = this.pageCache[data.pageIndex];
 
-        page._renderPageChunk(data.operatorList);
+        page._renderPageChunk(data.operatorList, data.intent);
       }, this);
 
       messageHandler.on('commonobj', function transportObj(data) {
         var id = data[0];
         var type = data[1];
         if (this.commonObjs.hasData(id))
           return;
 
@@ -4172,18 +4375,19 @@ var WorkerTransport = (function WorkerTr
         }
       }, this);
 
       messageHandler.on('obj', function transportObj(data) {
         var id = data[0];
         var pageIndex = data[1];
         var type = data[2];
         var pageProxy = this.pageCache[pageIndex];
-        if (pageProxy.objs.hasData(id))
+        if (pageProxy.objs.hasData(id)) {
           return;
+        }
 
         switch (type) {
           case 'JpegStream':
             var imageData = data[3];
             loadJpegStream(id, imageData, pageProxy.objs);
             break;
           case 'Image':
             var imageData = data[3];
@@ -4209,20 +4413,21 @@ var WorkerTransport = (function WorkerTr
           });
         }
       }, this);
 
       messageHandler.on('DocError', function transportDocError(data) {
         this.workerReadyPromise.reject(data);
       }, this);
 
-      messageHandler.on('PageError', function transportError(data) {
+      messageHandler.on('PageError', function transportError(data, intent) {
         var page = this.pageCache[data.pageNum - 1];
-        if (page.displayReadyPromise)
-          page.displayReadyPromise.reject(data.error);
+        var intentState = page.intentStates[intent];
+        if (intentState.displayReadyPromise)
+          intentState.displayReadyPromise.reject(data.error);
         else
           error(data.error);
       }, this);
 
       messageHandler.on('JpegDecode', function(data, deferred) {
         var imageUrl = data[0];
         var components = data[1];
         if (components != 3 && components != 1)
@@ -4614,30 +4819,32 @@ var Metadata = PDFJS.Metadata = (functio
 
   Metadata.prototype = {
     parse: function Metadata_parse() {
       var doc = this.metaDocument;
       var rdf = doc.documentElement;
 
       if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') { // Wrapped in <xmpmeta>
         rdf = rdf.firstChild;
-        while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf')
+        while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') {
           rdf = rdf.nextSibling;
+        }
       }
 
       var nodeName = (rdf) ? rdf.nodeName.toLowerCase() : null;
-      if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes())
+      if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) {
         return;
+      }
 
       var children = rdf.childNodes, desc, entry, name, i, ii, length, iLength;
-
       for (i = 0, length = children.length; i < length; i++) {
         desc = children[i];
-        if (desc.nodeName.toLowerCase() !== 'rdf:description')
+        if (desc.nodeName.toLowerCase() !== 'rdf:description') {
           continue;
+        }
 
         for (ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) {
           if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text') {
             entry = desc.childNodes[ii];
             name = entry.nodeName.toLowerCase();
             this.metadata[name] = entry.textContent.trim();
           }
         }
@@ -5138,30 +5345,25 @@ var CanvasGraphics = (function CanvasGra
           } while (destPos < destDataLength);
         }
 
         ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
       }
 
     } else if (imgData.kind === ImageKind.RGBA_32BPP) {
       // RGBA, 32-bits per pixel.
-      var haveSetAndSubarray = 'set' in dest && 'subarray' in src;
 
       for (var i = 0; i < totalChunks; i++) {
         var thisChunkHeight =
           (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
         var elemsInThisChunk = imgData.width * thisChunkHeight * 4;
-        if (haveSetAndSubarray) {
-          dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
-          srcPos += elemsInThisChunk;
-        } else {
-          for (var j = 0; j < elemsInThisChunk; j++) {
-            dest[j] = src[srcPos++];
-          }
-        }
+
+        dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
+        srcPos += elemsInThisChunk;
+
         ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
       }
 
     } else if (imgData.kind === ImageKind.RGB_24BPP) {
       // RGB, 24-bits per pixel.
       for (var i = 0; i < totalChunks; i++) {
         var thisChunkHeight =
           (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
@@ -7175,38 +7377,40 @@ var TilingPattern = (function TilingPatt
 
 
 PDFJS.disableFontFace = false;
 
 var FontLoader = {
   insertRule: function fontLoaderInsertRule(rule) {
     var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
     if (!styleElement) {
-        styleElement = document.createElement('style');
-        styleElement.id = 'PDFJS_FONT_STYLE_TAG';
-        document.documentElement.getElementsByTagName('head')[0].appendChild(
-          styleElement);
+      styleElement = document.createElement('style');
+      styleElement.id = 'PDFJS_FONT_STYLE_TAG';
+      document.documentElement.getElementsByTagName('head')[0].appendChild(
+        styleElement);
     }
 
     var styleSheet = styleElement.sheet;
     styleSheet.insertRule(rule, styleSheet.cssRules.length);
   },
+
   clear: function fontLoaderClear() {
     var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
     if (styleElement) {
       styleElement.parentNode.removeChild(styleElement);
     }
   },
   bind: function fontLoaderBind(fonts, callback) {
     assert(!isWorker, 'bind() shall be called from main thread');
   
     for (var i = 0, ii = fonts.length; i < ii; i++) {
       var font = fonts[i];
-      if (font.attached)
+      if (font.attached) {
         continue;
+      }
   
       font.attached = true;
       font.bindDOM()
     }
   
     setTimeout(callback);
   }
 };
@@ -7220,40 +7424,42 @@ var FontFace = (function FontFaceClosure
       for (var i in data) {
         this[i] = data[i];
       }
       return;
     }
   }
   FontFace.prototype = {
     bindDOM: function FontFace_bindDOM() {
-      if (!this.data)
+      if (!this.data) {
         return null;
+      }
 
       if (PDFJS.disableFontFace) {
         this.disableFontFace = true;
         return null;
       }
 
       var data = bytesToString(this.data);
       var fontName = this.loadedName;
 
       // Add the font-face rule to the document
       var url = ('url(data:' + this.mimetype + ';base64,' +
                  window.btoa(data) + ');');
       var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}';
-
       FontLoader.insertRule(rule);
 
       if (PDFJS.pdfBug && 'FontInspector' in globalScope &&
-          globalScope['FontInspector'].enabled)
+          globalScope['FontInspector'].enabled) {
         globalScope['FontInspector'].fontAdded(this, url);
+      }
 
       return rule;
     },
+
     getPathGenerator: function (objs, character) {
       if (!(character in this.compiledGlyphs)) {
         var js = objs.get(this.loadedName + '_path_' + character);
         /*jshint -W054 */
         this.compiledGlyphs[character] = new Function('c', 'size', js);
       }
       return this.compiledGlyphs[character];
     }
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -16,18 +16,18 @@
  */
 /*jshint globalstrict: false */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.1114';
-PDFJS.build = 'ea9c8f8';
+PDFJS.version = '0.8.1181';
+PDFJS.build = '31ea4e0';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -220,18 +220,19 @@ function backtrace() {
   try {
     throw new Error();
   } catch (e) {
     return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
   }
 }
 
 function assert(cond, msg) {
-  if (!cond)
+  if (!cond) {
     error(msg);
+  }
 }
 
 var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = {
   unknown: 'unknown',
   forms: 'forms',
   javaScript: 'javaScript',
   smask: 'smask',
   shadingPattern: 'shadingPattern',
@@ -252,20 +253,22 @@ var UnsupportedManager = PDFJS.Unsupport
       }
     }
   };
 })();
 
 // Combines two URLs. The baseUrl shall be absolute URL. If the url is an
 // absolute URL, it will be returned as is.
 function combineUrl(baseUrl, url) {
-  if (!url)
+  if (!url) {
     return baseUrl;
-  if (/^[a-z][a-z0-9+\-.]*:/i.test(url))
+  }
+  if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) {
     return url;
+  }
   if (url.charAt(0) == '/') {
     // absolute path
     var i = baseUrl.indexOf('://');
     if (url.charAt(1) === '/') {
       ++i;
     } else {
       i = baseUrl.indexOf('/', i + 3);
     }
@@ -304,18 +307,19 @@ function isValidUrl(url, allowRelative) 
       return false;
   }
 }
 PDFJS.isValidUrl = isValidUrl;
 
 // In a well-formed PDF, |cond| holds.  If it doesn't, subsequent
 // behavior is undefined.
 function assertWellFormed(cond, msg) {
-  if (!cond)
+  if (!cond) {
     error(msg);
+  }
 }
 
 function shadow(obj, prop, value) {
   Object.defineProperty(obj, prop, { value: value,
                                      enumerable: true,
                                      configurable: true,
                                      writable: false });
   return value;
@@ -422,18 +426,19 @@ function bytesToString(bytes) {
     strBuf.push(String.fromCharCode(bytes[n]));
   }
   return strBuf.join('');
 }
 
 function stringToBytes(str) {
   var length = str.length;
   var bytes = new Uint8Array(length);
-  for (var n = 0; n < length; ++n)
+  for (var n = 0; n < length; ++n) {
     bytes[n] = str.charCodeAt(n) & 0xFF;
+  }
   return bytes;
 }
 
 var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
 
 var Util = PDFJS.Util = (function UtilClosure() {
   function Util() {}
 
@@ -827,24 +832,25 @@ function isArrayBuffer(v) {
 }
 
 function isRef(v) {
   return v instanceof Ref;
 }
 
 function isPDFFunction(v) {
   var fnDict;
-  if (typeof v != 'object')
+  if (typeof v != 'object') {
     return false;
-  else if (isDict(v))
+  } else if (isDict(v)) {
     fnDict = v;
-  else if (isStream(v))
+  } else if (isStream(v)) {
     fnDict = v.dict;
-  else
+  } else {
     return false;
+  }
   return fnDict.has('FunctionType');
 }
 
 /**
  * Legacy support for PDFJS Promise implementation.
  * TODO remove eventually
  */
 var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() {
@@ -903,70 +909,77 @@ var LegacyPromise = PDFJS.LegacyPromise 
     }
     return;
   }
   throw new Error('DOM Promise is not present');
 })();
 
 var StatTimer = (function StatTimerClosure() {
   function rpad(str, pad, length) {
-    while (str.length < length)
+    while (str.length < length) {
       str += pad;
+    }
     return str;
   }
   function StatTimer() {
     this.started = {};
     this.times = [];
     this.enabled = true;
   }
   StatTimer.prototype = {
     time: function StatTimer_time(name) {
-      if (!this.enabled)
+      if (!this.enabled) {
         return;
-      if (name in this.started)
+      }
+      if (name in this.started) {
         warn('Timer is already running for ' + name);
+      }
       this.started[name] = Date.now();
     },
     timeEnd: function StatTimer_timeEnd(name) {
-      if (!this.enabled)
+      if (!this.enabled) {
         return;
-      if (!(name in this.started))
+      }
+      if (!(name in this.started)) {
         warn('Timer has not been started for ' + name);
+      }
       this.times.push({
         'name': name,
         'start': this.started[name],
         'end': Date.now()
       });
       // Remove timer from started so it can be called again.
       delete this.started[name];
     },
     toString: function StatTimer_toString() {
       var times = this.times;
       var out = '';
       // Find the longest name for padding purposes.
       var longest = 0;
       for (var i = 0, ii = times.length; i < ii; ++i) {
         var name = times[i]['name'];
-        if (name.length > longest)
+        if (name.length > longest) {
           longest = name.length;
+        }
       }
       for (var i = 0, ii = times.length; i < ii; ++i) {
         var span = times[i];
         var duration = span.end - span.start;
         out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
       }
       return out;
     }
   };
   return StatTimer;
 })();
 
 PDFJS.createBlob = function createBlob(data, contentType) {
-  if (typeof Blob !== 'undefined')
+  if (typeof Blob !== 'undefined') {
     return new Blob([data], { type: contentType });
+  }
   // Blob builder is deprecated in FF14 and removed in FF18.
   var bb = new MozBlobBuilder();
   bb.append(data);
   return bb.getBlob(contentType);
 };
 
 PDFJS.createObjectURL = (function createObjectURLClosure() {
   // Blob/createObjectURL is not available, falling back to data schema.
@@ -1233,19 +1246,19 @@ var ColorSpace = (function ColorSpaceClo
      * This should be true for all colorspaces except for lab color spaces
      * which are [0,100], [-128, 127], [-128, 127].
      */
     usesZeroToOneRange: true
   };
 
   ColorSpace.parse = function ColorSpace_parse(cs, xref, res) {
     var IR = ColorSpace.parseToIR(cs, xref, res);
-    if (IR instanceof AlternateCS)
+    if (IR instanceof AlternateCS) {
       return IR;
-
+    }
     return ColorSpace.fromIR(IR);
   };
 
   ColorSpace.fromIR = function ColorSpace_fromIR(IR) {
     var name = isArray(IR) ? IR[0] : IR;
 
     switch (name) {
       case 'DeviceGrayCS':
@@ -1256,18 +1269,19 @@ var ColorSpace = (function ColorSpaceClo
         return this.singletons.cmyk;
       case 'CalGrayCS':
         var whitePoint = IR[1].WhitePoint;
         var blackPoint = IR[1].BlackPoint;
         var gamma = IR[1].Gamma;
         return new CalGrayCS(whitePoint, blackPoint, gamma);
       case 'PatternCS':
         var basePatternCS = IR[1];
-        if (basePatternCS)
+        if (basePatternCS) {
           basePatternCS = ColorSpace.fromIR(basePatternCS);
+        }
         return new PatternCS(basePatternCS);
       case 'IndexedCS':
         var baseIndexedCS = IR[1];
         var hiVal = IR[2];
         var lookup = IR[3];
         return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup);
       case 'AlternateCS':
         var numComps = IR[1];
@@ -1287,18 +1301,19 @@ var ColorSpace = (function ColorSpaceClo
     return null;
   };
 
   ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) {
     if (isName(cs)) {
       var colorSpaces = res.get('ColorSpace');
       if (isDict(colorSpaces)) {
         var refcs = colorSpaces.get(cs.name);
-        if (refcs)
+        if (refcs) {
           cs = refcs;
+        }
       }
     }
 
     cs = xref.fetchIfRef(cs);
     var mode;
 
     if (isName(cs)) {
       mode = cs.name;
@@ -1337,45 +1352,48 @@ var ColorSpace = (function ColorSpaceClo
           var params = cs[1].getAll();
           return ['CalGrayCS', params];
         case 'CalRGB':
           return 'DeviceRgbCS';
         case 'ICCBased':
           var stream = xref.fetchIfRef(cs[1]);
           var dict = stream.dict;
           var numComps = dict.get('N');
-          if (numComps == 1)
+          if (numComps == 1) {
             return 'DeviceGrayCS';
-          if (numComps == 3)
+          } else if (numComps == 3) {
             return 'DeviceRgbCS';
-          if (numComps == 4)
+          } else if (numComps == 4) {
             return 'DeviceCmykCS';
+          }
           break;
         case 'Pattern':
           var basePatternCS = cs[1];
-          if (basePatternCS)
+          if (basePatternCS) {
             basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res);
+          }
           return ['PatternCS', basePatternCS];
         case 'Indexed':
         case 'I':
           var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res);
           var hiVal = cs[2] + 1;
           var lookup = xref.fetchIfRef(cs[3]);
           if (isStream(lookup)) {
             lookup = lookup.getBytes();
           }
           return ['IndexedCS', baseIndexedCS, hiVal, lookup];
         case 'Separation':
         case 'DeviceN':
           var name = cs[1];
           var numComps = 1;
-          if (isName(name))
+          if (isName(name)) {
             numComps = 1;
-          else if (isArray(name))
+          } else if (isArray(name)) {
             numComps = name.length;
+          }
           var alt = ColorSpace.parseToIR(cs[2], xref, res);
           var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
           return ['AlternateCS', numComps, alt, tintFnIR];
         case 'Lab':
           var params = cs[1].getAll();
           return ['LabCS', params];
         default:
           error('unimplemented color space object "' + mode + '"');
@@ -1390,26 +1408,28 @@ var ColorSpace = (function ColorSpaceClo
    * This handles the general decode maps where there are two values per
    * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color.
    * This does not handle Lab, Indexed, or Pattern decode maps since they are
    * slightly different.
    * @param {Array} decode Decode map (usually from an image).
    * @param {Number} n Number of components the color space has.
    */
   ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) {
-    if (!decode)
+    if (!decode) {
       return true;
+    }
 
     if (n * 2 !== decode.length) {
       warn('The decode map is not the correct length');
       return true;
     }
     for (var i = 0, ii = decode.length; i < ii; i += 2) {
-      if (decode[i] !== 0 || decode[i + 1] != 1)
+      if (decode[i] !== 0 || decode[i + 1] != 1) {
         return false;
+      }
     }
     return true;
   };
 
   ColorSpace.singletons = {
     get gray() {
       return shadow(this, 'gray', new DeviceGrayCS());
     },
@@ -1526,18 +1546,19 @@ var IndexedCS = (function IndexedCSClosu
     var lookupArray;
 
     if (isStream(lookup)) {
       lookupArray = new Uint8Array(length);
       var bytes = lookup.getBytes(length);
       lookupArray.set(bytes);
     } else if (isString(lookup)) {
       lookupArray = new Uint8Array(length);
-      for (var i = 0; i < length; ++i)
+      for (var i = 0; i < length; ++i) {
         lookupArray[i] = lookup.charCodeAt(i);
+      }
     } else if (lookup instanceof Uint8Array || lookup instanceof Array) {
       lookupArray = lookup;
     } else {
       error('Unrecognized lookup table: ' + lookup);
     }
     this.lookup = lookupArray;
   }
 
@@ -1860,18 +1881,19 @@ var CalGrayCS = (function CalGrayCSClosu
 // LabCS: Based on "PDF Reference, Sixth Ed", p.250
 //
 var LabCS = (function LabCSClosure() {
   function LabCS(whitePoint, blackPoint, range) {
     this.name = 'Lab';
     this.numComps = 3;
     this.defaultColor = new Float32Array([0, 0, 0]);
 
-    if (!whitePoint)
+    if (!whitePoint) {
       error('WhitePoint missing - required for color space Lab');
+    }
     blackPoint = blackPoint || [0, 0, 0];
     range = range || [-100, 100, -100, 100];
 
     // Translate args to spec variables
     this.XW = whitePoint[0];
     this.YW = whitePoint[1];
     this.ZW = whitePoint[2];
     this.amin = range[0];
@@ -1881,18 +1903,19 @@ var LabCS = (function LabCSClosure() {
 
     // These are here just for completeness - the spec doesn't offer any
     // formulas that use BlackPoint in Lab
     this.XB = blackPoint[0];
     this.YB = blackPoint[1];
     this.ZB = blackPoint[2];
 
     // Validate vars as per spec
-    if (this.XW < 0 || this.ZW < 0 || this.YW !== 1)
+    if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
       error('Invalid WhitePoint components, no fallback available');
+    }
 
     if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
       info('Invalid BlackPoint, falling back to default');
       this.XB = this.YB = this.ZB = 0;
     }
 
     if (this.amin > this.amax || this.bmin > this.bmax) {
       info('Invalid Range, falling back to defaults');
@@ -1900,20 +1923,21 @@ var LabCS = (function LabCSClosure() {
       this.amax = 100;
       this.bmin = -100;
       this.bmax = 100;
     }
   }
 
   // Function g(x) from spec
   function fn_g(x) {
-    if (x >= 6 / 29)
+    if (x >= 6 / 29) {
       return x * x * x;
-    else
+    } else {
       return (108 / 841) * (x - 4 / 29);
+    }
   }
 
   function decode(value, high1, low2, high2) {
     return low2 + (value) * (high2 - low2) / (high1);
   }
 
   // If decoding is needed maxVal should be 2^bits per component - 1.
   function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) {
@@ -2001,18 +2025,19 @@ var PDFFunction = (function PDFFunctionC
   var CONSTRUCT_INTERPOLATED = 2;
   var CONSTRUCT_STICHED = 3;
   var CONSTRUCT_POSTSCRIPT = 4;
 
   return {
     getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps,
                                                        str) {
       var length = 1;
-      for (var i = 0, ii = size.length; i < ii; i++)
+      for (var i = 0, ii = size.length; i < ii; i++) {
         length *= size[i];
+      }
       length *= outputSize;
 
       var array = [];
       var codeSize = 0;
       var codeBuf = 0;
       // 32 is a valid bps so shifting won't work
       var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1);
 
@@ -2028,29 +2053,31 @@ var PDFFunction = (function PDFFunctionC
         array.push((codeBuf >> codeSize) * sampleMul);
         codeBuf &= (1 << codeSize) - 1;
       }
       return array;
     },
 
     getIR: function PDFFunction_getIR(xref, fn) {
       var dict = fn.dict;
-      if (!dict)
+      if (!dict) {
         dict = fn;
+      }
 
       var types = [this.constructSampled,
                    null,
                    this.constructInterpolated,
                    this.constructStiched,
                    this.constructPostScript];
 
       var typeNum = dict.get('FunctionType');
       var typeFn = types[typeNum];
-      if (!typeFn)
+      if (!typeFn) {
         error('Unknown type of function');
+      }
 
       return typeFn.call(this, fn, dict, xref);
     },
 
     fromIR: function PDFFunction_fromIR(IR) {
       var type = IR[0];
       switch (type) {
         case CONSTRUCT_SAMPLED:
@@ -2080,18 +2107,19 @@ var PDFFunction = (function PDFFunctionC
           out[index] = [arr[i], arr[i + 1]];
           ++index;
         }
         return out;
       }
       var domain = dict.get('Domain');
       var range = dict.get('Range');
 
-      if (!domain || !range)
+      if (!domain || !range) {
         error('No domain or range');
+      }
 
       var inputSize = domain.length / 2;
       var outputSize = range.length / 2;
 
       domain = toMultiArray(domain);
       range = toMultiArray(range);
 
       var size = dict.get('Size');
@@ -2109,20 +2137,21 @@ var PDFFunction = (function PDFFunctionC
         for (var i = 0; i < inputSize; ++i) {
           encode.push(0);
           encode.push(size[i] - 1);
         }
       }
       encode = toMultiArray(encode);
 
       var decode = dict.get('Decode');
-      if (!decode)
+      if (!decode) {
         decode = range;
-      else
+      } else {
         decode = toMultiArray(decode);
+      }
 
       var samples = this.getSampleArray(size, outputSize, bps, str);
 
       return [
         CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size,
         outputSize, Math.pow(2, bps) - 1, range
       ];
     },
@@ -2140,29 +2169,31 @@ var PDFFunction = (function PDFFunctionC
         var encode = IR[3];
         var decode = IR[4];
         var samples = IR[5];
         var size = IR[6];
         var n = IR[7];
         var mask = IR[8];
         var range = IR[9];
 
-        if (m != args.length)
+        if (m != args.length) {
           error('Incorrect number of arguments: ' + m + ' != ' +
                 args.length);
+        }
 
         var x = args;
 
         // Building the cube vertices: its part and sample index
         // http://rjwagner49.com/Mathematics/Interpolation.pdf
         var cubeVertices = 1 << m;
         var cubeN = new Float64Array(cubeVertices);
         var cubeVertex = new Uint32Array(cubeVertices);
-        for (var j = 0; j < cubeVertices; j++)
+        for (var j = 0; j < cubeVertices; j++) {
           cubeN[j] = 1;
+        }
 
         var k = n, pos = 1;
         // Map x_i to y_j for 0 <= i < m using the sampled function.
         for (var i = 0; i < m; ++i) {
           // x_i' = min(max(x_i, Domain_2i), Domain_2i+1)
           var domain_2i = domain[i][0];
           var domain_2i_1 = domain[i][1];
           var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1);
@@ -2195,18 +2226,19 @@ var PDFFunction = (function PDFFunctionC
           k *= size_i;
           pos <<= 1;
         }
 
         var y = new Float64Array(n);
         for (var j = 0; j < n; ++j) {
           // Sum all cube vertices' samples portions
           var rj = 0;
-          for (var i = 0; i < cubeVertices; i++)
+          for (var i = 0; i < cubeVertices; i++) {
             rj += samples[cubeVertex[i] + j] * cubeN[i];
+          }
 
           // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1,
           //                    Decode_2j, Decode_2j+1)
           rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
 
           // y_j = min(max(r_j, range_2j), range_2j+1)
           y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]);
         }
@@ -2216,61 +2248,67 @@ var PDFFunction = (function PDFFunctionC
     },
 
     constructInterpolated: function PDFFunction_constructInterpolated(str,
                                                                       dict) {
       var c0 = dict.get('C0') || [0];
       var c1 = dict.get('C1') || [1];
       var n = dict.get('N');
 
-      if (!isArray(c0) || !isArray(c1))
+      if (!isArray(c0) || !isArray(c1)) {
         error('Illegal dictionary for interpolated function');
+      }
 
       var length = c0.length;
       var diff = [];
-      for (var i = 0; i < length; ++i)
+      for (var i = 0; i < length; ++i) {
         diff.push(c1[i] - c0[i]);
+      }
 
       return [CONSTRUCT_INTERPOLATED, c0, diff, n];
     },
 
     constructInterpolatedFromIR:
       function PDFFunction_constructInterpolatedFromIR(IR) {
       var c0 = IR[1];
       var diff = IR[2];
       var n = IR[3];
 
       var length = diff.length;
 
       return function constructInterpolatedFromIRResult(args) {
         var x = n == 1 ? args[0] : Math.pow(args[0], n);
 
         var out = [];
-        for (var j = 0; j < length; ++j)
+        for (var j = 0; j < length; ++j) {
           out.push(c0[j] + (x * diff[j]));
+        }
 
         return out;
 
       };
     },
 
     constructStiched: function PDFFunction_constructStiched(fn, dict, xref) {
       var domain = dict.get('Domain');
 
-      if (!domain)
+      if (!domain) {
         error('No domain');
+      }
 
       var inputSize = domain.length / 2;
-      if (inputSize != 1)
+      if (inputSize != 1) {
         error('Bad domain for stiched function');
+      }
 
       var fnRefs = dict.get('Functions');
       var fns = [];
-      for (var i = 0, ii = fnRefs.length; i < ii; ++i)
+      for (var i = 0, ii = fnRefs.length; i < ii; ++i) {
         fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i])));
+      }
 
       var bounds = dict.get('Bounds');
       var encode = dict.get('Encode');
 
       return [CONSTRUCT_STICHED, domain, bounds, encode, fns];
     },
 
     constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) {
@@ -2281,59 +2319,65 @@ var PDFFunction = (function PDFFunctionC
       var fns = [];
 
       for (var i = 0, ii = fnsIR.length; i < ii; i++) {
         fns.push(PDFFunction.fromIR(fnsIR[i]));
       }
 
       return function constructStichedFromIRResult(args) {
         var clip = function constructStichedFromIRClip(v, min, max) {
-          if (v > max)
+          if (v > max) {
             v = max;
-          else if (v < min)
+          } else if (v < min) {
             v = min;
+          }
           return v;
         };
 
         // clip to domain
         var v = clip(args[0], domain[0], domain[1]);
         // calulate which bound the value is in
         for (var i = 0, ii = bounds.length; i < ii; ++i) {
-          if (v < bounds[i])
-            break;
+          if (v < bounds[i]) {
+            break;
+          }
         }
 
         // encode value into domain of function
         var dmin = domain[0];
-        if (i > 0)
+        if (i > 0) {
           dmin = bounds[i - 1];
+        }
         var dmax = domain[1];
-        if (i < bounds.length)
+        if (i < bounds.length) {
           dmax = bounds[i];
+        }
 
         var rmin = encode[2 * i];
         var rmax = encode[2 * i + 1];
 
         var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
 
-        // call the appropropriate function
+        // call the appropriate function
         return fns[i]([v2]);
       };
     },
 
     constructPostScript: function PDFFunction_constructPostScript(fn, dict,
                                                                   xref) {
       var domain = dict.get('Domain');
       var range = dict.get('Range');
 
-      if (!domain)
+      if (!domain) {
         error('No domain.');
-
-      if (!range)
+      }
+
+      if (!range) {
         error('No range.');
+      }
 
       var lexer = new PostScriptLexer(fn);
       var parser = new PostScriptParser(lexer);
       var code = parser.parse();
 
       return [CONSTRUCT_POSTSCRIPT, domain, range, code];
     },
 
@@ -2349,28 +2393,30 @@ var PDFFunction = (function PDFFunctionC
       var cache = new FunctionCache();
       return function constructPostScriptFromIRResult(args) {
         var initialStack = [];
         for (var i = 0, ii = (domain.length / 2); i < ii; ++i) {
           initialStack.push(args[i]);
         }
 
         var key = initialStack.join('_');
-        if (cache.has(key))
+        if (cache.has(key)) {
           return cache.get(key);
+        }
 
         var stack = evaluator.execute(initialStack);
         var transformed = [];
         for (i = numOutputs - 1; i >= 0; --i) {
           var out = stack.pop();
           var rangeIndex = 2 * i;
-          if (out < range[rangeIndex])
+          if (out < range[rangeIndex]) {
             out = range[rangeIndex];
-          else if (out > range[rangeIndex + 1])
+          } else if (out > range[rangeIndex + 1]) {
             out = range[rangeIndex + 1];
+          }
           transformed[i] = out;
         }
         cache.set(key, transformed);
         return transformed;
       };
     }
   };
 })();
@@ -2403,31 +2449,35 @@ var FunctionCache = (function FunctionCa
 var PostScriptStack = (function PostScriptStackClosure() {
   var MAX_STACK_SIZE = 100;
   function PostScriptStack(initialStack) {
     this.stack = initialStack || [];
   }
 
   PostScriptStack.prototype = {
     push: function PostScriptStack_push(value) {
-      if (this.stack.length >= MAX_STACK_SIZE)
+      if (this.stack.length >= MAX_STACK_SIZE) {
         error('PostScript function stack overflow.');
+      }
       this.stack.push(value);
     },
     pop: function PostScriptStack_pop() {
-      if (this.stack.length <= 0)
+      if (this.stack.length <= 0) {
         error('PostScript function stack underflow.');
+      }
       return this.stack.pop();
     },
     copy: function PostScriptStack_copy(n) {
-      if (this.stack.length + n >= MAX_STACK_SIZE)
+      if (this.stack.length + n >= MAX_STACK_SIZE) {
         error('PostScript function stack overflow.');
+      }
       var stack = this.stack;
-      for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++)
+      for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) {
         stack.push(stack[i]);
+      }
     },
     index: function PostScriptStack_index(n) {
       this.push(this.stack[this.stack.length - n - 1]);
     },
     // rotate the last n stack elements p times
     roll: function PostScriptStack_roll(n, p) {
       var stack = this.stack;
       var l = stack.length - n;
@@ -2463,18 +2513,19 @@ var PostScriptEvaluator = (function Post
           stack.push(operator);
           continue;
         }
         switch (operator) {
           // non standard ps operators
           case 'jz': // jump if false
             b = stack.pop();
             a = stack.pop();
-            if (!a)
+            if (!a) {
               counter = b;
+            }
             break;
           case 'j': // jump
             a = stack.pop();
             counter = a;
             break;
 
           // all ps operators in alphabetical order (excluding if/ifelse)
           case 'abs':
@@ -2484,32 +2535,34 @@ var PostScriptEvaluator = (function Post
           case 'add':
             b = stack.pop();
             a = stack.pop();
             stack.push(a + b);
             break;
           case 'and':
             b = stack.pop();
             a = stack.pop();
-            if (isBool(a) && isBool(b))
+            if (isBool(a) && isBool(b)) {
               stack.push(a && b);
-            else
+            } else {
               stack.push(a & b);
+            }
             break;
           case 'atan':
             a = stack.pop();
             stack.push(Math.atan(a));
             break;
           case 'bitshift':
             b = stack.pop();
             a = stack.pop();
-            if (a > 0)
+            if (a > 0) {
               stack.push(a << b);
-            else
+            } else {
               stack.push(a >> b);
+            }
             break;
           case 'ceiling':
             a = stack.pop();
             stack.push(Math.ceil(a));
             break;
           case 'copy':
             a = stack.pop();
             stack.copy(a);
@@ -2606,28 +2659,30 @@ var PostScriptEvaluator = (function Post
             stack.push(a != b);
             break;
           case 'neg':
             a = stack.pop();
             stack.push(-b);
             break;
           case 'not':
             a = stack.pop();
-            if (isBool(a) && isBool(b))
+            if (isBool(a) && isBool(b)) {
               stack.push(a && b);
-            else
+            } else {
               stack.push(a & b);
+            }
             break;
           case 'or':
             b = stack.pop();
             a = stack.pop();
-            if (isBool(a) && isBool(b))
+            if (isBool(a) && isBool(b)) {
               stack.push(a || b);
-            else
+            } else {
               stack.push(a | b);
+            }
             break;
           case 'pop':
             stack.pop();
             break;
           case 'roll':
             b = stack.pop();
             a = stack.pop();
             stack.roll(a, b);
@@ -2655,33 +2710,37 @@ var PostScriptEvaluator = (function Post
           case 'truncate':
             a = stack.pop();
             a = a < 0 ? Math.ceil(a) : Math.floor(a);
             stack.push(a);
             break;
           case 'xor':
             b = stack.pop();
             a = stack.pop();
-            if (isBool(a) && isBool(b))
+            if (isBool(a) && isBool(b)) {
               stack.push(a != b);
-            else
+            } else {
               stack.push(a ^ b);
+            }
             break;
           default:
             error('Unknown operator ' + operator);
             break;
         }
       }
       return stack.stack;
     }
   };
   return PostScriptEvaluator;
 })();
 
 
+var HIGHLIGHT_OFFSET = 4; // px
+var SUPPORTED_TYPES = ['Link', 'Text', 'Widget'];
+
 var Annotation = (function AnnotationClosure() {
   // 12.5.5: Algorithm: Appearance streams
   function getTransformMatrix(rect, bbox, matrix) {
     var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
     var minX = bounds[0];
     var minY = bounds[1];
     var maxX = bounds[2];
     var maxY = bounds[3];
@@ -2794,35 +2853,60 @@ var Annotation = (function AnnotationClo
     },
 
     getHtmlElement: function Annotation_getHtmlElement(commonObjs) {
       throw new NotImplementedException(
         'getHtmlElement() should be implemented in subclass');
     },
 
     // TODO(mack): Remove this, it's not really that helpful.
-    getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect) {
+    getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect,
+                                                             borderWidth) {
       assert(!isWorker,
         'getEmptyContainer() should be called from main thread');
 
+      var bWidth = borderWidth || 0;
+
       rect = rect || this.data.rect;
       var element = document.createElement(tagName);
-      element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
-      element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
+      element.style.borderWidth = bWidth + 'px';
+      var width = rect[2] - rect[0] - 2 * bWidth;
+      var height = rect[3] - rect[1] - 2 * bWidth;
+      element.style.width = width + 'px';
+      element.style.height = height + 'px';
       return element;
     },
 
+    isInvisible: function Annotation_isInvisible() {
+      var data = this.data;
+      if (data && SUPPORTED_TYPES.indexOf(data.subtype) !== -1) {
+        return false;
+      } else {
+        return !!(data &&
+                  data.annotationFlags &&            // Default: not invisible
+                  data.annotationFlags & 0x1);       // Invisible
+      }
+    },
+
     isViewable: function Annotation_isViewable() {
       var data = this.data;
-      return !!(
-        data &&
-        (!data.annotationFlags ||
-         !(data.annotationFlags & 0x22)) && // Hidden or NoView
-        data.rect                            // rectangle is nessessary
-      );
+      return !!(!this.isInvisible() &&
+                data &&
+                (!data.annotationFlags ||
+                 !(data.annotationFlags & 0x22)) &&  // Hidden or NoView
+                data.rect);                          // rectangle is nessessary
+    },
+
+    isPrintable: function Annotation_isPrintable() {
+      var data = this.data;
+      return !!(!this.isInvisible() &&
+                data &&
+                data.annotationFlags &&              // Default: not printable
+                data.annotationFlags & 0x4 &&        // Print
+                data.rect);                          // rectangle is nessessary
     },
 
     loadResources: function(keys) {
       var promise = new LegacyPromise();
       this.appearance.dict.getAsync('Resources').then(function(resources) {
         if (!resources) {
           promise.resolve();
           return;
@@ -2833,17 +2917,17 @@ var Annotation = (function AnnotationClo
         objectLoader.load().then(function() {
           promise.resolve(resources);
         });
       }.bind(this));
 
       return promise;
     },
 
-    getOperatorList: function Annotation_getToOperatorList(evaluator) {
+    getOperatorList: function Annotation_getOperatorList(evaluator) {
 
       var promise = new LegacyPromise();
 
       if (!this.appearance) {
         promise.resolve(new OperatorList());
         return promise;
       }
 
@@ -2940,35 +3024,39 @@ var Annotation = (function AnnotationClo
 
     var params = {
       dict: dict,
       ref: ref,
     };
 
     var annotation = new Constructor(params);
 
-    if (annotation.isViewable()) {
+    if (annotation.isViewable() || annotation.isPrintable()) {
       return annotation;
     } else {
       warn('unimplemented annotation type: ' + subtype);
     }
   };
 
   Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
-      annotations, opList, pdfManager, partialEvaluator) {
+      annotations, opList, pdfManager, partialEvaluator, intent) {
 
     function reject(e) {
       annotationsReadyPromise.reject(e);
     }
 
     var annotationsReadyPromise = new LegacyPromise();
 
     var annotationPromises = [];
     for (var i = 0, n = annotations.length; i < n; ++i) {
-      annotationPromises.push(annotations[i].getOperatorList(partialEvaluator));
+      if (intent === 'display' && annotations[i].isViewable() ||
+          intent === 'print' && annotations[i].isPrintable()) {
+        annotationPromises.push(
+          annotations[i].getOperatorList(partialEvaluator));
+      }
     }
     Promise.all(annotationPromises).then(function(datas) {
       opList.addOp(OPS.beginAnnotations, []);
       for (var i = 0, n = datas.length; i < n; ++i) {
         var annotOpList = datas[i];
         opList.addOpList(annotOpList);
       }
       opList.addOp(OPS.endAnnotations, []);
@@ -3020,18 +3108,19 @@ var WidgetAnnotation = (function WidgetA
         // with the same name may exist. Replacing the empty name
         // with the '`' plus index in the parent's 'Kids' array.
         // This is not in the PDF spec but necessary to id the
         // the input controls.
         var kids = parent.get('Kids');
         var j, jj;
         for (j = 0, jj = kids.length; j < jj; j++) {
           var kidRef = kids[j];
-          if (kidRef.num == ref.num && kidRef.gen == ref.gen)
-            break;
+          if (kidRef.num == ref.num && kidRef.gen == ref.gen) {
+            break;
+          }
         }
         fieldName.unshift('`' + j);
       }
       namedItem = parent;
       ref = parentRef;
     }
     data.fullName = fieldName.join('.');
   }
@@ -3170,127 +3259,228 @@ var TextWidgetAnnotation = (function Tex
       promise.resolve(opList);
       return promise;
     }
   });
 
   return TextWidgetAnnotation;
 })();
 
+var InteractiveAnnotation = (function InteractiveAnnotationClosure() {
+  function InteractiveAnnotation(params) {
+    Annotation.call(this, params);
+  }
+
+  Util.inherit(InteractiveAnnotation, Annotation, {
+    hasHtml: function InteractiveAnnotation_hasHtml() {
+      return true;
+    },
+
+    highlight: function InteractiveAnnotation_highlight() {
+      if (this.highlightElement &&
+         this.highlightElement.hasAttribute('hidden')) {
+        this.highlightElement.removeAttribute('hidden');
+      }
+    },
+
+    unhighlight: function InteractiveAnnotation_unhighlight() {
+      if (this.highlightElement &&
+         !this.highlightElement.hasAttribute('hidden')) {
+        this.highlightElement.setAttribute('hidden', true);
+      }
+    },
+
+    initContainer: function InteractiveAnnotation_initContainer() {
+
+      var item = this.data;
+      var rect = item.rect;
+
+      var container = this.getEmptyContainer('section', rect, item.borderWidth);
+      container.style.backgroundColor = item.color;
+
+      var color = item.color;
+      var rgb = [];
+      for (var i = 0; i < 3; ++i) {
+        rgb[i] = Math.round(color[i] * 255);
+      }
+      item.colorCssRgb = Util.makeCssRgb(rgb);
+
+      var highlight = document.createElement('div');
+      highlight.className = 'annotationHighlight';
+      highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px';
+      highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px';
+      highlight.setAttribute('hidden', true);
+
+      this.highlightElement = highlight;
+      container.appendChild(this.highlightElement);
+
+      return container;
+    }
+  });
+
+  return InteractiveAnnotation;
+})();
+
 var TextAnnotation = (function TextAnnotationClosure() {
   function TextAnnotation(params) {
-    Annotation.call(this, params);
+    InteractiveAnnotation.call(this, params);
 
     if (params.data) {
       return;
     }
 
     var dict = params.dict;
     var data = this.data;
 
     var content = dict.get('Contents');
     var title = dict.get('T');
     data.content = stringToPDFString(content || '');
     data.title = stringToPDFString(title || '');
-    data.name = !dict.has('Name') ? 'Note' : dict.get('Name').name;
+
+    if (data.hasAppearance) {
+      data.name = 'NoIcon';
+    } else {
+      data.name = dict.has('Name') ? dict.get('Name').name : 'Note';
+    }
+
+    if (dict.has('C')) {
+      data.hasBgColor = true;
+    }
   }
 
   var ANNOT_MIN_SIZE = 10;
 
-  Util.inherit(TextAnnotation, Annotation, {
-
-    getOperatorList: function TextAnnotation_getOperatorList(evaluator) {
-      var promise = new LegacyPromise();
-      promise.resolve(new OperatorList());
-      return promise;
-    },
-
-    hasHtml: function TextAnnotation_hasHtml() {
-      return true;
-    },
+  Util.inherit(TextAnnotation, InteractiveAnnotation, {
 
     getHtmlElement: function TextAnnotation_getHtmlElement(commonObjs) {
       assert(!isWorker, 'getHtmlElement() shall be called from main thread');
 
       var item = this.data;
       var rect = item.rect;
 
       // sanity check because of OOo-generated PDFs
       if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) {
         rect[3] = rect[1] + ANNOT_MIN_SIZE;
       }
       if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
         rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
       }
 
-      var container = this.getEmptyContainer('section', rect);
+      var container = this.initContainer();
       container.className = 'annotText';
 
-      var image = document.createElement('img');
+      var image  = document.createElement('img');
       image.style.height = container.style.height;
+      image.style.width = container.style.width;
       var iconName = item.name;
       image.src = PDFJS.imageResourcesPath + 'annotation-' +
         iconName.toLowerCase() + '.svg';
       image.alt = '[{{type}} Annotation]';
       image.dataset.l10nId = 'text_annotation_type';
       image.dataset.l10nArgs = JSON.stringify({type: iconName});
+
+      var contentWrapper = document.createElement('div');
+      contentWrapper.className = 'annotTextContentWrapper';
+      contentWrapper.style.left = Math.floor(rect[2] - rect[0] + 5) + 'px';
+      contentWrapper.style.top = '-10px';
+
       var content = document.createElement('div');
+      content.className = 'annotTextContent';
       content.setAttribute('hidden', true);
+      if (item.hasBgColor) {
+        var color = item.color;
+        var rgb = [];
+        for (var i = 0; i < 3; ++i) {
+          // Enlighten the color (70%)
+          var c = Math.round(color[i] * 255);
+          rgb[i] = Math.round((255 - c) * 0.7) + c;
+        }
+        content.style.backgroundColor = Util.makeCssRgb(rgb);
+      }
+
       var title = document.createElement('h1');
       var text = document.createElement('p');
-      content.style.left = Math.floor(rect[2] - rect[0]) + 'px';
-      content.style.top = '0px';
       title.textContent = item.title;
 
       if (!item.content && !item.title) {
         content.setAttribute('hidden', true);
       } else {
         var e = document.createElement('span');
         var lines = item.content.split(/(?:\r\n?|\n)/);
         for (var i = 0, ii = lines.length; i < ii; ++i) {
           var line = lines[i];
           e.appendChild(document.createTextNode(line));
-          if (i < (ii - 1))
+          if (i < (ii - 1)) {
             e.appendChild(document.createElement('br'));
+          }
         }
         text.appendChild(e);
 
-        var showAnnotation = function showAnnotation() {
-          container.style.zIndex += 1;
-          content.removeAttribute('hidden');
+        var pinned = false;
+
+        var showAnnotation = function showAnnotation(pin) {
+          if (pin) {
+            pinned = true;
+          }
+          if (content.hasAttribute('hidden')) {
+            container.style.zIndex += 1;
+            content.removeAttribute('hidden');
+          }
         };
 
-        var hideAnnotation = function hideAnnotation(e) {
-          if (e.toElement || e.relatedTarget) { // No context menu is used
+        var hideAnnotation = function hideAnnotation(unpin) {
+          if (unpin) {
+            pinned = false;
+          }
+          if (!content.hasAttribute('hidden') && !pinned) {
             container.style.zIndex -= 1;
             content.setAttribute('hidden', true);
           }
         };
 
-        content.addEventListener('mouseover', showAnnotation, false);
-        content.addEventListener('mouseout', hideAnnotation, false);
-        image.addEventListener('mouseover', showAnnotation, false);
-        image.addEventListener('mouseout', hideAnnotation, false);
+        var toggleAnnotation = function toggleAnnotation() {
+          if (pinned) {
+            hideAnnotation(true);
+          } else {
+            showAnnotation(true);
+          }
+        };
+
+        var self = this;
+        image.addEventListener('click', function image_clickHandler() {
+          toggleAnnotation();
+        }, false);
+        image.addEventListener('mouseover', function image_mouseOverHandler() {
+          showAnnotation();
+        }, false);
+        image.addEventListener('mouseout', function image_mouseOutHandler() {
+          hideAnnotation();
+        }, false);
+
+        content.addEventListener('click', function content_clickHandler() {
+          hideAnnotation(true);
+        }, false);
       }
 
       content.appendChild(title);
       content.appendChild(text);
+      contentWrapper.appendChild(content);
       container.appendChild(image);
-      container.appendChild(content);
+      container.appendChild(contentWrapper);
 
       return container;
     }
   });
 
   return TextAnnotation;
 })();
 
 var LinkAnnotation = (function LinkAnnotationClosure() {
   function LinkAnnotation(params) {
-    Annotation.call(this, params);
+    InteractiveAnnotation.call(this, params);
 
     if (params.data) {
       return;
     }
 
     var dict = params.dict;
     var data = this.data;
 
@@ -3343,46 +3533,38 @@ var LinkAnnotation = (function LinkAnnot
   // Lets URLs beginning with 'www.' default to using the 'http://' protocol.
   function addDefaultProtocolToUrl(url) {
     if (url && url.indexOf('www.') === 0) {
       return ('http://' + url);
     }
     return url;
   }
 
-  Util.inherit(LinkAnnotation, Annotation, {
+  Util.inherit(LinkAnnotation, InteractiveAnnotation, {
     hasOperatorList: function LinkAnnotation_hasOperatorList() {
       return false;
     },
 
-    hasHtml: function LinkAnnotation_hasHtml() {
-      return true;
-    },
-
     getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) {
-      var rect = this.data.rect;
-      var element = document.createElement('a');
-      var borderWidth = this.data.borderWidth;
-
-      element.style.borderWidth = borderWidth + 'px';
-      var color = this.data.color;
-      var rgb = [];
-      for (var i = 0; i < 3; ++i) {
-        rgb[i] = Math.round(color[i] * 255);
-      }
-      element.style.borderColor = Util.makeCssRgb(rgb);
-      element.style.borderStyle = 'solid';
-
-      var width = rect[2] - rect[0] - 2 * borderWidth;
-      var height = rect[3] - rect[1] - 2 * borderWidth;
-      element.style.width = width + 'px';
-      element.style.height = height + 'px';
-
-      element.href = this.data.url || '';
-      return element;
+
+      var container = this.initContainer();
+      container.className = 'annotLink';
+
+      var item = this.data;
+      var rect = item.rect;
+
+      container.style.borderColor = item.colorCssRgb;
+      container.style.borderStyle = 'solid';
+
+      var link = document.createElement('a');
+      link.href = link.title = this.data.url || '';
+
+      container.appendChild(link);
+
+      return container;
     }
   });
 
   return LinkAnnotation;
 })();
 
 
 var ChunkedStream = (function ChunkedStreamClosure() {
@@ -3548,16 +3730,18 @@ var ChunkedStream = (function ChunkedStr
       this.pos = this.start;
     },
 
     moveStart: function ChunkedStream_moveStart() {
       this.start = this.pos;
     },
 
     makeSubStream: function ChunkedStream_makeSubStream(start, length, dict) {
+      this.ensureRange(start, start + length);
+
       function ChunkedStreamSubstream() {}
       ChunkedStreamSubstream.prototype = Object.create(this);
       ChunkedStreamSubstream.prototype.getMissingChunks = function() {
         var chunkSize = this.chunkSize;
         var beginChunk = Math.floor(this.start / chunkSize);
         var endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
         var missingChunks = [];
         for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
@@ -4174,17 +4358,17 @@ var Page = (function PageClosure() {
                                             keys,
                                             this.xref);
         objectLoader.load().then(function objectLoaderSuccess() {
           promise.resolve();
         });
       }.bind(this));
       return promise;
     },
-    getOperatorList: function Page_getOperatorList(handler) {
+    getOperatorList: function Page_getOperatorList(handler, intent) {
       var self = this;
       var promise = new LegacyPromise();
 
       function reject(e) {
         promise.reject(e);
       }
 
       var pageListPromise = new LegacyPromise();
@@ -4209,21 +4393,22 @@ var Page = (function PageClosure() {
             this.idCounters, this.fontCache);
 
       var dataPromises = Promise.all(
           [contentStreamPromise, resourcesPromise], reject);
       dataPromises.then(function(data) {
         var contentStream = data[0];
 
 
-        var opList = new OperatorList(handler, self.pageIndex);
+        var opList = new OperatorList(intent, handler, self.pageIndex);
 
         handler.send('StartRenderPage', {
           transparency: partialEvaluator.hasBlendModes(self.resources),
-          pageIndex: self.pageIndex
+          pageIndex: self.pageIndex,
+          intent: intent
         });
         partialEvaluator.getOperatorList(contentStream, self.resources, opList);
         pageListPromise.resolve(opList);
       });
 
       var annotationsPromise = pdfManager.ensure(this, 'annotations');
       Promise.all([pageListPromise, annotationsPromise]).then(function(datas) {
         var pageOpList = datas[0];
@@ -4231,17 +4416,17 @@ var Page = (function PageClosure() {
 
         if (annotations.length === 0) {
           pageOpList.flush(true);
           promise.resolve(pageOpList);
           return;
         }
 
         var annotationsReadyPromise = Annotation.appendToOperatorList(
-          annotations, pageOpList, pdfManager, partialEvaluator);
+          annotations, pageOpList, pdfManager, partialEvaluator, intent);
         annotationsReadyPromise.then(function () {
           pageOpList.flush(true);
           promise.resolve(pageOpList);
         }, reject);
       }, reject);
 
       return promise;
     },
@@ -12958,18 +13143,19 @@ var CIDToUnicodeMaps = {
 
 
 var ARCFourCipher = (function ARCFourCipherClosure() {
   function ARCFourCipher(key) {
     this.a = 0;
     this.b = 0;
     var s = new Uint8Array(256);
     var i, j = 0, tmp, keyLength = key.length;
-    for (i = 0; i < 256; ++i)
+    for (i = 0; i < 256; ++i) {
       s[i] = i;
+    }
     for (i = 0; i < 256; ++i) {
       tmp = s[i];
       j = (j + tmp + key[i % keyLength]) & 0xFF;
       s[i] = s[j];
       s[j] = tmp;
     }
     this.s = s;
   }
@@ -13019,22 +13205,24 @@ var calculateMD5 = (function calculateMD
     -145523070, -1120210379, 718787259, -343485551]);
 
   function hash(data, offset, length) {
     var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878;
     // pre-processing
     var paddedLength = (length + 72) & ~63; // data + 9 extra bytes
     var padded = new Uint8Array(paddedLength);
     var i, j, n;
-    for (i = 0; i < length; ++i)
+    for (i = 0; i < length; ++i) {
       padded[i] = data[offset++];
+    }
     padded[i++] = 0x80;
     n = paddedLength - 8;
-    while (i < n)
+    while (i < n) {
       padded[i++] = 0;
+    }
     padded[i++] = (length << 3) & 0xFF;
     padded[i++] = (length >> 5) & 0xFF;
     padded[i++] = (length >> 13) & 0xFF;
     padded[i++] = (length >> 21) & 0xFF;
     padded[i++] = (length >>> 29) & 0xFF;
     padded[i++] = 0;
     padded[i++] = 0;
     padded[i++] = 0;
@@ -13068,28 +13256,27 @@ var calculateMD5 = (function calculateMD
         a = tmp;
       }
       h0 = (h0 + a) | 0;
       h1 = (h1 + b) | 0;
       h2 = (h2 + c) | 0;
       h3 = (h3 + d) | 0;
     }
     return new Uint8Array([
-        h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF,
-        h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF,
-        h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF,
-        h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF
+      h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF,
+      h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF,
+      h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF,
+      h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF
     ]);
   }
   return hash;
 })();
 
 var NullCipher = (function NullCipherClosure() {
-  function NullCipher() {
-  }
+  function NullCipher() {}
 
   NullCipher.prototype = {
     decryptBlock: function NullCipher_decryptBlock(data) {
       return data;
     }
   };
 
   return NullCipher;
@@ -13235,32 +13422,35 @@ var AES128Cipher = (function AES128Ciphe
   }
 
   function decrypt128(input, key) {
     var state = new Uint8Array(16);
     state.set(input);
     var i, j, k;
     var t, u, v;
     // AddRoundKey
-    for (j = 0, k = 160; j < 16; ++j, ++k)
+    for (j = 0, k = 160; j < 16; ++j, ++k) {
       state[j] ^= key[k];
+    }
     for (i = 9; i >= 1; --i) {
       // InvShiftRows
       t = state[13]; state[13] = state[9]; state[9] = state[5];
       state[5] = state[1]; state[1] = t;
       t = state[14]; u = state[10]; state[14] = state[6];
       state[10] = state[2]; state[6] = t; state[2] = u;
       t = state[15]; u = state[11]; v = state[7]; state[15] = state[3];
       state[11] = t; state[7] = u; state[3] = v;
       // InvSubBytes
-      for (j = 0; j < 16; ++j)
+      for (j = 0; j < 16; ++j) {
         state[j] = inv_s[state[j]];
+      }
       // AddRoundKey
-      for (j = 0, k = i * 16; j < 16; ++j, ++k)
+      for (j = 0, k = i * 16; j < 16; ++j, ++k) {
         state[j] ^= key[k];
+      }
       // InvMixColumns
       for (j = 0; j < 16; j += 4) {
         var s0 = mix[state[j]], s1 = mix[state[j + 1]],
             s2 = mix[state[j + 2]], s3 = mix[state[j + 3]];
         t = (s0 ^ (s1 >>> 8) ^ (s1 << 24) ^ (s2 >>> 16) ^ (s2 << 16) ^
             (s3 >>> 24) ^ (s3 << 8));
         state[j] = (t >>> 24) & 0xFF;
         state[j + 1] = (t >> 16) & 0xFF;
@@ -13292,23 +13482,25 @@ var AES128Cipher = (function AES128Ciphe
 
   function decryptBlock2(data, finalize) {
     var i, j, ii, sourceLength = data.length,
         buffer = this.buffer, bufferLength = this.bufferPosition,
         result = [], iv = this.iv;
     for (i = 0; i < sourceLength; ++i) {
       buffer[bufferLength] = data[i];
       ++bufferLength;
-      if (bufferLength < 16)
+      if (bufferLength < 16) {
         continue;
+      }
       // buffer is full, decrypting
       var plain = decrypt128(buffer, this.key);
       // xor-ing the IV vector to get plain text
-      for (j = 0; j < 16; ++j)
+      for (j = 0; j < 16; ++j) {
         plain[j] ^= iv[j];
+      }
       iv = buffer;
       result.push(plain);
       buffer = new Uint8Array(16);
       bufferLength = 0;
     }
     // saving incomplete buffer
     this.buffer = buffer;
     this.bufferLength = bufferLength;
@@ -13320,28 +13512,30 @@ var AES128Cipher = (function AES128Ciphe
     var outputLength = 16 * result.length;
     if (finalize) {
       // undo a padding that is described in RFC 2898
       var lastBlock = result[result.length - 1];
       outputLength -= lastBlock[15];
       result[result.length - 1] = lastBlock.subarray(0, 16 - lastBlock[15]);
     }
     var output = new Uint8Array(outputLength);
-    for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16)
+    for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
       output.set(result[i], j);
+    }
     return output;
   }
 
   AES128Cipher.prototype = {
     decryptBlock: function AES128Cipher_decryptBlock(data, finalize) {
       var i, sourceLength = data.length;
       var buffer = this.buffer, bufferLength = this.bufferPosition;
       // waiting for IV values -- they are at the start of the stream
-      for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength)
+      for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength) {
         buffer[bufferLength] = data[i];
+      }
       if (bufferLength < 16) {
         // need more data
         this.bufferLength = bufferLength;
         return new Uint8Array([]);
       }
       this.iv = buffer;
       this.buffer = new Uint8Array(16);
       this.bufferLength = 0;
@@ -13386,174 +13580,192 @@ var CipherTransformFactory = (function C
     0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
 
   function prepareKeyData(fileId, password, ownerPassword, userPassword,
                           flags, revision, keyLength, encryptMetadata) {
     var hashDataSize = 40 + ownerPassword.length + fileId.length;
     var hashData = new Uint8Array(hashDataSize), i = 0, j, n;
     if (password) {
       n = Math.min(32, password.length);
-      for (; i < n; ++i)
+      for (; i < n; ++i) {
         hashData[i] = password[i];
+      }
     }
     j = 0;
     while (i < 32) {
       hashData[i++] = defaultPasswordBytes[j++];
     }
     // as now the padded password in the hashData[0..i]
-    for (j = 0, n = ownerPassword.length; j < n; ++j)
+    for (j = 0, n = ownerPassword.length; j < n; ++j) {
       hashData[i++] = ownerPassword[j];
+    }
     hashData[i++] = flags & 0xFF;
     hashData[i++] = (flags >> 8) & 0xFF;
     hashData[i++] = (flags >> 16) & 0xFF;
     hashData[i++] = (flags >>> 24) & 0xFF;
-    for (j = 0, n = fileId.length; j < n; ++j)
+    for (j = 0, n = fileId.length; j < n; ++j) {
       hashData[i++] = fileId[j];
+    }
     if (revision >= 4 && !encryptMetadata) {
       hashData[i++] = 0xFF;
       hashData[i++] = 0xFF;
       hashData[i++] = 0xFF;
       hashData[i++] = 0xFF;
     }
     var hash = calculateMD5(hashData, 0, i);
     var keyLengthInBytes = keyLength >> 3;
     if (revision >= 3) {
       for (j = 0; j < 50; ++j) {
-         hash = calculateMD5(hash, 0, keyLengthInBytes);
+        hash = calculateMD5(hash, 0, keyLengthInBytes);
       }
     }
     var encryptionKey = hash.subarray(0, keyLengthInBytes);
     var cipher, checkData;
 
     if (revision >= 3) {
-      for (i = 0; i < 32; ++i)
+      for (i = 0; i < 32; ++i) {
         hashData[i] = defaultPasswordBytes[i];
-      for (j = 0, n = fileId.length; j < n; ++j)
+      }
+      for (j = 0, n = fileId.length; j < n; ++j) {
         hashData[i++] = fileId[j];
+      }
       cipher = new ARCFourCipher(encryptionKey);
       var checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i));
       n = encryptionKey.length;
       var derivedKey = new Uint8Array(n), k;
       for (j = 1; j <= 19; ++j) {
-        for (k = 0; k < n; ++k)
+        for (k = 0; k < n; ++k) {
           derivedKey[k] = encryptionKey[k] ^ j;
+        }
         cipher = new ARCFourCipher(derivedKey);
         checkData = cipher.encryptBlock(checkData);
       }
       for (j = 0, n = checkData.length; j < n; ++j) {
-        if (userPassword[j] != checkData[j])
+        if (userPassword[j] != checkData[j]) {
           return null;
+        }
       }
     } else {
       cipher = new ARCFourCipher(encryptionKey);
       checkData = cipher.encryptBlock(defaultPasswordBytes);
       for (j = 0, n = checkData.length; j < n; ++j) {
-        if (userPassword[j] != checkData[j])
+        if (userPassword[j] != checkData[j]) {
           return null;
+        }
       }
     }
     return encryptionKey;
   }
+
   function decodeUserPassword(password, ownerPassword, revision, keyLength) {
     var hashData = new Uint8Array(32), i = 0, j, n;
     n = Math.min(32, password.length);
-    for (; i < n; ++i)
+    for (; i < n; ++i) {
       hashData[i] = password[i];
+    }
     j = 0;
     while (i < 32) {
       hashData[i++] = defaultPasswordBytes[j++];
     }
     var hash = calculateMD5(hashData, 0, i);
     var keyLengthInBytes = keyLength >> 3;
     if (revision >= 3) {
       for (j = 0; j < 50; ++j) {
-         hash = calculateMD5(hash, 0, hash.length);
+        hash = calculateMD5(hash, 0, hash.length);
       }
     }
 
     var cipher, userPassword;
     if (revision >= 3) {
       userPassword = ownerPassword;
       var derivedKey = new Uint8Array(keyLengthInBytes), k;
       for (j = 19; j >= 0; j--) {
-        for (k = 0; k < keyLengthInBytes; ++k)
+        for (k = 0; k < keyLengthInBytes; ++k) {
           derivedKey[k] = hash[k] ^ j;
+        }
         cipher = new ARCFourCipher(derivedKey);
         userPassword = cipher.encryptBlock(userPassword);
       }
     } else {
       cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
       userPassword = cipher.encryptBlock(ownerPassword);
     }
     return userPassword;
   }
 
   var identityName = Name.get('Identity');
 
   function CipherTransformFactory(dict, fileId, password) {
     var filter = dict.get('Filter');
-    if (!isName(filter) || filter.name != 'Standard')
+    if (!isName(filter) || filter.name != 'Standard') {
       error('unknown encryption method');
+    }
     this.dict = dict;
     var algorithm = dict.get('V');
     if (!isInt(algorithm) ||
-      (algorithm != 1 && algorithm != 2 && algorithm != 4))
+        (algorithm != 1 && algorithm != 2 && algorithm != 4)) {
       error('unsupported encryption algorithm');
+    }
     this.algorithm = algorithm;
     var keyLength = dict.get('Length') || 40;
     if (!isInt(keyLength) ||
-      keyLength < 40 || (keyLength % 8) !== 0)
+        keyLength < 40 || (keyLength % 8) !== 0) {
       error('invalid key length');
+    }
+
     // prepare keys
     var ownerPassword = stringToBytes(dict.get('O')).subarray(0, 32);
     var userPassword = stringToBytes(dict.get('U')).subarray(0, 32);
     var flags = dict.get('P');
     var revision = dict.get('R');
-    var encryptMetadata = algorithm == 4 &&  // meaningful when V is 4
-      dict.get('EncryptMetadata') !== false; // makes true as default value
+    var encryptMetadata = (algorithm == 4 &&  // meaningful when V is 4
+      dict.get('EncryptMetadata') !== false); // makes true as default value
     this.encryptMetadata = encryptMetadata;
 
     var fileIdBytes = stringToBytes(fileId);
     var passwordBytes;
-    if (password)
+    if (password) {
       passwordBytes = stringToBytes(password);
+    }
 
     var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
                                        ownerPassword, userPassword, flags,
                                        revision, keyLength, encryptMetadata);
     if (!encryptionKey && !password) {
       throw new PasswordException('No password given',
                                   PasswordResponses.NEED_PASSWORD);
     } else if (!encryptionKey && password) {
       // Attempting use the password as an owner password
       var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword,
                                                revision, keyLength);
       encryptionKey = prepareKeyData(fileIdBytes, decodedPassword,
                                      ownerPassword, userPassword, flags,
                                      revision, keyLength, encryptMetadata);
     }
 
-    if (!encryptionKey)
+    if (!encryptionKey) {
       throw new PasswordException('Incorrect Password',
                                   PasswordResponses.INCORRECT_PASSWORD);
+    }
 
     this.encryptionKey = encryptionKey;
 
     if (algorithm == 4) {
       this.cf = dict.get('CF');
       this.stmf = dict.get('StmF') || identityName;
       this.strf = dict.get('StrF') || identityName;
       this.eff = dict.get('EFF') || this.strf;
     }
   }
 
   function buildObjectKey(num, gen, encryptionKey, isAes) {
     var key = new Uint8Array(encryptionKey.length + 9), i, n;
-    for (i = 0, n = encryptionKey.length; i < n; ++i)
+    for (i = 0, n = encryptionKey.length; i < n; ++i) {
       key[i] = encryptionKey[i];
+    }
     key[i++] = num & 0xFF;
     key[i++] = (num >> 8) & 0xFF;
     key[i++] = (num >> 16) & 0xFF;
     key[i++] = gen & 0xFF;
     key[i++] = (gen >> 8) & 0xFF;
     if (isAes) {
       key[i++] = 0x73;
       key[i++] = 0x41;
@@ -13562,47 +13774,46 @@ var CipherTransformFactory = (function C
     }
     var hash = calculateMD5(key, 0, i);
     return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
   }
 
   function buildCipherConstructor(cf, name, num, gen, key) {
     var cryptFilter = cf.get(name.name);
     var cfm;
-    if (cryptFilter !== null && cryptFilter !== undefined)
+    if (cryptFilter !== null && cryptFilter !== undefined) {
       cfm = cryptFilter.get('CFM');
+    }
     if (!cfm || cfm.name == 'None') {
       return function cipherTransformFactoryBuildCipherConstructorNone() {
         return new NullCipher();
       };
     }
     if ('V2' == cfm.name) {
       return function cipherTransformFactoryBuildCipherConstructorV2() {
-        return new ARCFourCipher(
-          buildObjectKey(num, gen, key, false));
+        return new ARCFourCipher(buildObjectKey(num, gen, key, false));
       };
     }
     if ('AESV2' == cfm.name) {
       return function cipherTransformFactoryBuildCipherConstructorAESV2() {
-        return new AES128Cipher(
-          buildObjectKey(num, gen, key, true));
+        return new AES128Cipher(buildObjectKey(num, gen, key, true));
       };
     }
     error('Unknown crypto method');
   }
 
   CipherTransformFactory.prototype = {
     createCipherTransform:
       function CipherTransformFactory_createCipherTransform(num, gen) {
       if (this.algorithm == 4) {
         return new CipherTransform(
           buildCipherConstructor(this.cf, this.stmf,
-            num, gen, this.encryptionKey),
+                                 num, gen, this.encryptionKey),
           buildCipherConstructor(this.cf, this.strf,
-            num, gen, this.encryptionKey));
+                                 num, gen, this.encryptionKey));
       }
       // algorithms 1 and 2
       var key = buildObjectKey(num, gen, this.encryptionKey, false);
       var cipherConstructor = function buildCipherCipherConstructor() {
         return new ARCFourCipher(key);
       };
       return new CipherTransform(cipherConstructor, cipherConstructor);
     }
@@ -14530,20 +14741,22 @@ var PartialEvaluator = (function Partial
         // complete PDFImage, only read the information needed
         // for later.
 
         var width = dict.get('Width', 'W');
         var height = dict.get('Height', 'H');
         var bitStrideLength = (width + 7) >> 3;
         var imgArray = image.getBytes(bitStrideLength * height);
         var decode = dict.get('Decode', 'D');
+        var canTransfer = image instanceof DecodeStream;
         var inverseDecode = !!decode && decode[0] > 0;
 
         operatorList.addOp(OPS.paintImageMaskXObject,
-          [PDFImage.createMask(imgArray, width, height, inverseDecode)]
+          [PDFImage.createMask(imgArray, width, height, canTransfer,
+                               inverseDecode)]
         );
         return;
       }
 
       var softMask = dict.get('SMask', 'SM') || false;
       var mask = dict.get('Mask') || false;
 
       var SMALL_IMAGE_DIMENSIONS = 200;
@@ -15760,29 +15973,30 @@ var OperatorList = (function OperatorLis
             transfers.push(arg.data.buffer);
             break;
         }
       }
       return transfers;
     }
 
 
-    function OperatorList(messageHandler, pageIndex) {
+    function OperatorList(intent, messageHandler, pageIndex) {
     this.messageHandler = messageHandler;
     // When there isn't a message handler the fn array needs to be able to grow
     // since we can't flush the operators.
     if (messageHandler) {
       this.fnArray = new Uint8Array(CHUNK_SIZE);
     } else {
       this.fnArray = [];
     }
     this.argsArray = [];
     this.dependencies = {};
     this.pageIndex = pageIndex;
     this.fnIndex = 0;
+    this.intent = intent;
   }
 
   OperatorList.prototype = {
 
     get length() {
       return this.argsArray.length;
     },
 
@@ -15833,17 +16047,18 @@ var OperatorList = (function OperatorLis
       var transfers = getTransfers(this);
       this.messageHandler.send('RenderPageChunk', {
         operatorList: {
           fnArray: this.fnArray,
           argsArray: this.argsArray,
           lastChunk: lastChunk,
           length: this.length
         },
-        pageIndex: this.pageIndex
+        pageIndex: this.pageIndex,
+        intent: this.intent
       }, null, transfers);
       this.dependencies = [];
       this.fnIndex = 0;
       this.argsArray = [];
     }
   };
 
   return OperatorList;
@@ -19782,19 +19997,21 @@ var Font = (function FontClosure() {
           } else if (op === 0x1B) { // ELSE
             inELSE = ifLevel;
           } else if (op === 0x59) { // EIF
             if (inELSE === ifLevel) {
               inELSE = 0;
             }
             --ifLevel;
           } else if (op === 0x1C) { // JMPR
-            var offset = stack[stack.length - 1];
-            // only jumping forward to prevent infinite loop
-            if (offset > 0) { i += offset - 1; }
+            if (!inFDEF && !inELSE) {
+              var offset = stack[stack.length - 1];
+              // only jumping forward to prevent infinite loop
+              if (offset > 0) { i += offset - 1; }
+            }
           }
           // Adjusting stack not extactly, but just enough to get function id
           if (!inFDEF && !inELSE) {
             var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] :
               op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
             if (op >= 0x71 && op <= 0x75) {
               n = stack.pop();
               if (n === n) {
@@ -28381,45 +28598,51 @@ var PDFImage = (function PDFImageClosure
           temp[newIndex + 1] = pixels[oldIndex + 1];
           temp[newIndex + 2] = pixels[oldIndex + 2];
         }
       }
     }
     return temp;
   };
 
-  PDFImage.createMask = function PDFImage_createMask(imgArray, width, height,
-                                                     inverseDecode) {
-    // Copy imgArray into a typed array (inverting if necessary) so it can be
-    // transferred to the main thread.
-    var length = ((width + 7) >> 3) * height;
-    var data = new Uint8Array(length);
+  PDFImage.createMask =
+      function PDFImage_createMask(imgArray, width, height, canTransfer,
+                                   inverseDecode) {
+    // If imgArray came from a DecodeStream, we're safe to transfer it.
+    // Otherwise, copy it.
+    var actualLength = imgArray.byteLength;
+    var data;
+    if (canTransfer) {
+      data = imgArray;
+    } else {
+      data = new Uint8Array(actualLength);
+      data.set(imgArray);
+    }
+    // Invert if necessary. It's safe to modify the array -- whether it's the
+    // original or a copy, we're about to transfer it anyway, so nothing else
+    // in this thread can be relying on its contents.
     if (inverseDecode) {
-      for (var i = 0; i < length; i++) {
-        data[i] = ~imgArray[i];
-      }
-    } else {
-      for (var i = 0; i < length; i++) {
-        data[i] = imgArray[i];
+      for (var i = 0; i < actualLength; i++) {
+        data[i] = ~data[i];
       }
     }
 
     return {data: data, width: width, height: height};
   };
 
   PDFImage.prototype = {
     get drawWidth() {
-      if (!this.smask)
-        return this.width;
-      return Math.max(this.width, this.smask.width);
+      return Math.max(this.width,
+                      this.smask && this.smask.width || 0,
+                      this.mask && this.mask.width || 0);
     },
     get drawHeight() {
-      if (!this.smask)
-        return this.height;
-      return Math.max(this.height, this.smask.height);
+      return Math.max(this.height,
+                      this.smask && this.smask.height || 0,
+                      this.mask && this.mask.height || 0);
     },
     decodeBuffer: function PDFImage_decodeBuffer(buffer) {
       var bpc = this.bpc;
       var decodeMap = this.decode;
       var numComps = this.numComps;
 
       var decodeAddends, decodeCoefficients;
       var decodeAddends = this.decodeAddends;
@@ -31882,25 +32105,26 @@ var Parser = (function ParserClosure() {
         var SCAN_BLOCK_SIZE = 2048;
         var ENDSTREAM_SIGNATURE_LENGTH = 9;
         var ENDSTREAM_SIGNATURE = [0x65, 0x6E, 0x64, 0x73, 0x74, 0x72, 0x65,
                                    0x61, 0x6D];
         var skipped = 0, found = false;
         while (stream.pos < stream.end) {
           var scanBytes = stream.peekBytes(SCAN_BLOCK_SIZE);
           var scanLength = scanBytes.length - ENDSTREAM_SIGNATURE_LENGTH;
-          var found = false, i, ii, j;
+          var found = false, i, j;
           for (i = 0, j = 0; i < scanLength; i++) {
             var b = scanBytes[i];
             if (b !== ENDSTREAM_SIGNATURE[j]) {
               i -= j;
               j = 0;
             } else {
               j++;
               if (j >= ENDSTREAM_SIGNATURE_LENGTH) {
+                i++;
                 found = true;
                 break;
               }
             }
           }
           if (found) {
             skipped += i - ENDSTREAM_SIGNATURE_LENGTH;
             stream.pos += i - ENDSTREAM_SIGNATURE_LENGTH;
@@ -31966,38 +32190,35 @@ var Parser = (function ParserClosure() {
           if (params.has('EarlyChange'))
             earlyChange = params.get('EarlyChange');
           return new PredictorStream(
             new LZWStream(stream, earlyChange), params);
         }
         return new LZWStream(stream, earlyChange);
       }
       if (name == 'DCTDecode' || name == 'DCT') {
-        var bytes = stream.getBytes(length);
-        return new JpegStream(bytes, stream.dict, this.xref);
+        return new JpegStream(stream, length, stream.dict, this.xref);
       }
       if (name == 'JPXDecode' || name == 'JPX') {
-        var bytes = stream.getBytes(length);
-        return new JpxStream(bytes, stream.dict);
+        return new JpxStream(stream, length, stream.dict);
       }
       if (name == 'ASCII85Decode' || name == 'A85') {
         return new Ascii85Stream(stream);
       }
       if (name == 'ASCIIHexDecode' || name == 'AHx') {
         return new AsciiHexStream(stream);
       }
       if (name == 'CCITTFaxDecode' || name == 'CCF') {
         return new CCITTFaxStream(stream, params);
       }
       if (name == 'RunLengthDecode' || name == 'RL') {
         return new RunLengthStream(stream);
       }
       if (name == 'JBIG2Decode') {
-        var bytes = stream.getBytes(length);
-        return new Jbig2Stream(bytes, stream.dict);
+        return new Jbig2Stream(stream, length, stream.dict);
       }
       warn('filter "' + name + '" not supported yet');
       return stream;
     }
   };
 
   return Parser;
 })();
@@ -32502,18 +32723,19 @@ var PostScriptParser = (function PostScr
     accept: function PostScriptParser_accept(type) {
       if (this.token.type == type) {
         this.nextToken();
         return true;
       }
       return false;
     },
     expect: function PostScriptParser_expect(type) {
-      if (this.accept(type))
+      if (this.accept(type)) {
         return true;
+      }
       error('Unexpected symbol: found ' + this.token.type + ' expected ' +
         type + '.');
     },
     parse: function PostScriptParser_parse() {
       this.nextToken();
       this.expect(PostScriptTokenTypes.LBRACE);
       this.parseBlock();
       this.expect(PostScriptTokenTypes.RBRACE);
@@ -32580,19 +32802,19 @@ var PostScriptToken = (function PostScri
     this.type = type;
     this.value = value;
   }
 
   var opCache = {};
 
   PostScriptToken.getOperator = function PostScriptToken_getOperator(op) {
     var opValue = opCache[op];
-    if (opValue)
+    if (opValue) {
       return opValue;
-
+    }
     return opCache[op] = new PostScriptToken(PostScriptTokenTypes.OPERATOR, op);
   };
 
   PostScriptToken.LBRACE = new PostScriptToken(PostScriptTokenTypes.LBRACE,
     '{');
   PostScriptToken.RBRACE = new PostScriptToken(PostScriptTokenTypes.RBRACE,
     '}');
   PostScriptToken.IF = new PostScriptToken(PostScriptTokenTypes.IF, 'IF');
@@ -32631,54 +32853,55 @@ var PostScriptLexer = (function PostScri
           break;
         }
         ch = this.nextChar();
       }
       switch (ch | 0) {
         case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: // '0'-'4'
         case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // '5'-'9'
         case 0x2B: case 0x2D: case 0x2E: // '+', '-', '.'
-        return new PostScriptToken(PostScriptTokenTypes.NUMBER,
-          this.getNumber());
+          return new PostScriptToken(PostScriptTokenTypes.NUMBER,
+                                     this.getNumber());
         case 0x7B: // '{'
           this.nextChar();
           return PostScriptToken.LBRACE;
         case 0x7D: // '}'
           this.nextChar();
           return PostScriptToken.RBRACE;
       }
       // operator
       var str = String.fromCharCode(ch);
       while ((ch = this.nextChar()) >= 0 && // and 'A'-'Z', 'a'-'z'
-        ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) {
+             ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) {
         str += String.fromCharCode(ch);
       }
       switch (str.toLowerCase()) {
         case 'if':
           return PostScriptToken.IF;
         case 'ifelse':
           return PostScriptToken.IFELSE;
         default:
           return PostScriptToken.getOperator(str);
       }
     },
     getNumber: function PostScriptLexer_getNumber() {
       var ch = this.currentChar;
       var str = String.fromCharCode(ch);
       while ((ch = this.nextChar()) >= 0) {
         if ((ch >= 0x30 && ch <= 0x39) || // '0'-'9'
-          ch === 0x2D || ch === 0x2E) { // '-', '.'
+            ch === 0x2D || ch === 0x2E) { // '-', '.'
           str += String.fromCharCode(ch);
         } else {
           break;
         }
       }
       var value = parseFloat(str);
-      if (isNaN(value))
+      if (isNaN(value)) {
         error('Invalid floating point number: ' + value);
+      }
       return value;
     }
   };
   return PostScriptLexer;
 })();
 
 
 var Stream = (function StreamClosure() {
@@ -32775,21 +32998,23 @@ var DecodeStream = (function DecodeStrea
         current = buffer.byteLength;
         if (requested <= current) {
           return buffer;
         }
       } else {
         current = 0;
       }
       var size = 512;
-      while (size < requested)
-        size <<= 1;
+      while (size < requested) {
+        size *= 2;
+      }
       var buffer2 = new Uint8Array(size);
-      for (var i = 0; i < current; ++i)
+      for (var i = 0; i < current; ++i) {
         buffer2[i] = buffer[i];
+      }
       return (this.buffer = buffer2);
     },
     getByte: function DecodeStream_getByte() {
       var pos = this.pos;
       while (this.bufferLength <= pos) {
         if (this.eof)
           return -1;
         this.readBlock();
@@ -32983,86 +33208,81 @@ var FlateStream = (function FlateStreamC
 
   var fixedDistCodeTab = [new Uint32Array([
     0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c,
     0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000,
     0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d,
     0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000
   ]), 5];
 
-  function FlateStream(stream) {
-    var bytes = stream.getBytes();
-    var bytesPos = 0;
-
-    this.dict = stream.dict;
-    var cmf = bytes[bytesPos++];
-    var flg = bytes[bytesPos++];
+  function FlateStream(str) {
+    this.str = str;
+    this.dict = str.dict;
+
+    var cmf = str.getByte();
+    var flg = str.getByte();
     if (cmf == -1 || flg == -1)
       error('Invalid header in flate stream: ' + cmf + ', ' + flg);
     if ((cmf & 0x0f) != 0x08)
       error('Unknown compression method in flate stream: ' + cmf + ', ' + flg);
     if ((((cmf << 8) + flg) % 31) !== 0)
       error('Bad FCHECK in flate stream: ' + cmf + ', ' + flg);
     if (flg & 0x20)
       error('FDICT bit set in flate stream: ' + cmf + ', ' + flg);
 
-    this.bytes = bytes;
-    this.bytesPos = bytesPos;
-
     this.codeSize = 0;
     this.codeBuf = 0;
 
     DecodeStream.call(this);
   }
 
   FlateStream.prototype = Object.create(DecodeStream.prototype);
 
   FlateStream.prototype.getBits = function FlateStream_getBits(bits) {
+    var str = this.str;
     var codeSize = this.codeSize;
     var codeBuf = this.codeBuf;
-    var bytes = this.bytes;
-    var bytesPos = this.bytesPos;
 
     var b;
     while (codeSize < bits) {
-      if (typeof (b = bytes[bytesPos++]) == 'undefined')
+      if ((b = str.getByte()) === -1) {
         error('Bad encoding in flate stream');
+      }
       codeBuf |= b << codeSize;
       codeSize += 8;
     }
     b = codeBuf & ((1 << bits) - 1);
     this.codeBuf = codeBuf >> bits;
     this.codeSize = codeSize -= bits;
-    this.bytesPos = bytesPos;
+
     return b;
   };
 
   FlateStream.prototype.getCode = function FlateStream_getCode(table) {
+    var str = this.str;
     var codes = table[0];
     var maxLen = table[1];
     var codeSize = this.codeSize;
     var codeBuf = this.codeBuf;
-    var bytes = this.bytes;
-    var bytesPos = this.bytesPos;
 
     while (codeSize < maxLen) {
       var b;
-      if (typeof (b = bytes[bytesPos++]) == 'undefined')
+      if ((b = str.getByte()) === -1) {
         error('Bad encoding in flate stream');
+      }
       codeBuf |= (b << codeSize);
       codeSize += 8;
     }
     var code = codes[codeBuf & ((1 << maxLen) - 1)];
     var codeLen = code >> 16;
     var codeVal = code & 0xffff;
     if (codeSize === 0 || codeSize < codeLen || codeLen === 0)
       error('Bad encoding in flate stream');
     this.codeBuf = (codeBuf >> codeLen);
     this.codeSize = (codeSize - codeLen);
-    this.bytesPos = bytesPos;
     return codeVal;
   };
 
   FlateStream.prototype.generateHuffmanTable =
     function flateStreamGenerateHuffmanTable(lengths) {
     var n = lengths.length;
 
     // find max code length
@@ -33096,66 +33316,68 @@ var FlateStream = (function FlateStreamC
         }
       }
     }
 
     return [codes, maxLen];
   };
 
   FlateStream.prototype.readBlock = function FlateStream_readBlock() {
+    var str = this.str;
     // read block header
     var hdr = this.getBits(3);
     if (hdr & 1)
       this.eof = true;
     hdr >>= 1;
 
     if (hdr === 0) { // uncompressed block
-      var bytes = this.bytes;
-      var bytesPos = this.bytesPos;
       var b;
 
-      if (typeof (b = bytes[bytesPos++]) == 'undefined')
+      if ((b = str.getByte()) === -1) {
         error('Bad block header in flate stream');
+      }
       var blockLen = b;
-      if (typeof (b = bytes[bytesPos++]) == 'undefined')
+      if ((b = str.getByte()) === -1) {
         error('Bad block header in flate stream');
+      }
       blockLen |= (b << 8);
-      if (typeof (b = bytes[bytesPos++]) == 'undefined')
+      if ((b = str.getByte()) === -1) {
         error('Bad block header in flate stream');
+      }
       var check = b;
-      if (typeof (b = bytes[bytesPos++]) == 'undefined')
+      if ((b = str.getByte()) === -1) {
         error('Bad block header in flate stream');
+      }
       check |= (b << 8);
       if (check != (~blockLen & 0xffff) &&
           (blockLen !== 0 || check !== 0)) {
         // Ignoring error for bad "empty" block (see issue 1277)
         error('Bad uncompressed block length in flate stream');
       }
 
       this.codeBuf = 0;
       this.codeSize = 0;
 
       var bufferLength = this.bufferLength;
       var buffer = this.ensureBuffer(bufferLength + blockLen);
       var end = bufferLength + blockLen;
       this.bufferLength = end;
       if (blockLen === 0) {
-        if (typeof bytes[bytesPos] == 'undefined') {
+        if (str.peekBytes(1).length === 0) {
           this.eof = true;
         }
       } else {
         for (var n = bufferLength; n < end; ++n) {
-          if (typeof (b = bytes[bytesPos++]) == 'undefined') {
+          if ((b = str.getByte()) === -1) {
             this.eof = true;
             break;
           }
           buffer[n] = b;
         }
       }
-      this.bytesPos = bytesPos;
       return;
     }
 
     var litCodeTable;
     var distCodeTable;
     if (hdr == 1) { // compressed block, fixed codes
       litCodeTable = fixedLitCodeTab;
       distCodeTable = fixedDistCodeTab;
@@ -33432,27 +33654,35 @@ var PredictorStream = (function Predicto
 /**
  * Depending on the type of JPEG a JpegStream is handled in different ways. For
  * JPEG's that are supported natively such as DeviceGray and DeviceRGB the image
  * data is stored and then loaded by the browser.  For unsupported JPEG's we use
  * a library to decode these images and the stream behaves like all the other
  * DecodeStreams.
  */
 var JpegStream = (function JpegStreamClosure() {
-  function JpegStream(bytes, dict, xref) {
+  function JpegStream(stream, length, dict, xref) {
     // TODO: per poppler, some images may have 'junk' before that
     // need to be removed
+    this.stream = stream;
+    this.length = length;
     this.dict = dict;
-    this.bytes = bytes;
 
     DecodeStream.call(this);
   }
 
   JpegStream.prototype = Object.create(DecodeStream.prototype);
 
+  Object.defineProperty(JpegStream.prototype, 'bytes', {
+    get: function JpegStream_bytes() {
+      return shadow(this, 'bytes', this.stream.getBytes(this.length));
+    },
+    configurable: true
+  });
+
   JpegStream.prototype.ensureBuffer = function JpegStream_ensureBuffer(req) {
     if (this.bufferLength)
       return;
     try {
       var jpegImage = new JpegImage();
       if (this.colorTransform != -1)
         jpegImage.colorTransform = this.colorTransform;
       jpegImage.parse(this.bytes);
@@ -33491,25 +33721,33 @@ var JpegStream = (function JpegStreamClo
   return JpegStream;
 })();
 
 /**
  * For JPEG 2000's we use a library to decode these images and
  * the stream behaves like all the other DecodeStreams.
  */
 var JpxStream = (function JpxStreamClosure() {
-  function JpxStream(bytes, dict) {
+  function JpxStream(stream, length, dict) {
+    this.stream = stream;
+    this.length = length;
     this.dict = dict;
-    this.bytes = bytes;
 
     DecodeStream.call(this);
   }
 
   JpxStream.prototype = Object.create(DecodeStream.prototype);
 
+  Object.defineProperty(JpxStream.prototype, 'bytes', {
+    get: function JpxStream_bytes() {
+      return shadow(this, 'bytes', this.stream.getBytes(this.length));
+    },
+    configurable: true
+  });
+
   JpxStream.prototype.ensureBuffer = function JpxStream_ensureBuffer(req) {
     if (this.bufferLength)
       return;
 
     var jpxImage = new JpxImage();
     jpxImage.parse(this.bytes);
 
     var width = jpxImage.width;
@@ -33590,25 +33828,33 @@ var JpxStream = (function JpxStreamClosu
   return JpxStream;
 })();
 
 /**
  * For JBIG2's we use a library to decode these images and
  * the stream behaves like all the other DecodeStreams.
  */
 var Jbig2Stream = (function Jbig2StreamClosure() {
-  function Jbig2Stream(bytes, dict) {
+  function Jbig2Stream(stream, length, dict) {
+    this.stream = stream;
+    this.length = length;
     this.dict = dict;
-    this.bytes = bytes;
 
     DecodeStream.call(this);
   }
 
   Jbig2Stream.prototype = Object.create(DecodeStream.prototype);
 
+  Object.defineProperty(Jbig2Stream.prototype, 'bytes', {
+    get: function Jbig2Stream_bytes() {
+      return shadow(this, 'bytes', this.stream.getBytes(this.length));
+    },
+    configurable: true
+  });
+
   Jbig2Stream.prototype.ensureBuffer = function Jbig2Stream_ensureBuffer(req) {
     if (this.bufferLength)
       return;
 
     var jbig2Image = new Jbig2Image();
 
     var chunks = [], decodeParams = this.dict.get('DecodeParms');
 
@@ -35271,17 +35517,17 @@ var WorkerMessageHandler = PDFJS.WorkerM
     });
 
     handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
       pdfManager.getPage(data.pageIndex).then(function(page) {
 
         var pageNum = data.pageIndex + 1;
         var start = Date.now();
         // Pre compile the pdf page and fetch the fonts/images.
-        page.getOperatorList(handler).then(function(operatorList) {
+        page.getOperatorList(handler, data.intent).then(function(operatorList) {
 
           info('page=' + pageNum + ' - getOperatorList: time=' +
                (Date.now() - start) + 'ms, len=' + operatorList.fnArray.length);
 
         }, function(e) {
 
           var minimumStackMessage =
               'worker.js: while trying to getPage() and getOperatorList()';
@@ -35303,17 +35549,18 @@ var WorkerMessageHandler = PDFJS.WorkerM
             wrappedException = {
               message: 'Unknown exception type: ' + (typeof e),
               stack: minimumStackMessage
             };
           }
 
           handler.send('PageError', {
             pageNum: pageNum,
-            error: wrappedException
+            error: wrappedException,
+            intent: data.intent
           });
         });
       });
     }, this);
 
     handler.on('GetTextContent', function wphExtractText(data, deferred) {
       pdfManager.getPage(data.pageIndex).then(function(page) {
         var pageNum = data.pageIndex + 1;
@@ -35389,46 +35636,221 @@ if (typeof window === 'undefined') {
     });
   });
 
   var handler = new MessageHandler('worker_processor', this);
   WorkerMessageHandler.setup(handler);
 }
 
 
+/* This class implements the QM Coder decoding as defined in
+ *   JPEG 2000 Part I Final Committee Draft Version 1.0
+ *   Annex C.3 Arithmetic decoding procedure 
+ * available at http://www.jpeg.org/public/fcd15444-1.pdf
+ * 
+ * The arithmetic decoder is used in conjunction with context models to decode
+ * JPEG2000 and JBIG2 streams.
+ */
+var ArithmeticDecoder = (function ArithmeticDecoderClosure() {
+  // Table C-2
+  var QeTable = [
+    {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1},
+    {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0},
+    {qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0},
+    {qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0},
+    {qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0},
+    {qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0},
+    {qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1},
+    {qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0},
+    {qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0},
+    {qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0},
+    {qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0},
+    {qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0},
+    {qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0},
+    {qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0},
+    {qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1},
+    {qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0},
+    {qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0},
+    {qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0},
+    {qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0},
+    {qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0},
+    {qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0},
+    {qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0},
+    {qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0},
+    {qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0},
+    {qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0},
+    {qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0},
+    {qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0},
+    {qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0},
+    {qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0},
+    {qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0},
+    {qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0},
+    {qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0},
+    {qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0},
+    {qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0},
+    {qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0},
+    {qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0},
+    {qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0},
+    {qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0},
+    {qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0},
+    {qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0},
+    {qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0},
+    {qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0},
+    {qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0},
+    {qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0},
+    {qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0},
+    {qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0},
+    {qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0}
+  ];
+
+  // C.3.5 Initialisation of the decoder (INITDEC)
+  function ArithmeticDecoder(data, start, end) {
+    this.data = data;
+    this.bp = start;
+    this.dataEnd = end;
+
+    this.chigh = data[start];
+    this.clow = 0;
+
+    this.byteIn();
+
+    this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F);
+    this.clow = (this.clow << 7) & 0xFFFF;
+    this.ct -= 7;
+    this.a = 0x8000;
+  }
+
+  ArithmeticDecoder.prototype = {
+    // C.3.4 Compressed data input (BYTEIN)
+    byteIn: function ArithmeticDecoder_byteIn() {
+      var data = this.data;
+      var bp = this.bp;
+      if (data[bp] == 0xFF) {
+        var b1 = data[bp + 1];
+        if (b1 > 0x8F) {
+          this.clow += 0xFF00;
+          this.ct = 8;
+        } else {
+          bp++;
+          this.clow += (data[bp] << 9);
+          this.ct = 7;
+          this.bp = bp;
+        }
+      } else {
+        bp++;
+        this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00;
+        this.ct = 8;
+        this.bp = bp;
+      }
+      if (this.clow > 0xFFFF) {
+        this.chigh += (this.clow >> 16);
+        this.clow &= 0xFFFF;
+      }
+    },
+    // C.3.2 Decoding a decision (DECODE)
+    readBit: function ArithmeticDecoder_readBit(contexts, pos) {
+      // contexts are packed into 1 byte:
+      // highest 7 bits carry cx.index, lowest bit carries cx.mps
+      var cx_index = contexts[pos] >> 1, cx_mps = contexts[pos] & 1;
+      var qeTableIcx = QeTable[cx_index];
+      var qeIcx = qeTableIcx.qe;
+      var nmpsIcx = qeTableIcx.nmps;
+      var nlpsIcx = qeTableIcx.nlps;
+      var switchIcx = qeTableIcx.switchFlag;
+      var d;
+      this.a -= qeIcx;
+
+      if (this.chigh < qeIcx) {
+        // exchangeLps
+        if (this.a < qeIcx) {
+          this.a = qeIcx;
+          d = cx_mps;
+          cx_index = nmpsIcx;
+        } else {
+          this.a = qeIcx;
+          d = 1 - cx_mps;
+          if (switchIcx) {
+            cx_mps = d;
+          }
+          cx_index = nlpsIcx;
+        }
+      } else {
+        this.chigh -= qeIcx;
+        if ((this.a & 0x8000) !== 0) {
+          return cx_mps;
+        }
+        // exchangeMps
+        if (this.a < qeIcx) {
+          d = 1 - cx_mps;
+          if (switchIcx) {
+            cx_mps = d;
+          }
+          cx_index = nlpsIcx;
+        } else {
+          d = cx_mps;
+          cx_index = nmpsIcx;
+        }
+      }
+      // C.3.3 renormD;
+      do {
+        if (this.ct === 0) {
+          this.byteIn();
+        }
+
+        this.a <<= 1;
+        this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1);
+        this.clow = (this.clow << 1) & 0xFFFF;
+        this.ct--;
+      } while ((this.a & 0x8000) === 0);
+
+      contexts[pos] = cx_index << 1 | cx_mps;
+      return d;
+    }
+  };
+
+  return ArithmeticDecoder;
+})();
+
+
 var JpxImage = (function JpxImageClosure() {
   // Table E.1
   var SubbandsGainLog2 = {
     'LL': 0,
     'LH': 1,
     'HL': 1,
     'HH': 2
   };
+  var TransformType = {
+    IRREVERSIBLE: 0,
+    REVERSIBLE: 1
+  };
   function JpxImage() {
     this.failOnCorruptedImage = false;
   }
   JpxImage.prototype = {
     load: function JpxImage_load(url) {
       var xhr = new XMLHttpRequest();
       xhr.open('GET', url, true);
       xhr.responseType = 'arraybuffer';
       xhr.onload = (function() {
         // TODO catch parse error
         var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
         this.parse(data);
-        if (this.onload)
+        if (this.onload) {
           this.onload();
+        }
       }).bind(this);
       xhr.send(null);
     },
     parse: function JpxImage_parse(data) {
       function readUint(data, offset, bytes) {
         var n = 0;
-        for (var i = 0; i < bytes; i++)
+        for (var i = 0; i < bytes; i++) {
           n = n * 256 + (data[offset + i] & 0xFF);
+        }
         return n;
       }
 
       var head = readUint(data, 0, 2);
       // No box header, immediate start of codestream (SOC)
       if (head === 0xFF4F) {
         this.parseCodestream(data, 0, data.length);
         return;
@@ -35440,38 +35862,41 @@ var JpxImage = (function JpxImageClosure
         var lbox = readUint(data, position, 4);
         var tbox = readUint(data, position + 4, 4);
         position += headerSize;
         if (lbox == 1) {
           lbox = readUint(data, position, 8);
           position += 8;
           headerSize += 8;
         }
-        if (lbox === 0)
+        if (lbox === 0) {
           lbox = length - position + headerSize;
-        if (lbox < headerSize)
+        }
+        if (lbox < headerSize) {
           error('JPX error: Invalid box field size');
+        }
         var dataLength = lbox - headerSize;
         var jumpDataLength = true;
         switch (tbox) {
           case 0x6A501A1A: // 'jP\032\032'
             // TODO
             break;
           case 0x6A703268: // 'jp2h'
             jumpDataLength = false; // parsing child boxes
             break;
           case 0x636F6C72: // 'colr'
             // TODO
             break;
           case 0x6A703263: // 'jp2c'
             this.parseCodestream(data, position, position + dataLength);
             break;
         }
-        if (jumpDataLength)
+        if (jumpDataLength) {
           position += dataLength;
+        }
       }
     },
     parseCodestream: function JpxImage_parseCodestream(data, start, end) {
       var context = {};
       try {
         var position = start;
         while (position < end) {
           var code = readUint16(data, position);
@@ -35532,48 +35957,48 @@ var JpxImage = (function JpxImageClosure
                   break;
                 case 2:
                   spqcdSize = 16;
                   scalarExpounded = true;
                   break;
                 default:
                   throw 'Invalid SQcd value ' + sqcd;
               }
-              qcd.noQuantization = spqcdSize == 8;
+              qcd.noQuantization = (spqcdSize == 8);
               qcd.scalarExpounded = scalarExpounded;
               qcd.guardBits = sqcd >> 5;
               var spqcds = [];
               while (j < length + position) {
                 var spqcd = {};
                 if (spqcdSize == 8) {
                   spqcd.epsilon = data[j++] >> 3;
                   spqcd.mu = 0;
                 } else {
                   spqcd.epsilon = data[j] >> 3;
                   spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1];
                   j += 2;
                 }
                 spqcds.push(spqcd);
               }
               qcd.SPqcds = spqcds;
-              if (context.mainHeader)
+              if (context.mainHeader) {
                 context.QCD = qcd;
-              else {
+              } else {
                 context.currentTile.QCD = qcd;
                 context.currentTile.QCC = [];
               }
               break;
             case 0xFF5D: // Quantization component (QCC)
               length = readUint16(data, position);
               var qcc = {};
               j = position + 2;
               var cqcc;
-              if (context.SIZ.Csiz < 257)
+              if (context.SIZ.Csiz < 257) {
                 cqcc = data[j++];
-              else {
+              } else {
                 cqcc = readUint16(data, j);
                 j += 2;
               }
               var sqcd = data[j++];
               var spqcdSize, scalarExpounded;
               switch (sqcd & 0x1F) {
                 case 0:
                   spqcdSize = 8;
@@ -35585,37 +36010,38 @@ var JpxImage = (function JpxImageClosure
                   break;
                 case 2:
                   spqcdSize = 16;
                   scalarExpounded = true;
                   break;
                 default:
                   throw 'Invalid SQcd value ' + sqcd;
               }
-              qcc.noQuantization = spqcdSize == 8;
+              qcc.noQuantization = (spqcdSize == 8);
               qcc.scalarExpounded = scalarExpounded;
               qcc.guardBits = sqcd >> 5;
               var spqcds = [];
-              while (j < length + position) {
+              while (j < (length + position)) {
                 var spqcd = {};
                 if (spqcdSize == 8) {
                   spqcd.epsilon = data[j++] >> 3;
                   spqcd.mu = 0;
                 } else {
                   spqcd.epsilon = data[j] >> 3;
                   spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1];
                   j += 2;
                 }
                 spqcds.push(spqcd);
               }
               qcc.SPqcds = spqcds;
-              if (context.mainHeader)
+              if (context.mainHeader) {
                 context.QCC[cqcc] = qcc;
-              else
+              } else {
                 context.currentTile.QCC[cqcc] = qcc;
+              }
               break;
             case 0xFF52: // Coding style default (COD)
               length = readUint16(data, position);
               var cod = {};
               j = position + 2;
               var scod = data[j++];
               cod.entropyCoderWithCustomPrecincts = !!(scod & 1);
               cod.sopMarkerUsed = !!(scod & 2);
@@ -35648,23 +36074,24 @@ var JpxImage = (function JpxImageClosure
                 }
                 cod.precinctsSizes = precinctsSizes;
               }
 
               if (cod.sopMarkerUsed || cod.ephMarkerUsed ||
                   cod.selectiveArithmeticCodingBypass ||
                   cod.resetContextProbabilities ||
                   cod.terminationOnEachCodingPass ||
-                  cod.verticalyStripe || cod.predictableTermination)
+                  cod.verticalyStripe || cod.predictableTermination) {
                 throw 'Unsupported COD options: ' +
                   globalScope.JSON.stringify(cod);
-
-              if (context.mainHeader)
+              }
+
+              if (context.mainHeader) {
                 context.COD = cod;
-              else {
+              } else {
                 context.currentTile.COD = cod;
                 context.currentTile.COC = [];
               }
               break;
             case 0xFF90: // Start of tile-part (SOT)
               length = readUint16(data, position);
               var tile = {};
               tile.index = readUint16(data, position + 2);
@@ -35687,43 +36114,43 @@ var JpxImage = (function JpxImageClosure
               var tile = context.currentTile;
               if (tile.partIndex === 0) {
                 initializeTile(context, tile.index);
                 buildPackets(context);
               }
 
               // moving to the end of the data
               length = tile.dataEnd - position;
-
               parseTilePackets(context, data, position, length);
               break;
             case 0xFF64: // Comment (COM)
               length = readUint16(data, position);
               // skipping content
               break;
             default:
               throw 'Unknown codestream code: ' + code.toString(16);
           }
           position += length;
         }
       } catch (e) {
-        if (this.failOnCorruptedImage)
+        if (this.failOnCorruptedImage) {
           error('JPX error: ' + e);
-        else
+        } else {
           warn('JPX error: ' + e + '. Trying to recover');
+        }
       }
       this.tiles = transformComponents(context);
       this.width = context.SIZ.Xsiz - context.SIZ.XOsiz;
       this.height = context.SIZ.Ysiz - context.SIZ.YOsiz;
       this.componentsCount = context.SIZ.Csiz;
     }
   };
   function readUint32(data, offset) {
     return (data[offset] << 24) | (data[offset + 1] << 16) |
-      (data[offset + 2] << 8) | data[offset + 3];
+            (data[offset + 2] << 8) | data[offset + 3];
   }
   function readUint16(data, offset) {
     return (data[offset] << 8) | data[offset + 1];
   }
   function log2(x) {
     var n = 1, i = 0;
     while (x > n) {
       n <<= 1;
@@ -35783,37 +36210,37 @@ var JpxImage = (function JpxImageClosure
     if (!codOrCoc.entropyCoderWithCustomPrecincts) {
       result.PPx = 15;
       result.PPy = 15;
     } else {
       result.PPx = codOrCoc.precinctsSizes[r].PPx;
       result.PPy = codOrCoc.precinctsSizes[r].PPy;
     }
     // calculate codeblock size as described in section B.7
-    result.xcb_ = r > 0 ? Math.min(codOrCoc.xcb, result.PPx - 1) :
-      Math.min(codOrCoc.xcb, result.PPx);
-    result.ycb_ = r > 0 ? Math.min(codOrCoc.ycb, result.PPy - 1) :
-      Math.min(codOrCoc.ycb, result.PPy);
+    result.xcb_ = (r > 0 ? Math.min(codOrCoc.xcb, result.PPx - 1) :
+                   Math.min(codOrCoc.xcb, result.PPx));
+    result.ycb_ = (r > 0 ? Math.min(codOrCoc.ycb, result.PPy - 1) :
+                   Math.min(codOrCoc.ycb, result.PPy));
     return result;
   }
   function buildPrecincts(context, resolution, dimensions) {
     // Section B.6 Division resolution to precincts
     var precinctWidth = 1 << dimensions.PPx;
     var precinctHeight = 1 << dimensions.PPy;
-    var numprecinctswide = resolution.trx1 > resolution.trx0 ?
+    var numprecinctswide = (resolution.trx1 > resolution.trx0 ?
       Math.ceil(resolution.trx1 / precinctWidth) -
-      Math.floor(resolution.trx0 / precinctWidth) : 0;
-    var numprecinctshigh = resolution.try1 > resolution.try0 ?
+      Math.floor(resolution.trx0 / precinctWidth) : 0);
+    var numprecinctshigh = (resolution.try1 > resolution.try0 ?
       Math.ceil(resolution.try1 / precinctHeight) -
-      Math.floor(resolution.try0 / precinctHeight) : 0;
+      Math.floor(resolution.try0 / precinctHeight) : 0);
     var numprecincts = numprecinctswide * numprecinctshigh;
     var precinctXOffset = Math.floor(resolution.trx0 / precinctWidth) *
-      precinctWidth;
+                          precinctWidth;
     var precinctYOffset = Math.floor(resolution.try0 / precinctHeight) *
-      precinctHeight;
+                          precinctHeight;
     resolution.precinctParameters = {
       precinctXOffset: precinctXOffset,
       precinctYOffset: precinctYOffset,
       precinctWidth: precinctWidth,
       precinctHeight: precinctHeight,
       numprecinctswide: numprecinctswide,
       numprecinctshigh: numprecinctshigh,
       numprecincts: numprecincts
@@ -35839,31 +36266,31 @@ var JpxImage = (function JpxImageClosure
           cby: j,
           tbx0: codeblockWidth * i,
           tby0: codeblockHeight * j,
           tbx1: codeblockWidth * (i + 1),
           tby1: codeblockHeight * (j + 1)
         };
         // calculate precinct number
         var pi = Math.floor((codeblock.tbx0 -
-          precinctParameters.precinctXOffset) /
-          precinctParameters.precinctWidth);
+                 precinctParameters.precinctXOffset) /
+                 precinctParameters.precinctWidth);
         var pj = Math.floor((codeblock.tby0 -
-          precinctParameters.precinctYOffset) /
-          precinctParameters.precinctHeight);
+                 precinctParameters.precinctYOffset) /
+                 precinctParameters.precinctHeight);
         var precinctNumber = pj +
-          pi * precinctParameters.numprecinctswide;
+                             pi * precinctParameters.numprecinctswide;
         codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0);
         codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0);
         codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1);
         codeblock.tby1_ = Math.min(subband.tby1, codeblock.tby1);
         codeblock.precinctNumber = precinctNumber;
         codeblock.subbandType = subband.type;
         var coefficientsLength = (codeblock.tbx1_ - codeblock.tbx0_) *
-          (codeblock.tby1_ - codeblock.tby0_);
+                                 (codeblock.tby1_ - codeblock.tby0_);
         codeblock.Lblock = 3;
         codeblocks.push(codeblock);
         // building precinct for the sub-band
         var precinct;
         if (precinctNumber in precincts) {
           precinct = precincts[precinctNumber];
           precinct.cbxMin = Math.min(precinct.cbxMin, i);
           precinct.cbyMin = Math.min(precinct.cbyMin, j);
@@ -35929,18 +36356,19 @@ var JpxImage = (function JpxImageClosure
     var l = 0, r = 0, i = 0, k = 0;
 
     this.nextPacket = function JpxImage_nextPacket() {
       // Section B.12.1.1 Layer-resolution-component-position
       for (; l < layersCount; l++) {
         for (; r <= maxDecompositionLevelsCount; r++) {
           for (; i < componentsCount; i++) {
             var component = tile.components[i];
-            if (r > component.codingStyleParameters.decompositionLevelsCount)
+            if (r > component.codingStyleParameters.decompositionLevelsCount) {
               continue;
+            }
 
             var resolution = component.resolutions[r];
             var numprecincts = resolution.precinctParameters.numprecincts;
             for (; k < numprecincts;) {
               var packet = createPacket(resolution, k, l);
               k++;
               return packet;
             }
@@ -35968,18 +36396,19 @@ var JpxImage = (function JpxImageClosure
     var r = 0, l = 0, i = 0, k = 0;
 
     this.nextPacket = function JpxImage_nextPacket() {
       // Section B.12.1.2 Resolution-layer-component-position
       for (; r <= maxDecompositionLevelsCount; r++) {
         for (; l < layersCount; l++) {
           for (; i < componentsCount; i++) {
             var component = tile.components[i];
-            if (r > component.codingStyleParameters.decompositionLevelsCount)
+            if (r > component.codingStyleParameters.decompositionLevelsCount) {
               continue;
+            }
 
             var resolution = component.resolutions[r];
             var numprecincts = resolution.precinctParameters.numprecincts;
             for (; k < numprecincts;) {
               var packet = createPacket(resolution, k, l);
               k++;
               return packet;
             }
@@ -36238,31 +36667,30 @@ var JpxImage = (function JpxImageClosure
         position += packetItem.dataLength;
       }
     }
     return position;
   }
   function copyCoefficients(coefficients, x0, y0, width, height,
                             delta, mb, codeblocks, transformation,
                             segmentationSymbolUsed) {
-    var r = 0.5; // formula (E-6)
     for (var i = 0, ii = codeblocks.length; i < ii; ++i) {
       var codeblock = codeblocks[i];
       var blockWidth = codeblock.tbx1_ - codeblock.tbx0_;
       var blockHeight = codeblock.tby1_ - codeblock.tby0_;
       if (blockWidth === 0 || blockHeight === 0) {
         continue;
       }
       if (!('data' in codeblock)) {
         continue;
       }
 
       var bitModel, currentCodingpassType;
       bitModel = new BitModel(blockWidth, blockHeight, codeblock.subbandType,
-        codeblock.zeroBitPlanes);
+                              codeblock.zeroBitPlanes);
       currentCodingpassType = 2; // first bit plane starts from cleanup
 
       // collect data
       var data = codeblock.data, totalLength = 0, codingpasses = 0;
       for (var q = 0, qq = data.length; q < qq; q++) {
         var dataItem = data[q];
         totalLength += dataItem.end - dataItem.start;
         codingpasses += dataItem.codingpasses;
@@ -36292,30 +36720,26 @@ var JpxImage = (function JpxImageClosure
               bitModel.checkSegmentationSymbol();
             }
             break;
         }
         currentCodingpassType = (currentCodingpassType + 1) % 3;
       }
 
       var offset = (codeblock.tbx0_ - x0) + (codeblock.tby0_ - y0) * width;
-      var position = 0;
+      var n, nb, correction, position = 0;
+      var irreversible = (transformation === TransformType.IRREVERSIBLE);
+      var sign = bitModel.coefficentsSign;
+      var magnitude = bitModel.coefficentsMagnitude;
+      var bitsDecoded = bitModel.bitsDecoded;
       for (var j = 0; j < blockHeight; j++) {
         for (var k = 0; k < blockWidth; k++) {
-          var n = (bitModel.coefficentsSign[position] ? -1 : 1) *
-            bitModel.coefficentsMagnitude[position];
-          var nb = bitModel.bitsDecoded[position], correction;
-          if (transformation === 0 || mb > nb) {
-            // use r only if transformation is irreversible or
-            // not all bitplanes were decoded for reversible transformation
-            n += n < 0 ? n - r : n > 0 ? n + r : 0;
-            correction = 1 << (mb - nb);
-          } else {
-            correction = 1;
-          }
+          n = (sign[position] ? -1 : 1) * magnitude[position];
+          nb = bitsDecoded[position];
+          correction = (irreversible || mb > nb) ? 1 << (mb - nb) : 1;
           coefficients[offset++] = n * correction * delta;
           position++;
         }
         offset += width - blockWidth;
       }
     }
   }
   function transformTile(context, tile, c) {
@@ -36326,16 +36750,20 @@ var JpxImage = (function JpxImageClosure
       codingStyleParameters.decompositionLevelsCount;
     var spqcds = quantizationParameters.SPqcds;
     var scalarExpounded = quantizationParameters.scalarExpounded;
     var guardBits = quantizationParameters.guardBits;
     var transformation = codingStyleParameters.transformation;
     var segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed;
     var precision = context.components[c].precision;
 
+    var transformation = codingStyleParameters.transformation;
+    var transform = (transformation === TransformType.IRREVERSIBLE ?
+                     new IrreversibleTransform() : new ReversibleTransform());
+
     var subbandCoefficients = [];
     var k = 0, b = 0;
     for (var i = 0; i <= decompositionLevelsCount; i++) {
       var resolution = component.resolutions[i];
 
       for (var j = 0, jj = resolution.subbands.length; j < jj; j++) {
         var mu, epsilon;
         if (!scalarExpounded) {
@@ -36348,18 +36776,18 @@ var JpxImage = (function JpxImageClosure
         }
 
         var subband = resolution.subbands[j];
         var width = subband.tbx1 - subband.tbx0;
         var height = subband.tby1 - subband.tby0;
         var gainLog2 = SubbandsGainLog2[subband.type];
 
         // calulate quantization coefficient (Section E.1.1.1)
-        var delta = Math.pow(2, (precision + gainLog2) - epsilon) *
-          (1 + mu / 2048);
+        var delta = (transformation === TransformType.IRREVERSIBLE ?
+          Math.pow(2, precision + gainLog2 - epsilon) * (1 + mu / 2048) : 1);
         var mb = (guardBits + epsilon - 1);
 
         var coefficients = new Float32Array(width * height);
         copyCoefficients(coefficients, subband.tbx0, subband.tby0,
           width, height, delta, mb, subband.codeblocks, transformation,
           segmentationSymbolUsed);
 
         subbandCoefficients.push({
@@ -36367,21 +36795,18 @@ var JpxImage = (function JpxImageClosure
           height: height,
           items: coefficients
         });
 
         b++;
       }
     }
 
-    var transformation = codingStyleParameters.transformation;
-    var transform = transformation === 0 ? new IrreversibleTransform() :
-      new ReversibleTransform();
     var result = transform.calculate(subbandCoefficients,
-      component.tcx0, component.tcy0);
+                                     component.tcx0, component.tcy0);
     return {
       left: component.tcx0,
       top: component.tcy0,
       width: result.width,
       height: result.height,
       items: result.items
     };
   }
@@ -36395,25 +36820,41 @@ var JpxImage = (function JpxImageClosure
       var result = [];
       for (var c = 0; c < componentsCount; c++) {
         var image = transformTile(context, tile, c);
         result.push(image);
       }
 
       // Section G.2.2 Inverse multi component transform
       if (tile.codingStyleDefaultParameters.multipleComponentTransform) {
-        var y0items = result[0].items;
-        var y1items = result[1].items;
-        var y2items = result[2].items;
-        for (var j = 0, jj = y0items.length; j < jj; j++) {
-          var y0 = y0items[j], y1 = y1items[j], y2 = y2items[j];
-          var i1 = y0 - ((y2 + y1) >> 2);
-          y1items[j] = i1;
-          y0items[j] = y2 + i1;
-          y2items[j] = y1 + i1;
+        var component0 = tile.components[0];
+        var transformation = component0.codingStyleParameters.transformation;
+        if (transformation === TransformType.IRREVERSIBLE) {
+          // inverse irreversible multiple component transform
+          var y0items = result[0].items;
+          var y1items = result[1].items;
+          var y2items = result[2].items;
+          for (var j = 0, jj = y0items.length; j < jj; ++j) {
+            var y0 = y0items[j], y1 = y1items[j], y2 = y2items[j];
+            y0items[j] = y0 + 1.402 * y2 + 0.5;
+            y1items[j] = y0 - 0.34413 * y1 - 0.71414 * y2 + 0.5;
+            y2items[j] = y0 + 1.772 * y1 + 0.5;
+          }
+        } else {
+          // inverse reversible multiple component transform
+          var y0items = result[0].items;
+          var y1items = result[1].items;
+          var y2items = result[2].items;
+          for (var j = 0, jj = y0items.length; j < jj; ++j) {
+            var y0 = y0items[j], y1 = y1items[j], y2 = y2items[j];
+            var i1 = y0 - ((y2 + y1) >> 2);
+            y1items[j] = i1;
+            y0items[j] = y2 + i1;
+            y2items[j] = y1 + i1;
+          }
         }
       }
 
       // Section G.1 DC level shifting to unsigned component values
       for (var c = 0; c < componentsCount; c++) {
         var component = components[c];
         if (component.isSigned) {
           continue;
@@ -36448,21 +36889,21 @@ var JpxImage = (function JpxImageClosure
   }
   function initializeTile(context, tileIndex) {
     var siz = context.SIZ;
     var componentsCount = siz.Csiz;
     var tile = context.tiles[tileIndex];
     var resultTiles = [];
     for (var c = 0; c < componentsCount; c++) {
       var component = tile.components[c];
-      var qcdOrQcc = c in context.currentTile.QCC ?
-        context.currentTile.QCC[c] : context.currentTile.QCD;
+      var qcdOrQcc = (c in context.currentTile.QCC ?
+        context.currentTile.QCC[c] : context.currentTile.QCD);
       component.quantizationParameters = qcdOrQcc;
-      var codOrCoc = c in context.currentTile.COC ?
-        context.currentTile.COC[c] : context.currentTile.COD;
+      var codOrCoc = (c in context.currentTile.COC ?
+        context.currentTile.COC[c] : context.currentTile.COD);
       component.codingStyleParameters = codOrCoc;
     }
     tile.codingStyleDefaultParameters = context.currentTile.COD;
   }
 
   // Section B.10.2 Tag trees
   var TagTree = (function TagTreeClosure() {
     function TagTree(width, height) {
@@ -36599,182 +37040,20 @@ var JpxImage = (function JpxImageClosure
         var level = this.levels[currentLevel];
         level.items[level.index] = value;
         return true;
       }
     };
     return InclusionTree;
   })();
 
-  // Implements C.3. Arithmetic decoding procedures
-  var ArithmeticDecoder = (function ArithmeticDecoderClosure() {
-    var QeTable = [
-      {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1},
-      {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0},
-      {qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0},
-      {qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0},
-      {qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0},
-      {qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0},
-      {qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1},
-      {qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0},
-      {qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0},
-      {qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0},
-      {qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0},
-      {qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0},
-      {qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0},
-      {qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0},
-      {qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1},
-      {qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0},
-      {qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0},
-      {qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0},
-      {qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0},
-      {qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0},
-      {qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0},
-      {qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0},
-      {qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0},
-      {qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0},
-      {qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0},
-      {qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0},
-      {qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0},
-      {qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0},
-      {qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0},
-      {qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0},
-      {qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0},
-      {qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0},
-      {qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0},
-      {qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0},
-      {qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0},
-      {qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0},
-      {qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0},
-      {qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0},
-      {qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0},
-      {qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0},
-      {qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0},
-      {qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0},
-      {qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0},
-      {qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0},
-      {qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0},
-      {qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0},
-      {qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0}
-    ];
-
-    function ArithmeticDecoder(data, start, end) {
-      this.data = data;
-      this.bp = start;
-      this.dataEnd = end;
-
-      this.chigh = data[start];
-      this.clow = 0;
-
-      this.byteIn();
-
-      this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F);
-      this.clow = (this.clow << 7) & 0xFFFF;
-      this.ct -= 7;
-      this.a = 0x8000;
-    }
-
-    ArithmeticDecoder.prototype = {
-      byteIn: function ArithmeticDecoder_byteIn() {
-        var data = this.data;
-        var bp = this.bp;
-        if (data[bp] == 0xFF) {
-          var b1 = data[bp + 1];
-          if (b1 > 0x8F) {
-            this.clow += 0xFF00;
-            this.ct = 8;
-          } else {
-            bp++;
-            this.clow += (data[bp] << 9);
-            this.ct = 7;
-            this.bp = bp;
-          }
-        } else {
-          bp++;
-          this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00;
-          this.ct = 8;
-          this.bp = bp;
-        }
-        if (this.clow > 0xFFFF) {
-          this.chigh += (this.clow >> 16);
-          this.clow &= 0xFFFF;
-        }
-      },
-      readBit: function ArithmeticDecoder_readBit(cx) {
-        var qeIcx = QeTable[cx.index].qe;
-        this.a -= qeIcx;
-
-        if (this.chigh < qeIcx) {
-          var d = this.exchangeLps(cx);
-          this.renormD();
-          return d;
-        } else {
-          this.chigh -= qeIcx;
-          if ((this.a & 0x8000) === 0) {
-            var d = this.exchangeMps(cx);
-            this.renormD();
-            return d;
-          } else {
-            return cx.mps;
-          }
-        }
-      },
-      renormD: function ArithmeticDecoder_renormD() {
-        do {
-          if (this.ct === 0) {
-            this.byteIn();
-          }
-
-          this.a <<= 1;
-          this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1);
-          this.clow = (this.clow << 1) & 0xFFFF;
-          this.ct--;
-        } while ((this.a & 0x8000) === 0);
-      },
-      exchangeMps: function ArithmeticDecoder_exchangeMps(cx) {
-        var d;
-        var qeTableIcx = QeTable[cx.index];
-        if (this.a < qeTableIcx.qe) {
-          d = 1 - cx.mps;
-
-          if (qeTableIcx.switchFlag == 1) {
-            cx.mps = 1 - cx.mps;
-          }
-          cx.index = qeTableIcx.nlps;
-        } else {
-          d = cx.mps;
-          cx.index = qeTableIcx.nmps;
-        }
-        return d;
-      },
-      exchangeLps: function ArithmeticDecoder_exchangeLps(cx) {
-        var d;
-        var qeTableIcx = QeTable[cx.index];
-        if (this.a < qeTableIcx.qe) {
-          this.a = qeTableIcx.qe;
-          d = cx.mps;
-          cx.index = qeTableIcx.nmps;
-        } else {
-          this.a = qeTableIcx.qe;
-          d = 1 - cx.mps;
-
-          if (qeTableIcx.switchFlag == 1) {
-            cx.mps = 1 - cx.mps;
-          }
-          cx.index = qeTableIcx.nlps;
-        }
-        return d;
-      }
-    };
-
-    return ArithmeticDecoder;
-  })();
-
   // Section D. Coefficient bit modeling
   var BitModel = (function BitModelClosure() {
+    var UNIFORM_CONTEXT = 17;
+    var RUNLENGTH_CONTEXT = 18;
     // Table D-1
     // The index is binary presentation: 0dddvvhh, ddd - sum of Di (0..4),
     // vv - sum of Vi (0..2), and hh - sum of Hi (0..2)
     var LLAndLHContextsLabel = new Uint8Array([
       0, 5, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 1, 6, 8, 0, 3, 7, 8, 0, 4,
       7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6,
       8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8
     ]);
@@ -36813,48 +37092,51 @@ var JpxImage = (function JpxImageClosure
       {contextLabel: 12, xorBit: 1},
       {contextLabel: 13, xorBit: 1}
     ];
 
     function BitModel(width, height, subband, zeroBitPlanes) {
       this.width = width;
       this.height = height;
 
-      this.contextLabelTable = subband == 'HH' ? HHContextLabel :
-        subband == 'HL' ? HLContextLabel : LLAndLHContextsLabel;
+      this.contextLabelTable = (subband == 'HH' ? HHContextLabel :
+        (subband == 'HL' ? HLContextLabel : LLAndLHContextsLabel));
 
       var coefficientCount = width * height;
 
       // coefficients outside the encoding region treated as insignificant
       // add border state cells for significanceState
       this.neighborsSignificance = new Uint8Array(coefficientCount);
       this.coefficentsSign = new Uint8Array(coefficientCount);
       this.coefficentsMagnitude = new Uint32Array(coefficientCount);
       this.processingFlags = new Uint8Array(coefficientCount);
 
       var bitsDecoded = new Uint8Array(this.width * this.height);
-      for (var i = 0, ii = bitsDecoded.length; i < ii; i++)
+      for (var i = 0, ii = bitsDecoded.length; i < ii; i++) {
         bitsDecoded[i] = zeroBitPlanes;
+      }
       this.bitsDecoded = bitsDecoded;
 
       this.reset();
     }
 
     BitModel.prototype = {
       setDecoder: function BitModel_setDecoder(decoder) {
         this.decoder = decoder;
       },
       reset: function BitModel_reset() {
-        this.uniformContext = {index: 46, mps: 0};
-        this.runLengthContext = {index: 3, mps: 0};
-        this.contexts = [];
-        this.contexts.push({index: 4, mps: 0});
-        for (var i = 1; i <= 16; i++) {
-          this.contexts.push({index: 0, mps: 0});
-        }
+        // We have 17 contexts that are accessed via context labels, 
+        // plus the uniform and runlength context.
+        this.contexts = new Int8Array(19);
+
+        // Contexts are packed into 1 byte:
+        // highest 7 bits carry the index, lowest bit carries mps
+        this.contexts[0] = (4 << 1) | 0;
+        this.contexts[UNIFORM_CONTEXT] = (46 << 1) | 0;
+        this.contexts[RUNLENGTH_CONTEXT] = (3 << 1) | 0;
       },
       setNeighborsSignificance:
         function BitModel_setNeighborsSignificance(row, column) {
         var neighborsSignificance = this.neighborsSignificance;
         var width = this.width, height = this.height;
         var index = row * width + column;
         if (row > 0) {
           if (column > 0) {
@@ -36906,22 +37188,23 @@ var JpxImage = (function JpxImageClosure
           for (var j = 0; j < width; j++) {
             var index = i0 * width + j;
             for (var i1 = 0; i1 < 4; i1++, index += width) {
               var i = i0 + i1;
               if (i >= height) {
                 break;
               }
 
-              if (coefficentsMagnitude[index] || !neighborsSignificance[index])
+              if (coefficentsMagnitude[index] ||
+                  !neighborsSignificance[index]) {
                 continue;
+              }
 
               var contextLabel = labels[neighborsSignificance[index]];
-              var cx = contexts[contextLabel];
-              var decision = decoder.readBit(cx);
+              var decision = decoder.readBit(contexts, contextLabel);
               if (decision) {
                 var sign = this.decodeSignBit(i, j);
                 coefficentsSign[index] = sign;
                 coefficentsMagnitude[index] = 1;
                 this.setNeighborsSignificance(i, j);
                 processingFlags[index] |= firstMagnitudeBitMask;
               }
               bitsDecoded[index]++;
@@ -36944,18 +37227,17 @@ var JpxImage = (function JpxImageClosure
           row > 0 && coefficentsMagnitude[index - width],
           coefficentsSign[index - width],
           row + 1 < height && coefficentsMagnitude[index + width],
           coefficentsSign[index + width]);
 
         var contextLabelAndXor = SignContextLabels[
           3 * (1 - horizontalContribution) + (1 - verticalContribution)];
         var contextLabel = contextLabelAndXor.contextLabel;
-        var cx = this.contexts[contextLabel];
-        var decoded = this.decoder.readBit(cx);
+        var decoded = this.decoder.readBit(this.contexts, contextLabel);
         return decoded ^ contextLabelAndXor.xorBit;
       },
       runMagnitudeRefinementPass:
         function BitModel_runMagnitudeRefinementPass() {
         var decoder = this.decoder;
         var width = this.width, height = this.height;
         var coefficentsMagnitude = this.coefficentsMagnitude;
         var neighborsSignificance = this.neighborsSignificance;
@@ -36975,28 +37257,26 @@ var JpxImage = (function JpxImageClosure
 
               // significant but not those that have just become
               if (!coefficentsMagnitude[index] ||
                 (processingFlags[index] & processedMask) !== 0) {
                 continue;
               }
 
               var contextLabel = 16;
-              if ((processingFlags[index] &
-                firstMagnitudeBitMask) !== 0) {
+              if ((processingFlags[index] & firstMagnitudeBitMask) !== 0) {
                 processingFlags[i * width + j] ^= firstMagnitudeBitMask;
                 // first refinement
                 var significance = neighborsSignificance[index];
                 var sumOfSignificance = (significance & 3) +
                   ((significance >> 2) & 3) + ((significance >> 4) & 7);
                 contextLabel = sumOfSignificance >= 1 ? 15 : 14;
               }
 
-              var cx = contexts[contextLabel];
-              var bit = decoder.readBit(cx);
+              var bit = decoder.readBit(contexts, contextLabel);
               coefficentsMagnitude[index] =
                 (coefficentsMagnitude[index] << 1) | bit;
               bitsDecoded[index]++;
               processingFlags[index] |= processedMask;
             }
           }
         }
       },
@@ -37016,39 +37296,39 @@ var JpxImage = (function JpxImageClosure
         var oneRowDown = width;
         var twoRowsDown = width * 2;
         var threeRowsDown = width * 3;
         for (var i0 = 0; i0 < height; i0 += 4) {
           for (var j = 0; j < width; j++) {
             var index0 = i0 * width + j;
             // using the property: labels[neighborsSignificance[index]] == 0
             // when neighborsSignificance[index] == 0
-            var allEmpty = i0 + 3 < height &&
+            var allEmpty = (i0 + 3 < height &&
               processingFlags[index0] === 0 &&
               processingFlags[index0 + oneRowDown] === 0 &&
               processingFlags[index0 + twoRowsDown] === 0 &&
               processingFlags[index0 + threeRowsDown] === 0 &&
               neighborsSignificance[index0] === 0 &&
               neighborsSignificance[index0 + oneRowDown] === 0 &&
               neighborsSignificance[index0 + twoRowsDown] === 0 &&
-              neighborsSignificance[index0 + threeRowsDown] === 0;
+              neighborsSignificance[index0 + threeRowsDown] === 0);
             var i1 = 0, index = index0;
-            var cx, i;
+            var i;
             if (allEmpty) {
-              cx = this.runLengthContext;
-              var hasSignificantCoefficent = decoder.readBit(cx);
+              var hasSignificantCoefficent =
+                decoder.readBit(contexts, RUNLENGTH_CONTEXT);
               if (!hasSignificantCoefficent) {
                 bitsDecoded[index0]++;
                 bitsDecoded[index0 + oneRowDown]++;
                 bitsDecoded[index0 + twoRowsDown]++;
                 bitsDecoded[index0 + threeRowsDown]++;
                 continue; // next column
               }
-              cx = this.uniformContext;
-              i1 = (decoder.readBit(cx) << 1) | decoder.readBit(cx);
+              i1 = (decoder.readBit(contexts, UNIFORM_CONTEXT) << 1) |
+                    decoder.readBit(contexts, UNIFORM_CONTEXT);
               i = i0 + i1;
               index += i1 * width;
 
               var sign = this.decodeSignBit(i, j);
               coefficentsSign[index] = sign;
               coefficentsMagnitude[index] = 1;
               this.setNeighborsSignificance(i, j);
               processingFlags[index] |= firstMagnitudeBitMask;
@@ -37067,69 +37347,70 @@ var JpxImage = (function JpxImageClosure
               }
 
               if (coefficentsMagnitude[index] ||
                 (processingFlags[index] & processedMask) !== 0) {
                 continue;
               }
 
               var contextLabel = labels[neighborsSignificance[index]];
-              cx = contexts[contextLabel];
-              var decision = decoder.readBit(cx);
+              var decision = decoder.readBit(contexts, contextLabel);
               if (decision == 1) {
                 var sign = this.decodeSignBit(i, j);
                 coefficentsSign[index] = sign;
                 coefficentsMagnitude[index] = 1;
                 this.setNeighborsSignificance(i, j);
                 processingFlags[index] |= firstMagnitudeBitMask;
               }
               bitsDecoded[index]++;
             }
           }
         }
       },
       checkSegmentationSymbol: function BitModel_checkSegmentationSymbol() {
         var decoder = this.decoder;
-        var cx = this.uniformContext;
-        var symbol = (decoder.readBit(cx) << 3) | (decoder.readBit(cx) << 2) |
-                     (decoder.readBit(cx) << 1) | decoder.readBit(cx);
+        var contexts = this.contexts;
+        var symbol = (decoder.readBit(contexts, UNIFORM_CONTEXT) << 3) |
+                     (decoder.readBit(contexts, UNIFORM_CONTEXT) << 2) |
+                     (decoder.readBit(contexts, UNIFORM_CONTEXT) << 1) |
+                      decoder.readBit(contexts, UNIFORM_CONTEXT);
         if (symbol != 0xA) {
           throw 'Invalid segmentation symbol';
         }
       }
     };
 
     return BitModel;
   })();
 
-  // Section F, Discrete wavelet transofrmation
+  // Section F, Discrete wavelet transformation
   var Transform = (function TransformClosure() {
-    function Transform() {
-    }
+    function Transform() {}
+
     Transform.prototype.calculate =
       function transformCalculate(subbands, u0, v0) {
       var ll = subbands[0];
       for (var i = 1, ii = subbands.length, j = 1; i < ii; i += 3, j++) {
         ll = this.iterate(ll, subbands[i], subbands[i + 1],
                           subbands[i + 2], u0, v0);
       }
       return ll;
     };
     Transform.prototype.extend = function extend(buffer, offset, size) {
-        // Section F.3.7 extending... using max extension of 4
-        var i1 = offset - 1, j1 = offset + 1;
-        var i2 = offset + size - 2, j2 = offset + size;
-        buffer[i1--] = buffer[j1++];
-        buffer[j2++] = buffer[i2--];
-        buffer[i1--] = buffer[j1++];
-        buffer[j2++] = buffer[i2--];
-        buffer[i1--] = buffer[j1++];
-        buffer[j2++] = buffer[i2--];
-        buffer[i1] = buffer[j1];
-        buffer[j2] = buffer[i2];
+      // Section F.3.7 extending... using max extension of 4
+      var i1 = offset - 1, j1 = offset + 1;
+      var i2 = offset + size - 2, j2 = offset + size;
+      buffer[i1--] = buffer[j1++];
+      buffer[j2++] = buffer[i2--];
+      buffer[i1--] = buffer[j1++];
+      buffer[j2++] = buffer[i2--];
+      buffer[i1--] = buffer[j1++];
+      buffer[j2++] = buffer[i2--];
+      buffer[i1] = buffer[j1];
+      buffer[j2] = buffer[i2];
     };
     Transform.prototype.iterate = function Transform_iterate(ll, hl, lh, hh,
                                                             u0, v0) {
       var llWidth = ll.width, llHeight = ll.height, llItems = ll.items;
       var hlWidth = hl.width, hlHeight = hl.height, hlItems = hl.items;
       var lhWidth = lh.width, lhHeight = lh.height, lhItems = lh.items;
       var hhWidth = hh.width, hhHeight = hh.height, hhItems = hh.items;
 
@@ -37261,43 +37542,49 @@ var JpxImage = (function JpxImageClosure
       var beta = -0.052980118572961;
       var gamma = 0.882911075530934;
       var delta = 0.443506852043971;
       var K = 1.230174104914001;
       var K_ = 1 / K;
 
       // step 1
       var j = offset_ - 2;
-      for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2)
+      for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) {
         x[j] = K * y[j];
+      }
 
       // step 2
       var j = offset_ - 3;
-      for (var n = i0_ - 2, nn = i1_ + 2; n < nn; n++, j += 2)
+      for (var n = i0_ - 2, nn = i1_ + 2; n < nn; n++, j += 2) {
         x[j] = K_ * y[j];
+      }
 
       // step 3
       var j = offset_ - 2;
-      for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2)
+      for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) {
         x[j] -= delta * (x[j - 1] + x[j + 1]);
+      }
 
       // step 4
       var j = offset_ - 1;
-      for (var n = i0_ - 1, nn = i1_ + 1; n < nn; n++, j += 2)
+      for (var n = i0_ - 1, nn = i1_ + 1; n < nn; n++, j += 2) {
         x[j] -= gamma * (x[j - 1] + x[j + 1]);
+      }
 
       // step 5
       var j = offset_;
-      for (var n = i0_, nn = i1_ + 1; n < nn; n++, j += 2)
+      for (var n = i0_, nn = i1_ + 1; n < nn; n++, j += 2) {
         x[j] -= beta * (x[j - 1] + x[j + 1]);
+      }
 
       // step 6
       var j = offset_ + 1;
-      for (var n = i0_, nn = i1_; n < nn; n++, j += 2)
+      for (var n = i0_, nn = i1_; n < nn; n++, j += 2) {
         x[j] -= alpha * (x[j - 1] + x[j + 1]);
+      }
     };
 
     return IrreversibleTransform;
   })();
 
   // Section 3.8.1 Reversible 5-3 filter
   var ReversibleTransform = (function ReversibleTransformClosure() {
     function ReversibleTransform() {
@@ -37306,196 +37593,42 @@ var JpxImage = (function JpxImageClosure
 
     ReversibleTransform.prototype = Object.create(Transform.prototype);
     ReversibleTransform.prototype.filter =
       function reversibleTransformFilter(y, offset, length, i0, x) {
       var i0_ = Math.floor(i0 / 2);
       var i1_ = Math.floor((i0 + length) / 2);
       var offset_ = offset - (i0 % 1);
 
-      for (var n = i0_, nn = i1_ + 1, j = offset_; n < nn; n++, j += 2)
+      for (var n = i0_, nn = i1_ + 1, j = offset_; n < nn; n++, j += 2) {
         x[j] = y[j] - Math.floor((y[j - 1] + y[j + 1] + 2) / 4);
-
-      for (var n = i0_, nn = i1_, j = offset_ + 1; n < nn; n++, j += 2)
+      }
+
+      for (var n = i0_, nn = i1_, j = offset_ + 1; n < nn; n++, j += 2) {
         x[j] = y[j] + Math.floor((x[j - 1] + x[j + 1]) / 2);
+      }
     };
 
     return ReversibleTransform;
   })();
 
   return JpxImage;
 })();
 
 
 
 var Jbig2Image = (function Jbig2ImageClosure() {
-
-  // Annex E. Arithmetic Coding
-  var ArithmeticDecoder = (function ArithmeticDecoderClosure() {
-    var QeTable = [
-      {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1},
-      {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0},
-      {qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0},
-      {qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0},
-      {qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0},
-      {qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0},
-      {qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1},
-      {qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0},
-      {qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0},
-      {qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0},
-      {qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0},
-      {qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0},
-      {qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0},
-      {qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0},
-      {qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1},
-      {qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0},
-      {qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0},
-      {qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0},
-      {qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0},
-      {qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0},
-      {qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0},
-      {qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0},
-      {qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0},
-      {qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0},
-      {qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0},
-      {qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0},
-      {qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0},
-      {qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0},
-      {qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0},
-      {qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0},
-      {qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0},
-      {qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0},
-      {qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0},
-      {qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0},
-      {qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0},
-      {qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0},
-      {qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0},
-      {qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0},
-      {qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0},
-      {qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0},
-      {qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0},
-      {qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0},
-      {qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0},
-      {qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0},
-      {qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0},
-      {qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0},
-      {qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0}
-    ];
-
-    function ArithmeticDecoder(data, start, end) {
-      this.data = data;
-      this.bp = start;
-      this.dataEnd = end;
-
-      this.chigh = data[start];
-      this.clow = 0;
-
-      this.byteIn();
-
-      this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F);
-      this.clow = (this.clow << 7) & 0xFFFF;
-      this.ct -= 7;
-      this.a = 0x8000;
-    }
-
-    ArithmeticDecoder.prototype = {
-      byteIn: function ArithmeticDecoder_byteIn() {
-        var data = this.data;
-        var bp = this.bp;
-        if (data[bp] == 0xFF) {
-          var b1 = data[bp + 1];
-          if (b1 > 0x8F) {
-            this.clow += 0xFF00;
-            this.ct = 8;
-          } else {
-            bp++;
-            this.clow += (data[bp] << 9);
-            this.ct = 7;
-            this.bp = bp;
-          }
-        } else {
-          bp++;
-          this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00;
-          this.ct = 8;
-          this.bp = bp;
-        }
-        if (this.clow > 0xFFFF) {
-          this.chigh += (this.clow >> 16);
-          this.clow &= 0xFFFF;
-        }
-      },
-      readBit: function ArithmeticDecoder_readBit(contexts, pos) {
-        // contexts are packed into 1 byte:
-        // highest 7 bits carry cx.index, lowest bit carries cx.mps
-        var cx_index = contexts[pos] >> 1, cx_mps = contexts[pos] & 1;
-        var qeTableIcx = QeTable[cx_index];
-        var qeIcx = qeTableIcx.qe;
-        var nmpsIcx = qeTableIcx.nmps;
-        var nlpsIcx = qeTableIcx.nlps;
-        var switchIcx = qeTableIcx.switchFlag;
-        var d;
-        this.a -= qeIcx;
-
-        if (this.chigh < qeIcx) {
-          // exchangeLps
-          if (this.a < qeIcx) {
-            this.a = qeIcx;
-            d = cx_mps;
-            cx_index = nmpsIcx;
-          } else {
-            this.a = qeIcx;
-            d = 1 - cx_mps;
-            if (switchIcx) {
-              cx_mps = d;
-            }
-            cx_index = nlpsIcx;
-          }
-        } else {
-          this.chigh -= qeIcx;
-          if ((this.a & 0x8000) !== 0) {
-            return cx_mps;
-          }
-          // exchangeMps
-          if (this.a < qeIcx) {
-            d = 1 - cx_mps;
-            if (switchIcx) {
-              cx_mps = d;
-            }
-            cx_index = nlpsIcx;
-          } else {
-            d = cx_mps;
-            cx_index = nmpsIcx;
-          }
-        }
-        // renormD;
-        do {
-          if (this.ct === 0)
-            this.byteIn();
-
-          this.a <<= 1;
-          this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1);
-          this.clow = (this.clow << 1) & 0xFFFF;
-          this.ct--;
-        } while ((this.a & 0x8000) === 0);
-
-        contexts[pos] = cx_index << 1 | cx_mps;
-        return d;
-      }
-    };
-
-    return ArithmeticDecoder;
-  })();
-
   // Utility data structures
   function ContextCache() {}
 
   ContextCache.prototype = {
     getContexts: function(id) {
-      if (id in this)
+      if (id in this) {
         return this[id];
+      }
       return (this[id] = new Int8Array(1<<16));
     }
   };
 
   function DecodingContext(data, start, end) {
     this.data = data;
     this.start = start;
     this.end = end;
@@ -37517,77 +37650,88 @@ var Jbig2Image = (function Jbig2ImageClo
   function decodeInteger(contextCache, procedure, decoder) {
     var contexts = contextCache.getContexts(procedure);
 
     var prev = 1;
     var state = 1, v = 0, s;
     var toRead = 32, offset = 4436; // defaults for state 7
     while (state) {
       var bit = decoder.readBit(contexts, prev);
-      prev = prev < 256 ? (prev << 1) | bit :
-        (((prev << 1) | bit) & 511) | 256;
+      prev = (prev < 256 ? (prev << 1) | bit :
+              (((prev << 1) | bit) & 511) | 256);
       switch (state) {
         case 1:
           s = !!bit;
           break;
         case 2:
-          if (bit) break;
+          if (bit) {
+            break;
+          }
           state = 7;
           toRead = 2;
           offset = 0;
           break;
         case 3:
-          if (bit) break;
+          if (bit) {
+            break;
+          }
           state = 7;
           toRead = 4;
           offset = 4;
           break;
         case 4:
-          if (bit) break;
+          if (bit) {
+            break;
+          }
           state = 7;
           toRead = 6;
           offset = 20;
           break;
         case 5:
-          if (bit) break;
+          if (bit) {
+            break;
+          }
           state = 7;
           toRead = 8;
           offset = 84;
           break;
         case 6:
-          if (bit) break;
+          if (bit) {
+            break;
+          }
           state = 7;
           toRead = 12;
           offset = 340;
           break;
         default:
           v = v * 2 + bit;
-          if (--toRead === 0)
+          if (--toRead === 0) {
             state = 0;
+          }
           continue;
       }
       state++;
     }
     v += offset;
-    return !s ? v : v > 0 ? -v : null;
+    return (!s ? v : (v > 0 ? -v : null));
   }
 
   // A.3 The IAID decoding procedure
   function decodeIAID(contextCache, decoder, codeLength) {
     var contexts = contextCache.getContexts('IAID');
 
     var prev = 1;
     for (var i = 0; i < codeLength; i++) {
       var bit = decoder.readBit(contexts, prev);
       prev = (prev * 2) + bit;
     }
-    if (codeLength < 31)
+    if (codeLength < 31) {
       return prev & ((1 << codeLength) - 1);
-    else
-      return prev - Math.pow(2, codeLength);
+    }
+    return prev - Math.pow(2, codeLength);
   }
 
   // 7.3 Segment types
   var SegmentTypes = [
     'SymbolDictionary', null, null, null, 'IntermediateTextRegion', null,
     'ImmediateTextRegion', 'ImmediateLosslessTextRegion', null, null, null,
     null, null, null, null, null, 'patternDictionary', null, null, null,
     'IntermediateHalftoneRegion', null, 'ImmediateHalftoneRegion',
@@ -37666,18 +37810,19 @@ var Jbig2Image = (function Jbig2ImageClo
 
   function readInt8(data, start) {
     return (data[start] << 24) >> 24;
   }
 
   // 6.2 Generic Region Decoding Procedure
   function decodeBitmap(mmr, width, height, templateIndex, prediction, skip, at,
                         decodingContext) {
-    if (mmr)
+    if (mmr) {
       error('JBIG2 error: MMR encoding is not supported');
+    }
 
     var useskip = !!skip;
     var template = CodingTemplates[templateIndex].concat(at);
 
     // Sorting is non-standard, and it is not required. But sorting increases
     // the number of template bits that can be reused from the previous
     // contextLabel in the main loop.
     template.sort(function (a, b) {
@@ -37763,28 +37908,31 @@ var Jbig2Image = (function Jbig2ImageClo
     return bitmap;
   }
 
   // 6.3.2 Generic Refinement Region Decoding Procedure
   function decodeRefinement(width, height, templateIndex, referenceBitmap,
                             offsetX, offsetY, prediction, at,
                             decodingContext) {
     var codingTemplate = RefinementTemplates[templateIndex].coding;
-    if (templateIndex === 0)
+    if (templateIndex === 0) {
       codingTemplate = codingTemplate.concat([at[0]]);
+    }
     var codingTemplateLength = codingTemplate.length;
     var codingTemplateX = new Int32Array(codingTemplateLength);
     var codingTemplateY = new Int32Array(codingTemplateLength);
     for (var k = 0; k < codingTemplateLength; k++) {
       codingTemplateX[k] = codingTemplate[k].x;
       codingTemplateY[k] = codingTemplate[k].y;
     }
+
     var referenceTemplate = RefinementTemplates[templateIndex].reference;
-    if (templateIndex === 0)
+    if (templateIndex === 0) {
       referenceTemplate = referenceTemplate.concat([at[1]]);
+    }
     var referenceTemplateLength = referenceTemplate.length;
     var referenceTemplateX = new Int32Array(referenceTemplateLength);
     var referenceTemplateY = new Int32Array(referenceTemplateLength);
     for (var k = 0; k < referenceTemplateLength; k++) {
       referenceTemplateX[k] = referenceTemplate[k].x;
       referenceTemplateY[k] = referenceTemplate[k].y;
     }
     var referenceWidth = referenceBitmap[0].length;
@@ -37800,81 +37948,88 @@ var Jbig2Image = (function Jbig2ImageClo
     for (var i = 0; i < height; i++) {
       if (prediction) {
         var sltp = decoder.readBit(contexts, pseudoPixelContext);
         ltp ^= sltp;
       }
       var row = new Uint8Array(width);
       bitmap.push(row);
       for (var j = 0; j < width; j++) {
-        if (ltp)
+        if (ltp) {
           error('JBIG2 error: prediction is not supported');
+        }
 
         var contextLabel = 0;
         for (var k = 0; k < codingTemplateLength; k++) {
           var i0 = i + codingTemplateY[k], j0 = j + codingTemplateX[k];
-          if (i0 < 0 || j0 < 0 || j0 >= width)
+          if (i0 < 0 || j0 < 0 || j0 >= width) {
             contextLabel <<= 1; // out of bound pixel
-          else
+          } else {
             contextLabel = (contextLabel << 1) | bitmap[i0][j0];
+          }
         }
         for (var k = 0; k < referenceTemplateLength; k++) {
           var i0 = i + referenceTemplateY[k] + offsetY;
           var j0 = j + referenceTemplateX[k] + offsetX;
-          if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth)
+          if (i0 < 0 || i0 >= referenceHeight || j0 < 0 ||
+              j0 >= referenceWidth) {
             contextLabel <<= 1; // out of bound pixel
-          else
+          } else {
             contextLabel = (contextLabel << 1) | referenceBitmap[i0][j0];
+          }
         }
         var pixel = decoder.readBit(contexts, contextLabel);
         row[j] = pixel;
       }
     }
 
     return bitmap;
   }
 
   // 6.5.5 Decoding the symbol dictionary
   function decodeSymbolDictionary(huffman, refinement, symbols,
                                   numberOfNewSymbols, numberOfExportedSymbols,
                                   huffmanTables, templateIndex, at,
                                   refinementTemplateIndex, refinementAt,
                                   decodingContext) {
-    if (huffman)
+    if (huffman) {
       error('JBIG2 error: huffman is not supported');
+    }
 
     var newSymbols = [];
     var currentHeight = 0;
     var symbolCodeLength = log2(symbols.length + numberOfNewSymbols);
 
     var decoder = decodingContext.decoder;
     var contextCache = decodingContext.contextCache;
 
     while (newSymbols.length < numberOfNewSymbols) {
       var deltaHeight = decodeInteger(contextCache, 'IADH', decoder); // 6.5.6
       currentHeight += deltaHeight;
       var currentWidth = 0;
       var totalWidth = 0;
       while (true) {
         var deltaWidth = decodeInteger(contextCache, 'IADW', decoder); // 6.5.7
-        if (deltaWidth === null)
+        if (deltaWidth === null) {
           break; // OOB
+        }
         currentWidth += deltaWidth;
         totalWidth += currentWidth;
         var bitmap;
         if (refinement) {
           // 6.5.8.2 Refinement/aggregate-coded symbol bitmap
           var numberOfInstances = decodeInteger(contextCache, 'IAAI', decoder);
-          if (numberOfInstances > 1)
+          if (numberOfInstances > 1) {
             error('JBIG2 error: number of instances > 1 is not supported');
+          }
           var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength);
           var rdx = decodeInteger(contextCache, 'IARDX', decoder); // 6.4.11.3
           var rdy = decodeInteger(contextCache, 'IARDY', decoder); // 6.4.11.4
-          var symbol = symbolId < symbols.length ? symbols[symbolId] :
-            newSymbols[symbolId - symbols.length];
+          var symbol = (symbolId < symbols.length ? symbols[symbolId] :
+                        newSymbols[symbolId - symbols.length]);
           bitmap = decodeRefinement(currentWidth, currentHeight,
             refinementTemplateIndex, symbol, rdx, rdy, false, refinementAt,
             decodingContext);
         } else {
           // 6.5.8.1 Direct-coded symbol bitmap
           bitmap = decodeBitmap(false, currentWidth, currentHeight,
             templateIndex, false, null, at, decodingContext);
         }
@@ -37882,44 +38037,53 @@ var Jbig2Image = (function Jbig2ImageClo
       }
     }
     // 6.5.10 Exported symbols
     var exportedSymbols = [];
     var flags = [], currentFlag = false;
     var totalSymbolsLength = symbols.length + numberOfNewSymbols;
     while (flags.length < totalSymbolsLength) {
       var runLength = decodeInteger(contextCache, 'IAEX', decoder);
-      while (runLength--)
+      while (runLength--) {
         flags.push(currentFlag);
+      }
       currentFlag = !currentFlag;
     }
-    for (var i = 0, ii = symbols.length; i < ii; i++)
-      if (flags[i]) exportedSymbols.push(symbols[i]);
-    for (var j = 0; j < numberOfNewSymbols; i++, j++)
-      if (flags[i]) exportedSymbols.push(newSymbols[j]);
+    for (var i = 0, ii = symbols.length; i < ii; i++) {
+      if (flags[i]) {
+        exportedSymbols.push(symbols[i]);
+      }
+    }
+    for (var j = 0; j < numberOfNewSymbols; i++, j++) {
+      if (flags[i]) {
+        exportedSymbols.push(newSymbols[j]);
+      }
+    }
     return exportedSymbols;
   }
 
   function decodeTextRegion(huffman, refinement, width, height,
                             defaultPixelValue, numberOfSymbolInstances,
                             stripSize, inputSymbols, symbolCodeLength,
                             transposed, dsOffset, referenceCorner,
                             combinationOperator, huffmanTables,
                             refinementTemplateIndex, refinementAt,
                             decodingContext) {
-    if (huffman)
+    if (huffman) {
       error('JBIG2 error: huffman is not supported');
+    }
 
     // Prepare bitmap
     var bitmap = [];
     for (var i = 0; i < height; i++) {
       var row = new Uint8Array(width);
       if (defaultPixelValue) {
-        for (var j = 0; j < width; j++)
+        for (var j = 0; j < width; j++) {
           row[j] = defaultPixelValue;
+        }
       }
       bitmap.push(row);
     }
 
     var decoder = decodingContext.decoder;
     var contextCache = decodingContext.contextCache;
     var stripT = -decodeInteger(contextCache, 'IADT', decoder); // 6.4.6
     var firstS = 0;
@@ -38005,69 +38169,75 @@ var Jbig2Image = (function Jbig2ImageClo
                 error('JBIG2 error: operator ' + combinationOperator +
                       ' is not supported');
             }
           }
           currentS += symbolWidth - 1;
         }
         i++;
         var deltaS = decodeInteger(contextCache, 'IADS', decoder); // 6.4.8
-        if (deltaS === null)
+        if (deltaS === null) {
           break; // OOB
+        }
         currentS += deltaS + dsOffset;
       } while (true);
     }
     return bitmap;
   }
 
   function readSegmentHeader(data, start) {
     var segmentHeader = {};
     segmentHeader.number = readUint32(data, start);
     var flags = data[start + 4];
     var segmentType = flags & 0x3F;
-    if (!SegmentTypes[segmentType])
+    if (!SegmentTypes[segmentType]) {
       error('JBIG2 error: invalid segment type: ' + segmentType);
+    }
     segmentHeader.type = segmentType;
     segmentHeader.typeName = SegmentTypes[segmentType];
     segmentHeader.deferredNonRetain = !!(flags & 0x80);
+
     var pageAssociationFieldSize = !!(flags & 0x40);
     var referredFlags = data[start + 5];
     var referredToCount = (referredFlags >> 5) & 7;
     var retainBits = [referredFlags & 31];
     var position = start + 6;
     if (referredFlags == 7) {
       referredToCount = readInt32(data, position - 1) & 0x1FFFFFFF;
       position += 3;
       var bytes = (referredToCount + 7) >> 3;
       retainBits[0] = data[position++];
       while (--bytes > 0) {
         retainBits.push(data[position++]);
       }
-    } else if (referredFlags == 5 || referredFlags == 6)
+    } else if (referredFlags == 5 || referredFlags == 6) {
       error('JBIG2 error: invalid referred-to flags');
+    }
+
     segmentHeader.retainBits = retainBits;
-    var referredToSegmentNumberSize = segmentHeader.number <= 256 ? 1 :
-      segmentHeader.number <= 65536 ? 2 : 4;
+    var referredToSegmentNumberSize = (segmentHeader.number <= 256 ? 1 :
+      (segmentHeader.number <= 65536 ? 2 : 4));
     var referredTo = [];
     for (var i = 0; i < referredToCount; i++) {
-      var number = referredToSegmentNumberSize == 1 ? data[position] :
-        referredToSegmentNumberSize == 2 ? readUint16(data, position) :
-        readUint32(data, position);
+      var number = (referredToSegmentNumberSize == 1 ? data[position] :
+        (referredToSegmentNumberSize == 2 ? readUint16(data, position) :
+        readUint32(data, position)));
       referredTo.push(number);
       position += referredToSegmentNumberSize;
     }
     segmentHeader.referredTo = referredTo;
-    if (!pageAssociationFieldSize)
+    if (!pageAssociationFieldSize) {
       segmentHeader.pageAssociation = data[position++];
-    else {
+    } else {
       segmentHeader.pageAssociation = readUint32(data, position);
       position += 4;
     }
     segmentHeader.length = readUint32(data, position);
     position += 4;
+
     if (segmentHeader.length == 0xFFFFFFFF) {
       // 7.2.7 Segment data length, unknown segment length
       if (segmentType === 38) { // ImmediateGenericRegion
         var genericRegionInfo = readRegionSegmentInformation(data, position);
         var genericRegionSegmentFlags = data[position +
           RegionSegmentInformationFieldLength];
         var genericRegionMmr = !!(genericRegionSegmentFlags & 1);
         // searching for the segment end
@@ -38113,18 +38283,19 @@ var Jbig2Image = (function Jbig2ImageClo
         data: data
       };
       if (!header.randomAccess) {
         segment.start = position;
         position += segmentHeader.length;
         segment.end = position;
       }
       segments.push(segment);
-      if (segmentHeader.type == 51)
+      if (segmentHeader.type == 51) {
         break; // end of file is found
+      }
     }
     if (header.randomAccess) {
       for (var i = 0, ii = segments.length; i < ii; i++) {
         segments[i].start = position;
         position += segments[i].header.length;
         segments[i].end = position;
       }
     }
@@ -38232,18 +38403,19 @@ var Jbig2Image = (function Jbig2ImageClo
             });
             position += 2;
           }
           textRegion.refinementAt = at;
         }
         textRegion.numberOfSymbolInstances = readUint32(data, position);
         position += 4;
         // TODO 7.4.3.1.7 Symbol ID Huffman table decoding
-        if (textRegion.huffman)
+        if (textRegion.huffman) {
           error('JBIG2 error: huffman is not supported');
+        }
         args = [textRegion, header.referredTo, data, position, end];
         break;
       case 38: // ImmediateGenericRegion
       case 39: // ImmediateLosslessGenericRegion
         var genericRegion = {};
         genericRegion.info = readRegionSegmentInformation(data, position);
         position += RegionSegmentInformationFieldLength;
         var genericRegionSegmentFlags = data[position++];
@@ -38266,18 +38438,19 @@ var Jbig2Image = (function Jbig2ImageClo
         break;
       case 48: // PageInformation
         var pageInfo = {
           width: readUint32(data, position),
           height: readUint32(data, position + 4),
           resolutionX: readUint32(data, position + 8),
           resolutionY: readUint32(data, position + 12)
         };
-        if (pageInfo.height == 0xFFFFFFFF)
+        if (pageInfo.height == 0xFFFFFFFF) {
           delete pageInfo.height;
+        }
         var pageSegmentFlags = data[position + 16];
         var pageStripingInformatiom = readUint16(data, position + 17);
         pageInfo.lossless = !!(pageSegmentFlags & 1);
         pageInfo.refinement = !!(pageSegmentFlags & 2);
         pageInfo.defaultPixelValue = (pageSegmentFlags >> 2) & 1;
         pageInfo.combinationOperator = (pageSegmentFlags >> 3) & 3;
         pageInfo.requiresBuffer = !!(pageSegmentFlags & 32);
         pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64);
@@ -38292,32 +38465,35 @@ var Jbig2Image = (function Jbig2ImageClo
       case 62: // 7.4.15 defines 2 extension types which
                // are comments and can be ignored.
         break;
       default:
         error('JBIG2 error: segment type ' + header.typeName + '(' +
               header.type + ') is not implemented');
     }
     var callbackName = 'on' + header.typeName;
-    if (callbackName in visitor)
+    if (callbackName in visitor) {
       visitor[callbackName].apply(visitor, args);
+    }
   }
 
   function processSegments(segments, visitor) {
-    for (var i = 0, ii = segments.length; i < ii; i++)
+    for (var i = 0, ii = segments.length; i < ii; i++) {
       processSegment(segments[i], visitor);
+    }
   }
 
   function parseJbig2(data, start, end) {
     var position = start;
     if (data[position] != 0x97 || data[position + 1] != 0x4A ||
         data[position + 2] != 0x42 || data[position + 3] != 0x32 ||
         data[position + 4] != 0x0D || data[position + 5] != 0x0A ||
-        data[position + 6] != 0x1A || data[position + 7] != 0x0A)
+        data[position + 6] != 0x1A || data[position + 7] != 0x0A) {
       error('JBIG2 error: invalid header');
+    }
     var header = {};
     position += 8;
     var flags = data[position++];
     header.randomAccess = !(flags & 1);
     if (!(flags & 2)) {
       header.numberOfPages = readUint32(data, position);
       position += 4;
     }
@@ -38404,27 +38580,30 @@ var Jbig2Image = (function Jbig2ImageClo
       this.onImmediateGenericRegion.apply(this, arguments);
     },
     onSymbolDictionary:
       function SimpleSegmentVisitor_onSymbolDictionary(dictionary,
                                                        currentSegment,
                                                        referredSegments,
                                                        data, start, end) {
       var huffmanTables;
-      if (dictionary.huffman)
+      if (dictionary.huffman) {
         error('JBIG2 error: huffman is not supported');
+      }
 
       // Combines exported symbols from all referred segments
       var symbols = this.symbols;
-      if (!symbols)
+      if (!symbols) {
         this.symbols = symbols = {};
+      }
 
       var inputSymbols = [];
-      for (var i = 0, ii = referredSegments.length; i < ii; i++)
+      for (var i = 0, ii = referredSegments.length; i < ii; i++) {
         inputSymbols = inputSymbols.concat(symbols[referredSegments[i]]);
+      }
 
       var decodingContext = new DecodingContext(data, start, end);
       symbols[currentSegment] = decodeSymbolDictionary(dictionary.huffman,
         dictionary.refinement, inputSymbols, dictionary.numberOfNewSymbols,
         dictionary.numberOfExportedSymbols, huffmanTables,
         dictionary.template, dictionary.at,
         dictionary.refinementTemplate, dictionary.refinementAt,
         decodingContext);
@@ -38434,32 +38613,33 @@ var Jbig2Image = (function Jbig2ImageClo
                                                           referredSegments,
                                                           data, start, end) {
       var regionInfo = region.info;
       var huffmanTables;
 
       // Combines exported symbols from all referred segments
       var symbols = this.symbols;
       var inputSymbols = [];
-      for (var i = 0, ii = referredSegments.length; i < ii; i++)
+      for (var i = 0, ii = referredSegments.length; i < ii; i++) {
         inputSymbols = inputSymbols.concat(symbols[referredSegments[i]]);
+      }
       var symbolCodeLength = log2(inputSymbols.length);
 
       var decodingContext = new DecodingContext(data, start, end);
       var bitmap = decodeTextRegion(region.huffman, region.refinement,
         regionInfo.width, regionInfo.height, region.defaultPixelValue,
         region.numberOfSymbolInstances, region.stripSize, inputSymbols,
         symbolCodeLength, region.transposed, region.dsOffset,
         region.referenceCorner, region.combinationOperator, huffmanTables,
         region.refinementTemplate, region.refinementAt, decodingContext);
       this.drawBitmap(regionInfo, bitmap);
     },
     onImmediateLosslessTextRegion:
       function SimpleSegmentVisitor_onImmediateLosslessTextRegion() {
-        this.onImmediateTextRegion.apply(this, arguments);
+      this.onImmediateTextRegion.apply(this, arguments);
     }
   };
 
   function Jbig2Image() {}
 
   Jbig2Image.prototype = {
     parseChunks: function Jbig2Image_parseChunks(chunks) {
       return parseJbig2Chunks(chunks);
@@ -38526,18 +38706,19 @@ var bidi = PDFJS.bidi = (function bidiCl
 
   function isEven(i) {
     return (i & 1) === 0;
   }
 
   function findUnequal(arr, start, value) {
     var j;
     for (var j = start, jj = arr.length; j < jj; ++j) {
-      if (arr[j] != value)
+      if (arr[j] != value) {
         return j;
+      }
     }
     return j;
   }
 
   function setValues(arr, start, end, value) {
     for (var j = start; j < end; ++j) {
       arr[j] = value;
     }
@@ -38588,236 +38769,240 @@ var bidi = PDFJS.bidi = (function bidiCl
         return '\u00AB';
       default:
         return c;
     }
   }
 
   function BidiResult(str, isLTR, vertical) {
     this.str = str;
-    this.dir = vertical ? 'ttb' : isLTR ? 'ltr' : 'rtl';
+    this.dir = (vertical ? 'ttb' : (isLTR ? 'ltr' : 'rtl'));
   }
 
   function bidi(str, startLevel, vertical) {
     var isLTR = true;
     var strLength = str.length;
-    if (strLength === 0 || vertical)
+    if (strLength === 0 || vertical) {
       return new BidiResult(str, isLTR, vertical);
-
-    // get types, fill arrays
-
+    }
+
+    // Get types and fill arrays
     var chars = [];
     var types = [];
     var numBidi = 0;
 
     for (var i = 0; i < strLength; ++i) {
       chars[i] = str.charAt(i);
 
       var charCode = str.charCodeAt(i);
       var charType = 'L';
-      if (charCode <= 0x00ff)
+      if (charCode <= 0x00ff) {
         charType = baseTypes[charCode];
-      else if (0x0590 <= charCode && charCode <= 0x05f4)
+      } else if (0x0590 <= charCode && charCode <= 0x05f4) {
         charType = 'R';
-      else if (0x0600 <= charCode && charCode <= 0x06ff)
+      } else if (0x0600 <= charCode && charCode <= 0x06ff) {
         charType = arabicTypes[charCode & 0xff];
-      else if (0x0700 <= charCode && charCode <= 0x08AC)
+      } else if (0x0700 <= charCode && charCode <= 0x08AC) {
         charType = 'AL';
-
-      if (charType == 'R' || charType == 'AL' || charType == 'AN')
+      }
+      if (charType == 'R' || charType == 'AL' || charType == 'AN') {
         numBidi++;
-
+      }
       types[i] = charType;
     }
 
-    // detect the bidi method
-    //  if there are no rtl characters then no bidi needed
-    //  if less than 30% chars are rtl then string is primarily ltr
-    //  if more than 30% chars are rtl then string is primarily rtl
+    // Detect the bidi method
+    // - If there are no rtl characters then no bidi needed
+    // - If less than 30% chars are rtl then string is primarily ltr
+    // - If more than 30% chars are rtl then string is primarily rtl
     if (numBidi === 0) {
       isLTR = true;
       return new BidiResult(str, isLTR);
     }
 
     if (startLevel == -1) {
       if ((strLength / numBidi) < 0.3) {
         isLTR = true;
         startLevel = 0;
       } else {
         isLTR = false;
         startLevel = 1;
       }
     }
 
     var levels = [];
-
     for (var i = 0; i < strLength; ++i) {
       levels[i] = startLevel;
     }
 
     /*
      X1-X10: skip most of this, since we are NOT doing the embeddings.
      */
-
-    var e = isOdd(startLevel) ? 'R' : 'L';
+    var e = (isOdd(startLevel) ? 'R' : 'L');
     var sor = e;
     var eor = sor;
 
     /*
      W1. Examine each non-spacing mark (NSM) in the level run, and change the
      type of the NSM to the type of the previous character. If the NSM is at the
      start of the level run, it will get the type of sor.
      */
-
     var lastType = sor;
     for (var i = 0; i < strLength; ++i) {
-      if (types[i] == 'NSM')
+      if (types[i] == 'NSM') {
         types[i] = lastType;
-      else
+      } else {
         lastType = types[i];
+      }
     }
 
     /*
      W2. Search backwards from each instance of a European number until the
      first strong type (R, L, AL, or sor) is found.  If an AL is found, change
      the type of the European number to Arabic number.
      */
-
     var lastType = sor;
     for (var i = 0; i < strLength; ++i) {
       var t = types[i];
-      if (t == 'EN')
+      if (t == 'EN') {
         types[i] = (lastType == 'AL') ? 'AN' : 'EN';
-      else if (t == 'R' || t == 'L' || t == 'AL')
+      } else if (t == 'R' || t == 'L' || t == 'AL') {
         lastType = t;
+      }
     }
 
     /*
      W3. Change all ALs to R.
      */
-
     for (var i = 0; i < strLength; ++i) {
       var t = types[i];
-      if (t == 'AL')
+      if (t == 'AL') {
         types[i] = 'R';
+      }
     }
 
     /*
      W4. A single European separator between two European numbers changes to a
      European number. A single common separator between two numbers of the same
      type changes to that type:
      */
-
     for (var i = 1; i < strLength - 1; ++i) {
-      if (types[i] == 'ES' && types[i - 1] == 'EN' && types[i + 1] == 'EN')
+      if (types[i] == 'ES' && types[i - 1] == 'EN' && types[i + 1] == 'EN') {
         types[i] = 'EN';
+      }
       if (types[i] == 'CS' && (types[i - 1] == 'EN' || types[i - 1] == 'AN') &&
-          types[i + 1] == types[i - 1])
+          types[i + 1] == types[i - 1]) {
         types[i] = types[i - 1];
+      }
     }
 
     /*
      W5. A sequence of European terminators adjacent to European numbers changes
      to all European numbers:
      */
-
     for (var i = 0; i < strLength; ++i) {
       if (types[i] == 'EN') {
         // do before
         for (var j = i - 1; j >= 0; --j) {
-          if (types[j] != 'ET')
-            break;
+          if (types[j] != 'ET') {
+            break;
+          }
           types[j] = 'EN';
         }
         // do after
         for (var j = i + 1; j < strLength; --j) {
-          if (types[j] != 'ET')
-            break;
+          if (types[j] != 'ET') {
+            break;
+          }
           types[j] = 'EN';
         }
       }
     }
 
     /*
      W6. Otherwise, separators and terminators change to Other Neutral:
      */
-
     for (var i = 0; i < strLength; ++i) {
       var t = types[i];
-      if (t == 'WS' || t == 'ES' || t == 'ET' || t == 'CS')
+      if (t == 'WS' || t == 'ES' || t == 'ET' || t == 'CS') {
         types[i] = 'ON';
+      }
     }
 
     /*
      W7. Search backwards from each instance of a European number until the
      first strong type (R, L, or sor) is found. If an L is found,  then change
      the type of the European number to L.
      */
-
     var lastType = sor;
     for (var i = 0; i < strLength; ++i) {
       var t = types[i];
-      if (t == 'EN')
-        types[i] = (lastType == 'L') ? 'L' : 'EN';
-      else if (t == 'R' || t == 'L')
+      if (t == 'EN') {
+        types[i] = ((lastType == 'L') ? 'L' : 'EN');
+      } else if (t == 'R' || t == 'L') {
         lastType = t;
+      }
     }
 
     /*
      N1. A sequence of neutrals takes the direction of the surrounding strong
      text if the text on both sides has the same direction. European and Arabic
      numbers are treated as though they were R. Start-of-level-run (sor) and
      end-of-level-run (eor) are used at level run boundaries.
      */
-
     for (var i = 0; i < strLength; ++i) {
       if (types[i] == 'ON') {
         var end = findUnequal(types, i + 1, 'ON');
         var before = sor;
-        if (i > 0)
+        if (i > 0) {
           before = types[i - 1];
+        }
+
         var after = eor;
-        if (end + 1 < strLength)
+        if (end + 1 < strLength) {
           after = types[end + 1];
-        if (before != 'L')
+        }
+        if (before != 'L') {
           before = 'R';
-        if (after != 'L')
+        }
+        if (after != 'L') {
           after = 'R';
-        if (before == after)
+        }
+        if (before == after) {
           setValues(types, i, end, before);
+        }
         i = end - 1; // reset to end (-1 so next iteration is ok)
       }
     }
 
     /*
      N2. Any remaining neutrals take the embedding direction.
      */
-
     for (var i = 0; i < strLength; ++i) {
-      if (types[i] == 'ON')
+      if (types[i] == 'ON') {
         types[i] = e;
+      }
     }
 
     /*
      I1. For all characters with an even (left-to-right) embedding direction,
      those of type R go up one level and those of type AN or EN go up two
      levels.
      I2. For all characters with an odd (right-to-left) embedding direction,
      those of type L, EN or AN go up one level.
      */
-
     for (var i = 0; i < strLength; ++i) {
       var t = types[i];
       if (isEven(levels[i])) {
         if (t == 'R') {
           levels[i] += 1;
         } else if (t == 'AN' || t == 'EN') {
           levels[i] += 2;
         }
-      } else { // isOdd, so
+      } else { // isOdd
         if (t == 'L' || t == 'AN' || t == 'EN') {
           levels[i] += 1;
         }
       }
     }
 
     /*
      L1. On each line, reset the embedding level of the following characters to
@@ -38834,29 +39019,29 @@ var bidi = PDFJS.bidi = (function bidiCl
 
     /*
      L2. From the highest level found in the text to the lowest odd level on
      each line, reverse any contiguous sequence of characters that are at that
      level or higher.
      */
 
     // find highest level & lowest odd level
-
     var highestLevel = -1;
     var lowestOddLevel = 99;
     for (var i = 0, ii = levels.length; i < ii; ++i) {
       var level = levels[i];
-      if (highestLevel < level)
+      if (highestLevel < level) {
         highestLevel = level;
-      if (lowestOddLevel > level && isOdd(level))
+      }
+      if (lowestOddLevel > level && isOdd(level)) {
         lowestOddLevel = level;
+      }
     }
 
     // now reverse between those limits
-
     for (var level = highestLevel; level >= lowestOddLevel; --level) {
       // find segments to reverse
       var start = -1;
       for (var i = 0, ii = levels.length; i < ii; ++i) {
         if (levels[i] < level) {
           if (start >= 0) {
             reverseValues(chars, start, i);
             start = -1;
@@ -38883,24 +39068,23 @@ var bidi = PDFJS.bidi = (function bidiCl
      L4. A character that possesses the mirrored property as specified by
      Section 4.7, Mirrored, must be depicted by a mirrored glyph if the resolved
      directionality of that character is R.
      */
 
     // don't mirror as characters are already mirrored in the pdf
 
     // Finally, return string
-
     var result = '';
     for (var i = 0, ii = chars.length; i < ii; ++i) {
       var ch = chars[i];
-      if (ch != '<' && ch != '>')
+      if (ch != '<' && ch != '>') {
         result += ch;
-    }
-
+      }
+    }
     return new BidiResult(result, isLTR);
   }
 
   return bidi;
 })();
 
 
 
--- a/browser/extensions/pdfjs/content/web/debugger.js
+++ b/browser/extensions/pdfjs/content/web/debugger.js
@@ -41,23 +41,27 @@ var FontInspector = (function FontInspec
     var divs = document.querySelectorAll('div[' + fontAttribute + '=' +
                                          fontName + ']');
     for (var i = 0, ii = divs.length; i < ii; ++i) {
       var div = divs[i];
       div.className = show ? 'debuggerShowText' : 'debuggerHideText';
     }
   }
   function textLayerClick(e) {
-    if (!e.target.dataset.fontName || e.target.tagName.toUpperCase() !== 'DIV')
+    if (!e.target.dataset.fontName ||
+        e.target.tagName.toUpperCase() !== 'DIV') {
       return;
+    }
     var fontName = e.target.dataset.fontName;
     var selects = document.getElementsByTagName('input');
     for (var i = 0; i < selects.length; ++i) {
       var select = selects[i];
-      if (select.dataset.fontName != fontName) continue;
+      if (select.dataset.fontName != fontName) {
+        continue;
+      }
       select.checked = !select.checked;
       selectFont(fontName, select.checked);
       select.scrollIntoView();
     }
   }
   return {
     // Properties/functions needed by PDFBug.
     id: 'FontInspector',
@@ -135,18 +139,19 @@ var FontInspector = (function FontInspec
       font.appendChild(download);
       font.appendChild(document.createTextNode(' '));
       font.appendChild(logIt);
       font.appendChild(moreInfo);
       fonts.appendChild(font);
       // Somewhat of a hack, should probably add a hook for when the text layer
       // is done rendering.
       setTimeout(function() {
-        if (this.active)
+        if (this.active) {
           resetSelection();
+        }
       }.bind(this), 2000);
     }
   };
 })();
 
 // Manages all the page steppers.
 var StepperManager = (function StepperManagerClosure() {
   var steppers = [];
@@ -167,18 +172,19 @@ var StepperManager = (function StepperMa
       stepperChooser = document.createElement('select');
       stepperChooser.addEventListener('change', function(event) {
         self.selectStepper(this.value);
       });
       stepperControls.appendChild(stepperChooser);
       stepperDiv = document.createElement('div');
       this.panel.appendChild(stepperControls);
       this.panel.appendChild(stepperDiv);
-      if (sessionStorage.getItem('pdfjsBreakPoints'))
+      if (sessionStorage.getItem('pdfjsBreakPoints')) {
         breakPoints = JSON.parse(sessionStorage.getItem('pdfjsBreakPoints'));
+      }
     },
     enabled: false,
     active: false,
     // Stepper specific functions.
     create: function create(pageIndex) {
       var debug = document.createElement('div');
       debug.id = 'stepper' + pageIndex;
       debug.setAttribute('hidden', true);
@@ -186,29 +192,32 @@ var StepperManager = (function StepperMa
       stepperDiv.appendChild(debug);
       var b = document.createElement('option');
       b.textContent = 'Page ' + (pageIndex + 1);
       b.value = pageIndex;
       stepperChooser.appendChild(b);
       var initBreakPoints = breakPoints[pageIndex] || [];
       var stepper = new Stepper(debug, pageIndex, initBreakPoints);
       steppers.push(stepper);
-      if (steppers.length === 1)
+      if (steppers.length === 1) {
         this.selectStepper(pageIndex, false);
+      }
       return stepper;
     },
     selectStepper: function selectStepper(pageIndex, selectPanel) {
-      if (selectPanel)
+      if (selectPanel) {
         this.manager.selectPanel(1);
+      }
       for (var i = 0; i < steppers.length; ++i) {
         var stepper = steppers[i];
-        if (stepper.pageIndex == pageIndex)
+        if (stepper.pageIndex == pageIndex) {
           stepper.panel.removeAttribute('hidden');
-        else
+        } else {
           stepper.panel.setAttribute('hidden', true);
+        }
       }
       var options = stepperChooser.options;
       for (var i = 0; i < options.length; ++i) {
         var option = options[i];
         option.selected = option.value == pageIndex;
       }
     },
     saveBreakPoints: function saveBreakPoints(pageIndex, bps) {
@@ -218,18 +227,19 @@ var StepperManager = (function StepperMa
   };
 })();
 
 // The stepper for each page's IRQueue.
 var Stepper = (function StepperClosure() {
   // Shorter way to create element and optionally set textContent.
   function c(tag, textContent) {
     var d = document.createElement(tag);
-    if (textContent)
+    if (textContent) {
       d.textContent = textContent;
+    }
     return d;
   }
 
   function glyphsToString(glyphs) {
     var out = '';
     for (var i = 0; i < glyphs.length; i++) {
       if (glyphs[i] === null) {
         out += ' ';
@@ -292,20 +302,21 @@ var Stepper = (function StepperClosure()
 
         var breakCell = c('td');
         var cbox = c('input');
         cbox.type = 'checkbox';
         cbox.className = 'points';
         cbox.checked = checked;
         cbox.onclick = (function(x) {
           return function() {
-            if (this.checked)
+            if (this.checked) {
               self.breakPoints.push(x);
-            else
+            } else {
               self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
+            }
             StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
           };
         })(i);
 
         breakCell.appendChild(cbox);
         line.appendChild(breakCell);
         line.appendChild(c('td', i.toString()));
         var fn = opMap[operatorList.fnArray[i]];
@@ -331,18 +342,19 @@ var Stepper = (function StepperClosure()
         }
         line.appendChild(c('td', fn));
         line.appendChild(c('td', JSON.stringify(decArgs)));
       }
     },
     getNextBreakPoint: function getNextBreakPoint() {
       this.breakPoints.sort(function(a, b) { return a - b; });
       for (var i = 0; i < this.breakPoints.length; i++) {
-        if (this.breakPoints[i] > this.currentIdx)
+        if (this.breakPoints[i] > this.currentIdx) {
           return this.breakPoints[i];
+        }
       }
       return null;
     },
     breakIt: function breakIt(idx, callback) {
       StepperManager.selectStepper(this.pageIndex, true);
       var self = this;
       var dom = document;
       self.currentIdx = idx;
@@ -380,41 +392,45 @@ var Stepper = (function StepperClosure()
     }
   };
   return Stepper;
 })();
 
 var Stats = (function Stats() {
   var stats = [];
   function clear(node) {
-    while (node.hasChildNodes())
+    while (node.hasChildNodes()) {
       node.removeChild(node.lastChild);
+    }
   }
   function getStatIndex(pageNumber) {
-    for (var i = 0, ii = stats.length; i < ii; ++i)
-      if (stats[i].pageNumber === pageNumber)
+    for (var i = 0, ii = stats.length; i < ii; ++i) {
+      if (stats[i].pageNumber === pageNumber) {
         return i;
+      }
+    }
     return false;
   }
   return {
     // Properties/functions needed by PDFBug.
     id: 'Stats',
     name: 'Stats',
     panel: null,
     manager: null,
     init: function init() {
       this.panel.setAttribute('style', 'padding: 5px;');
       PDFJS.enableStats = true;
     },
     enabled: false,
     active: false,
     // Stats specific functions.
     add: function(pageNumber, stat) {
-      if (!stat)
+      if (!stat) {
         return;
+      }
       var statsIndex = getStatIndex(pageNumber);
       if (statsIndex !== false) {
         var b = stats[statsIndex];
         this.panel.removeChild(b.div);
         stats.splice(statsIndex, 1);
       }
       var wrapper = document.createElement('div');
       wrapper.className = 'stats';
@@ -423,18 +439,19 @@ var Stats = (function Stats() {
       title.textContent = 'Page: ' + pageNumber;
       var statsDiv = document.createElement('div');
       statsDiv.textContent = stat.toString();
       wrapper.appendChild(title);
       wrapper.appendChild(statsDiv);
       stats.push({ pageNumber: pageNumber, div: wrapper });
       stats.sort(function(a, b) { return a.pageNumber - b.pageNumber; });
       clear(this.panel);
-      for (var i = 0, ii = stats.length; i < ii; ++i)
+      for (var i = 0, ii = stats.length; i < ii; ++i) {
         this.panel.appendChild(stats[i].div);
+      }
     }
   };
 })();
 
 // Manages all the debugging tools.
 var PDFBug = (function PDFBugClosure() {
   var panelWidth = 300;
   var buttons = [];
@@ -443,22 +460,24 @@ var PDFBug = (function PDFBugClosure() {
   return {
     tools: [
       FontInspector,
       StepperManager,
       Stats
     ],
     enable: function(ids) {
       var all = false, tools = this.tools;
-      if (ids.length === 1 && ids[0] === 'all')
+      if (ids.length === 1 && ids[0] === 'all') {
         all = true;
+      }
       for (var i = 0; i < tools.length; ++i) {
         var tool = tools[i];
-        if (all || ids.indexOf(tool.id) !== -1)
+        if (all || ids.indexOf(tool.id) !== -1) {
           tool.enabled = true;
+        }
       }
       if (!all) {
         // Sort the tools by the order they are enabled.
         tools.sort(function(a, b) {
           var indexA = ids.indexOf(a.id);
           indexA = indexA < 0 ? tools.length : indexA;
           var indexB = ids.indexOf(b.id);
           indexB = indexB < 0 ? tools.length : indexB;
@@ -504,29 +523,31 @@ var PDFBug = (function PDFBugClosure() {
             event.preventDefault();
             self.selectPanel(selected);
           };
         })(i));
         controls.appendChild(panelButton);
         panels.appendChild(panel);
         tool.panel = panel;
         tool.manager = this;
-        if (tool.enabled)
+        if (tool.enabled) {
           tool.init();
-        else
+        } else {
           panel.textContent = tool.name + ' is disabled. To enable add ' +
                               ' "' + tool.id + '" to the pdfBug parameter ' +
                               'and refresh (seperate multiple by commas).';
+        }
         buttons.push(panelButton);
       }
       this.selectPanel(0);
     },
     selectPanel: function selectPanel(index) {
-      if (index === activePanel)
+      if (index === activePanel) {
         return;
+      }
       activePanel = index;
       var tools = this.tools;
       for (var j = 0; j < tools.length; ++j) {
         if (j == index) {
           buttons[j].setAttribute('class', 'active');
           tools[j].active = true;
           tools[j].panel.removeAttribute('hidden');
         } else {
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/annotation-noicon.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   width="40"
+   height="40"
+   viewBox="0 0 40 40">
+</svg>
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -1152,24 +1152,17 @@ canvas {
   position: relative;
   overflow: visible;
   border: 9px solid transparent;
   background-clip: content-box;
   border-image: url(images/shadow.png) 9 9 repeat;
   background-color: white;
 }
 
-.page > a,
-.annotationLayer > a {
-  display: block;
-  position: absolute;
-}
-
-.page > a:hover,
-.annotationLayer > a:hover {
+.annotLink > a:hover {
   opacity: 0.2;
   background: #ff0;
   box-shadow: 0px 2px 10px #ff0;
 }
 
 .loadingIcon {
   position: absolute;
   display: block;
@@ -1224,39 +1217,59 @@ canvas {
 }
 
 /* TODO: file FF bug to support ::-moz-selection:window-inactive
    so we can override the opaque grey background when the window is inactive;
    see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
 ::selection { background:rgba(0,0,255,0.3); }
 ::-moz-selection { background:rgba(0,0,255,0.3); }
 
-.annotText > div {
-  z-index: 200;
+.annotationHighlight {
   position: absolute;
-  padding: 0.6em;
-  max-width: 20em;
-  background-color: #FFFF99;
-  box-shadow: 0px 2px 10px #333;
-  border-radius: 7px;
+  border: 2px #FFFF99 solid;
 }
 
 .annotText > img {
   position: absolute;
-  opacity: 0.6;
+  cursor: pointer;
+}
+
+.annotTextContentWrapper {
+  position: absolute;
+  width: 20em;
 }
 
-.annotText > img:hover {
-  opacity: 1;
+.annotTextContent {
+  z-index: 200;
+  float: left;
+  max-width: 20em;
+  background-color: #FFFF99;
+  box-shadow: 0px 2px 5px #333;
+  border-radius: 2px;
+  padding: 0.6em;
+  cursor: pointer;
 }
 
-.annotText > div > h1 {
-  font-size: 1.2em;
+.annotTextContent > h1 {
+  font-size: 1em;
   border-bottom: 1px solid #000000;
-  margin: 0px;
+  padding-bottom: 0.2em;
+}
+
+.annotTextContent > p {
+  padding-top: 0.2em;
+}
+
+.annotLink > a {
+  position: absolute;
+  font-size: 1em;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
 }
 
 #errorWrapper {
   background: none repeat scroll 0 0 #FF5555;
   color: white;
   left: 0;
   position: absolute;
   right: 0;
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -63,20 +63,19 @@ var mozL10n = document.mozL10n || docume
 // optimised CSS custom property getter/setter
 var CustomStyle = (function CustomStyleClosure() {
 
   // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
   //              animate-css-transforms-firefox-webkit.html
   // in some versions of IE9 it is critical that ms appear in this list
   // before Moz
   var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
-  var _cache = { };
-
-  function CustomStyle() {
-  }
+  var _cache = {};
+
+  function CustomStyle() {}
 
   CustomStyle.getProp = function get(propName, element) {
     // check cache only when no element is given
     if (arguments.length == 1 && typeof _cache[propName] == 'string') {
       return _cache[propName];
     }
 
     element = element || document.documentElement;
@@ -99,18 +98,19 @@ var CustomStyle = (function CustomStyleC
     }
 
     //if all fails then set to undefined
     return (_cache[propName] = 'undefined');
   };
 
   CustomStyle.setProp = function set(propName, element, str) {
     var prop = this.getProp(propName);
-    if (prop != 'undefined')
+    if (prop != 'undefined') {
       element.style[prop] = str;
+    }
   };
 
   return CustomStyle;
 })();
 
 function getFileName(url) {
   var anchor = url.indexOf('#');
   var query = url.indexOf('?');
@@ -286,21 +286,23 @@ var ProgressBar = (function ProgressBarC
 
   return ProgressBar;
 })();
 
 var Cache = function cacheCache(size) {
   var data = [];
   this.push = function cachePush(view) {
     var i = data.indexOf(view);
-    if (i >= 0)
+    if (i >= 0) {
       data.splice(i);
+    }
     data.push(view);
-    if (data.length > size)
+    if (data.length > size) {
       data.shift().destroy();
+    }
   };
 };
 
 
 
 
 var EXPORTED_SYMBOLS = ['DEFAULT_PREFERENCES'];
 
@@ -310,38 +312,39 @@ var DEFAULT_PREFERENCES = {
   ifAvailableShowOutlineOnLoad: false
 };
 
 
 var Preferences = (function PreferencesClosure() {
   function Preferences() {
     this.prefs = {};
     this.isInitializedPromiseResolved = false;
-    this.initializedPromise = this.readFromStorage().then(function(prefObj) {
-      this.isInitializedPromiseResolved = true;
-      if (prefObj) {
-        this.prefs = prefObj;
-      }
-    }.bind(this));
+    this.initializedPromise = this.readFromStorage(DEFAULT_PREFERENCES).then(
+      function(prefObj) {
+        this.isInitializedPromiseResolved = true;
+        if (prefObj) {
+          this.prefs = prefObj;
+        }
+      }.bind(this));
   }
 
   Preferences.prototype = {
     writeToStorage: function Preferences_writeToStorage(prefObj) {
       return;
     },
 
-    readFromStorage: function Preferences_readFromStorage() {
+    readFromStorage: function Preferences_readFromStorage(prefObj) {
       var readFromStoragePromise = Promise.resolve();
       return readFromStoragePromise;
     },
 
     reset: function Preferences_reset() {
       if (this.isInitializedPromiseResolved) {
         this.prefs = {};
-        this.writeToStorage(this.prefs);
+        this.writeToStorage(DEFAULT_PREFERENCES);
       }
     },
 
     set: function Preferences_set(name, value) {
       if (!this.isInitializedPromiseResolved) {
         return;
       } else if (DEFAULT_PREFERENCES[name] === undefined) {
         console.error('Preferences_set: \'' + name + '\' is undefined.');
@@ -356,16 +359,22 @@ var Preferences = (function PreferencesC
       if (valueType !== defaultType) {
         if (valueType === 'number' && defaultType === 'string') {
           value = value.toString();
         } else {
           console.error('Preferences_set: \'' + value + '\' is a \"' +
                         valueType + '\", expected a \"' + defaultType + '\".');
           return;
         }
+      } else {
+        if (valueType === 'number' && (value | 0) !== value) {
+          console.error('Preferences_set: \'' + value +
+                        '\' must be an \"integer\".');
+          return;
+        }
       }
       this.prefs[name] = value;
       this.writeToStorage(this.prefs);
     },
 
     get: function Preferences_get(name) {
       var defaultPref = DEFAULT_PREFERENCES[name];
 
@@ -422,18 +431,18 @@ var FirefoxCom = (function FirefoxComClo
      * @param {String} data Optional data to send.
      * @param {Function} callback Optional response callback that will be called
      * with one data argument.
      */
     request: function(action, data, callback) {
       var request = document.createTextNode('');
       if (callback) {
         document.addEventListener('pdf.js.response', function listener(event) {
-          var node = event.target,
-              response = event.detail.response;
+          var node = event.target;
+          var response = event.detail.response;
 
           document.documentElement.removeChild(node);
 
           document.removeEventListener('pdf.js.response', listener, false);
           return callback(response);
         }, false);
       }
       document.documentElement.appendChild(request);
@@ -478,19 +487,20 @@ var DownloadManager = (function Download
 
   return DownloadManager;
 })();
 
 Preferences.prototype.writeToStorage = function(prefObj) {
   FirefoxCom.requestSync('setPreferences', prefObj);
 };
 
-Preferences.prototype.readFromStorage = function() {
+Preferences.prototype.readFromStorage = function(prefObj) {
   var readFromStoragePromise = new Promise(function (resolve) {
-    var readPrefs = JSON.parse(FirefoxCom.requestSync('getPreferences'));
+    var readPrefs = JSON.parse(FirefoxCom.requestSync('getPreferences',
+                                                      prefObj));
     resolve(readPrefs);
   });
   return readFromStoragePromise;
 };
 
 
 
 var cache = new Cache(CACHE_SIZE);
@@ -571,41 +581,38 @@ var ViewHistory = (function ViewHistoryC
       return this.file[name] || defaultValue;
     }
   };
 
   return ViewHistory;
 })();
 
 
-/* globals PDFFindController, FindStates, mozL10n */
-
 /**
  * Creates a "search bar" given set of DOM elements
  * that act as controls for searching, or for setting
  * search preferences in the UI. This object also sets
  * up the appropriate events for the controls. Actual
  * searching is done by PDFFindController
  */
 var PDFFindBar = {
-
   opened: false,
   bar: null,
   toggleButton: null,
   findField: null,
   highlightAll: null,
   caseSensitive: null,
   findMsg: null,
   findStatusIcon: null,
   findPreviousButton: null,
   findNextButton: null,
 
   initialize: function(options) {
     if(typeof PDFFindController === 'undefined' || PDFFindController === null) {
-        throw 'PDFFindBar cannot be initialized ' +
+      throw 'PDFFindBar cannot be initialized ' +
             'without a PDFFindController instance.';
     }
 
     this.bar = options.bar;
     this.toggleButton = options.toggleButton;
     this.findField = options.findField;
     this.highlightAll = options.highlightAllCheckbox;
     this.caseSensitive = options.caseSensitiveCheckbox;
@@ -710,18 +717,19 @@ var PDFFindBar = {
       this.bar.classList.remove('hidden');
     }
 
     this.findField.select();
     this.findField.focus();
   },
 
   close: function() {
-    if (!this.opened) return;
-
+    if (!this.opened) {
+      return;
+    }
     this.opened = false;
     this.toggleButton.classList.remove('toggled');
     this.bar.classList.add('hidden');
 
     PDFFindController.active = false;
   },
 
   toggle: function() {
@@ -730,18 +738,16 @@ var PDFFindBar = {
     } else {
       this.open();
     }
   }
 };
 
 
 
-/* globals PDFFindBar, PDFJS, FindStates, FirefoxCom, Promise */
-
 /**
  * Provides a "search" or "find" functionality for the PDF.
  * This object actually performs the search for a given string.
  */
 
 var PDFFindController = {
   startedTextExtraction: false,
 
@@ -871,18 +877,19 @@ var PDFFindController = {
           for (var i = 0; i < bidiTexts.length; i++) {
             str += bidiTexts[i].str;
           }
 
           // Store the pageContent as a string.
           self.pageContents.push(str);
 
           extractTextPromisesResolves[pageIndex](pageIndex);
-          if ((pageIndex + 1) < self.pdfPageSource.pages.length)
+          if ((pageIndex + 1) < self.pdfPageSource.pages.length) {
             extractPageText(pageIndex + 1);
+          }
         }
       );
     }
     extractPageText(0);
   },
 
   handleEvent: function(e) {
     if (this.state === null || e.type !== 'findagain') {
@@ -2427,20 +2434,21 @@ var PDFView = {
   // Helper function to keep track whether a div was scrolled up or down and
   // then call a callback.
   watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) {
     state.down = true;
     state.lastY = viewAreaElement.scrollTop;
     viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) {
       var currentY = viewAreaElement.scrollTop;
       var lastY = state.lastY;
-      if (currentY > lastY)
+      if (currentY > lastY) {
         state.down = true;
-      else if (currentY < lastY)
+      } else if (currentY < lastY) {
         state.down = false;
+      }
       // else do nothing and use previous value
       state.lastY = currentY;
       callback();
     }, true);
   },
 
   _setScaleUpdatePages: function pdfView_setScaleUpdatePages(
       newScale, newValue, resetAutoSettings, noScroll) {
@@ -2683,18 +2691,19 @@ var PDFView = {
     window.addEventListener('message', function windowMessage(e) {
       if (e.source !== null) {
         // The message MUST originate from Chrome code.
         console.warn('Rejected untrusted message from ' + e.origin);
         return;
       }
       var args = e.data;
 
-      if (typeof args !== 'object' || !('pdfjsLoadAction' in args))
+      if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) {
         return;
+      }
       switch (args.pdfjsLoadAction) {
         case 'supportsRangedLoading':
           PDFView.open(args.pdfUrl, 0, undefined, pdfDataRangeTransport, {
             length: args.length,
             initialData: args.data
           });
           break;
         case 'range':
@@ -2879,18 +2888,19 @@ var PDFView = {
       if (!(dest instanceof Array)) {
         return; // invalid destination
       }
       goToDestination(dest[0]);
     });
   },
 
   getDestinationHash: function pdfViewGetDestinationHash(dest) {
-    if (typeof dest === 'string')
+    if (typeof dest === 'string') {
       return PDFView.getAnchorUrl('#' + escape(dest));
+    }
     if (dest instanceof Array) {
       var destRef = dest[0]; // see navigateTo method for dest format
       var pageNumber = destRef instanceof Object ?
         this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
         (destRef + 1);
       if (pageNumber) {
         var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber);
         var destKind = dest[1];
@@ -2998,25 +3008,28 @@ var PDFView = {
       PDFView.loadingBar.hide();
       var outerContainer = document.getElementById('outerContainer');
       outerContainer.classList.remove('loadingInProgress');
     });
 
     var thumbsView = document.getElementById('thumbnailView');
     thumbsView.parentNode.scrollTop = 0;
 
-    while (thumbsView.hasChildNodes())
+    while (thumbsView.hasChildNodes()) {
       thumbsView.removeChild(thumbsView.lastChild);
-
-    if ('_loadingInterval' in thumbsView)
+    }
+
+    if ('_loadingInterval' in thumbsView) {
       clearInterval(thumbsView._loadingInterval);
+    }
 
     var container = document.getElementById('viewer');
-    while (container.hasChildNodes())
+    while (container.hasChildNodes()) {
       container.removeChild(container.lastChild);
+    }
 
     var pagesCount = pdfDocument.numPages;
 
     var id = pdfDocument.fingerprint;
     document.getElementById('numPages').textContent =
       mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
     document.getElementById('pageNumber').max = pagesCount;
 
@@ -3173,26 +3186,27 @@ var PDFView = {
 
       // Provides some basic debug information
       console.log('PDF ' + pdfDocument.fingerprint + ' [' +
                   info.PDFFormatVersion + ' ' + (info.Producer || '-') +
                   ' / ' + (info.Creator || '-') + ']' +
                   (PDFJS.version ? ' (PDF.js: ' + PDFJS.version + ')' : ''));
 
       var pdfTitle;
-      if (metadata) {
-        if (metadata.has('dc:title'))
-          pdfTitle = metadata.get('dc:title');
+      if (metadata && metadata.has('dc:title')) {
+        pdfTitle = metadata.get('dc:title');
       }
 
-      if (!pdfTitle && info && info['Title'])
+      if (!pdfTitle && info && info['Title']) {
         pdfTitle = info['Title'];
-
-      if (pdfTitle)
+      }
+
+      if (pdfTitle) {
         self.setTitle(pdfTitle + ' - ' + document.title);
+      }
 
       if (info.IsAcroFormPresent) {
         console.warn('Warning: AcroForm/XFA is not supported');
         PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.forms);
       }
 
       var versionId = String(info.PDFFormatVersion).slice(-1) | 0;
       var generatorId = 0;
@@ -3308,31 +3322,34 @@ var PDFView = {
     var visibleViews = visible.views;
 
     var numVisible = visibleViews.length;
     if (numVisible === 0) {
       return false;
     }
     for (var i = 0; i < numVisible; ++i) {
       var view = visibleViews[i].view;
-      if (!this.isViewFinished(view))
+      if (!this.isViewFinished(view)) {
         return view;
+      }
     }
 
     // All the visible views have rendered, try to render next/previous pages.
     if (scrolledDown) {
       var nextPageIndex = visible.last.id;
       // ID's start at 1 so no need to add 1.
-      if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex]))
+      if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) {
         return views[nextPageIndex];
+      }
     } else {
       var previousPageIndex = visible.first.id - 2;
       if (views[previousPageIndex] &&
-          !this.isViewFinished(views[previousPageIndex]))
+          !this.isViewFinished(views[previousPageIndex])) {
         return views[previousPageIndex];
+      }
     }
     // Everything that needs to be rendered has been.
     return false;
   },
 
   isViewFinished: function pdfViewIsViewFinished(view) {
     return view.renderingState === RenderingStates.FINISHED;
   },
@@ -3355,18 +3372,19 @@ var PDFView = {
         PDFView.highestPriorityPage = type + view.id;
         view.draw(this.renderHighestPriority.bind(this));
         break;
     }
     return true;
   },
 
   setHash: function pdfViewSetHash(hash) {
-    if (!hash)
+    if (!hash) {
       return;
+    }
 
     if (hash.indexOf('=') >= 0) {
       var params = PDFView.parseQueryString(hash);
       // borrowing syntax from "Parameters for Opening PDF Files"
       if ('nameddest' in params) {
         PDFHistory.updateNextHashParam(params.nameddest);
         PDFView.navigateTo(params.nameddest);
         return;
@@ -3444,18 +3462,19 @@ var PDFView = {
         break;
 
       case 'outline':
         thumbsButton.classList.remove('toggled');
         outlineButton.classList.add('toggled');
         thumbsView.classList.add('hidden');
         outlineView.classList.remove('hidden');
 
-        if (outlineButton.getAttribute('disabled'))
+        if (outlineButton.getAttribute('disabled')) {
           return;
+        }
         break;
     }
   },
 
   getVisiblePages: function pdfViewGetVisiblePages() {
     if (!PresentationMode.active) {
       return this.getVisibleElements(this.container, this.pages, true);
     } else {
@@ -3562,18 +3581,19 @@ var PDFView = {
     body.setAttribute('data-mozPrintCallback', true);
     for (var i = 0, ii = this.pages.length; i < ii; ++i) {
       this.pages[i].beforePrint();
     }
   },
 
   afterPrint: function pdfViewSetupAfterPrint() {
     var div = document.getElementById('printContainer');
-    while (div.hasChildNodes())
+    while (div.hasChildNodes()) {
       div.removeChild(div.lastChild);
+    }
   },
 
   rotatePages: function pdfViewRotatePages(delta) {
     var currentPage = this.pages[this.page - 1];
     this.pageRotation = (this.pageRotation + 360 + delta) % 360;
 
     for (var i = 0, l = this.pages.length; i < l; i++) {
       var page = this.pages[i];
@@ -3605,24 +3625,26 @@ var PDFView = {
     var MOUSE_SCROLL_COOLDOWN_TIME = 50;
 
     var currentTime = (new Date()).getTime();
     var storedTime = this.mouseScrollTimeStamp;
 
     // In case one page has already been flipped there is a cooldown time
     // which has to expire before next page can be scrolled on to.
     if (currentTime > storedTime &&
-        currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME)
+        currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
       return;
+    }
 
     // In case the user decides to scroll to the opposite direction than before
     // clear the accumulated delta.
     if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) ||
-        (this.mouseScrollDelta < 0 && mouseScrollDelta > 0))
+        (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) {
       this.clearMouseScrollState();
+    }
 
     this.mouseScrollDelta += mouseScrollDelta;
 
     var PAGE_FLIP_THRESHOLD = 120;
     if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) {
 
       var PageFlipDirection = {
         UP: -1,
@@ -3635,18 +3657,19 @@ var PDFView = {
                                 PageFlipDirection.DOWN;
       this.clearMouseScrollState();
       var currentPage = this.page;
 
       // In case we are already on the first or the last page there is no need
       // to do anything.
       if ((currentPage == 1 && pageFlipDirection == PageFlipDirection.UP) ||
           (currentPage == this.pages.length &&
-           pageFlipDirection == PageFlipDirection.DOWN))
+           pageFlipDirection == PageFlipDirection.DOWN)) {
         return;
+      }
 
       this.page += pageFlipDirection;
       this.mouseScrollTimeStamp = currentTime;
     }
   },
 
   /**
    * This function clears the member attributes used with mouse scrolling in
@@ -3941,20 +3964,23 @@ var PageView = function pageView(contain
 
         var transform = viewport.transform;
         var transformStr = 'matrix(' + transform.join(',') + ')';
         CustomStyle.setProp('transform', element, transformStr);
         var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
         CustomStyle.setProp('transformOrigin', element, transformOriginStr);
 
         if (data.subtype === 'Link' && !data.url) {
-          if (data.action) {
-            bindNamedAction(element, data.action);
-          } else {
-            bindLink(element, ('dest' in data) ? data.dest : null);
+          var link = element.getElementsByTagName('a')[0];
+          if (link) {
+            if (data.action) {
+              bindNamedAction(link, data.action);
+            } else {
+              bindLink(link, ('dest' in data) ? data.dest : null);
+            }
           }
         }
 
         if (!self.annotationLayer) {
           var annotationLayerDiv = document.createElement('div');
           annotationLayerDiv.className = 'annotationLayer';
           pageDiv.appendChild(annotationLayerDiv);
           self.annotationLayer = annotationLayerDiv;
@@ -4210,16 +4236,17 @@ var PageView = function pageView(contain
       // TODO add stream types report here
       callback();
     }
 
     var renderContext = {
       canvasContext: ctx,
       viewport: this.viewport,
       textLayer: textLayer,
+      // intent: 'default', // === 'display'
       continueCallback: function pdfViewcContinueCallback(cont) {
         if (PDFView.highestPriorityPage !== 'page' + self.id) {
           self.renderingState = RenderingStates.PAUSED;
           self.resume = function resumeCallback() {
             self.renderingState = RenderingStates.RUNNING;
             cont();
           };
           return;
@@ -4281,33 +4308,32 @@ var PageView = function pageView(contain
       ctx.save();
       ctx.fillStyle = 'rgb(255, 255, 255)';
       ctx.fillRect(0, 0, canvas.width, canvas.height);
       ctx.restore();
       ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
 
       var renderContext = {
         canvasContext: ctx,
-        viewport: viewport
+        viewport: viewport,
+        intent: 'print'
       };
 
       pdfPage.render(renderContext).promise.then(function() {
         // Tell the printEngine that rendering this canvas/page has finished.
         obj.done();
-        self.pdfPage.destroy();
       }, function(error) {
         console.error(error);
         // Tell the printEngine that rendering this canvas/page has failed.
         // This will make the print proces stop.
         if ('abort' in obj) {
           obj.abort();
         } else {
           obj.done();
         }
-        self.pdfPage.destroy();
       });
     };
   };
 
   this.updateStats = function pageViewUpdateStats() {
     if (!this.stats) {
       return;
     }
@@ -4517,22 +4543,22 @@ var TextLayerBuilder = function textLaye
   this.layoutDone = false;
   this.divContentDone = false;
   this.pageIdx = options.pageIndex;
   this.matches = [];
   this.lastScrollSource = options.lastScrollSource;
   this.viewport = options.viewport;
   this.isViewerInPresentationMode = options.isViewerInPresentationMode;
 
-  if(typeof PDFFindController === 'undefined') {
-      window.PDFFindController = null;
+  if (typeof PDFFindController === 'undefined') {
+    window.PDFFindController = null;
   }
 
-  if(typeof this.lastScrollSource === 'undefined') {
-      this.lastScrollSource = null;
+  if (typeof this.lastScrollSource === 'undefined') {
+    this.lastScrollSource = null;
   }
 
   this.beginLayout = function textLayerBuilderBeginLayout() {
     this.textDivs = [];
     this.renderingDone = false;
   };
 
   this.endLayout = function textLayerBuilderEndLayout() {
@@ -4543,18 +4569,19 @@ var TextLayerBuilder = function textLaye
   this.renderLayer = function textLayerBuilderRenderLayer() {
     var textDivs = this.textDivs;
     var canvas = document.createElement('canvas');
     var ctx = canvas.getContext('2d');
 
     // No point in rendering so many divs as it'd make the browser unusable
     // even after the divs are rendered
     var MAX_TEXT_DIVS_TO_RENDER = 100000;
-    if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER)
+    if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) {
       return;
+    }
 
     for (var i = 0, ii = textDivs.length; i < ii; i++) {
       var textDiv = textDivs[i];
       if ('isWhitespace' in textDiv.dataset) {
         continue;
       }
 
       ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily;
@@ -4576,26 +4603,27 @@ var TextLayerBuilder = function textLaye
     this.updateMatches();
   };
 
   this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() {
     // Schedule renderLayout() if user has been scrolling, otherwise
     // run it right away
     var RENDER_DELAY = 200; // in ms
     var self = this;
-    var lastScroll = this.lastScrollSource === null ?
-        0 : this.lastScrollSource.lastScroll;
+    var lastScroll = (this.lastScrollSource === null ?
+                      0 : this.lastScrollSource.lastScroll);
 
     if (Date.now() - lastScroll > RENDER_DELAY) {
       // Render right away
       this.renderLayer();
     } else {
       // Schedule
-      if (this.renderTimer)
+      if (this.renderTimer) {
         clearTimeout(this.renderTimer);
+      }
       this.renderTimer = setTimeout(function() {
         self.setupRenderLayoutTimer();
       }, RENDER_DELAY);
     }
   };
 
   this.appendText = function textLayerBuilderAppendText(geom) {
     var textDiv = document.createElement('div');
@@ -4603,31 +4631,32 @@ var TextLayerBuilder = function textLaye
     // vScale and hScale already contain the scaling to pixel units
     var fontHeight = geom.fontSize * Math.abs(geom.vScale);
     textDiv.dataset.canvasWidth = geom.canvasWidth * Math.abs(geom.hScale);
     textDiv.dataset.fontName = geom.fontName;
     textDiv.dataset.angle = geom.angle * (180 / Math.PI);
 
     textDiv.style.fontSize = fontHeight + 'px';
     textDiv.style.fontFamily = geom.fontFamily;
-    var fontAscent = geom.ascent ? geom.ascent * fontHeight :
-      geom.descent ? (1 + geom.descent) * fontHeight : fontHeight;
+    var fontAscent = (geom.ascent ? geom.ascent * fontHeight :
+      (geom.descent ? (1 + geom.descent) * fontHeight : fontHeight));
     textDiv.style.left = (geom.x + (fontAscent * Math.sin(geom.angle))) + 'px';
     textDiv.style.top = (geom.y - (fontAscent * Math.cos(geom.angle))) + 'px';
 
     // The content of the div is set in the `setTextContent` function.
 
     this.textDivs.push(textDiv);
   };
 
   this.insertDivContent = function textLayerUpdateTextContent() {
     // Only set the content of the divs once layout has finished, the content
     // for the divs is available and content is not yet set on the divs.
-    if (!this.layoutDone || this.divContentDone || !this.textContent)
+    if (!this.layoutDone || this.divContentDone || !this.textContent) {
       return;
+    }
 
     this.divContentDone = true;
 
     var textDivs = this.textDivs;
     var bidiTexts = this.textContent;
 
     for (var i = 0; i < bidiTexts.length; i++) {
       var bidiText = bidiTexts[i];
@@ -4656,18 +4685,18 @@ var TextLayerBuilder = function textLaye
     this.insertDivContent();
   };
 
   this.convertMatches = function textLayerBuilderConvertMatches(matches) {
     var i = 0;
     var iIndex = 0;
     var bidiTexts = this.textContent;
     var end = bidiTexts.length - 1;
-    var queryLen = PDFFindController === null ?
-        0 : PDFFindController.state.query.length;
+    var queryLen = (PDFFindController === null ?
+                    0 : PDFFindController.state.query.length);
 
     var lastDivIdx = -1;
     var pos;
 
     var ret = [];
 
     // Loop over all the matches.
     for (var m = 0; m < matches.length; m++) {
@@ -4716,24 +4745,24 @@ var TextLayerBuilder = function textLaye
     // Early exit if there is nothing to render.
     if (matches.length === 0) {
       return;
     }
 
     var bidiTexts = this.textContent;
     var textDivs = this.textDivs;
     var prevEnd = null;
-    var isSelectedPage = PDFFindController === null ?
-        false : (this.pageIdx === PDFFindController.selected.pageIdx);
-
-    var selectedMatchIdx = PDFFindController === null ?
-        -1 : PDFFindController.selected.matchIdx;
-
-    var highlightAll = PDFFindController === null ?
-        false : PDFFindController.state.highlightAll;
+    var isSelectedPage = (PDFFindController === null ?
+      false : (this.pageIdx === PDFFindController.selected.pageIdx));
+
+    var selectedMatchIdx = (PDFFindController === null ?
+                            -1 : PDFFindController.selected.matchIdx);
+
+    var highlightAll = (PDFFindController === null ?
+                        false : PDFFindController.state.highlightAll);
 
     var infty = {
       divIdx: -1,
       offset: undefined
     };
 
     function beginText(begin, className) {
       var divIdx = begin.divIdx;
@@ -4822,18 +4851,19 @@ var TextLayerBuilder = function textLaye
 
     if (prevEnd) {
       appendText(prevEnd, infty);
     }
   };
 
   this.updateMatches = function textLayerUpdateMatches() {
     // Only show matches, once all rendering is done.
-    if (!this.renderingDone)
+    if (!this.renderingDone) {
       return;
+    }
 
     // Clear out all matches.
     var matches = this.matches;
     var textDivs = this.textDivs;
     var bidiTexts = this.textContent;
     var clearedUntilDivIdx = -1;
 
     // Clear out all current matches.
@@ -4843,41 +4873,42 @@ var TextLayerBuilder = function textLaye
       for (var n = begin; n <= match.end.divIdx; n++) {
         var div = textDivs[n];
         div.textContent = bidiTexts[n].str;
         div.className = '';
       }
       clearedUntilDivIdx = match.end.divIdx + 1;
     }
 
-    if (PDFFindController === null || !PDFFindController.active)
+    if (PDFFindController === null || !PDFFindController.active) {
       return;
+    }
 
     // Convert the matches on the page controller into the match format used
     // for the textLayer.
-    this.matches = matches =
-      this.convertMatches(PDFFindController === null ?
-          [] : (PDFFindController.pageMatches[this.pageIdx] || []));
+    this.matches = matches = (this.convertMatches(PDFFindController === null ?
+      [] : (PDFFindController.pageMatches[this.pageIdx] || [])));
 
     this.renderMatches(this.matches);
   };
 };
 
 
 
 var DocumentOutlineView = function documentOutlineView(outline) {
   var outlineView = document.getElementById('outlineView');
   var outlineButton = document.getElementById('viewOutline');
-  while (outlineView.firstChild)
+  while (outlineView.firstChild) {
     outlineView.removeChild(outlineView.firstChild);
+  }
 
   if (!outline) {
-    if (!outlineView.classList.contains('hidden'))
+    if (!outlineView.classList.contains('hidden')) {
       PDFView.switchSidebarView('thumbs');
-
+    }
     return;
   }
 
   function bindItemLink(domObj, item) {
     domObj.href = PDFView.getDestinationHash(item.dest);
     domObj.onclick = function documentOutlineViewOnclick(e) {
       PDFView.navigateTo(item.dest);
       return false;
@@ -5096,36 +5127,37 @@ function webViewerLoad(evt) {
   }
 
 }
 
 document.addEventListener('DOMContentLoaded', webViewerLoad, true);
 
 function updateViewarea() {
 
-  if (!PDFView.initialized)
+  if (!PDFView.initialized) {
     return;
+  }
   var visible = PDFView.getVisiblePages();
   var visiblePages = visible.views;
   if (visiblePages.length === 0) {
     return;
   }
 
   PDFView.renderHighestPriority();
 
   var currentId = PDFView.page;
   var firstPage = visible.first;
 
   for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
        i < ii; ++i) {
     var page = visiblePages[i];
 
-    if (page.percent < 100)
+    if (page.percent < 100) {
       break;
-
+    }
     if (page.id === PDFView.page) {
       stillFullyVisible = true;
       break;
     }
   }
 
   if (!stillFullyVisible) {
     currentId = visiblePages[0].id;
@@ -5468,33 +5500,16 @@ window.addEventListener('keydown', funct
         if (!PresentationMode.active) {
           HandTool.toggle();
         }
         break;
       case 82: // 'r'
         PDFView.rotatePages(90);
         break;
     }
-    if (!handled && !PresentationMode.active) {
-      // 33=Page Up  34=Page Down  35=End    36=Home
-      // 37=Left     38=Up         39=Right  40=Down
-      if (evt.keyCode >= 33 && evt.keyCode <= 40 &&
-          !PDFView.container.contains(curElement)) {
-        // The page container is not focused, but a page navigation key has been
-        // pressed. Change the focus to the viewer container to make sure that
-        // navigation by keyboard works as expected.
-        PDFView.container.focus();
-      }
-      // 32=Spacebar
-      if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') {
-  // Workaround for issue in Firefox, that prevents scroll keys from working
-  // when elements with 'tabindex' are focused. (#3499)
-        PDFView.container.blur();
-      }
-    }
   }
 
   if (cmd === 4) { // shift-key
     switch (evt.keyCode) {
       case 32: // spacebar
         if (!PresentationMode.active &&
             PDFView.currentScaleValue !== 'page-fit') {
           break;
@@ -5504,16 +5519,34 @@ window.addEventListener('keydown', funct
         break;
 
       case 82: // 'r'
         PDFView.rotatePages(-90);
         break;
     }
   }
 
+  if (!handled && !PresentationMode.active) {
+    // 33=Page Up  34=Page Down  35=End    36=Home
+    // 37=Left     38=Up         39=Right  40=Down
+    if (evt.keyCode >= 33 && evt.keyCode <= 40 &&
+        !PDFView.container.contains(curElement)) {
+      // The page container is not focused, but a page navigation key has been
+      // pressed. Change the focus to the viewer container to make sure that
+      // navigation by keyboard works as expected.
+      PDFView.container.focus();
+    }
+    // 32=Spacebar
+    if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') {
+      // Workaround for issue in Firefox, that prevents scroll keys from
+      // working when elements with 'tabindex' are focused. (#3498)
+      PDFView.container.blur();
+    }
+  }
+
   if (cmd === 2) { // alt-key
     switch (evt.keyCode) {
       case 37: // left arrow
         if (PresentationMode.active) {
           PDFHistory.back();
           handled = true;
         }
         break;
--- a/config/milestone.txt
+++ b/config/milestone.txt
@@ -5,9 +5,9 @@
 #    x.x.x.x
 #    x.x.x+
 #
 # Referenced by milestone.pl.
 # Hopefully I'll be able to automate replacement of *all*
 # hardcoded milestones in the tree from these two files.
 #--------------------------------------------------------
 
-30.0a1
+31.0a1
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -1216,16 +1216,26 @@ MediaStreamGraphImpl::RunThread()
     for (uint32_t i = 0; i < mStreams.Length(); ++i) {
       SourceMediaStream* is = mStreams[i]->AsSourceStream();
       if (is) {
         UpdateConsumptionState(is);
         ExtractPendingInput(is, endBlockingDecisions, &ensureNextIteration);
       }
     }
 
+    // The loop is woken up so soon that mCurrentTime barely advances and we
+    // end up having endBlockingDecisions == mStateComputedTime.
+    // Since stream blocking is computed in the interval of
+    // [mStateComputedTime, endBlockingDecisions), it won't be computed at all.
+    // We should ensure next iteration so that pending blocking changes will be
+    // computed in next loop.
+    if (endBlockingDecisions == mStateComputedTime) {
+      ensureNextIteration = true;
+    }
+
     // Figure out which streams are blocked and when.
     GraphTime prevComputedTime = mStateComputedTime;
     RecomputeBlocking(endBlockingDecisions);
 
     // Play stream contents.
     bool allBlockedForever = true;
     // True when we've done ProcessInput for all processed streams.
     bool doneAllProducing = false;
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -375,17 +375,16 @@ skip-if = (buildapp == 'b2g' && (toolkit
 [test_preload_actions.html]
 [test_preload_attribute.html]
 [test_progress.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(bug 901716 - timeouts) b2g-desktop(bug 901716 - timeouts)
 [test_reactivate.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(timed out in small-shot.mp3) b2g-desktop(timed out in small-shot.mp3)
 [test_readyState.html]
 [test_referer.html]
-skip-if = buildapp == 'b2g'
 [test_reset_events_async.html]
 [test_replay_metadata.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
 [test_seek2.html]
 [test_seek_out_of_range.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
 [test_seekable2.html]
 [test_seekable3.html]
--- a/content/media/test/test_preload_actions.html
+++ b/content/media/test/test_preload_actions.html
@@ -69,77 +69,83 @@ var tests = [
       var v = e.target;
       is(v._gotLoadStart, true, "(1) Must get loadstart.");
       is(v._gotLoadedMetaData, false, "(1) Must not get loadedmetadata.");
       is(v.readyState, v.HAVE_NOTHING, "(1) ReadyState must be HAVE_NOTHING");
       // bug 962949
       // is(v.networkState, v.NETWORK_IDLE, "(1) NetworkState must be NETWORK_IDLE");
       maybeFinish(v, 1);
     },
-    
+
     setup:
     function(v) {
       v._gotLoadStart = false;
       v._gotLoadedMetaData = false;
       v.preload = "none";
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
       v.addEventListener("suspend", this.suspend, false);
       v.src = test.name;
       document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none.
     },
+
+    name: "test1",
   },
   {
     // 2. Add preload:metadata video with src to document. Should halt with NETWORK_IDLE, HAVE_CURRENT_DATA
     // after suspend event and after loadedmetadata.
     loadeddata:
     function(e) {
       var v = e.target;
       is(v._gotLoadStart, true, "(2) Must get loadstart.");
       is(v._gotLoadedMetaData, true, "(2) Must get loadedmetadata.");
       ok(v.readyState >= v.HAVE_CURRENT_DATA, "(2) ReadyState must be >= HAVE_CURRENT_DATA");
       // bug 962949
       // is(v.networkState, v.NETWORK_IDLE, "(2) NetworkState must be NETWORK_IDLE");
       maybeFinish(v, 2);
     },
-    
+
     setup:
     function(v) {
       v._gotLoadStart = false;
       v._gotLoadedMetaData = false;
       v.preload = "metadata";
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("loadeddata", this.loadeddata, false);
       v.src = test.name;
       document.body.appendChild(v); // Causes implicit load, which will be halted after
                                      // metadata due to preload:metadata.
     },
+
+    name: "test2",
   },
   {
     // 3. Add preload:auto to document. Should receive canplaythrough eventually.
     canplaythrough:
     function(e) {
       var v = e.target;
       is(v._gotLoadStart, true, "(3) Must get loadstart.");
       is(v._gotLoadedMetaData, true, "(3) Must get loadedmetadata.");
       maybeFinish(v, 3);
     },
-    
+
     setup:
     function(v) {
       v._gotLoadStart = false;
       v._gotLoadedMetaData = false;
       v.preload = "auto";
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("canplaythrough", this.canplaythrough, false);
       v.src = test.name; // Causes implicit load.
       document.body.appendChild(v);
     },
+
+    name: "test3",
   },
   {
     // 4. Add preload:none video to document. Call play(), should load then play through.
     suspend:
     function(e) {
       var v = e.target;
       if (v._gotSuspend) {
         return; // We can receive multiple suspend events, like the one after download completes.
@@ -147,91 +153,97 @@ var tests = [
       v._gotSuspend = true;
       is(v._gotLoadStart, true, "(4) Must get loadstart.");
       is(v._gotLoadedMetaData, false, "(4) Must not get loadedmetadata.");
       is(v.readyState, v.HAVE_NOTHING, "(4) ReadyState must be HAVE_NOTHING");
       // bug 962949
       // is(v.networkState, v.NETWORK_IDLE, "(4) NetworkState must be NETWORK_IDLE");
       v.play(); // Should load and play through.
     },
-    
+
     ended:
     function(e) {
       ok(true, "(4) Got playback ended");
       maybeFinish(e.target, 4);
     },
-      
+
     setup:
     function(v) {
       v._gotLoadStart = false;
       v._gotLoadedMetaData = false;
       v._gotSuspend = false;
       v.preload = "none";
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
       v.addEventListener("suspend", this.suspend, false);
       v.addEventListener("ended", this.ended, false);
       v.src = test.name;
       document.body.appendChild(v);
     },
+
+    name: "test4",
   },
   {
     // 5. preload:none video without resource, add to document, will implicitly start a
     // preload:none load. Add a src, it shouldn't load.
     suspend:
     function(e) {
       var v = e.target;
       is(v._gotLoadStart, true, "(5) Must get loadstart.");
       is(v._gotLoadedMetaData, false, "(5) Must not get loadedmetadata.");
       is(v.readyState, v.HAVE_NOTHING, "(5) ReadyState must be HAVE_NOTHING");
       // bug 962949
       // is(v.networkState, v.NETWORK_IDLE, "(5) NetworkState must be NETWORK_IDLE");
       maybeFinish(v, 5);
     },
-      
+
     setup:
     function(v) {
       v._gotLoadStart = false;
       v._gotLoadedMetaData = false;
       v.preload = "none";
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
       v.addEventListener("suspend", this.suspend, false);
       document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource.
       v.src = test.name; // Load should start, and halt at preload:none.
     },
+
+    name: "test5",
   },
   {
     // 6. preload:none video without resource, add to document, will implicitly start a
     // preload:none load. Add a source, it shouldn't load.
     suspend:
     function(e) {
       var v = e.target;
       is(v._gotLoadStart, true, "(6) Must get loadstart.");
       is(v._gotLoadedMetaData, false, "(6) Must not get loadedmetadata.");
       is(v.readyState, v.HAVE_NOTHING, "(6) ReadyState must be HAVE_NOTHING");
       // bug 962949
       // is(v.networkState, v.NETWORK_IDLE, "(6) NetworkState must be NETWORK_IDLE");
       maybeFinish(v, 6);
     },
-      
+
     setup:
     function(v) {
       v._gotLoadStart = false;
       v._gotLoadedMetaData = false;
       v.preload = "none";
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
       v.addEventListener("suspend", this.suspend, false);
       document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource.
       var s = document.createElement("source");
       s.src = test.name;
       s.type = test.type;
       v.appendChild(s); // Load should start, and halt at preload:none.
     },
+
+    name: "test6",
   },
   {
     // 7. create a preload:none document with multiple sources, the first of which is invalid.
     // Add to document, then play. It should load and play through the second source.
     suspend:
     function(e) {
       var v = e.target;
       if (v._gotSuspend) 
@@ -247,17 +259,17 @@ var tests = [
 
     ended:
     function(e) {
       ok(true, "(7) Got playback ended");
       var v = e.target;
       is(v._gotErrorEvent, true, "(7) Should get error event from first source load failure");      
       maybeFinish(v, 7);
     },
-      
+
     setup:
     function(v) {
       v._gotLoadStart = false;
       v._gotLoadedMetaData = false;
       v.preload = "none";
       v._gotErrorEvent = false;
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
@@ -269,52 +281,56 @@ var tests = [
       s1.addEventListener("error", function(e){v._gotErrorEvent = true;}, false);
       v.appendChild(s1);
       var s2 = document.createElement("source");
       s2.src = test.name;
       s2.type = test.type;
       v.appendChild(s2);
       document.body.appendChild(v); // Causes implicit load, which will be halt at preload:none on the second resource.
     },
+
+    name: "test7",
   },
   {
     // 8. Change preload value from none to metadata should cause metadata to be loaded.
     loadeddata:
     function(e) {
       var v = e.target;
       is(v._gotLoadedMetaData, true, "(8) Must get loadedmetadata.");
       ok(v.readyState >= v.HAVE_CURRENT_DATA, "(8) ReadyState must be >= HAVE_CURRENT_DATA on suspend.");
       // bug 962949
       // is(v.networkState, v.NETWORK_IDLE, "(8) NetworkState must be NETWORK_IDLE when load is halted");
       maybeFinish(v, 8);
     },
-    
+
     setup:
     function(v) {
       v._gotLoadedMetaData = false;
       v.preload = "none";
       v.addEventListener("loadstart", function(e){v.preload = "metadata";}, false);
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("loadeddata", this.loadeddata, false);
       v.src = test.name; // Causes implicit load.
       document.body.appendChild(v);
     },
+
+    name: "test8",
   },
   /*{
     // 9. Change preload value from metadata to auto should cause entire media to be loaded.
     // For some reason we don't always receive the canplaythrough event, particuarly on this test.
     // We've disabled this test until bug 568402 is fixed.
     canplaythrough:
     function(e) {
       var v = e.target;
       is(v._gotLoadStart, true, "(9) Must get loadstart.");
       is(v._gotLoadedMetaData, true, "(9) Must get loadedmetadata.");
       maybeFinish(v, 9);
     },
-    
+
     setup:
     function(v) {
       v._gotLoadStart = false;
       v._gotLoadedMetaData = false;
       v.preload = "metadata";
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("loadeddata", function(){v.preload = "auto"}, false);
@@ -326,27 +342,29 @@ var tests = [
   {
     // 10. Change preload value from none to auto should cause entire media to be loaded.
     canplaythrough:
     function(e) {
       var v = e.target;
       is(v._gotLoadedMetaData, true, "(10) Must get loadedmetadata.");
       maybeFinish(v, 10);
     },
-    
+
     setup:
     function(v) {
       v._gotLoadedMetaData = false;
       v.preload = "none";
       v.addEventListener("loadstart", function(e){v.preload = "auto";}, false);
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("canplaythrough", this.canplaythrough, false);
       v.src = test.name; // Causes implicit load.
       document.body.appendChild(v);
     },
+
+    name: "test10",
   },
   {
     // 11. Change preload value from none to metadata should cause metadata to load.
     loadeddata:
     function(e) {
       var v = e.target;
       is(v._gotLoadedMetaData, true, "(11) Must get loadedmetadata.");
       ok(v.readyState >= v.HAVE_CURRENT_DATA, "(11) ReadyState must be >= HAVE_CURRENT_DATA.");
@@ -360,20 +378,24 @@ var tests = [
       v._gotLoadedMetaData = false;
       v.preload = "none";
       v.addEventListener("loadstart", function(e){v.preload = "metadata";}, false);
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("loadeddata", this.loadeddata, false);
       v.src = test.name; // Causes implicit load.
       document.body.appendChild(v);
     },
+
+    name: "test11",
   },
-  {
+  /*{
     // 12. Change preload value from auto to metadata after load started,
     // should still do full load, should not halt after metadata only.
+    // disable this test since the spec is no longer found in the document
+    // http://dev.w3.org/html5/spec-preview/media-elements.html
     canplaythrough:
     function(e) {
       var v = e.target;
       is(v._gotLoadedMetaData, true, "(12) Must get loadedmetadata.");
       is(v._gotLoadStart, true, "(12) Must get loadstart.");
       maybeFinish(v, 12);
     },
 
@@ -384,17 +406,19 @@ var tests = [
       v.preload = "auto";
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("canplaythrough", this.canplaythrough, false);
       v.src = test.name; // Causes implicit load.
       document.body.appendChild(v);
       v.preload = "metadata";
     },
-  },
+
+    name: "test12",
+  },*/
   {
     // 13. Change preload value from auto to none after specifying a src
     // should load according to preload none, no buffering should have taken place
     suspend:
     function(e) {
       var v = e.target;
       is(v._gotLoadStart, true, "(13) Must get loadstart.");
       is(v._gotLoadedMetaData, false, "(13) Must not get loadedmetadata.");
@@ -411,31 +435,33 @@ var tests = [
       v.preload = "auto";
       v.src = test.name;
       v.preload = "none";
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
       v.addEventListener("suspend", this.suspend, false);
       document.body.appendChild(v); // Causes implicit load, should load according to preload none
       var s = document.createElement("source");
-    }
+    },
+
+    name: "test13",
   },
   {
     // 14. Add preload:metadata video with src to document. Play(), should play through.
     loadeddata:
     function(e) {
       var v = e.target;
       is(v._gotLoadStart, true, "(14) Must get loadstart.");
       is(v._gotLoadedMetaData, true, "(14) Must get loadedmetadata.");
       ok(v.readyState >= v.HAVE_CURRENT_DATA, "(14) ReadyState must be >= HAVE_CURRENT_DATA");
       // bug 962949
       // is(v.networkState, v.NETWORK_IDLE, "(14) NetworkState must be NETWORK_IDLE");
       v.play();
     },
-    
+
     ended:
     function(e) {
       ok(true, "(14) Got playback ended");
       var v = e.target;
       maybeFinish(v, 14);
     },
 
     setup:
@@ -446,93 +472,103 @@ var tests = [
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("ended", this.ended, false);
       v.addEventListener("loadeddata", this.loadeddata, false);
       v.src = test.name;
       document.body.appendChild(v); // Causes implicit load, which will be halted after
                                      // metadata due to preload:metadata.
     },
+
+    name: "test14",
   },
   {
     // 15. Autoplay should override preload:none.
     ended:
     function(e) {
       ok(true, "(15) Got playback ended.");
       var v = e.target;
       maybeFinish(v, 15);
     },
-    
+
     setup:
     function(v) {
       v._gotLoadStart = false;
       v._gotLoadedMetaData = false;
       v.preload = "none";
       v.autoplay = true;
       v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("ended", this.ended, false);
       v.src = test.name; // Causes implicit load.
       document.body.appendChild(v);
     },
+
+    name: "test15",
   },
   {
     // 16. Autoplay should override preload:metadata.
     ended:
     function(e) {
       ok(true, "(16) Got playback ended.");
       var v = e.target;
       maybeFinish(v, 16);
     },
-    
+
     setup:
     function(v) {
       v.preload = "metadata";
       v.autoplay = true;
       v.addEventListener("ended", this.ended, false);
       v.src = test.name; // Causes implicit load.
       document.body.appendChild(v);
     },
+
+    name: "test16",
   },
   {
     // 17. On a preload:none video, adding autoplay should disable preload none, i.e. don't break autoplay!
     ended:
     function(e) {
       ok(true, "(17) Got playback ended.");
       var v = e.target;
       maybeFinish(v, 17);
     },
-    
+
     setup:
     function(v) {
       v.addEventListener("ended", this.ended, false);
       v.preload = "none";
       document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none.
       v.autoplay = true;
       v.src = test.name;
-    },    
+    },
+
+    name: "test17",
   },
   {
     // 18. On a preload='none' video, call play() before load algorithms's sync
     // has run, the play() call should override preload='none'.
     ended:
     function(e) {
       ok(true, "(18) Got playback ended.");
       var v = e.target;
       maybeFinish(v, 18);
     },
-    
+
     setup:
     function(v) {
       v.addEventListener("ended", this.ended, false);
       v.preload = "none";
       v.src = test.name; // Schedules async section to continue load algorithm.
       document.body.appendChild(v);
       v.play(); // Should cause preload:none to be overridden.
-    },  
+    },
+
+    name: "test18",
   },
   {
     // 19. Set preload='auto' on first video source then switching preload='none' and swapping the video source to another.
     // The second video should not start playing as it's preload state has been changed to 'none' from 'auto'
     loadedmetadata: function(e) {
       var v = e.target;
       is(v.preload === "auto", true, "(19) preload is initially auto");
       setTimeout(function() {
@@ -550,24 +586,27 @@ var tests = [
     setup:
     function(v) {
       var that = this;
       v.preload = "auto";
       v.src = test.name;
       // add a listener for when the video has loaded, so we know preload auto has worked
       v.addEventListener( "loadedmetadata", this.loadedmetadata, false);
       document.body.appendChild(v);
-    }
+    },
+
+    name: "test19",
   }
 ];
 
 var iterationCount = 0;
 function startTest(test, token) {
   if (test == tests[0]) {
     ++iterationCount;
+    info("iterationCount=" + iterationCount);
   }
   if (iterationCount == 2) {
     // Do this series of tests on logically different resources
     test.name = baseName + "?" + Math.floor(Math.random()*100000);
   }
   var v = document.createElement("video");
   v.token = token;
   test.setup(v);
--- a/content/media/test/test_referer.html
+++ b/content/media/test/test_referer.html
@@ -23,44 +23,55 @@ function checkComplete() {
   for (var i=0; i<media.length; ++i) {
     if (!media[i]._complete) {
       return;
     }
   }
 
   SimpleTest.finish();
 }
- 
+
+function removeNode(v) {
+  v.removeEventListener("error", loadError, false);
+  v.removeEventListener("loadedmetadata", loadedMetadata, false);
+  v.remove();
+  v.src = "";
+}
+
 function loadError(evt) {
   // If no referer is sent then the sjs returns an error
   ok(false, "check referer is sent with media request");
   evt.target._complete = true;
   checkComplete();
+  removeNode(evt.target);
 }
 
 function loadedMetadata(evt) {
   // If a referer is sent then the sjs returns a valid media
   ok(true, "check referer is sent with media request");
   evt.target._complete = true;
   checkComplete();
+  removeNode(evt.target);
 }
 
 // Create all media objects.
 for (var i=0; i<gSmallTests.length; ++i) {
   var test = gSmallTests[i];
   var type;
   if (/^video/.test(test.type)) {
     type = "video"
   } else {
     type = "audio";
   }
   var v = document.createElement(type);
   if (!v.canPlayType(test.type)) {
     continue;
   }
+  // ensure metadata is loaded for default preload is none on b2g 
+  v.preload = "metadata";
   v._complete = false;
   v.addEventListener("error", loadError, false);
   v.addEventListener("loadedmetadata", loadedMetadata, false);
   v.src = 'referer.sjs?name=' + test.name + '&type=' + test.type;
   document.body.appendChild(v); // Will start load.
   media.push(v);
 }
 
--- a/dom/mobilemessage/interfaces/nsIRilMobileMessageDatabaseService.idl
+++ b/dom/mobilemessage/interfaces/nsIRilMobileMessageDatabaseService.idl
@@ -20,17 +20,26 @@ interface nsIRilMobileMessageDatabaseRec
 {
   /**
    * |aMessageRecord| Object: the mobile-message database record
    * |aDomMessage|: the nsIDOMMoz{Mms,Sms}Message. Noted, this value might be null.
    */
   void notify(in nsresult aRv, in jsval aMessageRecord, in nsISupports aDomMessage);
 };
 
-[scriptable, uuid(596ef782-9f7a-11e3-8508-ff3a7d599531)]
+[scriptable, function, uuid(1b0ff03c-a2bc-11e3-a443-838d034c9805)]
+interface nsIRilMobileMessageDatabaseConcatenationCallback : nsISupports
+{
+  /**
+   * |aCompleteMessage|: jsval: the completely concatenated message. Noted, this value might be null.
+   */
+  void notify(in nsresult aRv, in jsval aCompleteMessage);
+};
+
+[scriptable, uuid(0b437a5c-a2bc-11e3-bd1b-dbb173eb35f8)]
 interface nsIRilMobileMessageDatabaseService : nsIMobileMessageDatabaseService
 {
   /**
    * |aMessage| Object: should contain the following properties for internal use:
    *   - |type| DOMString: "sms" or "mms"
    *   - |timestamp| Number: the timestamp of received message
    *   - |iccId| DOMString: [optional] the ICC ID of the SIM for receiving
    *                        message if available.
@@ -121,9 +130,17 @@ interface nsIRilMobileMessageDatabaseSer
                                        in nsIRilMobileMessageDatabaseRecordCallback aCallback);
 
   /**
    * |aCrError| nsresult: the NS_ERROR defined in Components.results.
    *
    * @returns the error code defined in nsIMobileMessageCallback
    */
   jsval translateCrErrorToMessageCallbackError(in nsresult aCrError);
+
+  /**
+   * |aSmsSegment| jsval: Decoded Single SMS PDU.
+   * |aCallback| nsIRilMobileMessageDatabaseConcatenationCallback: a callback which
+   *   takes result flag, and complete mesage as parameters.
+   */
+  void saveSmsSegment(in jsval aSmsSegment,
+                      in nsIRilMobileMessageDatabaseConcatenationCallback aCallback);
 };
--- a/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
+++ b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
@@ -18,21 +18,22 @@ const RIL_GETMESSAGESCURSOR_CID =
   Components.ID("{484d1ad8-840e-4782-9dc4-9ebc4d914937}");
 const RIL_GETTHREADSCURSOR_CID =
   Components.ID("{95ee7c3e-d6f2-4ec4-ade5-0c453c036d35}");
 
 const DEBUG = false;
 const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
 
 
-const DB_VERSION = 21;
+const DB_VERSION = 22;
 const MESSAGE_STORE_NAME = "sms";
 const THREAD_STORE_NAME = "thread";
 const PARTICIPANT_STORE_NAME = "participant";
 const MOST_RECENT_STORE_NAME = "most-recent";
+const SMS_SEGMENT_STORE_NAME = "sms-segment";
 
 const DELIVERY_SENDING = "sending";
 const DELIVERY_SENT = "sent";
 const DELIVERY_RECEIVED = "received";
 const DELIVERY_NOT_DOWNLOADED = "not-downloaded";
 const DELIVERY_ERROR = "error";
 
 const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
@@ -235,16 +236,20 @@ MobileMessageDB.prototype = {
             if (DEBUG) debug("Upgrade to version 20. Add readStatus and readTimestamp.");
             self.upgradeSchema19(event.target.transaction, next);
             break;
           case 20:
             if (DEBUG) debug("Upgrade to version 21. Add sentTimestamp.");
             self.upgradeSchema20(event.target.transaction, next);
             break;
           case 21:
+            if (DEBUG) debug("Upgrade to version 22. Add sms-segment store.");
+            self.upgradeSchema21(db, event.target.transaction, next);
+            break;
+          case 22:
             // This will need to be moved for each new version
             if (DEBUG) debug("Upgrade finished.");
             break;
           default:
             event.target.transaction.abort();
             if (DEBUG) debug("unexpected db version: " + event.oldVersion);
             callback(Cr.NS_ERROR_FAILURE, null);
             break;
@@ -1339,16 +1344,75 @@ MobileMessageDB.prototype = {
         messageRecord.sentTimestamp = messageRecord.headers["date"].getTime();
       }
 
       cursor.update(messageRecord);
       cursor.continue();
     };
   },
 
+  /**
+   * Add smsSegmentStore to store uncomplete SMS segments.
+   */
+  upgradeSchema21: function(db, transaction, next) {
+    /**
+     * This smsSegmentStore is used to store uncomplete SMS segments.
+     * Each entry looks like this:
+     *
+     * {
+     *   [Common fields in SMS segment]
+     *   messageType: <Number>,
+     *   teleservice: <Number>,
+     *   SMSC: <String>,
+     *   sentTimestamp: <Number>,
+     *   timestamp: <Number>,
+     *   sender: <String>,
+     *   pid: <Number>,
+     *   encoding: <Number>,
+     *   messageClass: <String>,
+     *   iccId: <String>,
+     *
+     *   [Concatenation Info]
+     *   segmentRef: <Number>,
+     *   segmentSeq: <Number>,
+     *   segmentMaxSeq: <Number>,
+     *
+     *   [Application Port Info]
+     *   originatorPort: <Number>,
+     *   destinationPort: <Number>,
+     *
+     *   [MWI status]
+     *   mwiPresent: <Boolean>,
+     *   mwiDiscard: <Boolean>,
+     *   mwiMsgCount: <Number>,
+     *   mwiActive: <Boolean>,
+     *
+     *   [CDMA Cellbroadcast related fields]
+     *   serviceCategory: <Number>,
+     *   language: <String>,
+     *
+     *   [Message Body]
+     *   data: <Uint8Array>, (available if it's 8bit encoding)
+     *   body: <String>, (normal text body)
+     *
+     *   [Handy fields created by DB for concatenation]
+     *   id: <Number>, keypath of this objectStore.
+     *   hash: <String>, Use to identify the segments to the same SMS.
+     *   receivedSegments: <Number>,
+     *   segments: []
+     * }
+     *
+     */
+    let smsSegmentStore = db.createObjectStore(SMS_SEGMENT_STORE_NAME,
+                                               { keyPath: "id",
+                                                 autoIncrement: true });
+    smsSegmentStore.createIndex("hash", "hash", { unique: true });
+    next();
+  },
+
   matchParsedPhoneNumbers: function(addr1, parsedAddr1, addr2, parsedAddr2) {
     if ((parsedAddr1.internationalNumber &&
          parsedAddr1.internationalNumber === parsedAddr2.internationalNumber) ||
         (parsedAddr1.nationalNumber &&
          parsedAddr1.nationalNumber === parsedAddr2.nationalNumber)) {
       return true;
     }
 
@@ -2423,16 +2487,138 @@ MobileMessageDB.prototype = {
         return Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR;
       case Cr.NS_ERROR_FILE_NO_DEVICE_SPACE:
         return Ci.nsIMobileMessageCallback.STORAGE_FULL_ERROR;
       default:
         return Ci.nsIMobileMessageCallback.INTERNAL_ERROR;
     }
   },
 
+  saveSmsSegment: function(aSmsSegment, aCallback) {
+    let completeMessage = null;
+    this.newTxn(READ_WRITE, function(error, txn, segmentStore) {
+      if (error) {
+        if (DEBUG) debug(error);
+        aCallback.notify(error, null);
+        return;
+      }
+
+      txn.oncomplete = function oncomplete(event) {
+        if (DEBUG) debug("Transaction " + txn + " completed.");
+        if (completeMessage) {
+          // Rebuild full body
+          if (completeMessage.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+            // Uint8Array doesn't have `concat`, so
+            // we have to merge all segements by hand.
+            let fullDataLen = 0;
+            for (let i = 1; i <= completeMessage.segmentMaxSeq; i++) {
+              fullDataLen += completeMessage.segments[i].length;
+            }
+
+            completeMessage.fullData = new Uint8Array(fullDataLen);
+            for (let d = 0, i = 1; i <= completeMessage.segmentMaxSeq; i++) {
+              let data = completeMessage.segments[i];
+              for (let j = 0; j < data.length; j++) {
+                completeMessage.fullData[d++] = data[j];
+              }
+            }
+          } else {
+            completeMessage.fullBody = completeMessage.segments.join("");
+          }
+
+          // Remove handy fields after completing the concatenation.
+          delete completeMessage.id;
+          delete completeMessage.hash;
+          delete completeMessage.receivedSegments;
+          delete completeMessage.segments;
+        }
+        aCallback.notify(Cr.NS_OK, completeMessage);
+      };
+
+      txn.onabort = function onerror(event) {
+        if (DEBUG) debug("Caught error on transaction", event.target.errorCode);
+        aCallback.notify(Cr.NS_ERROR_FAILURE, null, null);
+      };
+
+      aSmsSegment.hash = aSmsSegment.sender + ":" +
+                         aSmsSegment.segmentRef + ":" +
+                         aSmsSegment.segmentMaxSeq + ":" +
+                         aSmsSegment.iccId;
+      let seq = aSmsSegment.segmentSeq;
+      if (DEBUG) {
+        debug("Saving SMS Segment: " + aSmsSegment.hash + ", seq: " + seq);
+      }
+      let getRequest = segmentStore.index("hash").get(aSmsSegment.hash);
+      getRequest.onsuccess = function(event) {
+        let segmentRecord = event.target.result;
+        if (!segmentRecord) {
+          if (DEBUG) {
+            debug("Not found! Create a new record to store the segments.");
+          }
+          aSmsSegment.receivedSegments = 1;
+          aSmsSegment.segments = [];
+          if (aSmsSegment.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+            aSmsSegment.segments[seq] = aSmsSegment.data;
+          } else {
+            aSmsSegment.segments[seq] = aSmsSegment.body;
+          }
+
+          segmentStore.add(aSmsSegment);
+
+          return;
+        }
+
+        if (DEBUG) {
+          debug("Append SMS Segment into existed message object: " + segmentRecord.id);
+        }
+
+        if (segmentRecord.segments[seq]) {
+          if (DEBUG) debug("Got duplicated segment no. " + seq);
+          return;
+        }
+
+        segmentRecord.timestamp = aSmsSegment.timestamp;
+
+        if (segmentRecord.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+          segmentRecord.segments[seq] = aSmsSegment.data;
+        } else {
+          segmentRecord.segments[seq] = aSmsSegment.body;
+        }
+        segmentRecord.receivedSegments++;
+
+        // The port information is only available in 1st segment for CDMA WAP Push.
+        // If the segments of a WAP Push are not received in sequence
+        // (e.g., SMS with seq == 1 is not the 1st segment received by the device),
+        // we have to retrieve the port information from 1st segment and
+        // save it into the segmentRecord.
+        if (aSmsSegment.teleservice === RIL.PDU_CDMA_MSG_TELESERIVCIE_ID_WAP
+            && seq === 1) {
+          if (aSmsSegment.originatorPort) {
+            segmentRecord.originatorPort = aSmsSegment.originatorPort;
+          }
+
+          if (aSmsSegment.destinationPort) {
+            segmentRecord.destinationPort = aSmsSegment.destinationPort;
+          }
+        }
+
+        if (segmentRecord.receivedSegments < segmentRecord.segmentMaxSeq) {
+          if (DEBUG) debug("Message is incomplete.");
+          segmentStore.put(segmentRecord);
+          return;
+        }
+
+        completeMessage = segmentRecord;
+
+        // Delete Record in DB
+        segmentStore.delete(segmentRecord.id);
+      };
+    }, [SMS_SEGMENT_STORE_NAME]);
+  },
+
   /**
    * nsIMobileMessageDatabaseService API
    */
 
   getMessage: function(aMessageId, aRequest) {
     if (DEBUG) debug("Retrieving message with ID " + aMessageId);
     let self = this;
     let notifyCallback = {
--- a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
@@ -94,16 +94,20 @@ MobileMessageDatabaseService.prototype =
   getMessageRecordById: function(aMessageId, aCallback) {
     this.mmdb.getMessageRecordById(aMessageId, aCallback);
   },
 
   translateCrErrorToMessageCallbackError: function(aCrError) {
     return this.mmdb.translateCrErrorToMessageCallbackError(aCrError);
   },
 
+  saveSmsSegment: function(aSmsSegment, aCallback) {
+    this.mmdb.saveSmsSegment(aSmsSegment, aCallback);
+  },
+
   /**
    * nsIMobileMessageDatabaseService API
    */
 
   getMessage: function(aMessageId, aRequest) {
     this.mmdb.getMessage(aMessageId, aRequest);
   },
 
--- a/dom/mobilemessage/tests/marionette/head.js
+++ b/dom/mobilemessage/tests/marionette/head.js
@@ -41,16 +41,41 @@ function ensureMobileMessage() {
       deferred.reject();
     }
   });
 
   return deferred.promise;
 }
 
 /**
+ * Wait for one named MobileMessageManager event.
+ *
+ * Resolve if that named event occurs.  Never reject.
+ *
+ * Fulfill params: the DOMEvent passed.
+ *
+ * @param aEventName
+ *        A string event name.
+ *
+ * @return A deferred promise.
+ */
+function waitForManagerEvent(aEventName) {
+  let deferred = Promise.defer();
+
+  manager.addEventListener(aEventName, function onevent(aEvent) {
+    manager.removeEventListener(aEventName, onevent);
+
+    ok(true, "MobileMessageManager event '" + aEventName + "' got.");
+    deferred.resolve(aEvent);
+  });
+
+  return deferred.promise;
+}
+
+/**
  * Send a SMS message to a single receiver.  Resolve if it succeeds, reject
  * otherwise.
  *
  * Fulfill params:
  *   message -- the sent SmsMessage.
  *
  * Reject params:
  *   error -- a DOMError.
@@ -326,16 +351,19 @@ function runEmulatorCmdSafe(aCommand) {
 function sendTextSmsToEmulator(aFrom, aText) {
   let command = "sms send " + aFrom + " " + aText;
   return runEmulatorCmdSafe(command);
 }
 
 /**
  * Send raw SMS TPDU to emulator.
  *
+ * @param: aPdu
+ *         A hex string representing the whole SMS T-PDU.
+ *
  * Fulfill params:
  *   result -- an array of emulator response lines.
  *
  * Reject params:
  *   result -- an array of emulator response lines.
  *
  * @return A deferred promise.
  */
--- a/dom/mobilemessage/tests/marionette/manifest.ini
+++ b/dom/mobilemessage/tests/marionette/manifest.ini
@@ -39,8 +39,9 @@ qemu = true
 [test_smsc_address.js]
 [test_dsds_default_service_id.js]
 [test_thread_subject.js]
 [test_mmdb_new.js]
 [test_mmdb_setmessagedeliverybyid_sms.js]
 [test_mmdb_foreachmatchedmmsdeliveryinfo.js]
 [test_mmdb_full_storage.js]
 [test_replace_short_message_type.js]
+[test_mt_sms_concatenation.js]
new file mode 100644
--- /dev/null
+++ b/dom/mobilemessage/tests/marionette/test_mt_sms_concatenation.js
@@ -0,0 +1,138 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const PDU_SMSC_NONE = "00"; // no SMSC Address
+
+const PDU_FIRST_OCTET = "40"; // RP:no, UDHI:yes, SRI:no, MMS:no, MTI:SMS-DELIVER
+
+const PDU_SENDER = "0A912143658709"; // +1234567890
+const SENDER = "+1234567890";
+
+const PDU_PID_NORMAL = "00";
+
+const PDU_DCS_NORMAL_UCS2 = "08";
+const PDU_DCS_CLASS0_UCS2 = "18";
+const PDU_DCS_NORMAL_8BIT = "04";
+const PDU_DCS_CLASS0_8BIT = "14";
+
+const PDU_TIMESTAMP = "00101000000000"; // 2000/01/01
+
+function byteValueToHexString(aValue) {
+  let str = Number(aValue).toString(16).toUpperCase();
+  return str.length == 1 ? "0" + str : str;
+}
+
+let ref_num = 0;
+function buildTextPdus(aDcs) {
+  ref_num++;
+
+  let IEI_CONCATE_1 = "0003" + byteValueToHexString(ref_num) + "0301";
+  let IEI_CONCATE_2 = "0003" + byteValueToHexString(ref_num) + "0302";
+  let IEI_CONCATE_3 = "0003" + byteValueToHexString(ref_num) + "0303";
+  let PDU_UDL = "08"; // UDHL(1) + UDH(5) + UCS2 Char (2)
+  let PDU_UDHL = "05";
+
+  let PDU_UD_A = "0041"; // "A"
+  let PDU_UD_B = "0042"; // "B"
+  let PDU_UD_C = "0043"; // "C"
+
+  let PDU_COMMON = PDU_SMSC_NONE + PDU_FIRST_OCTET + PDU_SENDER +
+    PDU_PID_NORMAL + aDcs + PDU_TIMESTAMP + PDU_UDL + PDU_UDHL;
+
+  return [
+    PDU_COMMON + IEI_CONCATE_1 + PDU_UD_A,
+    PDU_COMMON + IEI_CONCATE_2 + PDU_UD_B,
+    PDU_COMMON + IEI_CONCATE_3 + PDU_UD_C
+  ];
+}
+
+function buildBinaryPdus(aDcs) {
+  ref_num++;
+  let IEI_PORT = "05040B8423F0";
+
+  let PDU_DATA1 = "C106316170706C69636174696F6E2F76" +
+                  "6E642E7761702E6D6D732D6D65737361" +
+                  "676500B131302E382E3133302E313800" +
+                  "AF84B4818C82986B4430595538595347" +
+                  "77464E446741416B4876736C58303141" +
+                  "41414141414141008D90890380310096" +
+                  "05EA4D4D53008A808E02024188058103" +
+                  "015F9083687474703A2F2F6D6D732E65";
+
+  let PDU_DATA2 = "6D6F6D652E6E65743A383030322F6B44" +
+                  "3059553859534777464E446741416B48" +
+                  "76736C583031414141414141414100";
+
+  let PDU_COMMON = PDU_SMSC_NONE + PDU_FIRST_OCTET + PDU_SENDER +
+    PDU_PID_NORMAL + aDcs + PDU_TIMESTAMP;
+
+  function construstBinaryUserData(aBinaryData, aSeqNum) {
+    let ieiConcat = "0003" + byteValueToHexString(ref_num) + "02" +
+                    byteValueToHexString(aSeqNum);
+
+    let udh = IEI_PORT + ieiConcat;
+    let udhl = byteValueToHexString(udh.length / 2);
+    let ud = udhl + udh + aBinaryData;
+    let udl = byteValueToHexString(ud.length / 2);
+
+    return udl + ud;
+  }
+
+  return [
+    PDU_COMMON + construstBinaryUserData(PDU_DATA1, 1),
+    PDU_COMMON + construstBinaryUserData(PDU_DATA2, 2)
+  ];
+}
+
+function sendRawSmsAndWait(aPdus) {
+  let promises = [];
+
+  promises.push(waitForManagerEvent("received"));
+  for (let pdu of aPdus) {
+    promises.push(sendRawSmsToEmulator(pdu));
+  }
+
+  return Promise.all(promises);
+}
+
+function verifyTextMessage(aMessage, aMessageClass) {
+  is(aMessage.messageClass, aMessageClass, "SmsMessage class");
+  is(aMessage.sender, SENDER, "SmsMessage sender");
+  is(aMessage.body, "ABC", "SmsMessage body");
+}
+
+function verifyBinaryMessage(aMessage) {
+  is(aMessage.type, "mms", "MmsMessage type");
+  is(aMessage.delivery, "not-downloaded", "MmsMessage delivery");
+
+  // remove duplicated M-Notification.ind for next test.
+  return deleteMessagesById([aMessage.id]);
+}
+
+function testText(aDcs, aClass) {
+  log("testText(): aDcs = " + aDcs + ", aClass = " + aClass);
+  return sendRawSmsAndWait(buildTextPdus(aDcs))
+    .then((resolutions) => verifyTextMessage(resolutions[0].message, aClass));
+}
+
+function testBinary(aDcs) {
+  log("testBinary(): aDcs = " + aDcs);
+  return sendRawSmsAndWait(buildBinaryPdus(aDcs))
+    .then((resolutions) => verifyBinaryMessage(resolutions[0].message));
+}
+
+SpecialPowers.pushPrefEnv(
+  {"set": [["dom.mms.retrieval_mode", "manual"]]},
+  function startTest() {
+    startTestCommon(function testCaseMain() {
+      return Promise.resolve()
+        .then(() => testText(PDU_DCS_NORMAL_UCS2, "normal"))
+        .then(() => testText(PDU_DCS_CLASS0_UCS2, "class-0"))
+        .then(() => testBinary(PDU_DCS_NORMAL_8BIT))
+        .then(() => testBinary(PDU_DCS_CLASS0_8BIT));
+    });
+  }
+);
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -1900,16 +1900,18 @@ function RadioInterface(aClientId, aWork
   Services.obs.addObserver(this, kScreenStateChangedTopic, false);
 
   Services.obs.addObserver(this, kNetworkConnStateChangedTopic, false);
   Services.prefs.addObserver(kPrefCellBroadcastDisabled, this, false);
 
   this.portAddressedSmsApps = {};
   this.portAddressedSmsApps[WAP.WDP_PORT_PUSH] = this.handleSmsWdpPortPush.bind(this);
 
+  this._receivedSmsSegmentsMap = {};
+
   this._sntp = new Sntp(this.setClockBySntp.bind(this),
                         Services.prefs.getIntPref("network.sntp.maxRetryCount"),
                         Services.prefs.getIntPref("network.sntp.refreshPeriod"),
                         Services.prefs.getIntPref("network.sntp.timeout"),
                         Services.prefs.getCharPref("network.sntp.pools").split(";"),
                         Services.prefs.getIntPref("network.sntp.port"));
 }
 
@@ -2202,23 +2204,18 @@ RadioInterface.prototype = {
         break;
       case "cardstatechange":
         this.rilContext.cardState = message.cardState;
         gRadioEnabledController.receiveCardState(this.clientId);
         gMessageManager.sendIccMessage("RIL:CardStateChanged",
                                        this.clientId, message);
         break;
       case "sms-received":
-        let ackOk = this.handleSmsReceived(message);
-        // Note: ACK has been done by modem for NEW_SMS_ON_SIM
-        if (ackOk && message.simStatus === undefined) {
-          this.workerMessenger.send("ackSMS", { result: RIL.PDU_FCS_OK });
-        }
-        return;
-      case "broadcastsms-received":
+        this.handleSmsMultipart(message);
+        break;
       case "cellbroadcast-received":
         message.timestamp = Date.now();
         gMessageManager.sendCellBroadcastMessage("RIL:CellBroadcastReceived",
                                                  this.clientId, message);
         break;
       case "nitzTime":
         this.handleNitzTime(message);
         break;
@@ -2714,19 +2711,19 @@ RadioInterface.prototype = {
                    " Drop!");
       }
       return;
     }
 
     let options = {
       bearer: WAP.WDP_BEARER_GSM_SMS_GSM_MSISDN,
       sourceAddress: message.sender,
-      sourcePort: message.header.originatorPort,
+      sourcePort: message.originatorPort,
       destinationAddress: this.rilContext.iccInfo.msisdn,
-      destinationPort: message.header.destinationPort,
+      destinationPort: message.destinationPort,
       serviceId: this.clientId
     };
     WAP.WapPushManager.receiveWdpPDU(message.fullData, message.fullData.length,
                                      0, options);
   },
 
   /**
    * A helper to broadcast the system message to launch registered apps
@@ -2762,75 +2759,291 @@ RadioInterface.prototype = {
   },
 
   // The following attributes/functions are used for acquiring/releasing the
   // CPU wake lock when the RIL handles the received SMS. Note that we need
   // a timer to bound the lock's life cycle to avoid exhausting the battery.
   _smsHandledWakeLock: null,
   _smsHandledWakeLockTimer: null,
 
-  _releaseSmsHandledWakeLock: function() {
-    if (DEBUG) this.debug("Releasing the CPU wake lock for handling SMS.");
-    if (this._smsHandledWakeLockTimer) {
-      this._smsHandledWakeLockTimer.cancel();
-    }
-    if (this._smsHandledWakeLock) {
-      this._smsHandledWakeLock.unlock();
-      this._smsHandledWakeLock = null;
-    }
-  },
-
-  portAddressedSmsApps: null,
-  handleSmsReceived: function(message) {
-    if (DEBUG) this.debug("handleSmsReceived: " + JSON.stringify(message));
-
-    // We need to acquire a CPU wake lock to avoid the system falling into
-    // the sleep mode when the RIL handles the received SMS.
+  _acquireSmsHandledWakeLock: function() {
     if (!this._smsHandledWakeLock) {
       if (DEBUG) this.debug("Acquiring a CPU wake lock for handling SMS.");
       this._smsHandledWakeLock = gPowerManagerService.newWakeLock("cpu");
     }
     if (!this._smsHandledWakeLockTimer) {
       if (DEBUG) this.debug("Creating a timer for releasing the CPU wake lock.");
       this._smsHandledWakeLockTimer =
         Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     }
     if (DEBUG) this.debug("Setting the timer for releasing the CPU wake lock.");
     this._smsHandledWakeLockTimer
         .initWithCallback(this._releaseSmsHandledWakeLock.bind(this),
                           SMS_HANDLED_WAKELOCK_TIMEOUT,
                           Ci.nsITimer.TYPE_ONE_SHOT);
-
-    // FIXME: Bug 737202 - Typed arrays become normal arrays when sent to/from workers
-    if (message.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
-      message.fullData = new Uint8Array(message.fullData);
+  },
+
+  _releaseSmsHandledWakeLock: function() {
+    if (DEBUG) this.debug("Releasing the CPU wake lock for handling SMS.");
+    if (this._smsHandledWakeLockTimer) {
+      this._smsHandledWakeLockTimer.cancel();
+    }
+    if (this._smsHandledWakeLock) {
+      this._smsHandledWakeLock.unlock();
+      this._smsHandledWakeLock = null;
+    }
+  },
+
+  /**
+   * Hash map for received multipart sms fragments. Messages are hashed with
+   * its sender address and concatenation reference number. Three additional
+   * attributes `segmentMaxSeq`, `receivedSegments`, `segments` are inserted.
+   */
+  _receivedSmsSegmentsMap: null,
+
+  /**
+   * Helper for processing received multipart SMS.
+   *
+   * @return null for handled segments, and an object containing full message
+   *         body/data once all segments are received.
+   */
+  _processReceivedSmsSegment: function(aSegment) {
+
+    // Directly replace full message body for single SMS.
+    if (!(aSegment.segmentMaxSeq && (aSegment.segmentMaxSeq > 1))) {
+      if (aSegment.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+        aSegment.fullData = aSegment.data;
+      } else {
+        aSegment.fullBody = aSegment.body;
+      }
+      return aSegment;
+    }
+
+    // Handle Concatenation for Class 0 SMS
+    let hash = aSegment.sender + ":" +
+               aSegment.segmentRef + ":" +
+               aSegment.segmentMaxSeq;
+    let seq = aSegment.segmentSeq;
+
+    let options = this._receivedSmsSegmentsMap[hash];
+    if (!options) {
+      options = aSegment;
+      this._receivedSmsSegmentsMap[hash] = options;
+
+      options.receivedSegments = 0;
+      options.segments = [];
+    } else if (options.segments[seq]) {
+      // Duplicated segment?
+      if (DEBUG) {
+        this.debug("Got duplicated segment no." + seq +
+                           " of a multipart SMS: " + JSON.stringify(aSegment));
+      }
+      return null;
+    }
+
+    if (options.receivedSegments > 0) {
+      // Update received timestamp.
+      options.timestamp = aSegment.timestamp;
+    }
+
+    if (options.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+      options.segments[seq] = aSegment.data;
+    } else {
+      options.segments[seq] = aSegment.body;
+    }
+    options.receivedSegments++;
+
+    // The port information is only available in 1st segment for CDMA WAP Push.
+    // If the segments of a WAP Push are not received in sequence
+    // (e.g., SMS with seq == 1 is not the 1st segment received by the device),
+    // we have to retrieve the port information from 1st segment and
+    // save it into the cached options.
+    if (aSegment.teleservice === RIL.PDU_CDMA_MSG_TELESERIVCIE_ID_WAP
+        && seq === 1) {
+      if (!options.originatorPort && aSegment.originatorPort) {
+        options.originatorPort = aSegment.originatorPort;
+      }
+
+      if (!options.destinationPort && aSegment.destinationPort) {
+        options.destinationPort = aSegment.destinationPort;
+      }
+    }
+
+    if (options.receivedSegments < options.segmentMaxSeq) {
+      if (DEBUG) {
+        this.debug("Got segment no." + seq + " of a multipart SMS: " +
+                           JSON.stringify(options));
+      }
+      return null;
+    }
+
+    // Remove from map
+    delete this._receivedSmsSegmentsMap[hash];
+
+    // Rebuild full body
+    if (options.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+      // Uint8Array doesn't have `concat`, so we have to merge all segements
+      // by hand.
+      let fullDataLen = 0;
+      for (let i = 1; i <= options.segmentMaxSeq; i++) {
+        fullDataLen += options.segments[i].length;
+      }
+
+      options.fullData = new Uint8Array(fullDataLen);
+      for (let d= 0, i = 1; i <= options.segmentMaxSeq; i++) {
+        let data = options.segments[i];
+        for (let j = 0; j < data.length; j++) {
+          options.fullData[d++] = data[j];
+        }
+      }
+    } else {
+      options.fullBody = options.segments.join("");
+    }
+
+    // Remove handy fields after completing the concatenation.
+    delete options.receivedSegments;
+    delete options.segments;
+
+    if (DEBUG) {
+      this.debug("Got full multipart SMS: " + JSON.stringify(options));
+    }
+
+    return options;
+  },
+
+  /**
+   * Helper to create Savable SmsSegment.
+   */
+  _createSavableSmsSegment: function(aMessage) {
+    // We precisely define what data fields to be stored into
+    // DB here for better data migration.
+    let segment = {};
+    segment.messageType = aMessage.messageType;
+    segment.teleservice = aMessage.teleservice;
+    segment.SMSC = aMessage.SMSC;
+    segment.sentTimestamp = aMessage.sentTimestamp;
+    segment.timestamp = Date.now();
+    segment.sender = aMessage.sender;
+    segment.pid = aMessage.pid;
+    segment.encoding = aMessage.encoding;
+    segment.messageClass = aMessage.messageClass;
+    segment.iccId = this.getIccId();
+    if (aMessage.header) {
+      segment.segmentRef = aMessage.header.segmentRef;
+      segment.segmentSeq = aMessage.header.segmentSeq;
+      segment.segmentMaxSeq = aMessage.header.segmentMaxSeq;
+      segment.originatorPort = aMessage.header.originatorPort;
+      segment.destinationPort = aMessage.header.destinationPort;
+    }
+    segment.mwiPresent = (aMessage.mwi)? true: false;
+    segment.mwiDiscard = (segment.mwiPresent)? aMessage.mwi.discard: false;
+    segment.mwiMsgCount = (segment.mwiPresent)? aMessage.mwi.msgCount: 0;
+    segment.mwiActive = (segment.mwiPresent)? aMessage.mwi.active: false;
+    segment.serviceCategory = aMessage.serviceCategory;
+    segment.language = aMessage.language;
+    segment.data = aMessage.data;
+    segment.body = aMessage.body;
+
+    return segment;
+  },
+
+  /**
+   * Helper to purge complete message.
+   *
+   * We remove unnessary fields defined in _createSavableSmsSegment() after
+   * completing the concatenation.
+   */
+  _purgeCompleteSmsMessage: function(aMessage) {
+    // Purge concatenation info
+    delete aMessage.segmentRef;
+    delete aMessage.segmentSeq;
+    delete aMessage.segmentMaxSeq;
+
+    // Purge partial message body
+    delete aMessage.data;
+    delete aMessage.body;
+  },
+
+  /**
+   * handle concatenation of received SMS.
+   */
+  handleSmsMultipart: function(aMessage) {
+    if (DEBUG) this.debug("handleSmsMultipart: " + JSON.stringify(aMessage));
+
+    this._acquireSmsHandledWakeLock();
+
+    let segment = this._createSavableSmsSegment(aMessage);
+
+    let isMultipart = (segment.segmentMaxSeq && (segment.segmentMaxSeq > 1));
+    let messageClass = segment.messageClass;
+
+    let handleReceivedAndAck = function(aRvOfIncompleteMsg, aCompleteMessage) {
+      if (aCompleteMessage) {
+        this._purgeCompleteSmsMessage(aCompleteMessage);
+        if (this.handleSmsReceived(aCompleteMessage)) {
+          this.sendAckSms(Cr.NS_OK, aCompleteMessage);
+        }
+        // else Ack will be sent after further process in handleSmsReceived.
+      } else {
+        this.sendAckSms(aRvOfIncompleteMsg, segment);
+      }
+    }.bind(this);
+
+    // No need to access SmsSegmentStore for Class 0 SMS and Single SMS.
+    if (!isMultipart ||
+        (messageClass == RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_0])) {
+      // `When a mobile terminated message is class 0 and the MS has the
+      // capability of displaying short messages, the MS shall display the
+      // message immediately and send an acknowledgement to the SC when the
+      // message has successfully reached the MS irrespective of whether
+      // there is memory available in the (U)SIM or ME. The message shall
+      // not be automatically stored in the (U)SIM or ME.`
+      // ~ 3GPP 23.038 clause 4
+
+      handleReceivedAndAck(Cr.NS_OK,  // ACK OK For Incomplete Class 0
+                           this._processReceivedSmsSegment(segment));
+    } else {
+      gMobileMessageDatabaseService
+        .saveSmsSegment(segment, function notifyResult(aRv, aCompleteMessage) {
+        handleReceivedAndAck(aRv,  // Ack according to the result after saving
+                             aCompleteMessage);
+      });
+    }
+  },
+
+  portAddressedSmsApps: null,
+  handleSmsReceived: function(message) {
+    if (DEBUG) this.debug("handleSmsReceived: " + JSON.stringify(message));
+
+    if (message.messageType == RIL.PDU_CDMA_MSG_TYPE_BROADCAST) {
+      gMessageManager.sendCellBroadcastMessage("RIL:CellBroadcastReceived",
+                                               this.clientId, message);
+      return true;
     }
 
     // Dispatch to registered handler if application port addressing is
     // available. Note that the destination port can possibly be zero when
     // representing a UDP/TCP port.
-    if (message.header && message.header.destinationPort != null) {
-      let handler = this.portAddressedSmsApps[message.header.destinationPort];
+    if (message.destinationPort != null) {
+      let handler = this.portAddressedSmsApps[message.destinationPort];
       if (handler) {
         handler(message);
       }
       return true;
     }
 
     if (message.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
       // Don't know how to handle binary data yet.
       return true;
     }
 
     message.type = "sms";
     message.sender = message.sender || null;
     message.receiver = this.getPhoneNumber();
     message.body = message.fullBody = message.fullBody || null;
-    message.timestamp = Date.now();
-    message.iccId = this.getIccId();
 
     if (gSmsService.isSilentNumber(message.sender)) {
       message.id = -1;
       message.threadId = 0;
       message.delivery = DOM_MOBILE_MESSAGE_DELIVERY_RECEIVED;
       message.deliveryStatus = RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS;
       message.read = false;
 
@@ -2850,41 +3063,40 @@ RadioInterface.prototype = {
                                                message.read);
 
       Services.obs.notifyObservers(domMessage,
                                    kSilentSmsReceivedObserverTopic,
                                    null);
       return true;
     }
 
-    let mwi = message.mwi;
-    if (mwi) {
+    if (message.mwiPresent) {
+      let mwi = {
+        discard: message.mwiDiscard,
+        msgCount: message.mwiMsgCount,
+        active: message.mwiActive
+      };
+      this.workerMessenger.send("updateMwis", { mwi: mwi });
+
       mwi.returnNumber = message.sender;
       mwi.returnMessage = message.fullBody;
       gMessageManager.sendVoicemailMessage("RIL:VoicemailNotification",
                                            this.clientId, mwi);
 
       // Dicarded MWI comes without text body.
       // Hence, we discard it here after notifying the MWI status.
-      if (mwi.discard) {
+      if (message.mwiDiscard) {
         return true;
       }
     }
 
     let notifyReceived = function notifyReceived(rv, domMessage) {
       let success = Components.isSuccessCode(rv);
 
-      // Acknowledge the reception of the SMS.
-      // Note: Ack has been done by modem for NEW_SMS_ON_SIM
-      if (message.simStatus === undefined) {
-        this.workerMessenger.send("ackSMS", {
-          result: (success ? RIL.PDU_FCS_OK
-                           : RIL.PDU_FCS_MEMORY_CAPACITY_EXCEEDED)
-        });
-      }
+      this.sendAckSms(rv, message);
 
       if (!success) {
         // At this point we could send a message to content to notify the user
         // that storing an incoming SMS failed, most likely due to a full disk.
         if (DEBUG) {
           this.debug("Could not store SMS, error code " + rv);
         }
         return;
@@ -2922,16 +3134,36 @@ RadioInterface.prototype = {
       notifyReceived(Cr.NS_OK, domMessage);
     }
 
     // SMS ACK will be sent in notifyReceived. Return false here.
     return false;
   },
 
   /**
+   * Handle ACK response of received SMS.
+   */
+  sendAckSms: function(aRv, aMessage) {
+    if (aMessage.messageClass === RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_2]) {
+      return;
+    }
+
+    let result = RIL.PDU_FCS_OK;
+    if (!Components.isSuccessCode(aRv)) {
+      if (DEBUG) this.debug("Failed to handle received sms: " + aRv);
+      result = (aRv === Cr.NS_ERROR_FILE_NO_DEVICE_SPACE)
+                ? RIL.PDU_FCS_MEMORY_CAPACITY_EXCEEDED
+                : RIL.PDU_FCS_UNSPECIFIED;
+    }
+
+    this.workerMessenger.send("ackSMS", { result: result });
+
+  },
+
+  /**
    * Set the setting value of "time.clock.automatic-update.available".
    */
   setClockAutoUpdateAvailable: function(value) {
     gSettingsService.createLock().set(kSettingsClockAutoUpdateAvailable, value, null,
                                       "fromInternalSetting");
   },
 
   /**
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -208,17 +208,16 @@ BufObject.prototype = {
  * and acts upon state changes accordingly.
  */
 function RilObject(aContext) {
   this.context = aContext;
 
   this.currentCalls = {};
   this.currentConference = {state: null, participants: {}};
   this.currentDataCalls = {};
-  this._receivedSmsSegmentsMap = {};
   this._pendingSentSmsMap = {};
   this.pendingNetworkType = {};
   this._receivedSmsCbPagesMap = {};
 
   // Init properties that are only initialized once.
   this.v5Legacy = RILQUIRKS_V5_LEGACY;
   this.cellBroadcastDisabled = RIL_CELLBROADCAST_DISABLED;
   this.clirMode = RIL_CLIR_MODE;
@@ -239,23 +238,16 @@ RilObject.prototype = {
   currentConference: null,
 
   /**
    * Existing data calls.
    */
   currentDataCalls: null,
 
   /**
-   * Hash map for received multipart sms fragments. Messages are hashed with
-   * its sender address and concatenation reference number. Three additional
-   * attributes `segmentMaxSeq`, `receivedSegments`, `segments` are inserted.
-   */
-  _receivedSmsSegmentsMap: null,
-
-  /**
    * Outgoing messages waiting for SMS-STATUS-REPORT.
    */
   _pendingSentSmsMap: null,
 
   /**
    * Index of the RIL_PREFERRED_NETWORK_TYPE_TO_GECKO. Its value should be
    * preserved over rild reset.
    */
@@ -373,17 +365,17 @@ RilObject.prototype = {
       this._handleDisconnectedCall(currentCall);
     }
 
     // Deactivate this.currentDataCalls: rild might have restarted.
     for each (let datacall in this.currentDataCalls) {
       this.deactivateDataCall(datacall);
     }
 
-    // Don't clean up this._receivedSmsSegmentsMap or this._pendingSentSmsMap
+    // Don't clean up this._pendingSentSmsMap
     // because on rild restart: we may continue with the pending segments.
 
     /**
      * Whether or not the multiple requests in requestNetworkInfo() are currently
      * being processed
      */
     this._processingNetworkInfo = false;
 
@@ -1809,16 +1801,25 @@ RilObject.prototype = {
   acknowledgeCdmaSms: function(success, cause) {
     let Buf = this.context.Buf;
     Buf.newParcel(REQUEST_CDMA_SMS_ACKNOWLEDGE);
     Buf.writeInt32(success ? 0 : 1);
     Buf.writeInt32(cause);
     Buf.sendParcel();
   },
 
+  /**
+   * Update received MWI into EF_MWIS.
+   */
+  updateMwis: function(options) {
+    if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
+      this.context.SimRecordHelper.updateMWIS(options.mwi);
+    }
+  },
+
   setCellBroadcastDisabled: function(options) {
     this.cellBroadcastDisabled = options.disabled;
 
     // If |this.mergedCellBroadcastConfig| is null, either we haven't finished
     // reading required SIM files, or no any channel is ever configured.  In
     // the former case, we'll call |this.updateCellBroadcastConfig()| later
     // with correct configs; in the latter case, we don't bother resetting CB
     // to disabled again.
@@ -4391,65 +4392,29 @@ RilObject.prototype = {
     }
     // Write 2 string delimitors for the total string length must be even.
     Buf.writeStringDelimiter(0);
 
     Buf.sendParcel();
   },
 
   /**
-   * Helper for processing multipart SMS.
+   * Helper to delegate the received sms segment to RadioInterface to process.
    *
    * @param message
    *        Received sms message.
    *
-   * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
+   * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK
    */
   _processSmsMultipart: function(message) {
-    if (message.header && message.header.segmentMaxSeq &&
-        (message.header.segmentMaxSeq > 1)) {
-      message = this._processReceivedSmsSegment(message);
-    } else {
-      if (message.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
-        message.fullData = message.data;
-        delete message.data;
-      } else {
-        message.fullBody = message.body;
-        delete message.body;
-      }
-    }
-
-    if (message) {
-      message.result = PDU_FCS_OK;
-      if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
-        // `MS shall ensure that the message has been to the SMS data field in
-        // the (U)SIM before sending an ACK to the SC.`  ~ 3GPP 23.038 clause 4
-        message.result = PDU_FCS_RESERVED;
-      }
-
-      if (message.messageType == PDU_CDMA_MSG_TYPE_BROADCAST) {
-        message.rilMessageType = "broadcastsms-received";
-      } else {
-        message.rilMessageType = "sms-received";
-      }
-
-      this.sendChromeMessage(message);
-
-      // Update MWI Status into ICC if present.
-      if (message.mwi &&
-          this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
-        this.context.SimRecordHelper.updateMWIS(message.mwi);
-      }
-
-      // We will acknowledge receipt of the SMS after we try to store it
-      // in the database.
-      return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
-    }
-
-    return PDU_FCS_OK;
+    message.rilMessageType = "sms-received";
+
+    this.sendChromeMessage(message);
+
+    return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
   },
 
   /**
    * Helper for processing SMS-STATUS-REPORT PDUs.
    *
    * @param length
    *        Length of SMS string in the incoming parcel.
    *
@@ -4599,105 +4564,16 @@ RilObject.prototype = {
     }
 
     message.data = message.data.subarray(index);
 
     return this._processSmsMultipart(message);
   },
 
   /**
-   * Helper for processing received multipart SMS.
-   *
-   * @return null for handled segments, and an object containing full message
-   *         body/data once all segments are received.
-   */
-  _processReceivedSmsSegment: function(original) {
-    let hash = original.sender + ":" + original.header.segmentRef;
-    let seq = original.header.segmentSeq;
-
-    let options = this._receivedSmsSegmentsMap[hash];
-    if (!options) {
-      options = original;
-      this._receivedSmsSegmentsMap[hash] = options;
-
-      options.segmentMaxSeq = original.header.segmentMaxSeq;
-      options.receivedSegments = 0;
-      options.segments = [];
-    } else if (options.segments[seq]) {
-      // Duplicated segment?
-      if (DEBUG) {
-        this.context.debug("Got duplicated segment no." + seq +
-                           " of a multipart SMS: " + JSON.stringify(original));
-      }
-      return null;
-    }
-
-    if (options.encoding == PDU_DCS_MSG_CODING_8BITS_A