merge fx-team to mozilla-central
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 23 Apr 2014 14:54:09 +0200
changeset 180129 b8b7b62bc9e90dc9ff0819533860117174b1492b
parent 180094 b747c35ac54b17258559111b0bc1ddee32f12c2d (current diff)
parent 180128 00d62bd62227c1f492071b3d993a9c7049a6cd04 (diff)
child 180148 b79c2995d25e5e2ed11ba1706a42a62ee07d35f3
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
milestone31.0a1
merge fx-team to mozilla-central
mobile/android/themes/core/images/reader-list-off-icon-hdpi.png
mobile/android/themes/core/images/reader-list-off-icon-mdpi.png
mobile/android/themes/core/images/reader-list-off-icon-xhdpi.png
mobile/android/themes/core/images/reader-list-on-icon-hdpi.png
mobile/android/themes/core/images/reader-list-on-icon-mdpi.png
mobile/android/themes/core/images/reader-list-on-icon-xhdpi.png
--- a/browser/components/customizableui/src/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/src/CustomizableWidgets.jsm
@@ -279,57 +279,58 @@ const CustomizableWidgets = [{
         if (typeof win.OpenBrowserWindow == "function") {
           win.OpenBrowserWindow({private: true});
         }
       }
     }
   }, {
     id: "save-page-button",
     shortcutId: "key_savePage",
-    tooltiptext: "save-page-button.tooltiptext2",
+    tooltiptext: "save-page-button.tooltiptext3",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target &&
                 aEvent.target.ownerDocument &&
                 aEvent.target.ownerDocument.defaultView;
       if (win && typeof win.saveDocument == "function") {
         win.saveDocument(win.content.document);
       }
     }
   }, {
     id: "find-button",
     shortcutId: "key_find",
-    tooltiptext: "find-button.tooltiptext2",
+    tooltiptext: "find-button.tooltiptext3",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target &&
                 aEvent.target.ownerDocument &&
                 aEvent.target.ownerDocument.defaultView;
       if (win && win.gFindBar) {
         win.gFindBar.onFindCommand();
       }
     }
   }, {
     id: "open-file-button",
     shortcutId: "openFileKb",
-    tooltiptext: "open-file-button.tooltiptext2",
+    tooltiptext: "open-file-button.tooltiptext3",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target
                 && aEvent.target.ownerDocument
                 && aEvent.target.ownerDocument.defaultView;
       if (win && typeof win.BrowserOpenFileWindow == "function") {
         win.BrowserOpenFileWindow();
       }
     }
   }, {
     id: "developer-button",
     type: "view",
     viewId: "PanelUI-developer",
     shortcutId: "key_devToolboxMenuItem",
+    tooltiptext: "developer-button.tooltiptext2",
     defaultArea: CustomizableUI.AREA_PANEL,
     onViewShowing: function(aEvent) {
       // Populate the subview with whatever menuitems are in the developer
       // menu. We skip menu elements, because the menu panel has no way
       // of dealing with those right now.
       let doc = aEvent.target.ownerDocument;
       let win = doc.defaultView;
 
@@ -345,16 +346,17 @@ const CustomizableWidgets = [{
     onViewHiding: function(aEvent) {
       let doc = aEvent.target.ownerDocument;
       clearSubview(doc.getElementById("PanelUI-developerItems"));
     }
   }, {
     id: "sidebar-button",
     type: "view",
     viewId: "PanelUI-sidebar",
+    tooltiptext: "sidebar-button.tooltiptext2",
     onViewShowing: function(aEvent) {
       // Largely duplicated from the developer-button above with a couple minor
       // alterations.
       // Populate the subview with whatever menuitems are in the
       // sidebar menu. We skip menu elements, because the menu panel has no way
       // of dealing with those right now.
       let doc = aEvent.target.ownerDocument;
       let win = doc.defaultView;
@@ -372,17 +374,17 @@ const CustomizableWidgets = [{
     },
     onViewHiding: function(aEvent) {
       let doc = aEvent.target.ownerDocument;
       clearSubview(doc.getElementById("PanelUI-sidebarItems"));
     }
   }, {
     id: "add-ons-button",
     shortcutId: "key_openAddons",
-    tooltiptext: "add-ons-button.tooltiptext2",
+    tooltiptext: "add-ons-button.tooltiptext3",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target &&
                 aEvent.target.ownerDocument &&
                 aEvent.target.ownerDocument.defaultView;
       if (win && typeof win.BrowserOpenAddonsMgr == "function") {
         win.BrowserOpenAddonsMgr();
       }
@@ -407,16 +409,17 @@ const CustomizableWidgets = [{
                 aEvent.target.ownerDocument.defaultView;
       if (win && typeof win.openPreferences == "function") {
         win.openPreferences();
       }
     }
   }, {
     id: "zoom-controls",
     type: "custom",
+    tooltiptext: "zoom-controls.tooltiptext2",
     defaultArea: CustomizableUI.AREA_PANEL,
     onBuild: function(aDocument) {
       const kPanelId = "PanelUI-popup";
       let areaType = CustomizableUI.getAreaType(this.currentArea);
       let inPanel = areaType == CustomizableUI.TYPE_MENU_PANEL;
       let inToolbar = areaType == CustomizableUI.TYPE_TOOLBAR;
 
       let buttons = [{
@@ -579,16 +582,17 @@ const CustomizableWidgets = [{
       };
       CustomizableUI.addListener(listener);
 
       return node;
     }
   }, {
     id: "edit-controls",
     type: "custom",
+    tooltiptext: "edit-controls.tooltiptext2",
     defaultArea: CustomizableUI.AREA_PANEL,
     onBuild: function(aDocument) {
       let buttons = [{
         id: "cut-button",
         command: "cmd_cut",
         label: true,
         tooltiptext: "tooltiptext2",
         shortcutId: "key_cut",
@@ -670,16 +674,17 @@ const CustomizableWidgets = [{
 
       return node;
     }
   },
   {
     id: "feed-button",
     type: "view",
     viewId: "PanelUI-feeds",
+    tooltiptext: "feed-button.tooltiptext2",
     defaultArea: CustomizableUI.AREA_PANEL,
     onClick: function(aEvent) {
       let win = aEvent.target.ownerDocument.defaultView;
       let feeds = win.gBrowser.selectedBrowser.feeds;
 
       // Here, we only care about the case where we have exactly 1 feed and the
       // user clicked...
       let isClick = (aEvent.button == 0 || aEvent.button == 1);
@@ -872,17 +877,17 @@ const CustomizableWidgets = [{
       };
       CustomizableUI.addListener(listener);
       if (!this.charsetInfo) {
         this.charsetInfo = CharsetMenu.getData();
       }
     }
   }, {
     id: "email-link-button",
-    tooltiptext: "email-link-button.tooltiptext2",
+    tooltiptext: "email-link-button.tooltiptext3",
     onCommand: function(aEvent) {
       let win = aEvent.view;
       win.MailIntegration.sendLinkForWindow(win.content);
     }
   }];
 
 #ifdef XP_WIN
 #ifdef MOZ_METRO
--- a/browser/devtools/app-manager/app-projects.js
+++ b/browser/devtools/app-manager/app-projects.js
@@ -1,11 +1,11 @@
 const {Cc,Ci,Cu} = require("chrome");
 const ObservableObject = require("devtools/shared/observable-object");
-const promise = require("sdk/core/promise");
+const promise = require("devtools/toolkit/deprecated-sync-thenables");
 
 const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js");
 const {generateUUID} = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator);
 
 /**
  * IndexedDB wrapper that just save project objects
  *
  * The only constraint is that project objects have to have
--- a/browser/devtools/app-manager/app-validator.js
+++ b/browser/devtools/app-manager/app-validator.js
@@ -1,15 +1,15 @@
 /* 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";
 
 let {Ci,Cu,CC} = require("chrome");
-const promise = require("sdk/core/promise");
+const promise = require("devtools/toolkit/deprecated-sync-thenables");
 
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 let XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
 let strings = Services.strings.createBundle("chrome://browser/locale/devtools/app-manager.properties");
 
 function AppValidator(project) {
   this.project = project;
--- a/browser/devtools/app-manager/content/device.js
+++ b/browser/devtools/app-manager/content/device.js
@@ -12,17 +12,17 @@ const {require} = devtools;
 
 const {ConnectionManager, Connection}
   = require("devtools/client/connection-manager");
 const {getDeviceFront} = require("devtools/server/actors/device");
 const {getTargetForApp, launchApp, closeApp}
   = require("devtools/app-actor-front");
 const DeviceStore = require("devtools/app-manager/device-store");
 const WebappsStore = require("devtools/app-manager/webapps-store");
-const promise = require("sdk/core/promise");
+const promise = require("devtools/toolkit/deprecated-sync-thenables");
 const DEFAULT_APP_ICON = "chrome://browser/skin/devtools/app-manager/default-app-icon.png";
 
 window.addEventListener("message", function(event) {
   try {
     let message = JSON.parse(event.data);
     if (message.name == "connection") {
       let cid = parseInt(message.cid);
       for (let c of ConnectionManager.connections) {
--- a/browser/devtools/app-manager/content/index.js
+++ b/browser/devtools/app-manager/content/index.js
@@ -2,17 +2,17 @@
  * 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/. */
 
 const Cu = Components.utils;
 Cu.import("resource:///modules/devtools/gDevTools.jsm");
 const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const {require} = devtools;
 const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
-const promise = require("sdk/core/promise");
+const promise = require("devtools/toolkit/deprecated-sync-thenables");
 const prefs = require('sdk/preferences/service');
 
 
 let UI = {
   _toolboxTabCursor: 0,
   _handledTargets: new Map(),
 
   connection: null,
--- a/browser/devtools/app-manager/content/projects.js
+++ b/browser/devtools/app-manager/content/projects.js
@@ -13,17 +13,17 @@ const {ConnectionManager, Connection} = 
 const {AppProjects} = require("devtools/app-manager/app-projects");
 const {AppValidator} = require("devtools/app-manager/app-validator");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
 const {installHosted, installPackaged, getTargetForApp,
        reloadApp, launchApp, closeApp} = require("devtools/app-actor-front");
 const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js");
 
-const promise = require("sdk/core/promise");
+const promise = require("devtools/toolkit/deprecated-sync-thenables");
 
 const MANIFEST_EDITOR_ENABLED = "devtools.appmanager.manifestEditor.enabled";
 
 window.addEventListener("message", function(event) {
   try {
     let json = JSON.parse(event.data);
     if (json.name == "connection") {
       let cid = parseInt(json.cid);
--- a/browser/devtools/app-manager/test/head.js
+++ b/browser/devtools/app-manager/test/head.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
 
 const {Promise: promise} =
-  Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+  Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
 const {devtools} =
   Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const {require} = devtools;
 
 const {AppProjects} = require("devtools/app-manager/app-projects");
 
 const APP_MANAGER_URL = "about:app-manager";
 const TEST_BASE =
--- a/browser/devtools/app-manager/webapps-store.js
+++ b/browser/devtools/app-manager/webapps-store.js
@@ -1,14 +1,14 @@
 /* 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/. */
 
 const ObservableObject = require("devtools/shared/observable-object");
-const promise = require("sdk/core/promise");
+const promise = require("devtools/toolkit/deprecated-sync-thenables");
 const {Connection} = require("devtools/client/connection-manager");
 
 const {Cu} = require("chrome");
 const dbgClient = Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 const _knownWebappsStores = new WeakMap();
 
 let WebappsStore;
 
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -93,17 +93,17 @@ Cu.import("resource://gre/modules/devtoo
 Cu.import("resource:///modules/devtools/SimpleListWidget.jsm");
 Cu.import("resource:///modules/devtools/BreadcrumbsWidget.jsm");
 Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
 Cu.import("resource:///modules/devtools/VariablesView.jsm");
 Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
-const promise = require("sdk/core/promise");
+const promise = require("devtools/toolkit/deprecated-sync-thenables");
 const Editor = require("devtools/sourceeditor/editor");
 const DebuggerEditor = require("devtools/sourceeditor/debugger.js");
 const {Tooltip} = require("devtools/shared/widgets/Tooltip");
 const FastListWidget = require("devtools/shared/widgets/FastListWidget");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Parser",
   "resource:///modules/devtools/Parser.jsm");
 
--- a/browser/devtools/eyedropper/test/head.js
+++ b/browser/devtools/eyedropper/test/head.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_BASE = "chrome://mochitests/content/browser/browser/devtools/eyedropper/test/";
 const TEST_HOST = 'mochi.test:8888';
 
-const promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
+const promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js").Promise;
 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 const { Eyedropper } = require("devtools/eyedropper/eyedropper");
 
 
 waitForExplicitFinish();
 
 function cleanup()
 {
--- a/browser/devtools/inspector/breadcrumbs.js
+++ b/browser/devtools/inspector/breadcrumbs.js
@@ -11,17 +11,17 @@ const ENSURE_SELECTION_VISIBLE_DELAY = 5
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/devtools/DOMHelpers.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
 const MAX_LABEL_LENGTH = 40;
 
-let promise = require("sdk/core/promise");
+let promise = require("devtools/toolkit/deprecated-sync-thenables");
 
 const LOW_PRIORITY_ELEMENTS = {
   "HEAD": true,
   "BASE": true,
   "BASEFONT": true,
   "ISINDEX": true,
   "LINK": true,
   "META": true,
--- a/browser/devtools/inspector/inspector-panel.js
+++ b/browser/devtools/inspector/inspector-panel.js
@@ -3,17 +3,17 @@
 /* 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/. */
 
 const {Cc, Ci, Cu, Cr} = require("chrome");
 
 Cu.import("resource://gre/modules/Services.jsm");
 
-let promise = require("sdk/core/promise");
+let promise = require("devtools/toolkit/deprecated-sync-thenables");
 let EventEmitter = require("devtools/toolkit/event-emitter");
 let {CssLogic} = require("devtools/styleinspector/css-logic");
 
 loader.lazyGetter(this, "MarkupView", () => require("devtools/markupview/markup-view").MarkupView);
 loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/inspector/breadcrumbs").HTMLBreadcrumbs);
 loader.lazyGetter(this, "ToolSidebar", () => require("devtools/framework/sidebar").ToolSidebar);
 loader.lazyGetter(this, "SelectorSearch", () => require("devtools/inspector/selector-search").SelectorSearch);
 
--- a/browser/devtools/inspector/selector-search.js
+++ b/browser/devtools/inspector/selector-search.js
@@ -1,15 +1,15 @@
 /* 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";
 
-const promise = require("sdk/core/promise");
+const promise = require("devtools/toolkit/deprecated-sync-thenables");
 
 loader.lazyGetter(this, "AutocompletePopup", () => require("devtools/shared/autocomplete-popup").AutocompletePopup);
 
 // Maximum number of selector suggestions shown in the panel.
 const MAX_SUGGESTIONS = 15;
 
 /**
  * Converts any input box on a page to a CSS selector search and suggestion box.
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -15,17 +15,17 @@ const COLLAPSE_DATA_URL_REGEX = /^data.+
 const COLLAPSE_DATA_URL_LENGTH = 60;
 const CONTAINER_FLASHING_DURATION = 500;
 const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
 
 const {UndoStack} = require("devtools/shared/undo");
 const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
 const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 const {HTMLEditor} = require("devtools/markupview/html-editor");
-const promise = require("sdk/core/promise");
+const promise = require("devtools/toolkit/deprecated-sync-thenables");
 const {Tooltip} = require("devtools/shared/widgets/Tooltip");
 const EventEmitter = require("devtools/toolkit/event-emitter");
 
 Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource://gre/modules/devtools/Templater.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
--- a/browser/devtools/markupview/test/head.js
+++ b/browser/devtools/markupview/test/head.js
@@ -1,17 +1,17 @@
 /* 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/. */
 
 const Cu = Components.utils;
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
-let promise = devtools.require("sdk/core/promise");
+let promise = devtools.require("devtools/toolkit/deprecated-sync-thenables");
 let {getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor");
 
 // All test are asynchronous
 waitForExplicitFinish();
 
 //Services.prefs.setBoolPref("devtools.dump.emit", true);
 
 // Set the testing flag on gDevTools and reset it when the test ends
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -389,16 +389,17 @@ RequestsMenuView.prototype = Heritage.ex
       $("#headers-summary-resend").hidden = true;
     }
 
     if (NetMonitorController.supportsPerfStats) {
       $("#request-menu-context-perf").addEventListener("command", this._onContextPerfCommand, false);
       $("#requests-menu-perf-notice-button").addEventListener("command", this._onContextPerfCommand, false);
       $("#requests-menu-network-summary-button").addEventListener("command", this._onContextPerfCommand, false);
       $("#requests-menu-network-summary-label").addEventListener("click", this._onContextPerfCommand, false);
+      $("#network-statistics-back-button").addEventListener("command", this._onContextPerfCommand, false);
     } else {
       $("#notice-perf-message").hidden = true;
       $("#request-menu-context-perf").hidden = true;
       $("#requests-menu-network-summary-button").hidden = true;
       $("#requests-menu-network-summary-label").hidden = true;
     }
   },
 
@@ -423,16 +424,17 @@ RequestsMenuView.prototype = Heritage.ex
     $("#request-menu-context-copy-url").removeEventListener("command", this._onContextCopyUrlCommand, false);
     $("#request-menu-context-copy-image-as-data-uri").removeEventListener("command", this._onContextCopyImageAsDataUriCommand, false);
     $("#request-menu-context-resend").removeEventListener("command", this._onContextResendCommand, false);
     $("#request-menu-context-perf").removeEventListener("command", this._onContextPerfCommand, false);
 
     $("#requests-menu-perf-notice-button").removeEventListener("command", this._onContextPerfCommand, false);
     $("#requests-menu-network-summary-button").removeEventListener("command", this._onContextPerfCommand, false);
     $("#requests-menu-network-summary-label").removeEventListener("click", this._onContextPerfCommand, false);
+    $("#network-statistics-back-button").removeEventListener("command", this._onContextPerfCommand, false);
 
     $("#custom-request-send-button").removeEventListener("click", this.sendCustomRequestEvent, false);
     $("#custom-request-close-button").removeEventListener("click", this.closeCustomRequestEvent, false);
     $("#headers-summary-resend").removeEventListener("click", this.cloneSelectedRequestEvent, false);
   },
 
   /**
    * Resets this container (removes all the networking information).
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -511,17 +511,16 @@
 
     </vbox>
 
     <box id="network-statistics-view">
       <toolbar id="network-statistics-toolbar"
                class="devtools-toolbar">
         <button id="network-statistics-back-button"
                 class="devtools-toolbarbutton"
-                onclick="NetMonitorView.toggleFrontendMode()"
                 label="&netmonitorUI.backButton;"/>
       </toolbar>
       <box id="network-statistics-charts"
            class="devtools-responsive-container"
            flex="1">
         <vbox id="primed-cache-chart" pack="center" flex="1"/>
         <splitter id="network-statistics-view-splitter"
                   class="devtools-side-splitter"/>
--- a/browser/devtools/shared/test/browser_telemetry_button_paintflashing.js
+++ b/browser/devtools/shared/test/browser_telemetry_button_paintflashing.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_paintflashing.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
+let promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {}).Promise;
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/browser/devtools/shared/test/browser_telemetry_button_responsive.js
+++ b/browser/devtools/shared/test/browser_telemetry_button_responsive.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_responsive.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
+let promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {}).Promise;
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/browser/devtools/shared/test/browser_telemetry_button_scratchpad.js
+++ b/browser/devtools/shared/test/browser_telemetry_button_scratchpad.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_scratchpad.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
+let promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {}).Promise;
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 let numScratchpads = 0;
 
 function init() {
--- a/browser/devtools/shared/test/browser_telemetry_button_tilt.js
+++ b/browser/devtools/shared/test/browser_telemetry_button_tilt.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_tilt.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
+let promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {}).Promise;
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/browser/devtools/shared/test/browser_telemetry_sidebar.js
+++ b/browser/devtools/shared/test/browser_telemetry_sidebar.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_sidebar.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_inspector.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_inspector.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs_inspector.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_jsdebugger.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_jsdebugger.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs_jsdebugger.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_jsprofiler.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_jsprofiler.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs_jsprofiler.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_netmonitor.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_netmonitor.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs_netmonitor.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_options.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_options.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs_options.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_styleeditor.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_styleeditor.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs_styleeditor.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_webconsole.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_webconsole.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs_styleeditor_webconsole.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/browser/devtools/shared/test/browser_templater_basic.js
+++ b/browser/devtools/shared/test/browser_templater_basic.js
@@ -4,17 +4,17 @@
 // Tests that the DOM Template engine works properly
 
 /*
  * These tests run both in Mozilla/Mochitest and plain browsers (as does
  * domtemplate)
  * We should endevour to keep the source in sync.
  */
 
-var promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
+var promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {}).Promise;
 var template = Cu.import("resource://gre/modules/devtools/Templater.jsm", {}).template;
 
 const TEST_URI = "http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html";
 
 function test() {
   addTab(TEST_URI, function() {
     info("Starting DOM Templater Tests");
     runTest(0);
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -865,17 +865,17 @@ Messages.Simple.prototype = Heritage.ext
   /**
    * Render the message body DOM element.
    * @private
    * @return Element
    */
   _renderBody: function()
   {
     let body = this.document.createElementNS(XHTML_NS, "span");
-    body.className = "body devtools-monospace";
+    body.className = "message-body-wrapper message-body devtools-monospace";
 
     let anchor, container = body;
     if (this._link || this._linkCallback) {
       container = anchor = this.document.createElementNS(XHTML_NS, "a");
       anchor.href = this._link || "#";
       anchor.draggable = false;
       this._addLinkCallback(anchor, this._linkCallback);
       body.appendChild(anchor);
@@ -1218,70 +1218,158 @@ Messages.ConsoleGeneric = function(packe
     severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
     private: packet.private,
     filterDuplicates: true,
     location: {
       url: packet.filename,
       line: packet.lineNumber,
     },
   };
+
   switch (packet.level) {
     case "count": {
       let counter = packet.counter, label = counter.label;
       if (!label) {
         label = l10n.getStr("noCounterLabel");
       }
       Messages.Extended.call(this, [label+ ": " + counter.count], options);
       break;
     }
     default:
       Messages.Extended.call(this, packet.arguments, options);
       break;
   }
 
   this._repeatID.consoleApiLevel = packet.level;
   this._repeatID.styles = packet.styles;
+  this._stacktrace = this._repeatID.stacktrace = packet.stacktrace;
   this._styles = packet.styles || [];
+
+  this._onClickCollapsible = this._onClickCollapsible.bind(this);
 };
 
 Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
 {
   _styles: null,
+  _stacktrace: null,
+
+  /**
+   * Tells if the message can be expanded/collapsed.
+   * @type boolean
+   */
+  collapsible: false,
+
+  /**
+   * Getter that tells if this message is collapsed - no details are shown.
+   * @type boolean
+   */
+  get collapsed() {
+    return this.collapsible && this.element && !this.element.hasAttribute("open");
+  },
 
   _renderBodyPieceSeparator: function()
   {
     return this.document.createTextNode(" ");
   },
 
   render: function()
   {
+    let msg = this.document.createElementNS(XHTML_NS, "span");
+    msg.className = "message-body devtools-monospace";
+
+    this._renderBodyPieces(msg);
+
+    let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this);
+    let location = Messages.Simple.prototype._renderLocation.call(this);
+    if (location) {
+      location.target = "jsdebugger";
+    }
+
+    let stack = null;
+    let twisty = null;
+    if (this._stacktrace && this._stacktrace.length > 0) {
+      stack = new Widgets.Stacktrace(this, this._stacktrace).render().element;
+
+      twisty = this.document.createElementNS(XHTML_NS, "a");
+      twisty.className = "theme-twisty";
+      twisty.href = "#";
+      twisty.title = l10n.getStr("messageToggleDetails");
+      twisty.addEventListener("click", this._onClickCollapsible);
+    }
+
+    let flex = this.document.createElementNS(XHTML_NS, "span");
+    flex.className = "message-flex-body";
+
+    if (twisty) {
+      flex.appendChild(twisty);
+    }
+
+    flex.appendChild(msg);
+
+    if (repeatNode) {
+      flex.appendChild(repeatNode);
+    }
+    if (location) {
+      flex.appendChild(location);
+    }
+
+    let result = this.document.createDocumentFragment();
+    result.appendChild(flex);
+
+    if (stack) {
+      result.appendChild(this.document.createTextNode("\n"));
+      result.appendChild(stack);
+    }
+
+    this._message = result;
+    this._stacktrace = null;
+
+    Messages.Simple.prototype.render.call(this);
+
+    if (stack) {
+      this.collapsible = true;
+      this.element.setAttribute("collapsible", true);
+
+      let icon = this.element.querySelector(".icon");
+      icon.addEventListener("click", this._onClickCollapsible);
+    }
+
+    return this;
+  },
+
+  _renderBody: function()
+  {
+    let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
+    body.classList.remove("devtools-monospace", "message-body");
+    return body;
+  },
+
+  _renderBodyPieces: function(container)
+  {
     let lastStyle = null;
-    let result = this.document.createDocumentFragment();
 
     for (let i = 0; i < this._messagePieces.length; i++) {
       let separator = i > 0 ? this._renderBodyPieceSeparator() : null;
       if (separator) {
-        result.appendChild(separator);
+        container.appendChild(separator);
       }
 
       let piece = this._messagePieces[i];
       let style = this._styles[i];
 
       // No long string support.
       if (style && typeof style == "string" ) {
         lastStyle = this.cleanupStyle(style);
       }
 
-      result.appendChild(this._renderBodyPiece(piece, lastStyle));
+      container.appendChild(this._renderBodyPiece(piece, lastStyle));
     }
 
-    this._message = result;
     this._messagePieces = null;
     this._styles = null;
-    return Messages.Simple.prototype.render.call(this);
   },
 
   _renderBodyPiece: function(piece, style)
   {
     let elem = Messages.Extended.prototype._renderBodyPiece.call(this, piece);
     let result = elem;
 
     if (style) {
@@ -1293,16 +1381,51 @@ Messages.ConsoleGeneric.prototype = Heri
         span.appendChild(elem);
         result = span;
       }
     }
 
     return result;
   },
 
+  // no-op for the message location and .repeats elements.
+  // |this.render()| handles customized message output.
+  _renderLocation: function() { },
+  _renderRepeatNode: function() { },
+
+  /**
+   * Expand/collapse message details.
+   */
+  toggleDetails: function()
+  {
+    let twisty = this.element.querySelector(".theme-twisty");
+    if (this.element.hasAttribute("open")) {
+      this.element.removeAttribute("open");
+      twisty.removeAttribute("open");
+    } else {
+      this.element.setAttribute("open", true);
+      twisty.setAttribute("open", true);
+    }
+  },
+
+  /**
+   * The click event handler for the message expander arrow element. This method
+   * toggles the display of message details.
+   *
+   * @private
+   * @param nsIDOMEvent ev
+   *        The DOM event object.
+   * @see this.toggleDetails()
+   */
+  _onClickCollapsible: function(ev)
+  {
+    ev.preventDefault();
+    this.toggleDetails();
+  },
+
   /**
    * Given a style attribute value, return a cleaned up version of the string
    * such that:
    *
    * - no external URL is allowed to load. See RE_CLEANUP_STYLES.
    * - only some of the properties are allowed, based on a whitelist. See
    *   RE_ALLOWED_STYLES.
    *
@@ -1350,17 +1473,17 @@ Messages.ConsoleGeneric.prototype = Heri
  * @constructor
  * @extends Messages.Simple
  * @param object packet
  *        The Console API call packet received from the server.
  */
 Messages.ConsoleTrace = function(packet)
 {
   let options = {
-    className: "consoleTrace cm-s-mozilla",
+    className: "cm-s-mozilla",
     timestamp: packet.timeStamp,
     category: "webdev",
     severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
     private: packet.private,
     filterDuplicates: true,
     location: {
       url: packet.filename,
       line: packet.lineNumber,
@@ -1407,16 +1530,23 @@ Messages.ConsoleTrace.prototype = Herita
         }
       }
     }
     this._arguments = null;
 
     return result;
   },
 
+  render: function()
+  {
+    Messages.Simple.prototype.render.apply(this, arguments);
+    this.element.setAttribute("open", true);
+    return this;
+  },
+
   /**
    * Render the stack frames.
    *
    * @private
    * @return DOMElement
    */
   _renderStack: function()
   {
@@ -1424,31 +1554,32 @@ Messages.ConsoleTrace.prototype = Herita
     cmvar.className = "cm-variable";
     cmvar.textContent = "console";
 
     let cmprop = this.document.createElementNS(XHTML_NS, "span");
     cmprop.className = "cm-property";
     cmprop.textContent = "trace";
 
     let title = this.document.createElementNS(XHTML_NS, "span");
-    title.className = "title devtools-monospace";
+    title.className = "message-body devtools-monospace";
     title.appendChild(cmvar);
     title.appendChild(this.document.createTextNode("."));
     title.appendChild(cmprop);
     title.appendChild(this.document.createTextNode("():"));
 
     let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this);
     let location = Messages.Simple.prototype._renderLocation.call(this);
     if (location) {
       location.target = "jsdebugger";
     }
 
     let widget = new Widgets.Stacktrace(this, this._stacktrace).render();
 
-    let body = this.document.createElementNS(XHTML_NS, "div");
+    let body = this.document.createElementNS(XHTML_NS, "span");
+    body.className = "message-flex-body";
     body.appendChild(title);
     if (repeatNode) {
       body.appendChild(repeatNode);
     }
     if (location) {
       body.appendChild(location);
     }
     body.appendChild(this.document.createTextNode("\n"));
@@ -1458,17 +1589,17 @@ Messages.ConsoleTrace.prototype = Herita
     frag.appendChild(widget.element);
 
     return frag;
   },
 
   _renderBody: function()
   {
     let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
-    body.classList.remove("devtools-monospace");
+    body.classList.remove("devtools-monospace", "message-body");
     return body;
   },
 
   // no-op for the message location and .repeats elements.
   // |this._renderStack| handles customized message output.
   _renderLocation: function() { },
   _renderRepeatNode: function() { },
 }); // Messages.ConsoleTrace.prototype
@@ -2717,17 +2848,16 @@ Widgets.Stacktrace.prototype = Heritage.
 
     let elem = this.document.createElementNS(XHTML_NS, "li");
     elem.appendChild(fn);
     elem.appendChild(location);
     elem.appendChild(this.document.createTextNode("\n"));
 
     return elem;
   },
-
 }); // Widgets.Stacktrace.prototype
 
 
 function gSequenceId()
 {
   return gSequenceId.n++;
 }
 gSequenceId.n = 0;
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -108,16 +108,17 @@ support-files =
   test-bug_923281_console_log_filter.html
   test-bug_923281_test1.js
   test-bug_923281_test2.js
   test-bug_939783_console_trace_duplicates.html
   test-bug-952277-highlight-nodes-in-vview.html
   test-bug-609872-cd-iframe-parent.html
   test-bug-609872-cd-iframe-child.html
   test-bug-989025-iframe-parent.html
+  test-console-api-stackframe.html
 
 [browser_bug664688_sandbox_update_after_navigation.js]
 [browser_bug_638949_copy_link_location.js]
 [browser_bug_862916_console_dir_and_filter_off.js]
 [browser_bug_865288_repeat_different_objects.js]
 [browser_bug_865871_variables_view_close_on_esc_key.js]
 [browser_bug_869003_inspect_cross_domain_object.js]
 [browser_bug_871156_ctrlw_close_tab.js]
@@ -278,8 +279,9 @@ run-if = os == "mac"
 [browser_webconsole_output_dom_elements_04.js]
 [browser_webconsole_output_events.js]
 [browser_console_variables_view_highlighter.js]
 [browser_webconsole_start_netmon_first.js]
 [browser_webconsole_console_trace_duplicates.js]
 [browser_webconsole_cd_iframe.js]
 [browser_webconsole_autocomplete_crossdomain_iframe.js]
 [browser_webconsole_console_custom_styles.js]
+[browser_webconsole_console_api_stackframe.js]
--- a/browser/devtools/webconsole/test/browser_bug_638949_copy_link_location.js
+++ b/browser/devtools/webconsole/test/browser_bug_638949_copy_link_location.js
@@ -62,16 +62,17 @@ function onConsoleMessage(aResults) {
   output.focus();
   let message = [...aResults[0].matched][0];
 
   goUpdateCommand(COMMAND_NAME);
   ok(!isEnabled(), COMMAND_NAME + "is disabled");
 
   // Test that the "Copy Link Location" menu item is hidden for non-network
   // messages.
+  message.scrollIntoView();
   waitForContextMenu(menu, message, () => {
     let isHidden = menu.querySelector(CONTEXT_MENU_ID).hidden;
     ok(isHidden, CONTEXT_MENU_ID + " is hidden");
   }, testWithNetActivity);
 }
 
 function testWithNetActivity() {
   HUD.jsterm.clearOutput();
@@ -102,15 +103,16 @@ function onNetworkMessage(aResults) {
 
   waitForClipboard((aData) => { return aData.trim() == message.url; },
     () => { goDoCommand(COMMAND_NAME) },
     testMenuWithNetActivity, testMenuWithNetActivity);
 
   function testMenuWithNetActivity() {
     // Test that the "Copy Link Location" menu item is visible for network-related
     // messages.
+    message.scrollIntoView();
     waitForContextMenu(menu, message, () => {
       let isVisible = !menu.querySelector(CONTEXT_MENU_ID).hidden;
       ok(isVisible, CONTEXT_MENU_ID + " is visible");
     }, finishTest);
   }
 }
 
--- a/browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js
+++ b/browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js
@@ -48,17 +48,17 @@ function checkMessages([result])
   let m = -1;
 
   function nextMessage()
   {
     let msg = msgs[++m];
     if (msg) {
       ok(msg, "message element #" + m);
 
-      let clickable = msg.querySelector(".body a");
+      let clickable = msg.querySelector(".message-body a");
       ok(clickable, "clickable object #" + m);
 
       msg.scrollIntoView(false);
       clickObject(clickable);
     }
     else {
       finishTest();
     }
--- a/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
+++ b/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
@@ -18,17 +18,17 @@ function test()
     let {tab} = yield loadTab(TEST_URI);
     hud = yield openConsole(tab);
     let jsterm = hud.jsterm;
 
     let msg = yield execute("fooObj");
     ok(msg, "output message found");
 
     let anchor = msg.querySelector("a");
-    let body = msg.querySelector(".body");
+    let body = msg.querySelector(".message-body");
     ok(anchor, "object anchor");
     ok(body, "message body");
     ok(body.textContent.contains('testProp: "testValue"'), "message text check");
 
     msg.scrollIntoView();
     executeSoon(() => {
       EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow);
     });
@@ -54,17 +54,17 @@ function test()
     });
     yield jsterm.once("sidebar-closed");
 
     jsterm.clearOutput();
 
     msg = yield execute("window.location");
     ok(msg, "output message found");
 
-    body = msg.querySelector(".body");
+    body = msg.querySelector(".message-body");
     ok(body, "message body");
     anchor = msg.querySelector("a");
     ok(anchor, "object anchor");
     ok(body.textContent.contains("Location \u2192 http://example.com/browser/"),
        "message text check");
 
     msg.scrollIntoView();
     executeSoon(() => {
--- a/browser/devtools/webconsole/test/browser_bug_869003_inspect_cross_domain_object.js
+++ b/browser/devtools/webconsole/test/browser_bug_869003_inspect_cross_domain_object.js
@@ -42,17 +42,17 @@ function consoleOpened(hud)
   }).then(onConsoleMessage);
 }
 
 function onConsoleMessage(aResults)
 {
   let msg = [...aResults[0].matched][0];
   ok(msg, "message element");
 
-  let body = msg.querySelector(".body");
+  let body = msg.querySelector(".message-body");
   ok(body, "message body");
 
   let clickable = aResults[0].clickableElements[0];
   ok(clickable, "clickable object found");
   ok(body.textContent.contains('{ hello: "world!",'), "message text check");
 
   gJSTerm.once("variablesview-fetched", onObjFetch);
 
--- a/browser/devtools/webconsole/test/browser_console_click_focus.js
+++ b/browser/devtools/webconsole/test/browser_console_click_focus.js
@@ -20,17 +20,17 @@ function testInputFocus() {
       webconsole: hud,
       messages: [{
         text: "Dolske Digs Bacon",
         category: CATEGORY_WEBDEV,
         severity: SEVERITY_LOG,
       }],
     }).then(([result]) => {
       let msg = [...result.matched][0];
-      let outputItem = msg.querySelector(".body");
+      let outputItem = msg.querySelector(".message-body");
       ok(outputItem, "found a logged message");
       let inputNode = hud.jsterm.inputNode;
       ok(inputNode.getAttribute("focused"), "input node is focused, first");
 
       let lostFocus = () => {
         inputNode.removeEventListener("blur", lostFocus);
         info("input node lost focus");
       }
--- a/browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
+++ b/browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
@@ -29,17 +29,17 @@ function performTest(hud)
       text: "fooBug676722",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
       objects: true,
     }],
   }).then(([result]) => {
     let msg = [...result.matched][0];
     ok(msg, "message element");
-    let body = msg.querySelector(".body");
+    let body = msg.querySelector(".message-body");
     ok(body, "message body");
     let clickable = result.clickableElements[0];
     ok(clickable, "the console.log() object anchor was found");
     ok(body.textContent.contains('{ abba: "omgBug676722" }'),
        "clickable node content is correct");
 
     hud.jsterm.once("variablesview-fetched",
       (aEvent, aVar) => {
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js
@@ -31,17 +31,17 @@ function tabLoad2(aEvent) {
     webconsole: HUD,
     messages: [{
       text: "test-console.html",
       category: CATEGORY_NETWORK,
       severity: SEVERITY_LOG,
     }],
   }).then(([result]) => {
     let msg = [...result.matched][0];
-    outputItem = msg.querySelector(".body .url");
+    outputItem = msg.querySelector(".message-body .url");
     ok(outputItem, "found a network message");
     document.addEventListener("popupshown", networkPanelShown, false);
 
     // Send the mousedown and click events such that the network panel opens.
     EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
     EventUtils.sendMouseEvent({type: "click"}, outputItem);
   });
 }
@@ -97,17 +97,17 @@ function networkPanelHidden(aEvent) {
     document.removeEventListener("popupshown", networkPanelShowFailure, false);
 
     // Done with the network output. Now test the jsterm output and the property
     // panel.
     HUD.jsterm.execute("document", (msg) => {
       info("jsterm execute 'document' callback");
 
       HUD.jsterm.once("variablesview-open", onVariablesViewOpen);
-      let outputItem = msg.querySelector(".body a");
+      let outputItem = msg.querySelector(".message-body a");
       ok(outputItem, "jsterm output message found");
 
       // Send the mousedown and click events such that the property panel opens.
       EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
       EventUtils.sendMouseEvent({type: "click"}, outputItem);
     });
   });
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
@@ -61,17 +61,17 @@ function consoleOpened(HUD) {
   completion = JSPropertyProvider(dbgWindow, null, "window._container.");
   ok(completion, "matches available for window._container");
   ok(completion.matches.length, "matches available for window (length)");
 
   jsterm.clearOutput();
 
   jsterm.execute("window._container", (msg) => {
     jsterm.once("variablesview-fetched", testVariablesView.bind(null, HUD));
-    let anchor = msg.querySelector(".body a");
+    let anchor = msg.querySelector(".message-body a");
     EventUtils.synthesizeMouse(anchor, 2, 2, {}, HUD.iframeWindow);
   });
 }
 
 function testVariablesView(aWebconsole, aEvent, aView) {
   findVariableViewProperties(aView, [
     { name: "gen1", isGenerator: true },
     { name: "gen2", isGenerator: true },
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
@@ -70,17 +70,17 @@ function autocompletePopupHidden()
 }
 
 function testPropertyPanel()
 {
   let jsterm = gHUD.jsterm;
   jsterm.clearOutput();
   jsterm.execute("document", (msg) => {
     jsterm.once("variablesview-fetched", onVariablesViewReady);
-    let anchor = msg.querySelector(".body a");
+    let anchor = msg.querySelector(".message-body a");
     EventUtils.synthesizeMouse(anchor, 2, 2, {}, gHUD.iframeWindow);
   });
 }
 
 function onVariablesViewReady(aEvent, aView)
 {
   findVariableViewProperties(aView, [
     { name: "body", value: "<body>" },
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_764572_output_open_url.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_764572_output_open_url.js
@@ -53,16 +53,17 @@ function onConsoleMessage(aResults) {
   // Check if the command is disabled non-network messages.
   goUpdateCommand(COMMAND_NAME);
   let controller = top.document.commandDispatcher
                    .getControllerForCommand(COMMAND_NAME);
 
   let isDisabled = !controller || !controller.isCommandEnabled(COMMAND_NAME);
   ok(isDisabled, COMMAND_NAME + " should be disabled.");
 
+  outputNode.selectedItem.scrollIntoView();
   waitForContextMenu(contextMenu, outputNode.selectedItem, () => {
     let isHidden = contextMenu.querySelector(CONTEXT_MENU_ID).hidden;
     ok(isHidden, CONTEXT_MENU_ID + " should be hidden.");
   }, testOnNetActivity);
 }
 
 function testOnNetActivity() {
   HUD.jsterm.clearOutput();
@@ -112,13 +113,14 @@ function onNetworkMessage(aResults) {
   // Try to open the URL.
   goDoCommand(COMMAND_NAME);
 }
 
 function testOnNetActivity_contextmenu(msg) {
   outputNode.focus();
   HUD.ui.output.selectMessage(msg);
 
+  msg.scrollIntoView();
   waitForContextMenu(contextMenu, msg, () => {
     let isShown = !contextMenu.querySelector(CONTEXT_MENU_ID).hidden;
     ok(isShown, CONTEXT_MENU_ID + " should be shown.");
   }, finishTest);
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
@@ -1,13 +1,17 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Any copyright is dedicated to the Public Domain.
+/* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
- * ***** END LICENSE BLOCK ***** */
+ */
+
+// Test that message source links for js errors and console API calls open in
+// the jsdebugger when clicked.
+
+"use strict";
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test" +
                  "/test-bug-766001-js-console-links.html";
 
 function test() {
   let hud;
 
   requestLongerTimeout(2);
@@ -56,18 +60,16 @@ function test() {
 
     let line = node.sourceLine;
     ok(line, "found source line for index " + index);
 
     executeSoon(() => {
       EventUtils.sendMouseEvent({ type: "click" }, node);
     });
 
-    yield hud.ui.once("source-in-debugger-opened", checkLine.bind(null, url, line));
-  }
+    yield hud.ui.once("source-in-debugger-opened");
 
-  function* checkLine(url, line) {
     let toolbox = yield gDevTools.getToolbox(hud.target);
     let {panelWin: { DebuggerView: view }} = toolbox.getPanel("jsdebugger");
     is(view.Sources.selectedValue, url, "expected source url");
     is(view.editor.getCursor().line, line - 1, "expected source line");
   }
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_api_stackframe.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the console API messages for console.error()/exception()/assert()
+// include a stackframe. See bug 920116.
+
+function test() {
+  let hud;
+
+  const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-api-stackframe.html";
+  const TEST_FILE = TEST_URI.substr(TEST_URI.lastIndexOf("/"));
+
+  Task.spawn(runner).then(finishTest);
+
+  function* runner() {
+    const {tab} = yield loadTab(TEST_URI);
+    hud = yield openConsole(tab);
+
+    const stack = [{
+      file: TEST_FILE,
+      fn: "thirdCall",
+      line: /\b2[123]\b/, // 21,22,23
+    }, {
+      file: TEST_FILE,
+      fn: "secondCall",
+      line: 16,
+    }, {
+      file: TEST_FILE,
+      fn: "firstCall",
+      line: 12,
+    }];
+
+    let results = yield waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "foo-log",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_LOG,
+        collapsible: false,
+      }, {
+        text: "foo-error",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_ERROR,
+        collapsible: true,
+        stacktrace: stack,
+      }, {
+        text: "foo-exception",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_ERROR,
+        collapsible: true,
+        stacktrace: stack,
+      }, {
+        text: "foo-assert",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_ERROR,
+        collapsible: true,
+        stacktrace: stack,
+      }],
+    });
+
+    let elem = [...results[1].matched][0];
+    ok(elem, "message element");
+
+    let msg = elem._messageObject;
+    ok(msg, "message object");
+
+    ok(msg.collapsed, "message is collapsed");
+
+    msg.toggleDetails();
+
+    ok(!msg.collapsed, "message is not collapsed");
+
+    msg.toggleDetails();
+
+    ok(msg.collapsed, "message is collapsed");
+
+    yield closeConsole(tab);
+  }
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_console_custom_styles.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_custom_styles.js
@@ -54,17 +54,17 @@ function test() {
         text: "foobar",
         category: CATEGORY_WEBDEV,
       }],
     });
 
     let msg = [...result.matched][0];
     ok(msg, "message element");
 
-    let span = msg.querySelector(".body span[style]");
+    let span = msg.querySelector(".message-body span[style]");
     ok(span, "span element");
 
     info("span textContent is: " + span.textContent);
     isnot(span.textContent.indexOf("foobar"), -1, "span textContent check");
 
     let outputStyle = span.getAttribute("style").replace(/\s+|;+$/g, "");
     if (check.sameStyleExpected) {
       is(outputStyle, check.style, "span style is correct");
--- a/browser/devtools/webconsole/test/browser_webconsole_jsterm.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_jsterm.js
@@ -25,17 +25,17 @@ function nextTest() {
 function checkResult(msg, desc) {
   waitForMessages({
     webconsole: jsterm.hud.owner,
     messages: [{
       name: desc,
       category: CATEGORY_OUTPUT,
     }],
   }).then(([result]) => {
-    let node = [...result.matched][0].querySelector(".body");
+    let node = [...result.matched][0].querySelector(".message-body");
     if (typeof msg == "string") {
       is(node.textContent.trim(), msg,
         "correct message shown for " + desc);
     }
     else if (typeof msg == "function") {
       ok(msg(node), "correct message shown for " + desc);
     }
 
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -1,13 +1,15 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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";
+
 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let {require, TargetFactory} = devtools;
 let {Utils: WebConsoleUtils} = require("devtools/toolkit/webconsole/utils");
 let {Messages} = require("devtools/webconsole/console-output");
@@ -877,24 +879,28 @@ function openDebugger(aOptions = {})
  *            Provide this if you want to match a console.time() message.
  *            - consoleTimeEnd: same as above, but for console.timeEnd().
  *            - consoleDir: boolean, set to |true| to match a console.dir()
  *            message.
  *            - consoleGroup: boolean, set to |true| to match a console.group()
  *            message.
  *            - longString: boolean, set to |true} to match long strings in the
  *            message.
+ *            - collapsible: boolean, set to |true| to match messages that can
+ *            be collapsed/expanded.
  *            - type: match messages that are instances of the given object. For
  *            example, you can point to Messages.NavigationMarker to match any
  *            such message.
  *            - objects: boolean, set to |true| if you expect inspectable
  *            objects in the message.
  *            - source: object of the shape { url, line }. This is used to
  *            match the source URL and line number of the error message or
  *            console API call.
+ *            - stacktrace: array of objects of the form { file, fn, line } that
+ *            can match frames in the stacktrace associated with the message.
  *            - groupDepth: number used to check the depth of the message in
  *            a group.
  *            - url: URL to match for network requests.
  * @return object
  *         A promise object is returned once the messages you want are found.
  *         The promise is resolved with the array of rule objects you give in
  *         the |messages| property. Each objects is the same as provided, with
  *         additional properties:
@@ -913,73 +919,53 @@ function waitForMessages(aOptions)
   let rules = WebConsoleUtils.cloneObject(aOptions.messages, true);
   let rulesMatched = 0;
   let listenerAdded = false;
   let deferred = promise.defer();
   aOptions.matchCondition = aOptions.matchCondition || "all";
 
   function checkText(aRule, aText)
   {
-    let result;
+    let result = false;
     if (Array.isArray(aRule)) {
       result = aRule.every((s) => checkText(s, aText));
     }
     else if (typeof aRule == "string") {
       result = aText.indexOf(aRule) > -1;
     }
     else if (aRule instanceof RegExp) {
       result = aRule.test(aText);
     }
+    else {
+      result = aRule == aText;
+    }
     return result;
   }
 
   function checkConsoleTrace(aRule, aElement)
   {
     let elemText = aElement.textContent;
     let trace = aRule.consoleTrace;
 
     if (!checkText("console.trace():", elemText)) {
       return false;
     }
 
-    let frame = aElement.querySelector(".stacktrace li:first-child");
-    if (trace.file) {
-      let file = frame.querySelector(".message-location").title;
-      if (!checkText(trace.file, file)) {
-        ok(false, "console.trace() message is missing the file name: " +
-                  trace.file);
-        displayErrorContext(aRule, aElement);
-        return false;
-      }
-    }
-
-    if (trace.fn) {
-      let fn = frame.querySelector(".function").textContent;
-      if (!checkText(trace.fn, fn)) {
-        ok(false, "console.trace() message is missing the function name: " +
-                  trace.fn);
-        displayErrorContext(aRule, aElement);
-        return false;
-      }
-    }
-
-    if (trace.line) {
-      let line = frame.querySelector(".message-location").sourceLine;
-      if (!checkText(trace.line, line)) {
-        ok(false, "console.trace() message is missing the line number: " +
-                  trace.line);
-        displayErrorContext(aRule, aElement);
-        return false;
-      }
-    }
-
     aRule.category = CATEGORY_WEBDEV;
     aRule.severity = SEVERITY_LOG;
     aRule.type = Messages.ConsoleTrace;
 
+    if (!aRule.stacktrace && typeof trace == "object" && trace !== true) {
+      if (Array.isArray(trace)) {
+        aRule.stacktrace = trace;
+      } else {
+        aRule.stacktrace = [trace];
+      }
+    }
+
     return true;
   }
 
   function checkConsoleTime(aRule, aElement)
   {
     let elemText = aElement.textContent;
     let time = aRule.consoleTime;
 
@@ -1053,16 +1039,76 @@ function waitForMessages(aOptions)
 
     if ("line" in aRule.source && location.sourceLine != aRule.source.line) {
       return false;
     }
 
     return true;
   }
 
+  function checkCollapsible(aRule, aElement)
+  {
+    let msg = aElement._messageObject;
+    if (!msg || !!msg.collapsible != aRule.collapsible) {
+      return false;
+    }
+
+    return true;
+  }
+
+  function checkStacktrace(aRule, aElement)
+  {
+    let stack = aRule.stacktrace;
+    let frames = aElement.querySelectorAll(".stacktrace > li");
+    if (!frames.length) {
+      return false;
+    }
+
+    for (let i = 0; i < stack.length; i++) {
+      let frame = frames[i];
+      let expected = stack[i];
+      if (!frame) {
+        ok(false, "expected frame #" + i + " but didnt find it");
+        return false;
+      }
+
+      if (expected.file) {
+        let file = frame.querySelector(".message-location").title;
+        if (!checkText(expected.file, file)) {
+          ok(false, "frame #" + i + " does not match file name: " +
+                    expected.file);
+          displayErrorContext(aRule, aElement);
+          return false;
+        }
+      }
+
+      if (expected.fn) {
+        let fn = frame.querySelector(".function").textContent;
+        if (!checkText(expected.fn, fn)) {
+          ok(false, "frame #" + i + " does not match the function name: " +
+                    expected.fn);
+          displayErrorContext(aRule, aElement);
+          return false;
+        }
+      }
+
+      if (expected.line) {
+        let line = frame.querySelector(".message-location").sourceLine;
+        if (!checkText(expected.line, line)) {
+          ok(false, "frame #" + i + " does not match the line number: " +
+                    expected.line);
+          displayErrorContext(aRule, aElement);
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
   function checkMessage(aRule, aElement)
   {
     let elemText = aElement.textContent;
 
     if (aRule.text && !checkText(aRule.text, elemText)) {
       return false;
     }
 
@@ -1089,16 +1135,20 @@ function waitForMessages(aOptions)
     if (aRule.consoleGroup && !checkConsoleGroup(aRule, aElement)) {
       return false;
     }
 
     if (aRule.source && !checkSource(aRule, aElement)) {
       return false;
     }
 
+    if ("collapsible" in aRule && !checkCollapsible(aRule, aElement)) {
+      return false;
+    }
+
     let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime ||
                           aRule.consoleTimeEnd);
 
     // The rule tries to match the newer types of messages, based on their
     // object constructor.
     if (aRule.type) {
       if (!aElement._messageObject ||
           !(aElement._messageObject instanceof aRule.type)) {
@@ -1124,16 +1174,28 @@ function waitForMessages(aOptions)
       if (partialMatch) {
         is(aElement.severity, aRule.severity,
            "message severity for rule: " + displayRule(aRule));
         displayErrorContext(aRule, aElement);
       }
       return false;
     }
 
+    if (aRule.text) {
+      partialMatch = true;
+    }
+
+    if (aRule.stacktrace && !checkStacktrace(aRule, aElement)) {
+      if (partialMatch) {
+        ok(false, "failed to match stacktrace for rule: " + displayRule(aRule));
+        displayErrorContext(aRule, aElement);
+      }
+      return false;
+    }
+
     if (aRule.category == CATEGORY_NETWORK && "url" in aRule &&
         !checkText(aRule.url, aElement.url)) {
       return false;
     }
 
     if ("repeats" in aRule) {
       let repeats = aElement.querySelector(".message-repeats");
       if (!repeats || repeats.getAttribute("value") != aRule.repeats) {
@@ -1161,17 +1223,17 @@ function waitForMessages(aOptions)
           displayErrorContext(aRule, aElement);
         }
         return false;
       }
       aRule.longStrings = longStrings;
     }
 
     if ("objects" in aRule) {
-      let clickables = aElement.querySelectorAll(".body a");
+      let clickables = aElement.querySelectorAll(".message-body a");
       if (aRule.objects != !!clickables[0]) {
         if (partialMatch) {
           is(!!clickables[0], aRule.objects,
              "objects existence check failed for message rule: " +
              displayRule(aRule));
           displayErrorContext(aRule, aElement);
         }
         return false;
@@ -1405,17 +1467,18 @@ function checkOutputForInputs(hud, input
     }
     if (typeof entry.inspectorIcon == "boolean") {
       yield checkLinkToInspector(entry, msg);
     }
   }
 
   function checkObjectClick(entry, msg)
   {
-    let body = msg.querySelector(".body a") || msg.querySelector(".body");
+    let body = msg.querySelector(".message-body a") ||
+               msg.querySelector(".message-body");
     ok(body, "the message body");
 
     let deferred = promise.defer();
 
     entry._onVariablesViewOpen = onVariablesViewOpen.bind(null, entry, deferred);
     hud.jsterm.on("variablesview-open", entry._onVariablesViewOpen);
     eventHandlers.add(entry._onVariablesViewOpen);
 
--- a/browser/devtools/webconsole/test/test-bug-766001-console-log.js
+++ b/browser/devtools/webconsole/test/test-bug-766001-console-log.js
@@ -1,8 +1,10 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-window.addEventListener("load", function() {
+function onLoad123() {
   console.log("Blah Blah");
-}, false);
+}
+
+window.addEventListener("load", onLoad123, false);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-api-stackframe.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en">
+  <head>
+    <meta charset="utf8">
+    <!--
+    - Any copyright is dedicated to the Public Domain.
+    - http://creativecommons.org/publicdomain/zero/1.0/
+    -->
+    <title>Test for bug 920116 - stacktraces for console API messages</title>
+    <script>
+      function firstCall() {
+        secondCall();
+      }
+
+      function secondCall() {
+        thirdCall();
+      }
+
+      function thirdCall() {
+        console.log("foo-log");
+        console.error("foo-error");
+        console.exception("foo-exception");
+        console.assert("red" == "blue", "foo-assert");
+      }
+
+      window.onload = firstCall;
+    </script>
+  </head>
+  <body>
+    <p>Hello world!</p>
+  </body>
+</html>
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -1368,17 +1368,17 @@ WebConsoleFrame.prototype = {
 
     let node = this.createMessageNode(aCategory, severity,
                                       errorMessage,
                                       aScriptError.sourceName,
                                       aScriptError.lineNumber, null, null,
                                       aScriptError.timeStamp);
 
     // Select the body of the message node that is displayed in the console
-    let msgBody = node.getElementsByClassName("body")[0];
+    let msgBody = node.getElementsByClassName("message-body")[0];
     // Add the more info link node to messages that belong to certain categories
     this.addMoreInfoLink(msgBody, aScriptError);
 
     if (aScriptError.private) {
       node.setAttribute("private", true);
     }
 
     if (objectActors.size > 0) {
@@ -2456,17 +2456,17 @@ WebConsoleFrame.prototype = {
     iconContainer.className = "icon";
 
     // Apply the current group by indenting appropriately.
     let iconMarginLeft = this.groupDepth * GROUP_INDENT + GROUP_INDENT_DEFAULT;
     iconContainer.style.marginLeft = iconMarginLeft + "px";
 
     // Create the message body, which contains the actual text of the message.
     let bodyNode = this.document.createElementNS(XHTML_NS, "span");
-    bodyNode.className = "body devtools-monospace";
+    bodyNode.className = "message-body-wrapper message-body devtools-monospace";
 
     // Store the body text, since it is needed later for the variables view.
     let body = aBody;
     // If a string was supplied for the body, turn it into a DOM node and an
     // associated clipboard string now.
     aClipboardText = aClipboardText ||
                      (aBody + (aSourceURL ? " @ " + aSourceURL : "") +
                               (aSourceLine ? ":" + aSourceLine : ""));
@@ -2603,17 +2603,19 @@ WebConsoleFrame.prototype = {
     }
 
     filenameNode.className = "filename";
     filenameNode.textContent = " " + (filename || l10n.getStr("unknownLocation"));
     locationNode.appendChild(filenameNode);
 
     locationNode.href = isScratchpad || !fullURL ? "#" : fullURL;
     locationNode.draggable = false;
-    locationNode.target = aTarget;
+    if (aTarget) {
+      locationNode.target = aTarget;
+    }
     locationNode.setAttribute("title", aSourceURL);
     locationNode.className = "message-location theme-link devtools-monospace";
 
     // Make the location clickable.
     let onClick = () => {
       let target = locationNode.target;
       if (target == "scratchpad" || isScratchpad) {
         this.owner.viewSourceInScratchpad(aSourceURL);
--- 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: 1.0.21
+Current extension version is: 1.0.68
 
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -222,17 +222,18 @@ ChromeActions.prototype = {
   },
   download: function(data, sendResponse) {
     var self = this;
     var originalUrl = data.originalUrl;
     // The data may not be downloaded so we need just retry getting the pdf with
     // the original url.
     var originalUri = NetUtil.newURI(data.originalUrl);
     var filename = data.filename;
-    if (typeof filename !== 'string' || !/\.pdf$/i.test(filename)) {
+    if (typeof filename !== 'string' || 
+        (!/\.pdf$/i.test(filename) && !data.isAttachment)) {
       filename = 'document.pdf';
     }
     var blobUri = data.blobUrl ? NetUtil.newURI(data.blobUrl) : originalUri;
     var extHelperAppSvc =
           Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
              getService(Ci.nsIExternalHelperAppService);
     var frontWindow = Cc['@mozilla.org/embedcomp/window-watcher;1'].
                          getService(Ci.nsIWindowWatcher).activeWindow;
@@ -268,17 +269,18 @@ ChromeActions.prototype = {
       if ('nsIPrivateBrowsingChannel' in Ci &&
           channel instanceof Ci.nsIPrivateBrowsingChannel) {
         channel.setPrivate(docIsPrivate);
       }
 
       var listener = {
         extListener: null,
         onStartRequest: function(aRequest, aContext) {
-          this.extListener = extHelperAppSvc.doContent('application/pdf',
+          this.extListener = extHelperAppSvc.doContent((data.isAttachment ? '' :
+                                                        'application/pdf'),
                                 aRequest, frontWindow, false);
           this.extListener.onStartRequest(aRequest, aContext);
         },
         onStopRequest: function(aRequest, aContext, aStatusCode) {
           if (this.extListener)
             this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
           // Notify the content code we're done downloading.
           if (sendResponse)
--- 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 = '1.0.21';
-PDFJS.build = 'f954cde';
+PDFJS.version = '1.0.68';
+PDFJS.build = 'ead4cbf';
 
 (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
@@ -448,16 +448,38 @@ function stringToBytes(str) {
   return bytes;
 }
 
 function string32(value) {
   return String.fromCharCode((value >> 24) & 0xff, (value >> 16) & 0xff,
                              (value >> 8) & 0xff, value & 0xff);
 }
 
+function log2(x) {
+  var n = 1, i = 0;
+  while (x > n) {
+    n <<= 1;
+    i++;
+  }
+  return i;
+}
+
+function readInt8(data, start) {
+  return (data[start] << 24) >> 24;
+}
+
+function readUint16(data, offset) {
+  return (data[offset] << 8) | data[offset + 1];
+}
+
+function readUint32(data, offset) {
+  return ((data[offset] << 24) | (data[offset + 1] << 16) |
+         (data[offset + 2] << 8) | data[offset + 3]) >>> 0;
+}
+
 // Lazy test the endianness of the platform
 // NOTE: This will be 'true' for simulated TypedArrays
 function isLittleEndian() {
   var buffer8 = new Uint8Array(2);
   buffer8[0] = 1;
   var buffer16 = new Uint16Array(buffer8.buffer);
   return (buffer16[0] === 1);
 }
@@ -1740,41 +1762,41 @@ var DeviceCmykCS = (function DeviceCmykC
   //   f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255
   function convertToRgb(src, srcOffset, srcScale, dest, destOffset) {
     var c = src[srcOffset + 0] * srcScale;
     var m = src[srcOffset + 1] * srcScale;
     var y = src[srcOffset + 2] * srcScale;
     var k = src[srcOffset + 3] * srcScale;
 
     var r =
-      c * (-4.387332384609988 * c + 54.48615194189176 * m +
-           18.82290502165302 * y + 212.25662451639585 * k +
-           -285.2331026137004) +
-      m * (1.7149763477362134 * m - 5.6096736904047315 * y +
-           -17.873870861415444 * k - 5.497006427196366) +
-      y * (-2.5217340131683033 * y - 21.248923337353073 * k +
-           17.5119270841813) +
-      k * (-21.86122147463605 * k - 189.48180835922747) + 255;
+      (c * (-4.387332384609988 * c + 54.48615194189176 * m +
+            18.82290502165302 * y + 212.25662451639585 * k +
+            -285.2331026137004) +
+       m * (1.7149763477362134 * m - 5.6096736904047315 * y +
+            -17.873870861415444 * k - 5.497006427196366) +
+       y * (-2.5217340131683033 * y - 21.248923337353073 * k +
+            17.5119270841813) +
+       k * (-21.86122147463605 * k - 189.48180835922747) + 255) | 0;
     var g =
-      c * (8.841041422036149 * c + 60.118027045597366 * m +
-           6.871425592049007 * y + 31.159100130055922 * k +
-           -79.2970844816548) +
-      m * (-15.310361306967817 * m + 17.575251261109482 * y +
-           131.35250912493976 * k - 190.9453302588951) +
-      y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
-      k * (-20.737325471181034 * k - 187.80453709719578) + 255;
+      (c * (8.841041422036149 * c + 60.118027045597366 * m +
+            6.871425592049007 * y + 31.159100130055922 * k +
+            -79.2970844816548) +
+       m * (-15.310361306967817 * m + 17.575251261109482 * y +
+            131.35250912493976 * k - 190.9453302588951) +
+       y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
+       k * (-20.737325471181034 * k - 187.80453709719578) + 255) | 0;
     var b =
-      c * (0.8842522430003296 * c + 8.078677503112928 * m +
-           30.89978309703729 * y - 0.23883238689178934 * k +
-           -14.183576799673286) +
-      m * (10.49593273432072 * m + 63.02378494754052 * y +
-           50.606957656360734 * k - 112.23884253719248) +
-      y * (0.03296041114873217 * y + 115.60384449646641 * k +
-           -193.58209356861505) +
-      k * (-22.33816807309886 * k - 180.12613974708367) + 255;
+      (c * (0.8842522430003296 * c + 8.078677503112928 * m +
+            30.89978309703729 * y - 0.23883238689178934 * k +
+            -14.183576799673286) +
+       m * (10.49593273432072 * m + 63.02378494754052 * y +
+            50.606957656360734 * k - 112.23884253719248) +
+       y * (0.03296041114873217 * y + 115.60384449646641 * k +
+            -193.58209356861505) +
+       k * (-22.33816807309886 * k - 180.12613974708367) + 255) | 0;
 
     dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r;
     dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g;
     dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b;
   }
 
   function DeviceCmykCS() {
     this.name = 'DeviceCMYK';
@@ -2137,17 +2159,16 @@ var PDFFunction = (function PDFFunctionC
     parse: function PDFFunction_parse(xref, fn) {
       var IR = this.getIR(xref, fn);
       return this.fromIR(IR);
     },
 
     constructSampled: function PDFFunction_constructSampled(str, dict) {
       function toMultiArray(arr) {
         var inputLength = arr.length;
-        var outputLength = arr.length / 2;
         var out = [];
         var index = 0;
         for (var i = 0; i < inputLength; i += 2) {
           out[index] = [arr[i], arr[i + 1]];
           ++index;
         }
         return out;
       }
@@ -2208,17 +2229,17 @@ var PDFFunction = (function PDFFunctionC
         // See chapter 3, page 110 of the PDF reference.
         var m = IR[1];
         var domain = IR[2];
         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 mask = IR[8];
         var range = IR[9];
 
         if (m != args.length) {
           error('Incorrect number of arguments: ' + m + ' != ' +
                 args.length);
         }
 
         var x = args;
@@ -2879,16 +2900,17 @@ var Annotation = (function AnnotationClo
             data.borderWidth = 0;
           }
         }
       }
     }
 
     this.appearance = getDefaultAppearance(dict);
     data.hasAppearance = !!this.appearance;
+    data.id = params.ref.num;
   }
 
   Annotation.prototype = {
 
     getData: function Annotation_getData() {
       return this.data;
     },
 
@@ -2987,18 +3009,16 @@ var Annotation = (function AnnotationClo
         'Font'
         // ProcSet
         // Properties
       ]);
       var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1];
       var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0];
       var transform = getTransformMatrix(data.rect, bbox, matrix);
 
-      var border = data.border;
-
       resourcesPromise.then(function(resources) {
         var opList = new OperatorList();
         opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
         evaluator.getOperatorList(this.appearance, resources, opList);
         opList.addOp(OPS.endAnnotation, []);
         promise.resolve(opList);
 
         this.appearance.reset();
@@ -3214,17 +3234,16 @@ var TextWidgetAnnotation = (function Tex
     var fontName = fontObj.loadedName;
     var fontFamily = fontName ? '"' + fontName + '", ' : '';
     // Use a reasonable default font if the font doesn't specify a fallback
     var fallbackName = fontObj.fallbackName || 'Helvetica, sans-serif';
     style.fontFamily = fontFamily + fallbackName;
   }
 
 
-  var parent = WidgetAnnotation.prototype;
   Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
     hasHtml: function TextWidgetAnnotation_hasHtml() {
       return !this.data.hasAppearance && !!this.data.fieldValue;
     },
 
     getHtmlElement: function TextWidgetAnnotation_getHtmlElement(commonObjs) {
       assert(!isWorker, 'getHtmlElement() shall be called from main thread');
 
@@ -3237,17 +3256,17 @@ var TextWidgetAnnotation = (function Tex
       content.textContent = item.fieldValue;
       var textAlignment = item.textAlignment;
       content.style.textAlign = ['left', 'center', 'right'][textAlignment];
       content.style.verticalAlign = 'middle';
       content.style.display = 'table-cell';
 
       var fontObj = item.fontRefName ?
                     commonObjs.getData(item.fontRefName) : null;
-      var cssRules = setTextStyles(content, item, fontObj);
+      setTextStyles(content, item, fontObj);
 
       element.appendChild(content);
 
       return element;
     },
 
     getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
       if (this.appearance) {
@@ -3269,17 +3288,16 @@ var TextWidgetAnnotation = (function Tex
 
       // Include any font resources found in the default appearance
 
       var stream = new Stream(stringToBytes(defaultAppearance));
       evaluator.getOperatorList(stream, this.fieldResources, opList);
       var appearanceFnArray = opList.fnArray;
       var appearanceArgsArray = opList.argsArray;
       var fnArray = [];
-      var argsArray = [];
 
       // TODO(mack): Add support for stroke color
       data.rgb = [0, 0, 0];
       // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY!
       for (var i = 0, n = fnArray.length; i < n; ++i) {
         var fnId = appearanceFnArray[i];
         var args = appearanceArgsArray[i];
 
@@ -3485,17 +3503,16 @@ var TextAnnotation = (function TextAnnot
         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();
@@ -3590,17 +3607,16 @@ var LinkAnnotation = (function LinkAnnot
     },
 
     getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) {
 
       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);
@@ -3846,16 +3862,23 @@ var PDFDocumentProxy = (function PDFDocu
     /**
      * @return {Promise} A promise that is resolved with a lookup table for
      * mapping named destinations to reference numbers.
      */
     getDestinations: function PDFDocumentProxy_getDestinations() {
       return this.transport.getDestinations();
     },
     /**
+     * @return {Promise} A promise that is resolved with a lookup table for
+     * mapping named attachments to their content.
+     */
+    getAttachments: function PDFDocumentProxy_getAttachments() {
+      return this.transport.getAttachments();
+    },
+    /**
      * @return {Promise} A promise that is resolved with an array of all the
      * JavaScript strings in the name tree.
      */
     getJavaScript: function PDFDocumentProxy_getJavaScript() {
       var promise = new PDFJS.LegacyPromise();
       var js = this.pdfInfo.javaScript;
       promise.resolve(js);
       return promise;
@@ -4622,16 +4645,26 @@ var WorkerTransport = (function WorkerTr
       this.messageHandler.send('GetDestinations', null,
         function transportDestinations(destinations) {
           promise.resolve(destinations);
         }
       );
       return promise;
     },
 
+    getAttachments: function WorkerTransport_getAttachments() {
+      var promise = new PDFJS.LegacyPromise();
+      this.messageHandler.send('GetAttachments', null,
+        function transportAttachments(attachments) {
+          promise.resolve(attachments);
+        }
+      );
+      return promise;
+    },
+
     startCleanup: function WorkerTransport_startCleanup() {
       this.messageHandler.send('Cleanup', null,
         function endCleanup() {
           for (var i = 0, ii = this.pageCache.length; i < ii; i++) {
             var page = this.pageCache[i];
             if (page) {
               page.destroy();
             }
@@ -5416,17 +5449,16 @@ var CanvasGraphics = (function CanvasGra
     var src = imgData.data;
     var dest = chunkImgData.data;
     var i, j, thisChunkHeight, elemsInThisChunk;
 
     // There are multiple forms in which the pixel data can be passed, and
     // imgData.kind tells us which one this is.
     if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
       // Grayscale, 1 bit per pixel (i.e. black-and-white).
-      var destDataLength = dest.length;
       var srcLength = src.byteLength;
       var dest32 = PDFJS.hasCanvasTypedArrays ? new Uint32Array(dest.buffer) :
         new Uint32ArrayView(dest);
       var dest32DataLength = dest32.length;
       var fullSrcDiff = (width + 7) >> 3;
       var white = 0xFFFFFFFF;
       var black = (PDFJS.isLittleEndian || !PDFJS.hasCanvasTypedArrays) ?
         0xFF000000 : 0x000000FF;
@@ -5687,17 +5719,16 @@ var CanvasGraphics = (function CanvasGra
       var i = executionStartIdx || 0;
       var argsArrayLen = argsArray.length;
 
       // Sometimes the OperatorList to execute is empty.
       if (argsArrayLen == i) {
         return i;
       }
 
-      var executionEndIdx;
       var endTime = Date.now() + EXECUTION_TIME;
 
       var commonObjs = this.commonObjs;
       var objs = this.objs;
       var fnId;
       var deferred = Promise.resolve();
 
       while (true) {
@@ -6263,17 +6294,16 @@ var CanvasGraphics = (function CanvasGra
       var charSpacing = current.charSpacing;
       var wordSpacing = current.wordSpacing;
       var textHScale = current.textHScale * current.fontDirection;
       var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
       var glyphsLength = glyphs.length;
       var vertical = font.vertical;
       var defaultVMetrics = font.defaultVMetrics;
       var i, glyph, width;
-      var VERTICAL_TEXT_ROTATION = Math.PI / 2;
 
       if (fontSize === 0) {
         return;
       }
 
       // Type3 fonts - each glyph is a "mini-PDF"
       if (font.coded) {
         ctx.save();
@@ -6391,17 +6421,16 @@ var CanvasGraphics = (function CanvasGra
           current.y -= x * textHScale;
         } else {
           current.x += x * textHScale;
         }
         ctx.restore();
       }
     },
     showSpacedText: function CanvasGraphics_showSpacedText(arr) {
-      var ctx = this.ctx;
       var current = this.current;
       var font = current.font;
       var fontSize = current.fontSize;
       // TJ array's number is independent from fontMatrix
       var textHScale = current.textHScale * 0.001 * current.fontDirection;
       var arrLength = arr.length;
       var vertical = font.vertical;
 
@@ -6467,18 +6496,16 @@ var CanvasGraphics = (function CanvasGra
     },
     getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR, cs) {
       var pattern;
       if (IR[0] == 'TilingPattern') {
         var args = IR[1];
         var base = cs.base;
         var color;
         if (base) {
-          var baseComps = base.numComps;
-
           color = base.getRgb(args, 0);
         }
         pattern = new TilingPattern(IR, color, this.ctx, this.objs,
                                     this.commonObjs, this.baseTransform);
       } else {
         pattern = getShadingPatternFromIR(IR);
       }
       return pattern;
@@ -7727,23 +7754,23 @@ var createMeshCanvas = (function createM
     return {canvas: canvas, offsetX: offsetX, offsetY: offsetY,
             scaleX: scaleX, scaleY: scaleY};
   }
   return createMeshCanvas;
 })();
 
 ShadingIRs.Mesh = {
   fromIR: function Mesh_fromIR(raw) {
-    var type = raw[1];
+    //var type = raw[1];
     var coords = raw[2];
     var colors = raw[3];
     var figures = raw[4];
     var bounds = raw[5];
     var matrix = raw[6];
-    var bbox = raw[7];
+    //var bbox = raw[7];
     var background = raw[8];
     return {
       type: 'Pattern',
       getPattern: function Mesh_getPattern(ctx, owner, shadingFill) {
         var combinedScale;
         // Obtain scale from matrix and current transformation matrix.
         if (shadingFill) {
           combinedScale = Util.singularValueDecompose2dScale(
@@ -7830,17 +7857,16 @@ var TilingPattern = (function TilingPatt
       var bbox = this.bbox;
       var xstep = this.xstep;
       var ystep = this.ystep;
       var paintType = this.paintType;
       var tilingType = this.tilingType;
       var color = this.color;
       var objs = this.objs;
       var commonObjs = this.commonObjs;
-      var ctx = this.ctx;
 
       info('TilingType: ' + tilingType);
 
       var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
 
       var topLeft = [x0, y0];
       // we want the canvas to be as large as the step size
       var botRight = [x0 + xstep, y0 + ystep];
--- 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 = '1.0.21';
-PDFJS.build = 'f954cde';
+PDFJS.version = '1.0.68';
+PDFJS.build = 'ead4cbf';
 
 (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
@@ -448,16 +448,38 @@ function stringToBytes(str) {
   return bytes;
 }
 
 function string32(value) {
   return String.fromCharCode((value >> 24) & 0xff, (value >> 16) & 0xff,
                              (value >> 8) & 0xff, value & 0xff);
 }
 
+function log2(x) {
+  var n = 1, i = 0;
+  while (x > n) {
+    n <<= 1;
+    i++;
+  }
+  return i;
+}
+
+function readInt8(data, start) {
+  return (data[start] << 24) >> 24;
+}
+
+function readUint16(data, offset) {
+  return (data[offset] << 8) | data[offset + 1];
+}
+
+function readUint32(data, offset) {
+  return ((data[offset] << 24) | (data[offset + 1] << 16) |
+         (data[offset + 2] << 8) | data[offset + 3]) >>> 0;
+}
+
 // Lazy test the endianness of the platform
 // NOTE: This will be 'true' for simulated TypedArrays
 function isLittleEndian() {
   var buffer8 = new Uint8Array(2);
   buffer8[0] = 1;
   var buffer16 = new Uint16Array(buffer8.buffer);
   return (buffer16[0] === 1);
 }
@@ -1740,41 +1762,41 @@ var DeviceCmykCS = (function DeviceCmykC
   //   f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255
   function convertToRgb(src, srcOffset, srcScale, dest, destOffset) {
     var c = src[srcOffset + 0] * srcScale;
     var m = src[srcOffset + 1] * srcScale;
     var y = src[srcOffset + 2] * srcScale;
     var k = src[srcOffset + 3] * srcScale;
 
     var r =
-      c * (-4.387332384609988 * c + 54.48615194189176 * m +
-           18.82290502165302 * y + 212.25662451639585 * k +
-           -285.2331026137004) +
-      m * (1.7149763477362134 * m - 5.6096736904047315 * y +
-           -17.873870861415444 * k - 5.497006427196366) +
-      y * (-2.5217340131683033 * y - 21.248923337353073 * k +
-           17.5119270841813) +
-      k * (-21.86122147463605 * k - 189.48180835922747) + 255;
+      (c * (-4.387332384609988 * c + 54.48615194189176 * m +
+            18.82290502165302 * y + 212.25662451639585 * k +
+            -285.2331026137004) +
+       m * (1.7149763477362134 * m - 5.6096736904047315 * y +
+            -17.873870861415444 * k - 5.497006427196366) +
+       y * (-2.5217340131683033 * y - 21.248923337353073 * k +
+            17.5119270841813) +
+       k * (-21.86122147463605 * k - 189.48180835922747) + 255) | 0;
     var g =
-      c * (8.841041422036149 * c + 60.118027045597366 * m +
-           6.871425592049007 * y + 31.159100130055922 * k +
-           -79.2970844816548) +
-      m * (-15.310361306967817 * m + 17.575251261109482 * y +
-           131.35250912493976 * k - 190.9453302588951) +
-      y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
-      k * (-20.737325471181034 * k - 187.80453709719578) + 255;
+      (c * (8.841041422036149 * c + 60.118027045597366 * m +
+            6.871425592049007 * y + 31.159100130055922 * k +
+            -79.2970844816548) +
+       m * (-15.310361306967817 * m + 17.575251261109482 * y +
+            131.35250912493976 * k - 190.9453302588951) +
+       y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
+       k * (-20.737325471181034 * k - 187.80453709719578) + 255) | 0;
     var b =
-      c * (0.8842522430003296 * c + 8.078677503112928 * m +
-           30.89978309703729 * y - 0.23883238689178934 * k +
-           -14.183576799673286) +
-      m * (10.49593273432072 * m + 63.02378494754052 * y +
-           50.606957656360734 * k - 112.23884253719248) +
-      y * (0.03296041114873217 * y + 115.60384449646641 * k +
-           -193.58209356861505) +
-      k * (-22.33816807309886 * k - 180.12613974708367) + 255;
+      (c * (0.8842522430003296 * c + 8.078677503112928 * m +
+            30.89978309703729 * y - 0.23883238689178934 * k +
+            -14.183576799673286) +
+       m * (10.49593273432072 * m + 63.02378494754052 * y +
+            50.606957656360734 * k - 112.23884253719248) +
+       y * (0.03296041114873217 * y + 115.60384449646641 * k +
+            -193.58209356861505) +
+       k * (-22.33816807309886 * k - 180.12613974708367) + 255) | 0;
 
     dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r;
     dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g;
     dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b;
   }
 
   function DeviceCmykCS() {
     this.name = 'DeviceCMYK';
@@ -2137,17 +2159,16 @@ var PDFFunction = (function PDFFunctionC
     parse: function PDFFunction_parse(xref, fn) {
       var IR = this.getIR(xref, fn);
       return this.fromIR(IR);
     },
 
     constructSampled: function PDFFunction_constructSampled(str, dict) {
       function toMultiArray(arr) {
         var inputLength = arr.length;
-        var outputLength = arr.length / 2;
         var out = [];
         var index = 0;
         for (var i = 0; i < inputLength; i += 2) {
           out[index] = [arr[i], arr[i + 1]];
           ++index;
         }
         return out;
       }
@@ -2208,17 +2229,17 @@ var PDFFunction = (function PDFFunctionC
         // See chapter 3, page 110 of the PDF reference.
         var m = IR[1];
         var domain = IR[2];
         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 mask = IR[8];
         var range = IR[9];
 
         if (m != args.length) {
           error('Incorrect number of arguments: ' + m + ' != ' +
                 args.length);
         }
 
         var x = args;
@@ -2879,16 +2900,17 @@ var Annotation = (function AnnotationClo
             data.borderWidth = 0;
           }
         }
       }
     }
 
     this.appearance = getDefaultAppearance(dict);
     data.hasAppearance = !!this.appearance;
+    data.id = params.ref.num;
   }
 
   Annotation.prototype = {
 
     getData: function Annotation_getData() {
       return this.data;
     },
 
@@ -2987,18 +3009,16 @@ var Annotation = (function AnnotationClo
         'Font'
         // ProcSet
         // Properties
       ]);
       var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1];
       var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0];
       var transform = getTransformMatrix(data.rect, bbox, matrix);
 
-      var border = data.border;
-
       resourcesPromise.then(function(resources) {
         var opList = new OperatorList();
         opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
         evaluator.getOperatorList(this.appearance, resources, opList);
         opList.addOp(OPS.endAnnotation, []);
         promise.resolve(opList);
 
         this.appearance.reset();
@@ -3214,17 +3234,16 @@ var TextWidgetAnnotation = (function Tex
     var fontName = fontObj.loadedName;
     var fontFamily = fontName ? '"' + fontName + '", ' : '';
     // Use a reasonable default font if the font doesn't specify a fallback
     var fallbackName = fontObj.fallbackName || 'Helvetica, sans-serif';
     style.fontFamily = fontFamily + fallbackName;
   }
 
 
-  var parent = WidgetAnnotation.prototype;
   Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
     hasHtml: function TextWidgetAnnotation_hasHtml() {
       return !this.data.hasAppearance && !!this.data.fieldValue;
     },
 
     getHtmlElement: function TextWidgetAnnotation_getHtmlElement(commonObjs) {
       assert(!isWorker, 'getHtmlElement() shall be called from main thread');
 
@@ -3237,17 +3256,17 @@ var TextWidgetAnnotation = (function Tex
       content.textContent = item.fieldValue;
       var textAlignment = item.textAlignment;
       content.style.textAlign = ['left', 'center', 'right'][textAlignment];
       content.style.verticalAlign = 'middle';
       content.style.display = 'table-cell';
 
       var fontObj = item.fontRefName ?
                     commonObjs.getData(item.fontRefName) : null;
-      var cssRules = setTextStyles(content, item, fontObj);
+      setTextStyles(content, item, fontObj);
 
       element.appendChild(content);
 
       return element;
     },
 
     getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
       if (this.appearance) {
@@ -3269,17 +3288,16 @@ var TextWidgetAnnotation = (function Tex
 
       // Include any font resources found in the default appearance
 
       var stream = new Stream(stringToBytes(defaultAppearance));
       evaluator.getOperatorList(stream, this.fieldResources, opList);
       var appearanceFnArray = opList.fnArray;
       var appearanceArgsArray = opList.argsArray;
       var fnArray = [];
-      var argsArray = [];
 
       // TODO(mack): Add support for stroke color
       data.rgb = [0, 0, 0];
       // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY!
       for (var i = 0, n = fnArray.length; i < n; ++i) {
         var fnId = appearanceFnArray[i];
         var args = appearanceArgsArray[i];
 
@@ -3485,17 +3503,16 @@ var TextAnnotation = (function TextAnnot
         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();
@@ -3590,17 +3607,16 @@ var LinkAnnotation = (function LinkAnnot
     },
 
     getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) {
 
       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);
@@ -3737,17 +3753,17 @@ var ChunkedStream = (function ChunkedStr
     },
 
     getUint16: function ChunkedStream_getUint16() {
       var b0 = this.getByte();
       var b1 = this.getByte();
       return (b0 << 8) + b1;
     },
 
-    getUint32: function ChunkedStream_getUint32() {
+    getInt32: function ChunkedStream_getInt32() {
       var b0 = this.getByte();
       var b1 = this.getByte();
       var b2 = this.getByte();
       var b3 = this.getByte();
       return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
     },
 
     // returns subarray of original buffer
@@ -3826,17 +3842,16 @@ var ChunkedStream = (function ChunkedStr
   };
 
   return ChunkedStream;
 })();
 
 var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
 
   function ChunkedStreamManager(length, chunkSize, url, args) {
-    var self = this;
     this.stream = new ChunkedStream(length, chunkSize, this);
     this.length = length;
     this.chunkSize = chunkSize;
     this.url = url;
     this.disableAutoFetch = args.disableAutoFetch;
     var msgHandler = this.msgHandler = args.msgHandler;
 
     if (args.chunkedViewerLoading) {
@@ -5259,16 +5274,40 @@ var Catalog = (function CatalogClosure()
           if (!names.hasOwnProperty(name)) {
             continue;
           }
           dests[name] = fetchDestination(names[name]);
         }
       }
       return shadow(this, 'destinations', dests);
     },
+    get attachments() {
+      var xref = this.xref;
+      var attachments, nameTreeRef;
+      var obj = this.catDict.get('Names');
+      if (obj) {
+        nameTreeRef = obj.getRaw('EmbeddedFiles');
+      }
+
+      if (nameTreeRef) {
+        var nameTree = new NameTree(nameTreeRef, xref);
+        var names = nameTree.getAll();
+        for (var name in names) {
+          if (!names.hasOwnProperty(name)) {
+            continue;
+          }
+          var fs = new FileSpec(names[name], xref);
+          if (!attachments) {
+            attachments = {};
+          }
+          attachments[stringToPDFString(name)] = fs.serializable;
+        }
+      }
+      return shadow(this, 'attachments', attachments);
+    },
     get javaScript() {
       var xref = this.xref;
       var obj = this.catDict.get('Names');
 
       var javaScript = [];
       if (obj && obj.has('JavaScript')) {
         var nameTree = new NameTree(obj.getRaw('JavaScript'), xref);
         var names = nameTree.getAll();
@@ -5745,18 +5784,16 @@ var XRef = (function XRefClosure() {
       var endobjBytes = new Uint8Array([101, 110, 100, 111, 98, 106]);
       var xrefBytes = new Uint8Array([47, 88, 82, 101, 102]);
 
       var stream = this.stream;
       stream.pos = 0;
       var buffer = stream.getBytes();
       var position = stream.start, length = buffer.length;
       var trailers = [], xrefStms = [];
-      var state = 0;
-      var currentToken;
       while (position < length) {
         var ch = buffer[position];
         if (ch === 32 || ch === 9 || ch === 13 || ch === 10) {
           ++position;
           continue;
         }
         if (ch === 37) { // %-comment
           do {
@@ -6123,16 +6160,107 @@ var NameTree = (function NameTreeClosure
       }
       return dict;
     }
   };
   return NameTree;
 })();
 
 /**
+ * "A PDF file can refer to the contents of another file by using a File 
+ * Specification (PDF 1.1)", see the spec (7.11) for more details.
+ * NOTE: Only embedded files are supported (as part of the attachments support)
+ * TODO: support the 'URL' file system (with caching if !/V), portable 
+ * collections attributes and related files (/RF)
+ */
+var FileSpec = (function FileSpecClosure() {
+  function FileSpec(root, xref) {
+    if (!root || !isDict(root)) {
+      return;
+    }
+    this.xref = xref;
+    this.root = root;
+    if (root.has('FS')) {
+      this.fs = root.get('FS');
+    }
+    this.description = root.has('Desc') ?
+                         stringToPDFString(root.get('Desc')) :
+                         '';
+    if (root.has('RF')) {
+      warn('Related file specifications are not supported');
+    }
+    this.contentAvailable = true;
+    if (!root.has('EF')) {
+      this.contentAvailable = false;
+      warn('Non-embedded file specifications are not supported');
+    }
+  }
+
+  function pickPlatformItem(dict) {
+    // Look for the filename in this order:
+    // UF, F, Unix, Mac, DOS
+    if (dict.has('UF')) {
+      return dict.get('UF');
+    } else if (dict.has('F')) {
+      return dict.get('F');
+    } else if (dict.has('Unix')) {
+      return dict.get('Unix');
+    } else if (dict.has('Mac')) {
+      return dict.get('Mac');
+    } else if (dict.has('DOS')) {
+      return dict.get('DOS');
+    } else {
+      return null;
+    }
+  }
+
+  FileSpec.prototype = {
+    get filename() {
+      if (!this._filename && this.root) {
+        var filename = pickPlatformItem(this.root) || 'unnamed';
+        this._filename = stringToPDFString(filename).
+          replace(/\\\\/g, '\\').
+          replace(/\\\//g, '/').
+          replace(/\\/g, '/');
+      }
+      return this._filename;
+    },
+    get content() {
+      if (!this.contentAvailable) {
+        return null;
+      }
+      if (!this.contentRef && this.root) {
+        this.contentRef = pickPlatformItem(this.root.get('EF'));
+      }
+      var content = null;
+      if (this.contentRef) {
+        var xref = this.xref;
+        var fileObj = xref.fetchIfRef(this.contentRef);
+        if (fileObj && isStream(fileObj)) {
+          content = fileObj.getBytes();
+        } else {
+          warn('Embedded file specification points to non-existing/invalid ' +
+            'content');
+        }
+      } else {
+        warn('Embedded file specification does not have a content');
+      }
+      return content;
+    },
+    get serializable() {
+      return {
+        filename: this.filename,
+        content: this.content
+      };
+    }
+  };
+  return FileSpec;
+})();
+
+/**
  * A helper for loading missing data in object graphs. It traverses the graph
  * depth first and queues up any objects that have missing data. Once it has
  * has traversed as many objects that are available it attempts to bundle the
  * missing data requests and then resume from the nodes that weren't ready.
  *
  * NOTE: It provides protection from circular references by keeping track of
  * of loaded references. However, you must be careful not to load any graphs
  * that have references to the catalog or other pages since that will cause the
@@ -14321,17 +14449,16 @@ Shadings.Mesh = (function MeshClosure() 
       coords: psPacked,
       colors: psPacked
     });
   }
 
   function decodeType5Shading(mesh, reader, verticesPerRow) {
     var coords = mesh.coords;
     var colors = mesh.colors;
-    var operators = [];
     var ps = []; // not maintaining cs since that will match ps
     while (reader.hasData) {
       var coord = reader.readCoordinate();
       var color = reader.readComponents();
       ps.push(coords.length);
       coords.push(coord);
       colors.push(color);
     }
@@ -15275,28 +15402,26 @@ var PartialEvaluator = (function Partial
 
     getOperatorList: function PartialEvaluator_getOperatorList(stream,
                                                                resources,
                                                                operatorList,
                                                                initialState) {
 
       var self = this;
       var xref = this.xref;
-      var handler = this.handler;
       var imageCache = {};
 
       operatorList = (operatorList || new OperatorList());
 
       resources = (resources || Dict.empty);
       var xobjs = (resources.get('XObject') || Dict.empty);
       var patterns = (resources.get('Pattern') || Dict.empty);
       var stateManager = new StateManager(initialState || new EvalState());
       var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
 
-      var promise = new LegacyPromise();
       var operation, i, ii;
       while ((operation = preprocessor.read())) {
         var args = operation.args;
         var fn = operation.fn;
         var shading;
 
         switch (fn) {
           case OPS.setStrokeColorN:
@@ -15472,17 +15597,16 @@ var PartialEvaluator = (function Partial
 
       resources = (xref.fetchIfRef(resources) || Dict.empty);
 
       // The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd.
       var xobjs = null;
       var xobjsCache = {};
 
       var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
-      var res = resources;
 
       var operation;
       var textState;
 
       function newTextChunk() {
         var font = textState.font;
         if (!(font.loadedName in textContent.styles)) {
           textContent.styles[font.loadedName] = {
@@ -15534,16 +15658,17 @@ var PartialEvaluator = (function Partial
         }
         var width = 0;
         var height = 0;
         var glyphs = font.charsToGlyphs(chars);
         var defaultVMetrics = font.defaultVMetrics;
         for (var i = 0; i < glyphs.length; i++) {
           var glyph = glyphs[i];
           if (!glyph) { // Previous glyph was a space.
+            width += textState.wordSpacing * textState.textHScale;
             continue;
           }
           var vMetricX = null;
           var vMetricY = null;
           var glyphWidth = null;
           if (font.vertical) {
             if (glyph.vmetric) {
               glyphWidth = glyph.vmetric[0];
@@ -15639,40 +15764,41 @@ var PartialEvaluator = (function Partial
             break;
           case OPS.setTextMatrix:
             textState.setTextMatrix(args[0], args[1], args[2], args[3],
                                     args[4], args[5]);
             textState.setTextLineMatrix(args[0], args[1], args[2], args[3],
                                         args[4], args[5]);
             break;
           case OPS.setCharSpacing:
-            textState.charSpace = args[0];
+            textState.charSpacing = args[0];
             break;
           case OPS.setWordSpacing:
-            textState.wordSpace = args[0];
+            textState.wordSpacing = args[0];
             break;
           case OPS.beginText:
             textState.textMatrix = IDENTITY_MATRIX.slice();
             textState.textLineMatrix = IDENTITY_MATRIX.slice();
             break;
           case OPS.showSpacedText:
             var items = args[0];
             var textChunk = newTextChunk();
             var offset;
             for (var j = 0, jj = items.length; j < jj; j++) {
               if (typeof items[j] === 'string') {
                 buildTextGeometry(items[j], textChunk);
               } else {
                 var val = items[j] / 1000;
                 if (!textState.font.vertical) {
-                  offset = -val * textState.fontSize * textState.textHScale;
+                  offset = -val * textState.fontSize * textState.textHScale *
+                           textState.textMatrix[0];
                   textState.translateTextMatrix(offset, 0);
                   textChunk.width += offset;
                 } else {
-                  offset = -val * textState.fontSize;
+                  offset = -val * textState.fontSize * textState.textMatrix[3];
                   textState.translateTextMatrix(0, offset);
                   textChunk.height += offset;
                 }
                 if (items[j] < 0 && textState.font.spaceWidth > 0) {
                   var fakeSpaces = -items[j] / textState.font.spaceWidth;
                   if (fakeSpaces > MULTI_SPACE_FACTOR) {
                     fakeSpaces = Math.round(fakeSpaces);
                     while (fakeSpaces--) {
@@ -15857,17 +15983,16 @@ var PartialEvaluator = (function Partial
 
       properties.differences = differences;
       properties.baseEncodingName = baseEncodingName;
       properties.dict = dict;
     },
 
     readToUnicode: function PartialEvaluator_readToUnicode(toUnicode) {
       var cmapObj = toUnicode;
-      var charToUnicode = [];
       if (isName(cmapObj)) {
         return CMapFactory.create(cmapObj).map;
       } else if (isStream(cmapObj)) {
         var cmap = CMapFactory.create(cmapObj).map;
         // Convert UTF-16BE
         // NOTE: cmap can be a sparse array, so use forEach instead of for(;;)
         // to iterate over all keys.
         cmap.forEach(function(token, i) {
@@ -20222,17 +20347,16 @@ var Font = (function FontClosure() {
    * 'charCodeToGlyphId' - maps the new font char codes to glyph ids
    */
   function adjustMapping(charCodeToGlyphId, properties) {
     var toUnicode = properties.toUnicode;
     var isSymbolic = !!(properties.flags & FontFlags.Symbolic);
     var isIdentityUnicode = properties.isIdentityUnicode;
     var newMap = Object.create(null);
     var toFontChar = [];
-    var usedCharCodes = [];
     var usedFontCharCodes = [];
     var nextAvailableFontCharCode = PRIVATE_USE_OFFSET_START;
     for (var originalCharCode in charCodeToGlyphId) {
       originalCharCode |= 0;
       var glyphId = charCodeToGlyphId[originalCharCode];
       var fontCharCode = originalCharCode;
       // First try to map the value to a unicode position if a non identity map
       // was created.
@@ -20340,17 +20464,17 @@ var Font = (function FontClosure() {
 
     // Fill up the 4 parallel arrays describing the segments.
     var startCount = '';
     var endCount = '';
     var idDeltas = '';
     var idRangeOffsets = '';
     var glyphsIds = '';
     var bias = 0;
-    
+
     var range, start, end, codes;
     for (i = 0, ii = bmpLength; i < ii; i++) {
       range = ranges[i];
       start = range[0];
       end = range[1];
       startCount += string16(start);
       endCount += string16(end);
       codes = range[2];
@@ -20664,19 +20788,19 @@ var Font = (function FontClosure() {
       }
       return data;
     },
 
     checkAndRepair: function Font_checkAndRepair(name, font, properties) {
       function readTableEntry(file) {
         var tag = bytesToString(file.getBytes(4));
 
-        var checksum = file.getUint32();
-        var offset = file.getUint32();
-        var length = file.getUint32();
+        var checksum = file.getInt32();
+        var offset = file.getInt32() >>> 0;
+        var length = file.getInt32() >>> 0;
 
         // Read the table associated data
         var previousPosition = file.pos;
         file.pos = file.start ? file.start : 0;
         file.skip(offset);
         var data = file.getBytes(length);
         file.pos = previousPosition;
 
@@ -20723,17 +20847,17 @@ var Font = (function FontClosure() {
         // use:
         // - non-symbolic fonts the preference is a 3,1 table then a 1,0 table
         // - symbolic fonts the preference is a 3,0 table then a 1,0 table
         // The following takes advantage of the fact that the tables are sorted
         // to work.
         for (var i = 0; i < numTables; i++) {
           var platformId = font.getUint16();
           var encodingId = font.getUint16();
-          var offset = font.getUint32();
+          var offset = font.getInt32() >>> 0;
           var useTable = false;
 
           if (platformId == 1 && encodingId === 0) {
             useTable = true;
             // Continue the loop since there still may be a higher priority
             // table.
           } else if (!isSymbolicFont && platformId === 3 && encodingId === 1) {
             useTable = true;
@@ -20848,18 +20972,16 @@ var Font = (function FontClosure() {
           // Format 6 is a 2-bytes dense mapping, which means the font data
           // lives glue together even if they are pretty far in the unicode
           // table. (This looks weird, so I can have missed something), this
           // works on Linux but seems to fails on Mac so let's rewrite the
           // cmap table to a 3-1-4 style
           var firstCode = font.getUint16();
           var entryCount = font.getUint16();
 
-          var glyphs = [];
-          var ids = [];
           for (j = 0; j < entryCount; j++) {
             glyphId = font.getUint16();
             var charCode = firstCode + j;
 
             mappings.push({
               charCode: charCode,
               glyphId: glyphId
             });
@@ -21135,17 +21257,17 @@ var Font = (function FontClosure() {
         }
       }
 
       function readPostScriptTable(post, properties, maxpNumGlyphs) {
         var start = (font.start ? font.start : 0) + post.offset;
         font.pos = start;
 
         var length = post.length, end = start + length;
-        var version = font.getUint32();
+        var version = font.getInt32();
         // skip rest to the tables
         font.getBytes(28);
 
         var glyphNames;
         var valid = true;
         var i;
 
         switch (version) {
@@ -21237,17 +21359,16 @@ var Font = (function FontClosure() {
         for (i = 0, ii = records.length; i < ii; i++) {
           var record = records[i];
           var pos = start + stringsStart + record.offset;
           if (pos + record.length > end) {
             continue; // outside of name table, ignoring
           }
           font.pos = pos;
           var nameIndex = record.name;
-          var encoding = record.encoding ? 1 : 0;
           if (record.encoding) {
             // unicode
             var str = '';
             for (var j = 0, jj = record.length; j < jj; j += 2) {
               str += String.fromCharCode(font.getUint16());
             }
             names[1][nameIndex] = str;
           } else {
@@ -21542,17 +21663,17 @@ var Font = (function FontClosure() {
         }
       }
 
       if (!tables.maxp) {
         error('Required "maxp" table is not found');
       }
 
       font.pos = (font.start || 0) + tables.maxp.offset;
-      var version = font.getUint32();
+      var version = font.getInt32();
       var numGlyphs = font.getUint16();
       var maxFunctionDefs = 0;
       if (version >= 0x00010000 && tables.maxp.length >= 22) {
         // maxZones can be invalid
         font.pos += 8;
         var maxZones = font.getUint16();
         if (maxZones > 2) { // reset to 2 if font has invalid maxZones
           tables.maxp.data[14] = 0;
@@ -21829,18 +21950,16 @@ var Font = (function FontClosure() {
       return stringToArray(ttf.file);
     },
 
     convert: function Font_convert(fontName, font, properties) {
       // The offsets object holds at the same time a representation of where
       // to write the table entry information about a table and another offset
       // representing the offset where to draw the actual data of a particular
       // table
-      var REQ_TABLES_CNT = 9;
-
       var otf = {
         file: '',
         virtualOffset: 9 * (4 * 4)
       };
 
       createOpenTypeHeader('\x4F\x54\x54\x4F', otf, 9);
 
       // TODO: Check the charstring widths to determine this.
@@ -22681,40 +22800,72 @@ var Type1Parser = (function Type1ParserC
   /*
    * Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence
    * of Plaintext Bytes. The function took a key as a parameter which can be
    * for decrypting the eexec block of for decoding charStrings.
    */
   var EEXEC_ENCRYPT_KEY = 55665;
   var CHAR_STRS_ENCRYPT_KEY = 4330;
 
-  function decrypt(stream, key, discardNumber) {
-    var r = key, c1 = 52845, c2 = 22719;
-    var decryptedString = [];
-
-    var value = '';
-    var count = stream.length;
+  function isHexDigit(code) {
+    return code >= 48 && code <= 57 || // '0'-'9'
+           code >= 65 && code <= 70 || // 'A'-'F'
+           code >= 97 && code <= 102;  // 'a'-'f'
+  }
+
+  function decrypt(data, key, discardNumber) {
+    var r = key | 0, c1 = 52845, c2 = 22719;
+    var count = data.length;
+    var decrypted = new Uint8Array(count);
     for (var i = 0; i < count; i++) {
-      value = stream[i];
-      decryptedString[i] = value ^ (r >> 8);
+      var value = data[i];
+      decrypted[i] = value ^ (r >> 8);
       r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
     }
-    return decryptedString.slice(discardNumber);
+    return Array.prototype.slice.call(decrypted, discardNumber);
+  }
+
+  function decryptAscii(data, key, discardNumber) {
+    var r = key | 0, c1 = 52845, c2 = 22719;
+    var count = data.length, maybeLength = count >>> 1;
+    var decrypted = new Uint8Array(maybeLength);
+    var i, j;
+    for (i = 0, j = 0; i < count; i++) {
+      var digit1 = data[i];
+      if (!isHexDigit(digit1)) {
+        continue;
+      }
+      i++;
+      var digit2;
+      while (i < count && !isHexDigit(digit2 = data[i])) {
+        i++;
+      }
+      if (i < count) {
+        var value = parseInt(String.fromCharCode(digit1, digit2), 16);
+        decrypted[j++] = value ^ (r >> 8);
+        r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
+      }
+    }
+    return Array.prototype.slice.call(decrypted, discardNumber, j);
   }
 
   function isSpecial(c) {
     return c === 0x2F || // '/'
            c === 0x5B || c === 0x5D || // '[', ']'
            c === 0x7B || c === 0x7D || // '{', '}'
            c === 0x28 || c === 0x29; // '(', ')'
   }
 
   function Type1Parser(stream, encrypted) {
     if (encrypted) {
-      stream = new Stream(decrypt(stream.getBytes(), EEXEC_ENCRYPT_KEY, 4));
+      var data = stream.getBytes();
+      var isBinary = !(isHexDigit(data[0]) && isHexDigit(data[1]) &&
+                       isHexDigit(data[2]) && isHexDigit(data[3]));
+      stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) :
+                          decryptAscii(data, EEXEC_ENCRYPT_KEY, 4));
     }
     this.stream = stream;
     this.nextChar();
   }
 
   Type1Parser.prototype = {
     readNumberArray: function Type1Parser_readNumberArray() {
       this.getToken(); // read '[' or '{' (arrays can start with either)
@@ -23611,17 +23762,16 @@ var CFFParser = (function CFFParserClosu
       }
       return entries;
     },
     parseIndex: function CFFParser_parseIndex(pos) {
       var cffIndex = new CFFIndex();
       var bytes = this.bytes;
       var count = (bytes[pos++] << 8) | bytes[pos++];
       var offsets = [];
-      var start = pos;
       var end = pos;
       var i, ii;
 
       if (count !== 0) {
         var offsetSize = bytes[pos++];
         // add 1 for offset to determine size of last object
         var startPos = pos + ((count + 1) * offsetSize) - 1;
 
@@ -23654,17 +23804,17 @@ var CFFParser = (function CFFParserClosu
           var c = name[j];
           if (j === 0 && c === 0) {
             data[j] = c;
             continue;
           }
           if ((c < 33 || c > 126) || c === 91 /* [ */ || c === 93 /* ] */ ||
               c === 40 /* ( */ || c === 41 /* ) */ || c === 123 /* { */ ||
               c === 125 /* } */ || c === 60 /* < */ || c === 62 /* > */ ||
-              c === 47 /* / */ || c === 37 /* % */) {
+              c === 47 /* / */ || c === 37 /* % */ || c === 35 /* # */) {
             data[j] = 95;
             continue;
           }
           data[j] = c;
         }
         names.push(bytesToString(data));
       }
       return names;
@@ -23674,18 +23824,16 @@ var CFFParser = (function CFFParserClosu
       for (var i = 0, ii = index.count; i < ii; ++i) {
         var data = index.get(i);
         strings.add(bytesToString(data));
       }
       return strings;
     },
     createDict: function CFFParser_createDict(Type, dict, strings) {
       var cffDict = new Type(strings);
-      var types = cffDict.types;
-
       for (var i = 0, ii = dict.length; i < ii; ++i) {
         var pair = dict[i];
         var key = pair[0];
         var value = pair[1];
         cffDict.setByKey(key, value);
       }
       return cffDict;
     },
@@ -24243,39 +24391,26 @@ var CFFPrivateDict = (function CFFPrivat
   return CFFPrivateDict;
 })();
 
 var CFFCharsetPredefinedTypes = {
   ISO_ADOBE: 0,
   EXPERT: 1,
   EXPERT_SUBSET: 2
 };
-var CFFCharsetEmbeddedTypes = {
-  FORMAT0: 0,
-  FORMAT1: 1,
-  FORMAT2: 2
-};
 var CFFCharset = (function CFFCharsetClosure() {
   function CFFCharset(predefined, format, charset, raw) {
     this.predefined = predefined;
     this.format = format;
     this.charset = charset;
     this.raw = raw;
   }
   return CFFCharset;
 })();
 
-var CFFEncodingPredefinedTypes = {
-  STANDARD: 0,
-  EXPERT: 1
-};
-var CFFCharsetEmbeddedTypes = {
-  FORMAT0: 0,
-  FORMAT1: 1
-};
 var CFFEncoding = (function CFFEncodingClosure() {
   function CFFEncoding(predefined, format, encoding, raw) {
     this.predefined = predefined;
     this.format = format;
     this.encoding = encoding;
     this.raw = raw;
   }
   return CFFEncoding;
@@ -24729,17 +24864,16 @@ var CFFCompiler = (function CFFCompilerC
                     (relativeOffset >> 8) & 0xFF,
                      relativeOffset & 0xFF);
         }
 
         if (objects[i]) {
           relativeOffset += objects[i].length;
         }
       }
-      var offset = data.length;
 
       for (i = 0; i < count; i++) {
         // Notify the tracker where the object will be offset in the data.
         if (trackers[i]) {
           trackers[i].offset(data.length);
         }
         for (var j = 0, jj = objects[i].length; j < jj; j++) {
           data.push(objects[i][j]);
@@ -24892,20 +25026,16 @@ var FontRendererFactory = (function Font
     }
     function quadraticCurveTo(xa, ya, x, y) {
       js.push('c.quadraticCurveTo(' + xa + ',' + ya + ',' +
                                    x + ',' + y + ');');
     }
 
     var i = 0;
     var numberOfContours = ((code[i] << 24) | (code[i + 1] << 16)) >> 16;
-    var xMin = ((code[i + 2] << 24) | (code[i + 3] << 16)) >> 16;
-    var yMin = ((code[i + 4] << 24) | (code[i + 5] << 16)) >> 16;
-    var xMax = ((code[i + 6] << 24) | (code[i + 7] << 16)) >> 16;
-    var yMax = ((code[i + 8] << 24) | (code[i + 9] << 16)) >> 16;
     var flags;
     var x = 0, y = 0;
     i += 10;
     if (numberOfContours < 0) {
       // composite glyph
       do {
         flags = (code[i] << 8) | code[i + 1];
         var glyphIndex = (code[i + 2] << 8) | code[i + 3];
@@ -29710,17 +29840,16 @@ var PDFImage = (function PDFImageClosure
     return (value < 0 ? 0 : (value > max ? max : value));
   }
   function PDFImage(xref, res, image, inline, smask, mask, isMask) {
     this.image = image;
     var dict = image.dict;
     if (dict.has('Filter')) {
       var filter = dict.get('Filter').name;
       if (filter === 'JPXDecode') {
-        info('get image params from JPX stream');
         var jpxImage = new JpxImage();
         jpxImage.parseImageProperties(image.stream);
         image.stream.reset();
         image.bitsPerComponent = jpxImage.bitsPerComponent;
         image.numComps = jpxImage.componentsCount;
       } else if (filter === 'JBIG2Decode') {
         image.bitsPerComponent = 1;
         image.numComps = 1;
@@ -29759,18 +29888,20 @@ var PDFImage = (function PDFImageClosure
         info('JPX images (which do not require color spaces)');
         switch (image.numComps) {
           case 1:
             colorSpace = Name.get('DeviceGray');
             break;
           case 3:
             colorSpace = Name.get('DeviceRGB');
             break;
+          case 4:
+            colorSpace = Name.get('DeviceCMYK');
+            break;
           default:
-            // TODO: Find out how four color channels are handled. CMYK? Alpha?
             error('JPX images with ' + this.numComps +
                   ' color components not supported.');
         }
       }
       this.colorSpace = ColorSpace.parse(colorSpace, xref, res);
       this.numComps = this.colorSpace.numComps;
     }
 
@@ -29916,17 +30047,16 @@ var PDFImage = (function PDFImageClosure
     },
     get drawHeight() {
       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 = this.decodeAddends;
       var decodeCoefficients = this.decodeCoefficients;
       var max = (1 << bpc) - 1;
       var i, ii;
 
       if (bpc === 1) {
@@ -33677,17 +33807,16 @@ var Lexer = (function LexerClosure() {
           }
         } else if (ch === 0x2D) { // '-'
           // ignore minus signs in the middle of numbers to match
           // Adobe's behavior
           warn('Badly formated number');
         } else if (ch === 0x45 || ch === 0x65) { // 'E', 'e'
           // 'E' can be either a scientific notation or the beginning of a new
           // operator
-          var hasE = true;
           ch = this.peekChar();
           if (ch === 0x2B || ch === 0x2D) { // '+', '-'
             powerValueSign = (ch === 0x2D) ? -1 : 1;
             this.nextChar(); // Consume the sign character
           } else if (ch < 0x30 || ch > 0x39) { // '0' - '9'
             // The 'E' must be the beginning of a new operator
             break;
           }
@@ -33955,17 +34084,16 @@ var Lexer = (function LexerClosure() {
         return false;
       }
       if (str == 'null') {
         return null;
       }
       return Cmd.get(str);
     },
     skipToNextLine: function Lexer_skipToNextLine() {
-      var stream = this.stream;
       var ch = this.currentChar;
       while (ch >= 0) {
         if (ch === 0x0D) { // CR
           ch = this.nextChar();
           if (ch === 0x0A) { // LF
             this.nextChar();
           }
           break;
@@ -34175,17 +34303,16 @@ var PostScriptLexer = (function PostScri
     this.stream = stream;
     this.nextChar();
   }
   PostScriptLexer.prototype = {
     nextChar: function PostScriptLexer_nextChar() {
       return (this.currentChar = this.stream.getByte());
     },
     getToken: function PostScriptLexer_getToken() {
-      var s = '';
       var comment = false;
       var ch = this.currentChar;
 
       // skip comments
       while (true) {
         if (ch < 0) {
           return EOF;
         }
@@ -34273,17 +34400,17 @@ var Stream = (function StreamClosure() {
       }
       return this.bytes[this.pos++];
     },
     getUint16: function Stream_getUint16() {
       var b0 = this.getByte();
       var b1 = this.getByte();
       return (b0 << 8) + b1;
     },
-    getUint32: function Stream_getUint32() {
+    getInt32: function Stream_getInt32() {
       var b0 = this.getByte();
       var b1 = this.getByte();
       var b2 = this.getByte();
       var b3 = this.getByte();
       return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
     },
     // returns subarray of original buffer
     // should only be read
@@ -34391,17 +34518,17 @@ var DecodeStream = (function DecodeStrea
       }
       return this.buffer[this.pos++];
     },
     getUint16: function DecodeStream_getUint16() {
       var b0 = this.getByte();
       var b1 = this.getByte();
       return (b0 << 8) + b1;
     },
-    getUint32: function DecodeStream_getUint32() {
+    getInt32: function DecodeStream_getInt32() {
       var b0 = this.getByte();
       var b1 = this.getByte();
       var b2 = this.getByte();
       var b3 = this.getByte();
       return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
     },
     getBytes: function DecodeStream_getBytes(length) {
       var end, pos = this.pos;
@@ -35169,88 +35296,45 @@ var JpxStream = (function JpxStreamClosu
     }
 
     var jpxImage = new JpxImage();
     jpxImage.parse(this.bytes);
 
     var width = jpxImage.width;
     var height = jpxImage.height;
     var componentsCount = jpxImage.componentsCount;
-    if (componentsCount != 1 && componentsCount != 3 && componentsCount != 4) {
-      error('JPX with ' + componentsCount + ' components is not supported');
-    }
-
-    var data = new Uint8Array(width * height * componentsCount);
-
-    for (var k = 0, kk = jpxImage.tiles.length; k < kk; k++) {
-      var tileCompoments = jpxImage.tiles[k];
-      var tileWidth = tileCompoments[0].width;
-      var tileHeight = tileCompoments[0].height;
-      var tileLeft = tileCompoments[0].left;
-      var tileTop = tileCompoments[0].top;
-
-      var dataPosition, sourcePosition, data0, data1, data2, data3, rowFeed;
-      var i, j;
-      switch (componentsCount) {
-        case 1:
-          data0 = tileCompoments[0].items;
-
-          dataPosition = width * tileTop + tileLeft;
-          rowFeed = width - tileWidth;
-          sourcePosition = 0;
-          for (j = 0; j < tileHeight; j++) {
-            for (i = 0; i < tileWidth; i++) {
-              data[dataPosition++] = data0[sourcePosition++];
-            }
-            dataPosition += rowFeed;
-          }
-          break;
-        case 3:
-          data0 = tileCompoments[0].items;
-          data1 = tileCompoments[1].items;
-          data2 = tileCompoments[2].items;
-
-          dataPosition = (width * tileTop + tileLeft) * 3;
-          rowFeed = (width - tileWidth) * 3;
-          sourcePosition = 0;
-          for (j = 0; j < tileHeight; j++) {
-            for (i = 0; i < tileWidth; i++) {
-              data[dataPosition++] = data0[sourcePosition];
-              data[dataPosition++] = data1[sourcePosition];
-              data[dataPosition++] = data2[sourcePosition];
-              sourcePosition++;
-            }
-            dataPosition += rowFeed;
-          }
-          break;
-        case 4:
-          data0 = tileCompoments[0].items;
-          data1 = tileCompoments[1].items;
-          data2 = tileCompoments[2].items;
-          data3 = tileCompoments[3].items;
-
-          dataPosition = (width * tileTop + tileLeft) * 4;
-          rowFeed = (width - tileWidth) * 4;
-          sourcePosition = 0;
-          for (j = 0; j < tileHeight; j++) {
-            for (i = 0; i < tileWidth; i++) {
-              data[dataPosition++] = data0[sourcePosition];
-              data[dataPosition++] = data1[sourcePosition];
-              data[dataPosition++] = data2[sourcePosition];
-              data[dataPosition++] = data3[sourcePosition];
-              sourcePosition++;
-            }
-            dataPosition += rowFeed;
-          }
-          break;
-      }
-    }
-
-    this.buffer = data;
-    this.bufferLength = data.length;
+    var tileCount = jpxImage.tiles.length;
+    if (tileCount === 1) {
+      this.buffer = jpxImage.tiles[0].items;
+    } else {
+      var data = new Uint8Array(width * height * componentsCount);
+
+      for (var k = 0; k < tileCount; k++) {
+        var tileComponents = jpxImage.tiles[k];
+        var tileWidth = tileComponents.width;
+        var tileHeight = tileComponents.height;
+        var tileLeft = tileComponents.left;
+        var tileTop = tileComponents.top;
+
+        var src = tileComponents.items;
+        var srcPosition = 0;
+        var dataPosition = (width * tileTop + tileLeft) * componentsCount;
+        var imgRowSize = width * componentsCount;
+        var tileRowSize = tileWidth * componentsCount;
+
+        for (var j = 0; j < tileHeight; j++) {
+          var rowBytes = src.subarray(srcPosition, srcPosition + tileRowSize);
+          data.set(rowBytes, dataPosition);
+          srcPosition += tileRowSize;
+          dataPosition += imgRowSize;
+        }
+      }
+      this.buffer = data;
+    }
+    this.bufferLength = this.buffer.length;
     this.eof = true;
   };
 
   return JpxStream;
 })();
 
 /**
  * For JBIG2's we use a library to decode these images and
@@ -36439,17 +36523,16 @@ var CCITTFaxStream = (function CCITTFaxS
     return EOF;
   };
 
   CCITTFaxStream.prototype.getWhiteCode =
       function ccittFaxStreamGetWhiteCode() {
 
     var code = 0;
     var p;
-    var n;
     if (this.eoblock) {
       code = this.lookBits(12);
       if (code == EOF) {
         return 1;
       }
 
       if ((code >> 5) === 0) {
         p = whiteTable1[code];
@@ -36965,16 +37048,24 @@ var WorkerMessageHandler = PDFJS.WorkerM
     handler.on('GetDestinations',
       function wphSetupGetDestinations(data, deferred) {
         pdfManager.ensureCatalog('destinations').then(function(destinations) {
           deferred.resolve(destinations);
         });
       }
     );
 
+    handler.on('GetAttachments',
+      function wphSetupGetAttachments(data, deferred) {
+        pdfManager.ensureCatalog('attachments').then(function(attachments) {
+          deferred.resolve(attachments);
+        }, deferred.reject);
+      }
+    );
+
     handler.on('GetData', function wphSetupGetData(data, deferred) {
       pdfManager.requestLoadedStream();
       pdfManager.onLoadedStream().then(function(stream) {
         deferred.resolve(stream.bytes);
       });
     });
 
     handler.on('UpdatePassword', function wphSetupUpdatePassword(data) {
@@ -37309,39 +37400,35 @@ var JpxImage = (function JpxImageClosure
         this.parse(data);
         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++) {
-          n = n * 256 + (data[offset + i] & 0xFF);
-        }
-        return n;
-      }
-
-      var head = readUint(data, 0, 2);
+
+      var head = readUint16(data, 0);
       // No box header, immediate start of codestream (SOC)
       if (head === 0xFF4F) {
         this.parseCodestream(data, 0, data.length);
         return;
       }
 
       var position = 0, length = data.length;
       while (position < length) {
         var headerSize = 8;
-        var lbox = readUint(data, position, 4);
-        var tbox = readUint(data, position + 4, 4);
+        var lbox = readUint32(data, position);
+        var tbox = readUint32(data, position + 4);
         position += headerSize;
-        if (lbox == 1) {
-          lbox = readUint(data, position, 8);
+        if (lbox === 1) {
+          // XLBox: read UInt64 according to spec.
+          // JavaScript's int precision of 53 bit should be sufficient here.
+          lbox = readUint32(data, position) * 4294967296 +
+                 readUint32(data, position + 4);
           position += 8;
           headerSize += 8;
         }
         if (lbox === 0) {
           lbox = length - position + headerSize;
         }
         if (lbox < headerSize) {
           error('JPX error: Invalid box field size');
@@ -37372,26 +37459,26 @@ var JpxImage = (function JpxImageClosure
         var newByte = stream.getByte();
         while (newByte >= 0) {
           var oldByte = newByte;
           newByte = stream.getByte();
           var code = (oldByte << 8) | newByte;
           // Image and tile size (SIZ)
           if (code == 0xFF51) {
             stream.skip(4);
-            var Xsiz = stream.getUint32(); // Byte 4
-            var Ysiz = stream.getUint32(); // Byte 8
-            var XOsiz = stream.getUint32(); // Byte 12
-            var YOsiz = stream.getUint32(); // Byte 16
+            var Xsiz = stream.getInt32() >>> 0; // Byte 4
+            var Ysiz = stream.getInt32() >>> 0; // Byte 8
+            var XOsiz = stream.getInt32() >>> 0; // Byte 12
+            var YOsiz = stream.getInt32() >>> 0; // Byte 16
             stream.skip(16);
             var Csiz = stream.getUint16(); // Byte 36
             this.width = Xsiz - XOsiz;
             this.height = Ysiz - YOsiz;
             this.componentsCount = Csiz;
-            // Results are always returned as UInt8Arrays
+            // Results are always returned as Uint8Arrays
             this.bitsPerComponent = 8;
             return;
           }
         }
         throw 'No size marker found in JPX stream';
       } catch (e) {
         if (this.failOnCorruptedImage) {
           error('JPX error: ' + e);
@@ -37399,17 +37486,17 @@ var JpxImage = (function JpxImageClosure
           warn('JPX error: ' + e + '. Trying to recover');
         }
       }
     },
     parseCodestream: function JpxImage_parseCodestream(data, start, end) {
       var context = {};
       try {
         var position = start;
-        while (position < end) {
+        while (position + 1 < end) {
           var code = readUint16(data, position);
           position += 2;
 
           var length = 0, j, sqcd, spqcds, spqcdSize, scalarExpounded, tile;
           switch (code) {
             case 0xFF4F: // Start of codestream (SOC)
               context.mainHeader = true;
               break;
@@ -37545,17 +37632,16 @@ var JpxImage = (function JpxImageClosure
             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);
               cod.ephMarkerUsed = !!(scod & 4);
-              var codingStyle = {};
               cod.progressionOrder = data[j++];
               cod.layersCount = readUint16(data, j);
               j += 2;
               cod.multipleComponentTransform = data[j++];
 
               cod.decompositionLevelsCount = data[j++];
               cod.xcb = (data[j++] & 0xF) + 2;
               cod.ycb = (data[j++] & 0xF) + 2;
@@ -37644,31 +37730,16 @@ var JpxImage = (function JpxImageClosure
         }
       }
       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];
-  }
-  function readUint16(data, offset) {
-    return (data[offset] << 8) | data[offset + 1];
-  }
-  function log2(x) {
-    var n = 1, i = 0;
-    while (x > n) {
-      n <<= 1;
-      i++;
-    }
-    return i;
-  }
   function calculateComponentDimensions(component, siz) {
     // Section B.2 Component mapping
     component.x0 = Math.ceil(siz.XOsiz / component.XRsiz);
     component.x1 = Math.ceil(siz.Xsiz / component.XRsiz);
     component.y0 = Math.ceil(siz.YOsiz / component.YRsiz);
     component.y1 = Math.ceil(siz.Ysiz / component.YRsiz);
     component.width = component.x1 - component.x0;
     component.height = component.y1 - component.y0;
@@ -37692,17 +37763,16 @@ var JpxImage = (function JpxImageClosure
         tiles.push(tile);
       }
     }
     context.tiles = tiles;
 
     var componentsCount = siz.Csiz;
     for (var i = 0, ii = componentsCount; i < ii; i++) {
       var component = components[i];
-      var tileComponents = [];
       for (var j = 0, jj = tiles.length; j < jj; j++) {
         var tileComponent = {};
         tile = tiles[j];
         tileComponent.tcx0 = Math.ceil(tile.tx0 / component.XRsiz);
         tileComponent.tcy0 = Math.ceil(tile.ty0 / component.YRsiz);
         tileComponent.tcx1 = Math.ceil(tile.tx1 / component.XRsiz);
         tileComponent.tcy1 = Math.ceil(tile.ty1 / component.YRsiz);
         tileComponent.width = tileComponent.tcx1 - tileComponent.tcx0;
@@ -37761,17 +37831,17 @@ var JpxImage = (function JpxImageClosure
     var codeblockHeight = 1 << ycb_;
     var cbx0 = subband.tbx0 >> xcb_;
     var cby0 = subband.tby0 >> ycb_;
     var cbx1 = (subband.tbx1 + codeblockWidth - 1) >> xcb_;
     var cby1 = (subband.tby1 + codeblockHeight - 1) >> ycb_;
     var precinctParameters = subband.resolution.precinctParameters;
     var codeblocks = [];
     var precincts = [];
-    var i, ii, j, codeblock, precinctNumber;
+    var i, j, codeblock, precinctNumber;
     for (j = cby0; j < cby1; j++) {
       for (i = cbx0; i < cbx1; i++) {
         codeblock = {
           cbx: i,
           cby: j,
           tbx0: codeblockWidth * i,
           tby0: codeblockHeight * j,
           tbx1: codeblockWidth * (i + 1),
@@ -37786,18 +37856,16 @@ var JpxImage = (function JpxImageClosure
                  precinctParameters.precinctHeight);
         precinctNumber = pj + 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.Lblock = 3;
         codeblocks.push(codeblock);
         // building precinct for the sub-band
         var precinct = precincts[precinctNumber];
         if (precinct !== undefined) {
           if (i < precinct.cbxMin) {
             precinct.cbxMin = i;
           } else if (i > precinct.cbxMax) {
@@ -38006,17 +38074,16 @@ var JpxImage = (function JpxImageClosure
           resolution.subbands = resolutionSubbands;
         }
       }
       component.resolutions = resolutions;
       component.subbands = subbands;
     }
     // Generate the packets sequence
     var progressionOrder = tile.codingStyleDefaultParameters.progressionOrder;
-    var packetsIterator;
     switch (progressionOrder) {
       case 0:
         tile.packetsIterator =
           new LayerResolutionComponentPositionIterator(context);
         break;
       case 1:
         tile.packetsIterator =
           new ResolutionLayerComponentPositionIterator(context);
@@ -38050,34 +38117,32 @@ var JpxImage = (function JpxImageClosure
     function alignToByte() {
       bufferSize = 0;
       if (skipNextBit) {
         position++;
         skipNextBit = false;
       }
     }
     function readCodingpasses() {
-      var value = readBits(1);
-      if (value === 0) {
+      if (readBits(1) === 0) {
         return 1;
       }
-      value = (value << 1) | readBits(1);
-      if (value == 0x02) {
+      if (readBits(1) === 0) {
         return 2;
       }
-      value = (value << 2) | readBits(2);
-      if (value <= 0x0E) {
-        return (value & 0x03) + 3;
-      }
-      value = (value << 5) | readBits(5);
-      if (value <= 0x1FE) {
-        return (value & 0x1F) + 6;
-      }
-      value = (value << 7) | readBits(7);
-      return (value & 0x7F) + 37;
+      var value = readBits(2);
+      if (value < 3) {
+        return value + 3;
+      }
+      value = readBits(5);
+      if (value < 31) {
+        return value + 6;
+      }
+      value = readBits(7);
+      return value + 37;
     }
     var tileIndex = context.currentTile.index;
     var tile = context.tiles[tileIndex];
     var packetsIterator = tile.packetsIterator;
     while (position < dataLength) {
       var packet = packetsIterator.nextPacket();
       if (!readBits(1)) {
         alignToByte();
@@ -38173,55 +38238,62 @@ var JpxImage = (function JpxImageClosure
           end: offset + position + packetItem.dataLength,
           codingpasses: packetItem.codingpasses
         });
         position += packetItem.dataLength;
       }
     }
     return position;
   }
-  function copyCoefficients(coefficients, x0, y0, width, height,
-                            delta, mb, codeblocks, reversible,
-                            segmentationSymbolUsed) {
+  function copyCoefficients(coefficients, levelWidth, levelHeight, subband,
+                            delta, mb, reversible, segmentationSymbolUsed) {
+    var x0 = subband.tbx0;
+    var y0 = subband.tby0;
+    var width = subband.tbx1 - subband.tbx0;
+    var codeblocks = subband.codeblocks;
+    var right = subband.type.charAt(0) === 'H' ? 1 : 0;
+    var bottom = subband.type.charAt(1) === 'H' ? levelWidth : 0;
+
     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, mb);
       currentCodingpassType = 2; // first bit plane starts from cleanup
 
       // collect data
       var data = codeblock.data, totalLength = 0, codingpasses = 0;
-      var q, qq, dataItem;
-      for (q = 0, qq = data.length; q < qq; q++) {
-        dataItem = data[q];
+      var j, jj, dataItem;
+      for (j = 0, jj = data.length; j < jj; j++) {
+        dataItem = data[j];
         totalLength += dataItem.end - dataItem.start;
         codingpasses += dataItem.codingpasses;
       }
-      var encodedData = new Uint8Array(totalLength), k = 0;
-      for (q = 0, qq = data.length; q < qq; q++) {
-        dataItem = data[q];
+      var encodedData = new Uint8Array(totalLength);
+      var position = 0;
+      for (j = 0, jj = data.length; j < jj; j++) {
+        dataItem = data[j];
         var chunk = dataItem.data.subarray(dataItem.start, dataItem.end);
-        encodedData.set(chunk, k);
-        k += chunk.length;
+        encodedData.set(chunk, position);
+        position += chunk.length;
       }
       // decoding the item
       var decoder = new ArithmeticDecoder(encodedData, 0, totalLength);
       bitModel.setDecoder(decoder);
 
-      for (q = 0; q < codingpasses; q++) {
+      for (j = 0; j < codingpasses; j++) {
         switch (currentCodingpassType) {
           case 0:
             bitModel.runSignificancePropogationPass();
             break;
           case 1:
             bitModel.runMagnitudeRefinementPass();
             break;
           case 2:
@@ -38230,35 +38302,41 @@ var JpxImage = (function JpxImageClosure
               bitModel.checkSegmentationSymbol();
             }
             break;
         }
         currentCodingpassType = (currentCodingpassType + 1) % 3;
       }
 
       var offset = (codeblock.tbx0_ - x0) + (codeblock.tby0_ - y0) * width;
-      var n, nb, correction, position = 0;
-      var irreversible = !reversible;
       var sign = bitModel.coefficentsSign;
       var magnitude = bitModel.coefficentsMagnitude;
       var bitsDecoded = bitModel.bitsDecoded;
       var magnitudeCorrection = reversible ? 0 : 0.5;
-      for (var j = 0; j < blockHeight; j++) {
+      var k, n, nb;
+      position = 0;
+      // Do the interleaving of Section F.3.3 here, so we do not need
+      // to copy later. LL level is not interleaved, just copied.
+      var interleave = (subband.type !== 'LL');
+      for (j = 0; j < blockHeight; j++) {
+        var row = (offset / width) | 0; // row in the non-interleaved subband
+        var levelOffset = 2 * row * (levelWidth - width) + right + bottom;
         for (k = 0; k < blockWidth; k++) {
           n = magnitude[position];
           if (n !== 0) {
             n = (n + magnitudeCorrection) * delta;
             if (sign[position] !== 0) {
               n = -n;
             }
             nb = bitsDecoded[position];
-            if (irreversible || mb > nb) {
-              coefficients[offset] = n * (1 << (mb - nb));
+            var pos = interleave ? (levelOffset + (offset << 1)) : offset;
+            if (reversible && (nb >= mb)) {
+              coefficients[pos] = n;
             } else {
-              coefficients[offset] = n;
+              coefficients[pos] = n * (1 << (mb - nb));
             }
           }
           offset++;
           position++;
         }
         offset += width - blockWidth;
       }
     }
@@ -38279,50 +38357,54 @@ var JpxImage = (function JpxImageClosure
     var transform = (reversible ? new ReversibleTransform() :
                                   new IrreversibleTransform());
 
     var subbandCoefficients = [];
     var b = 0;
     for (var i = 0; i <= decompositionLevelsCount; i++) {
       var resolution = component.resolutions[i];
 
+      var width = resolution.trx1 - resolution.trx0;
+      var height = resolution.try1 - resolution.try0;
+      // Allocate space for the whole sublevel.
+      var coefficients = new Float32Array(width * height);
+
       for (var j = 0, jj = resolution.subbands.length; j < jj; j++) {
         var mu, epsilon;
         if (!scalarExpounded) {
           // formula E-5
           mu = spqcds[0].mu;
           epsilon = spqcds[0].epsilon + (i > 0 ? 1 - i : 0);
         } else {
           mu = spqcds[b].mu;
           epsilon = spqcds[b].epsilon;
+          b++;
         }
 
         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 = (reversible ? 1 :
           Math.pow(2, precision + gainLog2 - epsilon) * (1 + mu / 2048));
         var mb = (guardBits + epsilon - 1);
 
-        var coefficients = new Float32Array(width * height);
-        copyCoefficients(coefficients, subband.tbx0, subband.tby0,
-          width, height, delta, mb, subband.codeblocks, reversible,
-          segmentationSymbolUsed);
-
-        subbandCoefficients.push({
-          width: width,
-          height: height,
-          items: coefficients
-        });
-
-        b++;
-      }
+        // In the first resolution level, copyCoefficients will fill the
+        // whole array with coefficients. In the succeding passes,
+        // copyCoefficients will consecutively fill in the values that belong
+        // to the interleaved positions of the HL, LH, and HH coefficients.
+        // The LL coefficients will then be interleaved in Transform.iterate().
+        copyCoefficients(coefficients, width, height, subband, delta, mb,
+                         reversible, segmentationSymbolUsed);
+      }
+      subbandCoefficients.push({
+        width: width,
+        height: height,
+        items: coefficients
+      });
     }
 
     var result = transform.calculate(subbandCoefficients,
                                      component.tcx0, component.tcy0);
     return {
       left: component.tcx0,
       top: component.tcy0,
       width: result.width,
@@ -38332,79 +38414,110 @@ var JpxImage = (function JpxImageClosure
   }
   function transformComponents(context) {
     var siz = context.SIZ;
     var components = context.components;
     var componentsCount = siz.Csiz;
     var resultImages = [];
     for (var i = 0, ii = context.tiles.length; i < ii; i++) {
       var tile = context.tiles[i];
-      var result = [];
+      var transformedTiles = [];
       var c;
       for (c = 0; c < componentsCount; c++) {
-        var image = transformTile(context, tile, c);
-        result.push(image);
-      }
+        transformedTiles[c] = transformTile(context, tile, c);
+      }
+      var tile0 = transformedTiles[0];
+      var out = new Uint8Array(tile0.items.length * componentsCount);
+      var result = {
+        left: tile0.left,
+        top: tile0.top,
+        width: tile0.width,
+        height: tile0.height,
+        items: out
+      };
 
       // Section G.2.2 Inverse multi component transform
-      var y0items, y1items, y2items, j, jj, y0, y1, y2;
-      var component, offset, tileImage, items;
+      var shift, offset, max, min;
+      var pos = 0, j, jj, y0, y1, y2, r, g, b, k, val;
       if (tile.codingStyleDefaultParameters.multipleComponentTransform) {
+        var fourComponents = componentsCount === 4;
+        var y0items = transformedTiles[0].items;
+        var y1items = transformedTiles[1].items;
+        var y2items = transformedTiles[2].items;
+        var y3items = fourComponents ? transformedTiles[3].items : null;
+
+        // HACK: The multiple component transform formulas below assume that
+        // all components have the same precision. With this in mind, we
+        // compute shift and offset only once.
+        shift = components[0].precision - 8;
+        offset = (128 << shift) + 0.5;
+        max = (127.5 * (1 << shift));
+        min = -max;
+
         var component0 = tile.components[0];
         if (!component0.codingStyleParameters.reversibleTransformation) {
           // inverse irreversible multiple component transform
-          y0items = result[0].items;
-          y1items = result[1].items;
-          y2items = result[2].items;
           for (j = 0, jj = y0items.length; j < jj; ++j) {
-            y0 = y0items[j] + 0.5; y1 = y1items[j]; y2 = y2items[j];
-            y0items[j] = y0 + 1.402 * y2;
-            y1items[j] = y0 - 0.34413 * y1 - 0.71414 * y2;
-            y2items[j] = y0 + 1.772 * y1;
+            y0 = y0items[j];
+            y1 = y1items[j];
+            y2 = y2items[j];
+            r = y0 + 1.402 * y2;
+            g = y0 - 0.34413 * y1 - 0.71414 * y2;
+            b = y0 + 1.772 * y1;
+            out[pos++] = r <= min ? 0 : r >= max ? 255 : (r + offset) >> shift;
+            out[pos++] = g <= min ? 0 : g >= max ? 255 : (g + offset) >> shift;
+            out[pos++] = b <= min ? 0 : b >= max ? 255 : (b + offset) >> shift;
+            if (fourComponents) {
+              k = y3items[j];
+              out[pos++] =
+                k <= min ? 0 : k >= max ? 255 : (k + offset) >> shift;
+            }
           }
         } else {
           // inverse reversible multiple component transform
-          y0items = result[0].items;
-          y1items = result[1].items;
-          y2items = result[2].items;
           for (j = 0, jj = y0items.length; j < jj; ++j) {
-            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;
-          }
-        }
-      }
-
-      // To simplify things: shift and clamp output to 8 bit unsigned
-      for (c = 0; c < componentsCount; c++) {
-        component = components[c];
-        var shift = component.precision - 8;
-        tileImage = result[c];
-        items = tileImage.items;
-        var data = new Uint8Array(items.length);
-        var low = -(128 << shift);
-        var high = 127 << shift;
-        for (j = 0, jj = items.length; j < jj; j++) {
-          var val = items[j];
-          data[j] = val <= low ? 0 : val >= high ? 255 : (val >> shift) + 128;
-        }
-        result[c].items = data;
-      }
-
+            y0 = y0items[j];
+            y1 = y1items[j];
+            y2 = y2items[j];
+            g = y0 - ((y2 + y1) >> 2);
+            r = g + y2;
+            b = g + y1;
+            out[pos++] = r <= min ? 0 : r >= max ? 255 : (r + offset) >> shift;
+            out[pos++] = g <= min ? 0 : g >= max ? 255 : (g + offset) >> shift;
+            out[pos++] = b <= min ? 0 : b >= max ? 255 : (b + offset) >> shift;
+            if (fourComponents) {
+              k = y3items[j];
+              out[pos++] =
+                k <= min ? 0 : k >= max ? 255 : (k + offset) >> shift;
+            }
+          }
+        }
+      } else { // no multi-component transform
+        for (c = 0; c < componentsCount; c++) {
+          var items = transformedTiles[c].items;
+          shift = components[c].precision - 8;
+          offset = (128 << shift) + 0.5;
+          max = (127.5 * (1 << shift));
+          min = -max;
+          for (pos = c, j = 0, jj = items.length; j < jj; j++) {
+            val = items[j];
+            out[pos] = val <= min ? 0 :
+                       val >= max ? 255 : (val + offset) >> shift;
+            pos += componentsCount;
+          }
+        }
+      }
       resultImages.push(result);
     }
     return resultImages;
   }
   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);
       component.quantizationParameters = qcdOrQcc;
       var codOrCoc = (c in context.currentTile.COC ?
         context.currentTile.COC[c] : context.currentTile.COD);
       component.codingStyleParameters = codOrCoc;
@@ -38571,30 +38684,32 @@ var JpxImage = (function JpxImageClosure
       4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8
     ]);
     var HHContextLabel = new Uint8Array([
       0, 1, 2, 0, 1, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 0, 3, 4, 5, 0, 4, 5, 5, 0, 5,
       5, 5, 0, 0, 0, 0, 0, 6, 7, 7, 0, 7, 7, 7, 0, 7, 7, 7, 0, 0, 0, 0, 0, 8, 8,
       8, 0, 8, 8, 8, 0, 8, 8, 8, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8
     ]);
 
-    function BitModel(width, height, subband, zeroBitPlanes) {
+    function BitModel(width, height, subband, zeroBitPlanes, mb) {
       this.width = width;
       this.height = height;
 
       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.coefficentsMagnitude = mb > 14 ? new Uint32Array(coefficientCount) :
+                                  mb > 6 ? new Uint16Array(coefficientCount) :
+                                  new Uint8Array(coefficientCount);
       this.processingFlags = new Uint8Array(coefficientCount);
 
       var bitsDecoded = new Uint8Array(coefficientCount);
       if (zeroBitPlanes !== 0) {
         for (var i = 0; i < coefficientCount; i++) {
           bitsDecoded[i] = zeroBitPlanes;
         }
       }
@@ -38657,17 +38772,16 @@ var JpxImage = (function JpxImageClosure
         neighborsSignificance[index] |= 0x80;
       },
       runSignificancePropogationPass:
         function BitModel_runSignificancePropogationPass() {
         var decoder = this.decoder;
         var width = this.width, height = this.height;
         var coefficentsMagnitude = this.coefficentsMagnitude;
         var coefficentsSign = this.coefficentsSign;
-        var contextLabels = this.contextLabels;
         var neighborsSignificance = this.neighborsSignificance;
         var processingFlags = this.processingFlags;
         var contexts = this.contexts;
         var labels = this.contextLabelTable;
         var bitsDecoded = this.bitsDecoded;
         var processedInverseMask = ~1;
         var processedMask = 1;
         var firstMagnitudeBitMask = 2;
@@ -38795,86 +38909,86 @@ var JpxImage = (function JpxImageClosure
             }
           }
         }
       },
       runCleanupPass: function BitModel_runCleanupPass() {
         var decoder = this.decoder;
         var width = this.width, height = this.height;
         var neighborsSignificance = this.neighborsSignificance;
-        var significanceState = this.significanceState;
         var coefficentsMagnitude = this.coefficentsMagnitude;
         var coefficentsSign = this.coefficentsSign;
         var contexts = this.contexts;
         var labels = this.contextLabelTable;
         var bitsDecoded = this.bitsDecoded;
         var processingFlags = this.processingFlags;
         var processedMask = 1;
         var firstMagnitudeBitMask = 2;
         var oneRowDown = width;
         var twoRowsDown = width * 2;
         var threeRowsDown = width * 3;
-        for (var i0 = 0; i0 < height; i0 += 4) {
+        var iNext;
+        for (var i0 = 0; i0 < height; i0 = iNext) {
+          iNext = Math.min(i0 + 4, height);
+          var indexBase = i0 * width;
+          var checkAllEmpty = i0 + 3 < height;
           for (var j = 0; j < width; j++) {
-            var index0 = i0 * width + j;
+            var index0 = indexBase + j;
             // using the property: labels[neighborsSignificance[index]] == 0
             // when neighborsSignificance[index] == 0
-            var allEmpty = (i0 + 3 < height &&
+            var allEmpty = (checkAllEmpty &&
               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);
             var i1 = 0, index = index0;
-            var i, sign;
+            var i = i0, sign;
             if (allEmpty) {
               var hasSignificantCoefficent =
                 decoder.readBit(contexts, RUNLENGTH_CONTEXT);
               if (!hasSignificantCoefficent) {
                 bitsDecoded[index0]++;
                 bitsDecoded[index0 + oneRowDown]++;
                 bitsDecoded[index0 + twoRowsDown]++;
                 bitsDecoded[index0 + threeRowsDown]++;
                 continue; // next column
               }
               i1 = (decoder.readBit(contexts, UNIFORM_CONTEXT) << 1) |
                     decoder.readBit(contexts, UNIFORM_CONTEXT);
-              i = i0 + i1;
-              index += i1 * width;
+              if (i1 !== 0) {
+                i = i0 + i1;
+                index += i1 * width;
+              }
 
               sign = this.decodeSignBit(i, j, index);
               coefficentsSign[index] = sign;
               coefficentsMagnitude[index] = 1;
               this.setNeighborsSignificance(i, j, index);
               processingFlags[index] |= firstMagnitudeBitMask;
 
               index = index0;
               for (var i2 = i0; i2 <= i; i2++, index += width) {
                 bitsDecoded[index]++;
               }
 
               i1++;
             }
-            for (; i1 < 4; i1++, index += width) {
-              i = i0 + i1;
-              if (i >= height) {
-                break;
-              }
-
+            for (i = i0 + i1; i < iNext; i++, index += width) {
               if (coefficentsMagnitude[index] ||
                 (processingFlags[index] & processedMask) !== 0) {
                 continue;
               }
 
               var contextLabel = labels[neighborsSignificance[index]];
               var decision = decoder.readBit(contexts, contextLabel);
-              if (decision == 1) {
+              if (decision === 1) {
                 sign = this.decodeSignBit(i, j, index);
                 coefficentsSign[index] = sign;
                 coefficentsMagnitude[index] = 1;
                 this.setNeighborsSignificance(i, j, index);
                 processingFlags[index] |= firstMagnitudeBitMask;
               }
               bitsDecoded[index]++;
             }
@@ -38899,72 +39013,51 @@ var JpxImage = (function JpxImageClosure
 
   // Section F, Discrete wavelet transformation
   var Transform = (function TransformClosure() {
     function Transform() {}
 
     Transform.prototype.calculate =
       function transformCalculate(subbands, u0, v0) {
       var ll = subbands[0];
-      for (var i = 1, ii = subbands.length; i < ii; i += 3) {
-        ll = this.iterate(ll, subbands[i], subbands[i + 1],
-                          subbands[i + 2], u0, v0);
+      for (var i = 1, ii = subbands.length; i < ii; i++) {
+        ll = this.iterate(ll, subbands[i], 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];
     };
-    Transform.prototype.iterate = function Transform_iterate(ll, hl, lh, hh,
-                                                            u0, v0) {
+    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;
-
-      // Section F.3.3 interleave
-      var width = llWidth + hlWidth;
-      var height = llHeight + lhHeight;
-      var items = new Float32Array(width * height);
-      var i, j, k, l, v, u;
-
-      for (i = 0, k = 0; i < llHeight; i++) {
+      var width = hl_lh_hh.width;
+      var height = hl_lh_hh.height;
+      var items = hl_lh_hh.items;
+      var i, j, k, l, u, v;
+
+      // Interleave LL according to Section F.3.3
+      for (k = 0, i = 0; i < llHeight; i++) {
         l = i * 2 * width;
         for (j = 0; j < llWidth; j++, k++, l += 2) {
           items[l] = llItems[k];
         }
       }
-      for (i = 0, k = 0; i < hlHeight; i++) {
-        l = i * 2 * width + 1;
-        for (j = 0; j < hlWidth; j++, k++, l += 2) {
-          items[l] = hlItems[k];
-        }
-      }
-      for (i = 0, k = 0; i < lhHeight; i++) {
-        l = (i * 2 + 1) * width;
-        for (j = 0; j < lhWidth; j++, k++, l += 2) {
-          items[l] = lhItems[k];
-        }
-      }
-      for (i = 0, k = 0; i < hhHeight; i++) {
-        l = (i * 2 + 1) * width + 1;
-        for (j = 0; j < hhWidth; j++, k++, l += 2) {
-          items[l] = hhItems[k];
-        }
-      }
+      // The LL band is not needed anymore.
+      llItems = ll.items = null;
 
       var bufferPadding = 4;
       var rowBuffer = new Float32Array(width + 2 * bufferPadding);
 
       // Section F.3.4 HOR_SR
       if (width === 1) {
         // if width = 1, when u0 even keep items as is, when odd divide by 2
         if ((u0 & 1) !== 0) {
@@ -38972,17 +39065,17 @@ var JpxImage = (function JpxImageClosure
             items[k] *= 0.5;
           }
         }
       } else {
         for (v = 0, k = 0; v < height; v++, k += width) {
           rowBuffer.set(items.subarray(k, k + width), bufferPadding);
 
           this.extend(rowBuffer, bufferPadding, width);
-          this.filter(rowBuffer, bufferPadding, width, u0, rowBuffer);
+          this.filter(rowBuffer, bufferPadding, width);
 
           items.set(
             rowBuffer.subarray(bufferPadding, bufferPadding + width),
             k);
         }
       }
 
       // Accesses to the items array can take long, because it may not fit into
@@ -39018,17 +39111,17 @@ var JpxImage = (function JpxImageClosure
               }
             }
             currentBuffer = numBuffers;
           }
 
           currentBuffer--;
           var buffer = colBuffers[currentBuffer];
           this.extend(buffer, bufferPadding, height);
-          this.filter(buffer, bufferPadding, height, v0, buffer);
+          this.filter(buffer, bufferPadding, height);
 
           // If this is last buffer in this group of buffers, flush all buffers.
           if (currentBuffer === 0) {
             k = u - numBuffers + 1;
             for (l = bufferPadding; l < ll; k += width, l++) {
               for (b = 0; b < numBuffers; b++) {
                 items[k + b] = colBuffers[b][l];
               }
@@ -39049,79 +39142,121 @@ var JpxImage = (function JpxImageClosure
   // Section 3.8.2 Irreversible 9-7 filter
   var IrreversibleTransform = (function IrreversibleTransformClosure() {
     function IrreversibleTransform() {
       Transform.call(this);
     }
 
     IrreversibleTransform.prototype = Object.create(Transform.prototype);
     IrreversibleTransform.prototype.filter =
-      function irreversibleTransformFilter(y, offset, length, i0, x) {
+      function irreversibleTransformFilter(x, offset, length) {
       var len = length >> 1;
       offset = offset | 0;
+      var j, n, current, next;
 
       var alpha = -1.586134342059924;
       var beta = -0.052980118572961;
       var gamma = 0.882911075530934;
       var delta = 0.443506852043971;
       var K = 1.230174104914001;
       var K_ = 1 / K;
-      var j, n, nn;
 
       // step 1 is combined with step 3
 
       // step 2
-      for (j = offset - 3, n = len + 4; n--; j += 2) {
-        x[j] = K_ * y[j];
+      j = offset - 3;
+      for (n = len + 4; n--; j += 2) {
+        x[j] *= K_;
       }
 
       // step 1 & 3
-      for (j = offset - 2, n = len + 3; n--; j += 2) {
-        x[j] = K * y[j] -
-          delta * (x[j - 1] + x[j + 1]);
+      j = offset - 2;
+      current = delta * x[j -1];
+      for (n = len + 3; n--; j += 2) {
+        next = delta * x[j + 1];
+        x[j] = K * x[j] - current - next;
+        if (n--) {
+          j += 2;
+          current = delta * x[j + 1];
+          x[j] = K * x[j] - current - next;
+        } else {
+          break;
+        }
       }
 
       // step 4
-      for (j = offset - 1, n = len + 2; n--; j += 2) {
-        x[j] -= gamma * (x[j - 1] + x[j + 1]);
+      j = offset - 1;
+      current = gamma * x[j - 1];
+      for (n = len + 2; n--; j += 2) {
+        next = gamma * x[j + 1];
+        x[j] -= current + next;
+        if (n--) {
+          j += 2;
+          current = gamma * x[j + 1];
+          x[j] -= current + next;
+        } else {
+          break;
+        }
       }
 
       // step 5
-      for (j = offset, n = len + 1; n--; j += 2) {
-        x[j] -= beta * (x[j - 1] + x[j + 1]);
+      j = offset;
+      current = beta * x[j - 1];
+      for (n = len + 1; n--; j += 2) {
+        next = beta * x[j + 1];
+        x[j] -= current + next;
+        if (n--) {
+          j += 2;
+          current = beta * x[j + 1];
+          x[j] -= current + next;
+        } else {
+          break;
+        }
       }
 
       // step 6
-      for (j = offset + 1, n = len; n--; j += 2) {
-        x[j] -= alpha * (x[j - 1] + x[j + 1]);
+      if (len !== 0) {
+        j = offset + 1;
+        current = alpha * x[j - 1];
+        for (n = len; n--; j += 2) {
+          next = alpha * x[j + 1];
+          x[j] -= current + next;
+          if (n--) {
+            j += 2;
+            current = alpha * x[j + 1];
+            x[j] -= current + next;
+          } else {
+            break;
+          }
+        }
       }
     };
 
     return IrreversibleTransform;
   })();
 
   // Section 3.8.1 Reversible 5-3 filter
   var ReversibleTransform = (function ReversibleTransformClosure() {
     function ReversibleTransform() {
       Transform.call(this);
     }
 
     ReversibleTransform.prototype = Object.create(Transform.prototype);
     ReversibleTransform.prototype.filter =
-      function reversibleTransformFilter(y, offset, length, i0, x) {
+      function reversibleTransformFilter(x, offset, length) {
       var len = length >> 1;
       offset = offset | 0;
       var j, n;
 
       for (j = offset, n = len + 1; n--; j += 2) {
-        x[j] = y[j] - ((y[j - 1] + y[j + 1] + 2) >> 2);
+        x[j] -= (x[j - 1] + x[j + 1] + 2) >> 2;
       }
 
       for (j = offset + 1, n = len; n--; j += 2) {
-        x[j] = y[j] + ((x[j - 1] + x[j + 1]) >> 1);
+        x[j] += (x[j - 1] + x[j + 1]) >> 1;
       }
     };
 
     return ReversibleTransform;
   })();
 
   return JpxImage;
 })();
@@ -39293,43 +39428,16 @@ var Jbig2Image = (function Jbig2ImageClo
     0x018B  // '011000101' + '1'
   ];
 
   var RefinementReusedContexts = [
     0x0020, // '000' + '0' (coding) + '00010000' + '0' (reference)
     0x0008  // '0000' + '001000'
   ];
 
-  function log2(x) {
-    var n = 1, i = 0;
-    while (x > n) {
-      n <<= 1;
-      i++;
-    }
-    return i;
-  }
-
-  function readInt32(data, start) {
-    return (data[start] << 24) | (data[start + 1] << 16) |
-           (data[start + 2] << 8) | data[start + 3];
-  }
-
-  function readUint32(data, start) {
-    var value = readInt32(data, start);
-    return value & 0x80000000 ? (value + 4294967296) : value;
-  }
-
-  function readUint16(data, start) {
-    return (data[start] << 8) | data[start + 1];
-  }
-
-  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) {
       error('JBIG2 error: MMR encoding is not supported');
     }
 
     var useskip = !!skip;
@@ -39750,17 +39858,17 @@ var Jbig2Image = (function Jbig2ImageClo
     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;
+      referredToCount = readUint32(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) {
       error('JBIG2 error: invalid referred-to flags');
@@ -40294,56 +40402,16 @@ var bidi = PDFJS.bidi = (function bidiCl
   function reverseValues(arr, start, end) {
     for (var i = start, j = end - 1; i < j; ++i, --j) {
       var temp = arr[i];
       arr[i] = arr[j];
       arr[j] = temp;
     }
   }
 
-  function mirrorGlyphs(c) {
-    /*
-     # BidiMirroring-1.txt
-     0028; 0029 # LEFT PARENTHESIS
-     0029; 0028 # RIGHT PARENTHESIS
-     003C; 003E # LESS-THAN SIGN
-     003E; 003C # GREATER-THAN SIGN
-     005B; 005D # LEFT SQUARE BRACKET
-     005D; 005B # RIGHT SQUARE BRACKET
-     007B; 007D # LEFT CURLY BRACKET
-     007D; 007B # RIGHT CURLY BRACKET
-     00AB; 00BB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
-     00BB; 00AB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-     */
-    switch (c) {
-      case '(':
-        return ')';
-      case ')':
-        return '(';
-      case '<':
-        return '>';
-      case '>':
-        return '<';
-      case ']':
-        return '[';
-      case '[':
-        return ']';
-      case '}':
-        return '{';
-      case '{':
-        return '}';
-      case '\u00AB':
-        return '\u00BB';
-      case '\u00BB':
-        return '\u00AB';
-      default:
-        return c;
-    }
-  }
-
   function createBidiText(str, isLTR, vertical) {
     return {
       str: str,
       dir: (vertical ? 'ttb' : (isLTR ? 'ltr' : 'rtl'))
     };
   }
 
   // These are used in bidi(), which is called frequently. We re-use them on
--- a/browser/extensions/pdfjs/content/web/debugger.js
+++ b/browser/extensions/pdfjs/content/web/debugger.js
@@ -15,17 +15,16 @@
  * limitations under the License.
  */
 /* globals PDFJS */
 
 'use strict';
 
 var FontInspector = (function FontInspectorClosure() {
   var fonts;
-  var panelWidth = 300;
   var active = false;
   var fontAttribute = 'data-font-name';
   function removeSelection() {
     var divs = document.querySelectorAll('div[' + fontAttribute + ']');
     for (var i = 0, ii = divs.length; i < ii; ++i) {
       var div = divs[i];
       div.className = '';
     }
@@ -322,32 +321,33 @@ var Stepper = (function StepperClosure()
       if (!opMap) {
         opMap = Object.create(null);
         for (var key in PDFJS.OPS) {
           opMap[PDFJS.OPS[key]] = key;
         }
       }
     },
     updateOperatorList: function updateOperatorList(operatorList) {
+      var self = this;
+
       function cboxOnClick() {
         var x = +this.dataset.idx;
         if (this.checked) {
           self.breakPoints.push(x);
         } else {
           self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
         }
         StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
       }
 
       var MAX_OPERATORS_COUNT = 15000;
       if (this.operatorListIdx > MAX_OPERATORS_COUNT) {
         return;
       }
 
-      var self = this;
       var chunk = document.createDocumentFragment();
       var operatorsToDisplay = Math.min(MAX_OPERATORS_COUNT,
                                         operatorList.fnArray.length);
       for (var i = this.operatorListIdx; i < operatorsToDisplay; i++) {
         var line = c('tr');
         line.className = 'line';
         line.dataset.idx = i;
         chunk.appendChild(line);
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..fcd0b268a475662d421e9144764a09d20faf4155
GIT binary patch
literal 384
zc$@)%0e}99P)<h;3K|Lk000e1NJLTq000mG000mO1ONa4wfZ;e0003;Nkl<ZC{tr#
zAP4+c{J-V@rT_c?8=^}H|91eAn*Yvh$YQXZ8u#zqe;yR+e+NFa{@46_c2+8b2vC6k
z$(rvD$kO|7&79);=k(-c20^e0<LBL5O%T$1Z%v=z{^#VRBnE;1hX0HHGySi6vY3Gj
zCcXRS)IPU=$0jF&r2iaW;rYMf(;9t<BmWEj+j)C(x68jHlM;Z^27it=MSZOKyk5<W
zfeEbO_k>4t*wuc`Tb2cq{&%P`@>A8<^(NL(>Ho}s&vtk+Fua<7E9$@Hp92jMpDMqu
z1xm9(o%fjY|H;!pyNX@EE&si$I{ZV$w@o%SaA_c5db|2dDUjx3Fk~?LRQ7GXy(6+T
z5KNBxck+Mrf8+l~|GU4fb8ui_Wk3UhZHZr2{6GKy)RPIe_L$OOB@D*mE*uUFn%Jcw
efQf+-n=k;HAC|A!G+~AS0000<MNUMnLSTX%g~0Cs
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b979e523e742027645e980d8a27c54d0060ebf21
GIT binary patch
literal 871
zc$@)e1DO1YP)<h;3K|Lk000e1NJLTq001BW001Be1ONa4*>kdg0009nNkl<ZNXO;X
zYe-XJ7zgmjsfm##T}4UiLl7)ME2|F$d8w=@G%~9%Mi3EQAT2M1(T8pjLRo1AR#01N
zp^|B~teLBsZcdxc>*n00Qixp6B9(Lgy*p~IIal_z=X^QudHJ1}^E~hK9-{v*5Qt#-
z{wq$zD|}{GH?lE%s@NMj=*7nYb>AD_j2@`4U~2pZK7P|=?WfhWa$EeT3Rw9Hh&-bW
zD8u?uo`(+us$E+sK$!9qNA}mjiannACYWKd9vtP?fqKUMeoc$2Ps8-R%me35Fq%um
zp^WFkGEkSa*@?4|$HO?Jot$^0%Um2BA@|{OVft3P(}fdRlhI{Yy5}3w1-<2wgX227
z?aGN9-apEq*X6k926S2;hJ-VIjji%#jKd#?5Fy-oaTj42)FiKP!+Aa0Eky#q3WHrS
z&iNu3zh*f&F2FF{d|@BC?nrFlJJD(?6nG9g?KO#UW1K5QhplRFIOFG`=iX%>pji{^
z-VU9qz`?m3bl9t%cmdjM&o@Skr{NqrIBnh`3UuYMd>h`IrNZi0&}Nq>jPnO*wLKH~
zS+sLaH;<5OCm;$f(3&M7;b%mwQCjRToVXM^TV?bJZ{;;N57Qi1KA+d1$#g#?oV*YX
z-EUliW?Mx}6!~(QXyG-tk4@l2gQw8={YAt|@*=XGhvvcIU;I9_wsHr@N$8-ymqPR0
z)PAT6YQ-7ycHEmULEV%fa9{2on!Y?4<;}cGa-7_?FlM3wDoO0fsC+L}!%wjSVcZ=w
z_B|H(320F-O(pjyfPNwBQ1@ub$S7d0{uVndbS$Y2pNj>aig$QhoIye4KGFhsC2Bjx
zW8)cOX{s^be77=d(Hem_qP8@hf@#ta^+=)YOLlq^M9V0Q7Lo`l&_G$5;fb@TI)>MS
z6_`FDJ1HzXuVtqxggl2h)K&r|mk+1y2_Ixa4TUU=Lg|m;m9}F@hFV^Mbk@$1Y+oMb
zWvE9@)n9QTb16yvrKn-`XkbaxFs$HTmZbeH7wWfoU3q4&gqNcl)q|C~%exc)`0cLM
xV0mA@ASQ16s;xe2X({<njgP;^kEZbN`w1rY0*o@`?gan<002ovPDHLkV1kKWoksuw
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -528,16 +528,19 @@ html[dir='rtl'] .splitToolbarButton > .t
 .toolbarButton.textButton:hover,
 .toolbarButton.textButton:focus {
   background-color: hsla(0,0%,0%,.2);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.15) inset,
               0 0 1px hsla(0,0%,0%,.05);
   z-index: 199;
 }
+.splitToolbarButton > .toolbarButton {
+  position: relative;
+}
 html[dir='ltr'] .splitToolbarButton > .toolbarButton:first-child,
 html[dir='rtl'] .splitToolbarButton > .toolbarButton:last-child {
   position: relative;
   margin: 0;
   margin-right: -1px;
   border-top-left-radius: 2px;
   border-bottom-left-radius: 2px;
   border-right-color: transparent;
@@ -856,16 +859,20 @@ html[dir='rtl'] .toolbarButton.pageDown:
 
 html[dir="ltr"] #viewOutline.toolbarButton::before {
   content: url(images/toolbarButton-viewOutline.png);
 }
 html[dir="rtl"] #viewOutline.toolbarButton::before {
   content: url(images/toolbarButton-viewOutline-rtl.png);
 }
 
+#viewAttachments.toolbarButton::before {
+  content: url(images/toolbarButton-viewAttachments.png);
+}
+
 #viewFind.toolbarButton::before {
   content: url(images/toolbarButton-search.png);
 }
 
 .secondaryToolbarButton {
   position: relative;
   margin: 0 0 4px 0;
   padding: 3px 0 1px 0;
@@ -1065,17 +1072,18 @@ a:focus > .thumbnail > .thumbnailSelecti
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.1) inset,
               0 0 1px hsla(0,0%,0%,.2);
   color: hsla(0,0%,100%,1);
 }
 
-#outlineView {
+#outlineView,
+#attachmentsView {
   position: absolute;
   width: 192px;
   top: 0;
   bottom: 0;
   padding: 4px 4px 0;
   overflow: auto;
   -moz-user-select: none;
 }
@@ -1083,39 +1091,43 @@ a:focus > .thumbnail > .thumbnailSelecti
 html[dir='ltr'] .outlineItem > .outlineItems {
   margin-left: 20px;
 }
 
 html[dir='rtl'] .outlineItem > .outlineItems {
   margin-right: 20px;
 }
 
-.outlineItem > a {
+.outlineItem > a,
+.attachmentsItem > a {
   text-decoration: none;
   display: inline-block;
   min-width: 95%;
   height: auto;
   margin-bottom: 1px;
   border-radius: 2px;
   color: hsla(0,0%,100%,.8);
   font-size: 13px;
   line-height: 15px;
   -moz-user-select: none;
   white-space: normal;
 }
 
-html[dir='ltr'] .outlineItem > a {
+html[dir='ltr'] .outlineItem > a,
+html[dir='ltr'] .attachmentsItem > a {
   padding: 2px 0 5px 10px;
 }
 
-html[dir='rtl'] .outlineItem > a {
+html[dir='rtl'] .outlineItem > a,
+html[dir='rtl'] .attachmentsItem > a {
   padding: 2px 10px 5px 0;
 }
 
-.outlineItem > a:hover {
+.outlineItem > a:hover,
+.attachmentsItem > a:hover {
   background-color: hsla(0,0%,100%,.02);
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.2) inset,
               0 0 1px hsla(0,0%,0%,.2);
   color: hsla(0,0%,100%,.9);
 }
@@ -1636,16 +1648,20 @@ html[dir='rtl'] #documentPropertiesConta
 
   html[dir="ltr"] #viewOutline.toolbarButton::before {
     content: url(images/toolbarButton-viewOutline@2x.png);
   }
   html[dir="rtl"] #viewOutline.toolbarButton::before {
     content: url(images/toolbarButton-viewOutline-rtl@2x.png);
   }
 
+  #viewAttachments.toolbarButton::before {
+    content: url(images/toolbarButton-viewAttachments@2x.png);
+  }
+
   #viewFind.toolbarButton::before {
     content: url(images/toolbarButton-search@2x.png);
   }
 
   .secondaryToolbarButton.firstPage::before {
     content: url(images/secondaryToolbarButton-firstPage@2x.png);
   }
 
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -18,17 +18,17 @@ Adobe CMap resources are covered by thei
 http://sourceforge.net/adobe/cmap/wiki/License/
 -->
 <html dir="ltr" mozdisallowselectionprint moznomarginboxes>
   <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
     <title>PDF.js viewer</title>
 
-<!-- This snippet is used in firefox extension, see Makefile -->
+<!-- This snippet is used in the Firefox extension (included from viewer.html) -->
 <base href="resource://pdf.js/web/" />
 <script type="text/javascript" src="l10n.js"></script>
 <script type="text/javascript" src="../build/pdf.js"></script>
 
 
     <link rel="stylesheet" href="viewer.css"/>
 
 
@@ -48,23 +48,28 @@ http://sourceforge.net/adobe/cmap/wiki/L
         <div id="toolbarSidebar">
           <div class="splitToolbarButton toggled">
             <button id="viewThumbnail" class="toolbarButton group toggled" title="Show Thumbnails" tabindex="2" data-l10n-id="thumbs">
                <span data-l10n-id="thumbs_label">Thumbnails</span>
             </button>
             <button id="viewOutline" class="toolbarButton group" title="Show Document Outline" tabindex="3" data-l10n-id="outline">
                <span data-l10n-id="outline_label">Document Outline</span>
             </button>
+            <button id="viewAttachments" class="toolbarButton group" title="Show Attachments" tabindex="4" data-l10n-id="attachments">
+               <span data-l10n-id="attachments_label">Attachments</span>
+            </button>
           </div>
         </div>
         <div id="sidebarContent">
           <div id="thumbnailView">
           </div>
           <div id="outlineView" class="hidden">
           </div>
+          <div id="attachmentsView" class="hidden">
+          </div>
         </div>
       </div>  <!-- sidebarContainer -->
 
       <div id="mainContainer">
         <div class="findbar hidden doorHanger hiddenSmallView" id="findbar">
           <label for="findInput" class="toolbarLabel" data-l10n-id="find_label">Find:</label>
           <input id="findInput" class="toolbarField" tabindex="41">
           <div class="splitToolbarButton">
@@ -80,143 +85,143 @@ http://sourceforge.net/adobe/cmap/wiki/L
           <label for="findHighlightAll" class="toolbarLabel" tabindex="44" data-l10n-id="find_highlight">Highlight all</label>
           <input type="checkbox" id="findMatchCase" class="toolbarField">
           <label for="findMatchCase" class="toolbarLabel" tabindex="45" data-l10n-id="find_match_case_label">Match case</label>
           <span id="findMsg" class="toolbarLabel"></span>
         </div>  <!-- findbar -->
 
         <div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight">
           <div id="secondaryToolbarButtonContainer">
-            <button id="secondaryPresentationMode" class="secondaryToolbarButton presentationMode visibleLargeView" title="Switch to Presentation Mode" tabindex="18" data-l10n-id="presentation_mode">
+            <button id="secondaryPresentationMode" class="secondaryToolbarButton presentationMode visibleLargeView" title="Switch to Presentation Mode" tabindex="19" data-l10n-id="presentation_mode">
               <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
             </button>
 
-            <button id="secondaryOpenFile" class="secondaryToolbarButton openFile visibleLargeView" title="Open File" tabindex="19" data-l10n-id="open_file">
+            <button id="secondaryOpenFile" class="secondaryToolbarButton openFile visibleLargeView" title="Open File" tabindex="20" data-l10n-id="open_file">
               <span data-l10n-id="open_file_label">Open</span>
             </button>
 
-            <button id="secondaryPrint" class="secondaryToolbarButton print visibleMediumView" title="Print" tabindex="20" data-l10n-id="print">
+            <button id="secondaryPrint" class="secondaryToolbarButton print visibleMediumView" title="Print" tabindex="21" data-l10n-id="print">
               <span data-l10n-id="print_label">Print</span>
             </button>
 
-            <button id="secondaryDownload" class="secondaryToolbarButton download visibleMediumView" title="Download" tabindex="21" data-l10n-id="download">
+            <button id="secondaryDownload" class="secondaryToolbarButton download visibleMediumView" title="Download" tabindex="22" data-l10n-id="download">
               <span data-l10n-id="download_label">Download</span>
             </button>
 
-            <a href="#" id="secondaryViewBookmark" class="secondaryToolbarButton bookmark visibleSmallView" title="Current view (copy or open in new window)" tabindex="22" data-l10n-id="bookmark">
+            <a href="#" id="secondaryViewBookmark" class="secondaryToolbarButton bookmark visibleSmallView" title="Current view (copy or open in new window)" tabindex="23" data-l10n-id="bookmark">
               <span data-l10n-id="bookmark_label">Current View</span>
             </a>
 
             <div class="horizontalToolbarSeparator visibleLargeView"></div>
 
-            <button id="firstPage" class="secondaryToolbarButton firstPage" title="Go to First Page" tabindex="23" data-l10n-id="first_page">
+            <button id="firstPage" class="secondaryToolbarButton firstPage" title="Go to First Page" tabindex="24" data-l10n-id="first_page">
               <span data-l10n-id="first_page_label">Go to First Page</span>
             </button>
-            <button id="lastPage" class="secondaryToolbarButton lastPage" title="Go to Last Page" tabindex="24" data-l10n-id="last_page">
+            <button id="lastPage" class="secondaryToolbarButton lastPage" title="Go to Last Page" tabindex="25" data-l10n-id="last_page">
               <span data-l10n-id="last_page_label">Go to Last Page</span>
             </button>
 
             <div class="horizontalToolbarSeparator"></div>
 
-            <button id="pageRotateCw" class="secondaryToolbarButton rotateCw" title="Rotate Clockwise" tabindex="25" data-l10n-id="page_rotate_cw">
+            <button id="pageRotateCw" class="secondaryToolbarButton rotateCw" title="Rotate Clockwise" tabindex="26" data-l10n-id="page_rotate_cw">
               <span data-l10n-id="page_rotate_cw_label">Rotate Clockwise</span>
             </button>
-            <button id="pageRotateCcw" class="secondaryToolbarButton rotateCcw" title="Rotate Counterclockwise" tabindex="26" data-l10n-id="page_rotate_ccw">
+            <button id="pageRotateCcw" class="secondaryToolbarButton rotateCcw" title="Rotate Counterclockwise" tabindex="27" data-l10n-id="page_rotate_ccw">
               <span data-l10n-id="page_rotate_ccw_label">Rotate Counterclockwise</span>
             </button>
 
             <div class="horizontalToolbarSeparator"></div>
 
-            <button id="toggleHandTool" class="secondaryToolbarButton handTool" title="Enable hand tool" tabindex="27" data-l10n-id="hand_tool_enable">
+            <button id="toggleHandTool" class="secondaryToolbarButton handTool" title="Enable hand tool" tabindex="28" data-l10n-id="hand_tool_enable">
               <span data-l10n-id="hand_tool_enable_label">Enable hand tool</span>
             </button>
             
             <div class="horizontalToolbarSeparator"></div>
 
-            <button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="28" data-l10n-id="document_properties">
+            <button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="29" data-l10n-id="document_properties">
               <span data-l10n-id="document_properties_label">Document Properties…</span>
             </button>
           </div>
         </div>  <!-- secondaryToolbar -->
 
         <div class="toolbar">
           <div id="toolbarContainer">
             <div id="toolbarViewer">
               <div id="toolbarViewerLeft">
-                <button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="4" data-l10n-id="toggle_sidebar">
+                <button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="5" data-l10n-id="toggle_sidebar">
                   <span data-l10n-id="toggle_sidebar_label">Toggle Sidebar</span>
                 </button>
                 <div class="toolbarButtonSpacer"></div>
-                <button id="viewFind" class="toolbarButton group hiddenSmallView" title="Find in Document" tabindex="5" data-l10n-id="findbar">
+                <button id="viewFind" class="toolbarButton group hiddenSmallView" title="Find in Document" tabindex="6" data-l10n-id="findbar">
                    <span data-l10n-id="findbar_label">Find</span>
                 </button>
                 <div class="splitToolbarButton">
-                  <button class="toolbarButton pageUp" title="Previous Page" id="previous" tabindex="6" data-l10n-id="previous">
+                  <button class="toolbarButton pageUp" title="Previous Page" id="previous" tabindex="7" data-l10n-id="previous">
                     <span data-l10n-id="previous_label">Previous</span>
                   </button>
                   <div class="splitToolbarButtonSeparator"></div>
-                  <button class="toolbarButton pageDown" title="Next Page" id="next" tabindex="7" data-l10n-id="next">
+                  <button class="toolbarButton pageDown" title="Next Page" id="next" tabindex="8" data-l10n-id="next">
                     <span data-l10n-id="next_label">Next</span>
                   </button>
                 </div>
                 <label id="pageNumberLabel" class="toolbarLabel" for="pageNumber" data-l10n-id="page_label">Page: </label>
-                <input type="number" id="pageNumber" class="toolbarField pageNumber" value="1" size="4" min="1" tabindex="8">
+                <input type="number" id="pageNumber" class="toolbarField pageNumber" value="1" size="4" min="1" tabindex="9">
                 <span id="numPages" class="toolbarLabel"></span>
               </div>
               <div id="toolbarViewerRight">
-                <button id="presentationMode" class="toolbarButton presentationMode hiddenLargeView" title="Switch to Presentation Mode" tabindex="12" data-l10n-id="presentation_mode">
+                <button id="presentationMode" class="toolbarButton presentationMode hiddenLargeView" title="Switch to Presentation Mode" tabindex="13" data-l10n-id="presentation_mode">
                   <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
                 </button>
 
-                <button id="openFile" class="toolbarButton openFile hiddenLargeView" title="Open File" tabindex="13" data-l10n-id="open_file">
+                <button id="openFile" class="toolbarButton openFile hiddenLargeView" title="Open File" tabindex="14" data-l10n-id="open_file">
                   <span data-l10n-id="open_file_label">Open</span>
                 </button>
 
-                <button id="print" class="toolbarButton print hiddenMediumView" title="Print" tabindex="14" data-l10n-id="print">
+                <button id="print" class="toolbarButton print hiddenMediumView" title="Print" tabindex="15" data-l10n-id="print">
                   <span data-l10n-id="print_label">Print</span>
                 </button>
 
-                <button id="download" class="toolbarButton download hiddenMediumView" title="Download" tabindex="15" data-l10n-id="download">
+                <button id="download" class="toolbarButton download hiddenMediumView" title="Download" tabindex="16" data-l10n-id="download">
                   <span data-l10n-id="download_label">Download</span>
                 </button>
                 <!-- <div class="toolbarButtonSpacer"></div> -->
-                <a href="#" id="viewBookmark" class="toolbarButton bookmark hiddenSmallView" title="Current view (copy or open in new window)" tabindex="16" data-l10n-id="bookmark">
+                <a href="#" id="viewBookmark" class="toolbarButton bookmark hiddenSmallView" title="Current view (copy or open in new window)" tabindex="17" data-l10n-id="bookmark">
                   <span data-l10n-id="bookmark_label">Current View</span>
                 </a>
 
                 <div class="verticalToolbarSeparator hiddenSmallView"></div>
                 
-                <button id="secondaryToolbarToggle" class="toolbarButton" title="Tools" tabindex="17" data-l10n-id="tools">
+                <button id="secondaryToolbarToggle" class="toolbarButton" title="Tools" tabindex="18" data-l10n-id="tools">
                   <span data-l10n-id="tools_label">Tools</span>
                 </button> 
               </div>
               <div class="outerCenter">
                 <div class="innerCenter" id="toolbarViewerMiddle">
                   <div class="splitToolbarButton">
-                    <button id="zoomOut" class="toolbarButton zoomOut" title="Zoom Out" tabindex="9" data-l10n-id="zoom_out">
+                    <button id="zoomOut" class="toolbarButton zoomOut" title="Zoom Out" tabindex="10" data-l10n-id="zoom_out">
                       <span data-l10n-id="zoom_out_label">Zoom Out</span>
                     </button>
                     <div class="splitToolbarButtonSeparator"></div>
-                    <button id="zoomIn" class="toolbarButton zoomIn" title="Zoom In" tabindex="10" data-l10n-id="zoom_in">
+                    <button id="zoomIn" class="toolbarButton zoomIn" title="Zoom In" tabindex="11" data-l10n-id="zoom_in">
                       <span data-l10n-id="zoom_in_label">Zoom In</span>
                      </button>
                   </div>
                   <span id="scaleSelectContainer" class="dropdownToolbarButton">
-                     <select id="scaleSelect" title="Zoom" tabindex="11" data-l10n-id="zoom">
-                      <option id="pageAutoOption" value="auto" selected="selected" data-l10n-id="page_scale_auto">Automatic Zoom</option>
-                      <option id="pageActualOption" value="page-actual" data-l10n-id="page_scale_actual">Actual Size</option>
-                      <option id="pageFitOption" value="page-fit" data-l10n-id="page_scale_fit">Fit Page</option>
-                      <option id="pageWidthOption" value="page-width" data-l10n-id="page_scale_width">Full Width</option>
-                      <option id="customScaleOption" value="custom"></option>
-                      <option value="0.5">50%</option>
-                      <option value="0.75">75%</option>
-                      <option value="1">100%</option>
-                      <option value="1.25">125%</option>
-                      <option value="1.5">150%</option>
-                      <option value="2">200%</option>
+                     <select id="scaleSelect" title="Zoom" tabindex="12" data-l10n-id="zoom">
+                      <option id="pageAutoOption" title="" value="auto" selected="selected" data-l10n-id="page_scale_auto">Automatic Zoom</option>
+                      <option id="pageActualOption" title="" value="page-actual" data-l10n-id="page_scale_actual">Actual Size</option>
+                      <option id="pageFitOption" title="" value="page-fit" data-l10n-id="page_scale_fit">Fit Page</option>
+                      <option id="pageWidthOption" title="" value="page-width" data-l10n-id="page_scale_width">Full Width</option>
+                      <option id="customScaleOption" title="" value="custom"></option>
+                      <option title="" value="0.5">50%</option>
+                      <option title="" value="0.75">75%</option>
+                      <option title="" value="1">100%</option>
+                      <option title="" value="1.25">125%</option>
+                      <option title="" value="1.5">150%</option>
+                      <option title="" value="2">200%</option>
                     </select>
                   </span>
                 </div>
               </div>
             </div>
             <div id="loadingBar">
               <div class="progress">
                 <div class="glimmer">
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -515,16 +515,28 @@ var DownloadManager = (function Download
   DownloadManager.prototype = {
     downloadUrl: function DownloadManager_downloadUrl(url, filename) {
       FirefoxCom.request('download', {
         originalUrl: url,
         filename: filename
       });
     },
 
+    downloadData: function DownloadManager_downloadData(data, filename,
+                                                        contentType) {
+      var blobUrl = PDFJS.createObjectURL(data, contentType);
+      
+      FirefoxCom.request('download', {
+        blobUrl: blobUrl,
+        originalUrl: blobUrl,
+        filename: filename,
+        isAttachment: true
+      });
+    },
+
     download: function DownloadManager_download(blob, url, filename) {
       var blobUrl = window.URL.createObjectURL(blob);
 
       FirefoxCom.request('download', {
         blobUrl: blobUrl,
         originalUrl: url,
         filename: filename
       },
@@ -2824,26 +2836,26 @@ var PDFView = {
     }
   },
 
   setTitle: function pdfViewSetTitle(title) {
     document.title = title;
   },
 
   close: function pdfViewClose() {
+    var errorWrapper = document.getElementById('errorWrapper');
+    errorWrapper.setAttribute('hidden', 'true');
+
     if (!this.pdfDocument) {
       return;
     }
 
     this.pdfDocument.destroy();
     this.pdfDocument = null;
 
-    var errorWrapper = document.getElementById('errorWrapper');
-    errorWrapper.setAttribute('hidden', 'true');
-
     var thumbsView = document.getElementById('thumbnailView');
     while (thumbsView.hasChildNodes()) {
       thumbsView.removeChild(thumbsView.lastChild);
     }
 
     if ('_loadingInterval' in thumbsView) {
       clearInterval(thumbsView._loadingInterval);
     }
@@ -2857,21 +2869,20 @@ var PDFView = {
       PDFBug.cleanup();
     }
   },
 
   // TODO(mack): This function signature should really be pdfViewOpen(url, args)
   open: function pdfViewOpen(url, scale, password,
                              pdfDataRangeTransport, args) {
     if (this.pdfDocument) {
-      this.close();
-
       // Reload the preferences if a document was previously opened.
       Preferences.reload();
     }
+    this.close();
 
     var parameters = {password: password};
     if (typeof url === 'string') { // URL
       this.setTitleUsingUrl(url);
       parameters.url = url;
     } else if (url && 'byteLength' in url) { // ArrayBuffer
       parameters.data = url;
     }
@@ -3302,16 +3313,20 @@ var PDFView = {
                 if (!self.sidebarOpen) {
                   document.getElementById('sidebarToggle').click();
                 }
                 self.switchSidebarView('outline');
               }
             });
         }
       });
+      pdfDocument.getAttachments().then(function(attachments) {
+        self.attachments = new DocumentAttachmentsView(attachments);
+        document.getElementById('viewAttachments').disabled = !attachments;
+      });
     });
 
     pdfDocument.getMetadata().then(function(data) {
       var info = data.info, metadata = data.metadata;
       self.documentInfo = info;
       self.metadata = metadata;
 
       // Provides some basic debug information
@@ -3543,70 +3558,91 @@ var PDFView = {
       if (dest) {
         var currentPage = this.pages[(pageNumber || this.page) - 1];
         currentPage.scrollIntoView(dest);
       } else if (pageNumber) {
         this.page = pageNumber; // simple page
       }
       if ('pagemode' in params) {
         var toggle = document.getElementById('sidebarToggle');
-        if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks') {
+        if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
+            params.pagemode === 'attachments') {
           if (!this.sidebarOpen) {
             toggle.click();
           }
-          this.switchSidebarView(params.pagemode === 'thumbs' ?
-                                 'thumbs' : 'outline');
+          this.switchSidebarView(params.pagemode === 'bookmarks' ?
+                                   'outline' :
+                                   params.pagemode);
         } else if (params.pagemode === 'none' && this.sidebarOpen) {
           toggle.click();
         }
       }
     } else if (/^\d+$/.test(hash)) { // page number
       this.page = hash;
     } else { // named destination
       PDFHistory.updateNextHashParam(unescape(hash));
       PDFView.navigateTo(unescape(hash));
     }
   },
 
   switchSidebarView: function pdfViewSwitchSidebarView(view) {
     var thumbsView = document.getElementById('thumbnailView');
     var outlineView = document.getElementById('outlineView');
+    var attachmentsView = document.getElementById('attachmentsView');
 
     var thumbsButton = document.getElementById('viewThumbnail');
     var outlineButton = document.getElementById('viewOutline');
+    var attachmentsButton = document.getElementById('viewAttachments');
 
     switch (view) {
       case 'thumbs':
-        var wasOutlineViewVisible = thumbsView.classList.contains('hidden');
+        var wasAnotherViewVisible = thumbsView.classList.contains('hidden');
 
         thumbsButton.classList.add('toggled');
         outlineButton.classList.remove('toggled');
+        attachmentsButton.classList.remove('toggled');
         thumbsView.classList.remove('hidden');
         outlineView.classList.add('hidden');
+        attachmentsView.classList.add('hidden');
 
         PDFView.renderHighestPriority();
 
-        if (wasOutlineViewVisible) {
+        if (wasAnotherViewVisible) {
           // Ensure that the thumbnail of the current page is visible
-          // when switching from the outline view.
+          // when switching from another view.
           scrollIntoView(document.getElementById('thumbnailContainer' +
                                                  this.page));
         }
         break;
 
       case 'outline':
         thumbsButton.classList.remove('toggled');
         outlineButton.classList.add('toggled');
+        attachmentsButton.classList.remove('toggled');
         thumbsView.classList.add('hidden');
         outlineView.classList.remove('hidden');
+        attachmentsView.classList.add('hidden');
 
         if (outlineButton.getAttribute('disabled')) {
           return;
         }
         break;
+
+      case 'attachments':
+        thumbsButton.classList.remove('toggled');
+        outlineButton.classList.remove('toggled');
+        attachmentsButton.classList.add('toggled');
+        thumbsView.classList.add('hidden');
+        outlineView.classList.add('hidden');
+        attachmentsView.classList.remove('hidden');
+
+        if (attachmentsButton.getAttribute('disabled')) {
+          return;
+        }
+        break;
     }
   },
 
   getVisiblePages: function pdfViewGetVisiblePages() {
     if (!PresentationMode.active) {
       return this.getVisibleElements(this.container, this.pages, true);
     } else {
       // The algorithm in getVisibleElements doesn't work in all browsers and
@@ -3668,17 +3704,17 @@ var PDFView = {
     }
     return {first: first, last: last, views: visible};
   },
 
   // Helper function to parse query string (e.g. ?param1=value&parm2=...).
   parseQueryString: function pdfViewParseQueryString(query) {
     var parts = query.split('&');
     var params = {};
-    for (var i = 0, ii = parts.length; i < parts.length; ++i) {
+    for (var i = 0, ii = parts.length; i < ii; ++i) {
       var param = parts[i].split('=');
       var key = param[0];
       var value = param.length > 1 ? param[1] : null;
       params[decodeURIComponent(key)] = decodeURIComponent(value);
     }
     return params;
   },
 
@@ -3859,37 +3895,46 @@ var PageView = function pageView(contain
   this.destroy = function pageViewDestroy() {
     this.zoomLayer = null;
     this.reset();
     if (this.pdfPage) {
       this.pdfPage.destroy();
     }
   };
 
-  this.reset = function pageViewReset() {
+  this.reset = function pageViewReset(keepAnnotations) {
     if (this.renderTask) {
       this.renderTask.cancel();
     }
     this.resume = null;
     this.renderingState = RenderingStates.INITIAL;
 
     div.style.width = Math.floor(this.viewport.width) + 'px';
     div.style.height = Math.floor(this.viewport.height) + 'px';
 
     var childNodes = div.childNodes;
     for (var i = div.childNodes.length - 1; i >= 0; i--) {
       var node = childNodes[i];
-      if (this.zoomLayer && this.zoomLayer === node) {
+      if ((this.zoomLayer && this.zoomLayer === node) ||
+          (keepAnnotations && this.annotationLayer === node)) {
         continue;
       }
       div.removeChild(node);
     }
     div.removeAttribute('data-loaded');
 
-    this.annotationLayer = null;
+    if (keepAnnotations) {
+      if (this.annotationLayer) {
+        // Hide annotationLayer until all elements are resized
+        // so they are not displayed on the already-resized page
+        this.annotationLayer.setAttribute('hidden', 'true');
+      }
+    } else {
+      this.annotationLayer = null;
+    }
 
     delete this.canvas;
 
     this.loadingIconDiv = document.createElement('div');
     this.loadingIconDiv.className = 'loadingIcon';
     div.appendChild(this.loadingIconDiv);
   };
 
@@ -3911,17 +3956,17 @@ var PageView = function pageView(contain
       return;
     } else if (this.canvas && !this.zoomLayer) {
       this.zoomLayer = this.canvas.parentNode;
       this.zoomLayer.style.position = 'absolute';
     }
     if (this.zoomLayer) {
       this.cssTransform(this.zoomLayer.firstChild);
     }
-    this.reset();
+    this.reset(true);
   };
 
   this.cssTransform = function pageCssTransform(canvas) {
     // Scale canvas, canvas wrapper, and page container.
     var width = this.viewport.width;
     var height = this.viewport.height;
     canvas.style.width = canvas.parentNode.style.width = div.style.width =
         Math.floor(width) + 'px';
@@ -4060,70 +4105,83 @@ var PageView = function pageView(contain
             break; // No action according to spec
         }
         return false;
       };
       link.className = 'internalLink';
     }
 
     pdfPage.getAnnotations().then(function(annotationsData) {
+      viewport = viewport.clone({ dontFlip: true });
+      var transform = viewport.transform;
+      var transformStr = 'matrix(' + transform.join(',') + ')';
+      var data, element, i, ii;
+
       if (self.annotationLayer) {
-        // If an annotationLayer already exists, delete it to avoid creating
-        // duplicate annotations when rapidly re-zooming the document.
-        pageDiv.removeChild(self.annotationLayer);
-        self.annotationLayer = null;
-      }
-      viewport = viewport.clone({ dontFlip: true });
-      for (var i = 0; i < annotationsData.length; i++) {
-        var data = annotationsData[i];
-        var annotation = PDFJS.Annotation.fromData(data);
-        if (!annotation || !annotation.hasHtml()) {
-          continue;
+        // If an annotationLayer already exists, refresh its children's
+        // transformation matrices
+        for (i = 0, ii = annotationsData.length; i < ii; i++) {
+          data = annotationsData[i];
+          element = self.annotationLayer.querySelector(
+            '[data-annotation-id="' + data.id + '"]');
+          if (element) {
+            CustomStyle.setProp('transform', element, transformStr);
+          }
         }
-
-        var element = annotation.getHtmlElement(pdfPage.commonObjs);
-        mozL10n.translate(element);
-
-        data = annotation.getData();
-        var rect = data.rect;
-        var view = pdfPage.view;
-        rect = PDFJS.Util.normalizeRect([
-          rect[0],
-          view[3] - rect[1] + view[1],
-          rect[2],
-          view[3] - rect[3] + view[1]
-        ]);
-        element.style.left = rect[0] + 'px';
-        element.style.top = rect[1] + 'px';
-        element.style.position = 'absolute';
-
-        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) {
-          var link = element.getElementsByTagName('a')[0];
-          if (link) {
-            if (data.action) {
-              bindNamedAction(link, data.action);
-            } else {
-              bindLink(link, ('dest' in data) ? data.dest : null);
+        // See this.reset()
+        self.annotationLayer.removeAttribute('hidden');
+      } else {
+        for (i = 0, ii = annotationsData.length; i < ii; i++) {
+          data = annotationsData[i];
+          var annotation = PDFJS.Annotation.fromData(data);
+          if (!annotation || !annotation.hasHtml()) {
+            continue;
+          }
+
+          element = annotation.getHtmlElement(pdfPage.commonObjs);
+          element.setAttribute('data-annotation-id', data.id);
+          mozL10n.translate(element);
+
+          data = annotation.getData();
+          var rect = data.rect;
+          var view = pdfPage.view;
+          rect = PDFJS.Util.normalizeRect([
+            rect[0],
+            view[3] - rect[1] + view[1],
+            rect[2],
+            view[3] - rect[3] + view[1]
+          ]);
+          element.style.left = rect[0] + 'px';
+          element.style.top = rect[1] + 'px';
+          element.style.position = 'absolute';
+
+          CustomStyle.setProp('transform', element, transformStr);
+          var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
+          CustomStyle.setProp('transformOrigin', element, transformOriginStr);
+
+          if (data.subtype === 'Link' && !data.url) {
+            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;
+          }
+
+          self.annotationLayer.appendChild(element);
         }
-
-        if (!self.annotationLayer) {
-          var annotationLayerDiv = document.createElement('div');
-          annotationLayerDiv.className = 'annotationLayer';
-          pageDiv.appendChild(annotationLayerDiv);
-          self.annotationLayer = annotationLayerDiv;
-        }
-        self.annotationLayer.appendChild(element);
       }
     });
   }
 
   this.getPagePoint = function pageViewGetPagePoint(x, y) {
     return this.viewport.convertToPdfPoint(x, y);
   };
 
@@ -4249,20 +4307,24 @@ var PageView = function pageView(contain
     var canvasWrapper = document.createElement('div');
     canvasWrapper.style.width = div.style.width;
     canvasWrapper.style.height = div.style.height;
     canvasWrapper.classList.add('canvasWrapper');
 
     var canvas = document.createElement('canvas');
     canvas.id = 'page' + this.id;
     canvasWrapper.appendChild(canvas);
-    div.appendChild(canvasWrapper);
+    if (this.annotationLayer) {
+      // annotationLayer needs to stay on top
+      div.insertBefore(canvasWrapper, this.annotationLayer);
+    } else {
+      div.appendChild(canvasWrapper);
+    }
     this.canvas = canvas;
 
-    var scale = this.scale;
     var ctx = canvas.getContext('2d');
     var outputScale = getOutputScale(ctx);
 
     if (USE_ONLY_CSS_ZOOM) {
       var actualSizeViewport = viewport.clone({ scale: CSS_UNITS });
       // Use a scale that will make the canvas be the original intended size
       // of the page.
       outputScale.sx *= actualSizeViewport.width / viewport.width;
@@ -4278,17 +4340,22 @@ var PageView = function pageView(contain
     canvas._viewport = viewport;
 
     var textLayerDiv = null;
     if (!PDFJS.disableTextLayer) {
       textLayerDiv = document.createElement('div');
       textLayerDiv.className = 'textLayer';
       textLayerDiv.style.width = canvas.style.width;
       textLayerDiv.style.height = canvas.style.height;
-      div.appendChild(textLayerDiv);
+      if (this.annotationLayer) {
+        // annotationLayer needs to stay on top
+        div.insertBefore(textLayerDiv, this.annotationLayer);
+      } else {
+        div.appendChild(textLayerDiv);
+      }
     }
     var textLayer = this.textLayer =
       textLayerDiv ? new TextLayerBuilder({
         textLayerDiv: textLayerDiv,
         pageIndex: this.id - 1,
         lastScrollSource: PDFView,
         viewport: this.viewport,
         isViewerInPresentationMode: PresentationMode.active
@@ -4420,17 +4487,16 @@ var PageView = function pageView(contain
 
     var printContainer = document.getElementById('printContainer');
     var canvasWrapper = document.createElement('div');
     canvasWrapper.style.width = viewport.width + 'pt';
     canvasWrapper.style.height = viewport.height + 'pt';
     canvasWrapper.appendChild(canvas);
     printContainer.appendChild(canvasWrapper);
 
-    var self = this;
     canvas.mozPrintCallback = function(obj) {
       var ctx = obj.context;
 
       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);
@@ -4789,19 +4855,16 @@ var TextLayerBuilder = function textLaye
   this.convertMatches = function textLayerBuilderConvertMatches(matches) {
     var i = 0;
     var iIndex = 0;
     var bidiTexts = this.textContent.items;
     var end = bidiTexts.length - 1;
     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++) {
       var matchIdx = matches[m];
       // # Calculate the begin position.
 
       // Loop over the divIdxs.
@@ -4982,17 +5045,16 @@ var TextLayerBuilder = function textLaye
     this.renderMatches(this.matches);
   };
 };
 
 
 
 var DocumentOutlineView = function documentOutlineView(outline) {
   var outlineView = document.getElementById('outlineView');
-  var outlineButton = document.getElementById('viewOutline');
   while (outlineView.firstChild) {
     outlineView.removeChild(outlineView.firstChild);
   }
 
   if (!outline) {
     if (!outlineView.classList.contains('hidden')) {
       PDFView.switchSidebarView('thumbs');
     }
@@ -5028,16 +5090,54 @@ var DocumentOutlineView = function docum
         queue.push({parent: itemsDiv, items: item.items});
       }
 
       levelData.parent.appendChild(div);
     }
   }
 };
 
+var DocumentAttachmentsView = function documentAttachmentsView(attachments) {
+  var attachmentsView = document.getElementById('attachmentsView');
+  while (attachmentsView.firstChild) {
+    attachmentsView.removeChild(attachmentsView.firstChild);
+  }
+
+  if (!attachments) {
+    if (!attachmentsView.classList.contains('hidden')) {
+      PDFView.switchSidebarView('thumbs');
+    }
+    return;
+  }
+
+  function bindItemLink(domObj, item) {
+    domObj.href = '#';
+    domObj.onclick = function documentAttachmentsViewOnclick(e) {
+      var downloadManager = new DownloadManager();
+      downloadManager.downloadData(item.content, getFileName(item.filename),
+                                   '');
+      return false;
+    };
+  }
+
+  var names = Object.keys(attachments).sort(function(a,b) {
+    return a.toLowerCase().localeCompare(b.toLowerCase());
+  });
+  for (var i = 0, ii = names.length; i < ii; i++) {
+    var item = attachments[names[i]];
+    var div = document.createElement('div');
+    div.className = 'attachmentsItem';
+    var a = document.createElement('a');
+    bindItemLink(a, item);
+    a.textContent = getFileName(item.filename);
+    div.appendChild(a);
+    attachmentsView.appendChild(div);
+  }
+};
+
 
 function webViewerLoad(evt) {
   PDFView.initialize().then(webViewerInitialized);
 }
 
 function webViewerInitialized() {
   var file = window.location.href.split('#')[0];
 
@@ -5161,16 +5261,21 @@ function webViewerInitialized() {
       PDFView.switchSidebarView('thumbs');
     });
 
   document.getElementById('viewOutline').addEventListener('click',
     function() {
       PDFView.switchSidebarView('outline');
     });
 
+  document.getElementById('viewAttachments').addEventListener('click',
+    function() {
+      PDFView.switchSidebarView('attachments');
+    });
+
   document.getElementById('previous').addEventListener('click',
     function() {
       PDFView.page--;
     });
 
   document.getElementById('next').addEventListener('click',
     function() {
       PDFView.page++;
--- a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
+++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
@@ -6,55 +6,52 @@ history-panelmenu.label = History
 # LOCALIZATION NOTE(history-panelmenu.tooltiptext2): %S is the keyboard shortcut
 history-panelmenu.tooltiptext2 = Show your history (%S)
 
 privatebrowsing-button.label = New Private Window
 # LOCALIZATION NOTE(privatebrowsing-button.tooltiptext): %S is the keyboard shortcut
 privatebrowsing-button.tooltiptext = Open a new Private Browsing window (%S)
 
 save-page-button.label = Save Page
-# LOCALIZATION NOTE(save-page-button.tooltiptext2): %S is the keyboard shortcut
-# Use the unicode ellipsis char, \u2026, or use "..." if \u2026 doesn't suit traditions in your locale.
-save-page-button.tooltiptext2 = Save this page… (%S)
+# LOCALIZATION NOTE(save-page-button.tooltiptext3): %S is the keyboard shortcut
+save-page-button.tooltiptext3 = Save this page (%S)
 
 find-button.label = Find
-# LOCALIZATION NOTE(find-button.tooltiptext2): %S is the keyboard shortcut.
-# Use the unicode ellipsis char, \u2026, or use "..." if \u2026 doesn't suit traditions in your locale.
-find-button.tooltiptext2 = Find in this page… (%S)
+# LOCALIZATION NOTE(find-button.tooltiptext3): %S is the keyboard shortcut.
+find-button.tooltiptext3 = Find in this page (%S)
 
 open-file-button.label = Open File
-# LOCALIZATION NOTE (open-file-button.tooltiptext2): %S is the keyboard shortcut.
-# Use the unicode ellipsis char, \u2026, or use "..." if \u2026 doesn't suit traditions in your locale.
-open-file-button.tooltiptext2 = Open file… (%S)
+# LOCALIZATION NOTE (open-file-button.tooltiptext3): %S is the keyboard shortcut.
+open-file-button.tooltiptext3 = Open a file (%S)
 
 developer-button.label = Developer
 # LOCALIZATION NOTE(developer-button.tooltiptext): %S is the keyboard shortcut
-developer-button.tooltiptext = Web Developer Tools (%S)
+developer-button.tooltiptext2 = Open Web developer tools (%S)
 
 sidebar-button.label = Sidebars
-sidebar-button.tooltiptext = Show Sidebars
+sidebar-button.tooltiptext2 = Show sidebars
 
 add-ons-button.label = Add-ons
-# LOCALIZATION NOTE(add-ons-button.tooltiptext2): %S is the keyboard shortcut
-add-ons-button.tooltiptext2 = Open Add-ons Manager (%S)
+# LOCALIZATION NOTE(add-ons-button.tooltiptext3): %S is the keyboard shortcut
+add-ons-button.tooltiptext3 = Manage your add-ons (%S)
 
 switch-to-metro-button2.label = Windows 8 Touch
 # LOCALIZATION NOTE(switch-to-metro-button2.tooltiptext): %S is the brand short name
 switch-to-metro-button2.tooltiptext = Relaunch in %S for Windows 8 Touch
 
 preferences-button.label = Preferences
-preferences-button.tooltiptext2 = Open Preferences
-preferences-button.tooltiptext.withshortcut = Open Preferences (%S)
+preferences-button.tooltiptext2 = Open preferences
+preferences-button.tooltiptext.withshortcut = Open preferences (%S)
 # LOCALIZATION NOTE (preferences-button.labelWin): Windows-only label for Options
 preferences-button.labelWin = Options
 # LOCALIZATION NOTE (preferences-button.tooltipWin): Windows-only tooltip for Options
-preferences-button.tooltipWin2 = Open Options
+preferences-button.tooltipWin2 = Open options
 
 zoom-controls.label = Zoom Controls
-zoom-controls.tooltiptext = Zoom Controls
+zoom-controls.tooltiptext2 = Zoom controls
 
 zoom-out-button.label = Zoom out
 # LOCALIZATION NOTE(zoom-out-button.tooltiptext2): %S is the keyboard shortcut.
 zoom-out-button.tooltiptext2 = Zoom out (%S)
 
 # LOCALIZATION NOTE(zoom-reset-button.label): %S is the current zoom level,
 # %% will be displayed as a single % character (% is commonly used to define
 # format specifiers, so it needs to be escaped).
@@ -62,44 +59,40 @@ zoom-reset-button.label = %S%%
 # LOCALIZATION NOTE(zoom-reset-button.tooltiptext2): %S is the keyboard shortcut.
 zoom-reset-button.tooltiptext2 = Reset zoom level (%S)
 
 zoom-in-button.label = Zoom in
 # LOCALIZATION NOTE(zoom-in-button.tooltiptext2): %S is the keyboard shortcut.
 zoom-in-button.tooltiptext2 = Zoom in (%S)
 
 edit-controls.label = Edit Controls
-edit-controls.tooltiptext = Edit Controls
+edit-controls.tooltiptext2 = Edit controls
 
 cut-button.label = Cut
 # LOCALIZATION NOTE(cut-button.tooltiptext2): %S is the keyboard shortcut.
 cut-button.tooltiptext2 = Cut (%S)
 
 copy-button.label = Copy
 # LOCALIZATION NOTE(copy-button.tooltiptext2): %S is the keyboard shortcut.
 copy-button.tooltiptext2 = Copy (%S)
 
 paste-button.label = Paste
 # LOCALIZATION NOTE(paste-button.tooltiptext2): %S is the keyboard shortcut.
 paste-button.tooltiptext2 = Paste (%S)
 
-# LOCALIZATION NOTE (feed-button.tooltiptext): Use the unicode ellipsis char,
-# \u2026, or use "..." if \u2026 doesn't suit traditions in your locale.
 feed-button.label = Subscribe
-feed-button.tooltiptext = Subscribe to this page…
+feed-button.tooltiptext2 = Subscribe to this page
 
 # LOCALIZATION NOTE (characterencoding-button.label): The \u00ad character at the beginning
 # of the string is used to disable auto hyphenation on the button text when it is displayed
 # in the menu panel.
 characterencoding-button.label = \u00adCharacter Encoding
-characterencoding-button.tooltiptext2 = Show Character Encoding options
+characterencoding-button.tooltiptext2 = Show character encoding options
 
 email-link-button.label = Email Link
-# LOCALIZATION NOTE (email-link-button.tooltiptext2): Use the unicode ellipsis char,
-# \u2026, or use "..." if \u2026 doesn't suit traditions in your locale.
-email-link-button.tooltiptext2 = Email Link…
+email-link-button.tooltiptext3 = Email a link to this page
 
 # LOCALIZATION NOTE(quit-button.tooltiptext.linux2): %1$S is the brand name (e.g. Firefox),
 # %2$S is the keyboard shortcut
 quit-button.tooltiptext.linux2 = Quit %1$S (%2$S)
 # LOCALIZATION NOTE(quit-button.tooltiptext.mac): %1$S is the brand name (e.g. Firefox),
 # %2$S is the keyboard shortcut
 quit-button.tooltiptext.mac = Quit %1$S (%2$S)
--- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
@@ -217,8 +217,13 @@ messageRepeats.tooltip2=#1 repeat;#1 rep
 # LOCALIZATION NOTE (openNodeInInspector): the text that is displayed in a
 # tooltip when hovering over the inspector icon next to a DOM Node in the console
 # output
 openNodeInInspector=Click to select the node in the inspector
 
 # LOCALIZATION NOTE (cdFunctionInvalidArgument): the text that is displayed when
 # cd() is invoked with an invalid argument.
 cdFunctionInvalidArgument=Cannot cd() to the given window. Invalid argument.
+
+# LOCALIZATION NOTE (messageToggleDetails): the text that is displayed when
+# you hover the arrow for expanding/collapsing the message details. For
+# console.error() and other messages we show the stacktrace.
+messageToggleDetails=Show/hide message details.
--- a/browser/locales/en-US/pdfviewer/viewer.properties
+++ b/browser/locales/en-US/pdfviewer/viewer.properties
@@ -84,16 +84,18 @@ document_properties_close=Close
 
 # Tooltips and alt text for side panel toolbar buttons
 # (the _label strings are alt text for the buttons, the .title strings are
 # tooltips)
 toggle_sidebar.title=Toggle Sidebar
 toggle_sidebar_label=Toggle Sidebar
 outline.title=Show Document Outline
 outline_label=Document Outline
+attachments.title=Show Attachments
+attachments_label=Attachments
 thumbs.title=Show Thumbnails
 thumbs_label=Thumbnails
 findbar.title=Find in Document
 findbar_label=Find
 
 # Thumbnails panel item (tooltip and alt text for images)
 # LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
 # number.
--- a/browser/themes/shared/devtools/webconsole.inc.css
+++ b/browser/themes/shared/devtools/webconsole.inc.css
@@ -33,20 +33,18 @@ a {
   background: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 0, 1, 0, 0) no-repeat;
   background-position: center 0.5em;
   flex: 0 0 auto;
   margin: 0 6px;
   padding: 0 4px;
   width: 8px;
 }
 
-.message > .body {
+.message > .message-body-wrapper {
   flex: 1 1 100%;
-  white-space: pre-wrap;
-  word-wrap: break-word;
   margin-top: 4px;
 }
 
 /* The red bubble that shows the number of times a message is repeated */
 .message-repeats {
   -moz-user-select: none;
   flex: 0 0 auto;
   margin: 2px 6px;
@@ -66,17 +64,17 @@ a {
 
 .message-location {
   -moz-margin-start: 6px;
   display: flex;
   flex: 0 0 auto;
   align-self: flex-start;
   justify-content: flex-end;
   width: 10em;
-  margin-top: 4px;
+  margin: 3px 0;
   color: -moz-nativehyperlinktext;
   text-decoration: none;
   white-space: nowrap;
 }
 
 .message-location:hover,
 .message-location:focus {
   text-decoration: underline;
@@ -87,16 +85,30 @@ a {
   text-align: end;
   overflow: hidden;
 }
 
 .message-location > .line-number {
   flex: 0 0 auto;
 }
 
+.message-flex-body {
+  display: flex;
+}
+
+.message-body {
+  white-space: pre-wrap;
+  word-wrap: break-word;
+}
+
+.message-flex-body > .message-body {
+  display: block;
+  flex: 1 1 auto;
+}
+
 .jsterm-input-container {
   border-top-width: 1px;
   border-top-style: solid;
 }
 
 #output-wrapper {
   direction: ltr;
   overflow: auto;
@@ -148,25 +160,33 @@ a {
 }
 
 /* Network styles */
 .webconsole-filter-button[category="net"] > .toolbarbutton-menubutton-button:before {
   background-image: linear-gradient(#444444, #000000);
   border-color: #777;
 }
 
+.theme-light .message[severity=error] {
+  background-color: rgba(255, 150, 150, 0.3);
+}
+
+.theme-dark .message[severity=error] {
+  background-color: rgba(255, 100, 100, 0.3);
+}
+
 .message[category=network] > .icon {
   -moz-border-start: solid #000 6px;
 }
 
 .message[category=network][severity=error] > .icon {
   background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 0, 16, 8, 8);
 }
 
-.message[category=network] > .body {
+.message[category=network] > .message-body {
   display: flex;
 }
 
 .message[category=network] .method {
   flex: 0 0 auto;
 }
 
 .message[category=network]:not(.navigation-marker) .url {
@@ -279,17 +299,17 @@ a {
   background: -moz-image-rect(url("chrome://browser/skin/devtools/commandline-icon.png"), 0, 32, 16, 16) no-repeat;
 }
 
 :-moz-any(.jsterm-input-node,
           .jsterm-complete-node) > .textbox-input-box > .textbox-textarea {
   overflow-x: hidden;
 }
 
-.inlined-variables-view .body {
+.inlined-variables-view .message-body {
   display: flex;
   flex-direction: column;
 }
 .inlined-variables-view iframe {
   display: block;
   flex: 1;
   margin-top: 5px;
   margin-bottom: 15px;
@@ -335,35 +355,43 @@ a {
   font-size: 0.9em;
 }
 
 .navigation-marker .url {
   -moz-padding-end: 9px;
   text-decoration: none;
 }
 
-.consoleTrace .body > div {
-  display: flex;
-  margin-bottom: 5px;
-}
-
-.consoleTrace .title {
-  display: block;
-  flex: 1 1 auto;
+.stacktrace {
+  display: none;
+  list-style: none;
+  padding: 0 1em 0 1.5em;
+  margin: 5px 0 0 0;
+  max-height: 10em;
+  overflow-y: auto;
+  border: 1px solid rgb(200,200,200);
+  border-radius: 3px;
 }
 
-.stacktrace {
-  list-style: none;
-  padding: 0 1em 0 1.5em;
-  margin: 0;
-  max-height: 10em;
-  overflow-y: auto;
+.theme-light .message[severity=error] .stacktrace {
+  background-color: rgba(255, 255, 255, 0.5);
+}
+
+.theme-dark .message[severity=error] .stacktrace {
+  background-color: rgba(0, 0, 0, 0.5);
+}
 
-  border: 1px solid rgba(128, 128, 128, .5);
-  border-radius: 3px;
+.message[open] .stacktrace {
+  display: block;
+}
+
+.message .theme-twisty {
+  display: inline-block;
+  vertical-align: middle;
+  margin: 2px 3px 0 0;
 }
 
 .stacktrace li {
   display: flex;
   margin: 0;
 }
 
 .stacktrace .function {
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -351,26 +351,16 @@ abstract public class BrowserApp extends
 
         if (mBrowserToolbar.onKey(keyCode, event)) {
             return true;
         }
 
         return super.onKeyDown(keyCode, event);
     }
 
-    void handleReaderListCountRequest() {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
-            @Override
-            public void run() {
-                final int count = BrowserDB.getReadingListCount(getContentResolver());
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListCountReturn", Integer.toString(count)));
-            }
-        });
-    }
-
     void handleReaderListStatusRequest(final String url) {
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 final int inReadingList = BrowserDB.isReadingListItem(getContentResolver(), url) ? 1 : 0;
 
                 final JSONObject json = new JSONObject();
                 try {
@@ -397,19 +387,16 @@ abstract public class BrowserApp extends
             return;
         }
 
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 BrowserDB.addReadingListItem(getContentResolver(), values);
                 showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
-
-                final int count = BrowserDB.getReadingListCount(getContentResolver());
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListCountUpdated", Integer.toString(count)));
             }
         });
     }
 
     private ContentValues messageToReadingListContentValues(JSONObject message) {
         final ContentValues values = new ContentValues();
         values.put(ReadingListItems.URL, message.optString("url"));
         values.put(ReadingListItems.TITLE, message.optString("title"));
@@ -1201,18 +1188,16 @@ abstract public class BrowserApp extends
                     DataReportingNotification.checkAndNotifyPolicy(GeckoAppShell.getContext());
                 }
 
             } else if (event.equals("Telemetry:Gather")) {
                 Telemetry.HistogramAdd("PLACES_PAGES_COUNT", BrowserDB.getCount(getContentResolver(), "history"));
                 Telemetry.HistogramAdd("PLACES_BOOKMARKS_COUNT", BrowserDB.getCount(getContentResolver(), "bookmarks"));
                 Telemetry.HistogramAdd("FENNEC_FAVICONS_COUNT", BrowserDB.getCount(getContentResolver(), "favicons"));
                 Telemetry.HistogramAdd("FENNEC_THUMBNAILS_COUNT", BrowserDB.getCount(getContentResolver(), "thumbnails"));
-            } else if (event.equals("Reader:ListCountRequest")) {
-                handleReaderListCountRequest();
             } else if (event.equals("Reader:ListStatusRequest")) {
                 handleReaderListStatusRequest(message.getString("url"));
             } else if (event.equals("Reader:Added")) {
                 final int result = message.getInt("result");
                 handleReaderAdded(result, messageToReadingListContentValues(message));
             } else if (event.equals("Reader:Removed")) {
                 final String url = message.getString("url");
                 handleReaderRemoved(url);
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1567,17 +1567,16 @@ public abstract class GeckoApp
             startActivity(settingsIntent);
         }
 
         //app state callbacks
         mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
 
         //register for events
         registerEventListener("log");
-        registerEventListener("Reader:ListCountRequest");
         registerEventListener("Reader:ListStatusRequest");
         registerEventListener("Reader:Added");
         registerEventListener("Reader:Removed");
         registerEventListener("Reader:Share");
         registerEventListener("Reader:FaviconRequest");
         registerEventListener("onCameraCapture");
         registerEventListener("Gecko:Ready");
         registerEventListener("Gecko:DelayedStartup");
@@ -2102,17 +2101,16 @@ public abstract class GeckoApp
 
         super.onRestart();
     }
 
     @Override
     public void onDestroy()
     {
         unregisterEventListener("log");
-        unregisterEventListener("Reader:ListCountRequest");
         unregisterEventListener("Reader:ListStatusRequest");
         unregisterEventListener("Reader:Added");
         unregisterEventListener("Reader:Removed");
         unregisterEventListener("Reader:Share");
         unregisterEventListener("Reader:FaviconRequest");
         unregisterEventListener("onCameraCapture");
         unregisterEventListener("Gecko:Ready");
         unregisterEventListener("Gecko:DelayedStartup");
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -141,17 +141,17 @@ public class BrowserToolbar extends Them
 
     private boolean shouldShrinkURLBar = false;
 
     private OnActivateListener activateListener;
     private OnFocusChangeListener focusChangeListener;
     private OnStartEditingListener startEditingListener;
     private OnStopEditingListener stopEditingListener;
 
-    final private BrowserApp activity;
+    private final BrowserApp activity;
     private boolean hasSoftMenuButton;
 
     private UIMode uiMode;
     private boolean isAnimatingEntry;
 
     private int urlBarViewOffset;
     private int defaultForwardMargin;
 
@@ -576,17 +576,17 @@ public class BrowserToolbar extends Them
         if (!isEditing() && selectedTab.getState() == Tab.STATE_LOADING) {
             progressBar.setProgress(progress);
             progressBar.setVisibility(View.VISIBLE);
         } else {
             progressBar.setVisibility(View.GONE);
         }
     }
 
-    public boolean isVisible() {
+    private boolean isVisible() {
         return ViewHelper.getTranslationY(this) == 0;
     }
 
     @Override
     public void setNextFocusDownId(int nextId) {
         super.setNextFocusDownId(nextId);
         tabsButton.setNextFocusDownId(nextId);
         backButton.setNextFocusDownId(nextId);
@@ -1195,17 +1195,17 @@ public class BrowserToolbar extends Them
                 updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount());
             }
         });
 
         isAnimatingEntry = true;
         contentAnimator.start();
     }
 
-    public void setButtonEnabled(ImageButton button, boolean enabled) {
+    private void setButtonEnabled(ImageButton button, boolean enabled) {
         final Drawable drawable = button.getDrawable();
         if (drawable != null) {
             // This alpha value has to be in sync with the one used
             // in updateChildrenForEditing().
             drawable.setAlpha(enabled ? 255 : 61);
         }
 
         button.setEnabled(enabled);
--- a/mobile/android/base/toolbar/CanvasDelegate.java
+++ b/mobile/android/base/toolbar/CanvasDelegate.java
@@ -8,39 +8,39 @@ import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Shader;
 import android.os.Build;
 
-public class CanvasDelegate {
+class CanvasDelegate {
     Paint mPaint;
     PorterDuffXfermode mMode;
     DrawManager mDrawManager;
 
     // DrawManager would do a default draw of the background.
-    public static interface DrawManager {
+    static interface DrawManager {
         public void defaultDraw(Canvas cavas);
     }
 
-    public CanvasDelegate(DrawManager drawManager, Mode mode) {
+    CanvasDelegate(DrawManager drawManager, Mode mode) {
         mDrawManager = drawManager;
 
         // DST_IN masks, DST_OUT clips.
         mMode = new PorterDuffXfermode(mode);
 
         mPaint = new Paint();
         mPaint.setAntiAlias(true);
         mPaint.setColor(0xFFFF0000);
         mPaint.setStrokeWidth(0.0f);
     }
 
-    public void draw(Canvas canvas, Path path, int width, int height) {
+    void draw(Canvas canvas, Path path, int width, int height) {
         // Save the canvas. All PorterDuff operations should be done in a offscreen bitmap.
         int count = canvas.saveLayer(0, 0, width, height, null,
                                      Canvas.MATRIX_SAVE_FLAG |
                                      Canvas.CLIP_SAVE_FLAG |
                                      Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                                      Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
                                      Canvas.CLIP_TO_LAYER_SAVE_FLAG);
 
@@ -65,12 +65,12 @@ public class CanvasDelegate {
                 mPaint.setXfermode(null);
             }
         }
 
         // Restore the canvas.
         canvas.restoreToCount(count);
     }
 
-    public void setShader(Shader shader) {
+    void setShader(Shader shader) {
         mPaint.setShader(shader);
     }
 }
--- a/mobile/android/base/toolbar/PageActionLayout.java
+++ b/mobile/android/base/toolbar/PageActionLayout.java
@@ -59,17 +59,17 @@ public class PageActionLayout extends Li
         mPageActionList = new ArrayList<PageAction>();
         setNumberShown(DEFAULT_PAGE_ACTIONS_SHOWN);
         refreshPageActionIcons();
 
         registerEventListener("PageActions:Add");
         registerEventListener("PageActions:Remove");
     }
 
-    public void setNumberShown(int count) {
+    private void setNumberShown(int count) {
         mMaxVisiblePageActions = count;
 
         for(int index = 0; index < count; index++) {
             if ((this.getChildCount() - 1) < index) {
                 mLayout.addView(createImageButton());
             }
         }
     }
@@ -113,17 +113,17 @@ public class PageActionLayout extends Li
 
                 removePageAction(id);
             }
         } catch(JSONException ex) {
             Log.e(LOGTAG, "Error deocding", ex);
         }
     }
 
-    public void addPageAction(final String id, final String title, final String imageData, final OnPageActionClickListeners mOnPageActionClickListeners, boolean mImportant) {
+    private void addPageAction(final String id, final String title, final String imageData, final OnPageActionClickListeners mOnPageActionClickListeners, boolean mImportant) {
         final PageAction pageAction = new PageAction(id, title, null, mOnPageActionClickListeners, mImportant);
 
         int insertAt = mPageActionList.size();
         while(insertAt > 0 && mPageActionList.get(insertAt-1).isImportant()) {
           insertAt--;
         }
         mPageActionList.add(insertAt, pageAction);
 
@@ -133,17 +133,17 @@ public class PageActionLayout extends Li
                 if (mPageActionList.contains(pageAction)) {
                     pageAction.setDrawable(d);
                     refreshPageActionIcons();
                 }
             }
         });
     }
 
-    public void removePageAction(String id) {
+    private void removePageAction(String id) {
         for(int i = 0; i < mPageActionList.size(); i++) {
             if (mPageActionList.get(i).getID().equals(id)) {
                 mPageActionList.remove(i);
                 refreshPageActionIcons();
                 return;
             }
         }
     }
@@ -286,17 +286,17 @@ public class PageActionLayout extends Li
                 PageAction pageAction = mPageActionList.get(i);
                 MenuItem item = menu.add(Menu.NONE, pageAction.key(), Menu.NONE, pageAction.getTitle());
                 item.setIcon(pageAction.getDrawable());
             }
         }
         mPageActionsMenu.show();
     }
 
-    public static interface OnPageActionClickListeners {
+    private static interface OnPageActionClickListeners {
         public void onClick(String id);
         public boolean onLongClick(String id);
     }
 
     private static class PageAction {
         private OnPageActionClickListeners mOnPageActionClickListeners;
         private Drawable mDrawable;
         private String mTitle;
--- a/mobile/android/base/toolbar/TabCounter.java
+++ b/mobile/android/base/toolbar/TabCounter.java
@@ -62,17 +62,17 @@ public class TabCounter extends ThemedTe
             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
             setAccessibilityDelegate(new View.AccessibilityDelegate() {
                     @Override
                     public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {}
                 });
         }
     }
 
-    public void setCountWithAnimation(int count) {
+    void setCountWithAnimation(int count) {
         // Don't animate from initial state
         if (mCount == 0) {
             setCount(count);
             return;
         }
 
         if (mCount == count) {
             return;
@@ -92,17 +92,17 @@ public class TabCounter extends ThemedTe
 
         // Set In value, trigger animation to Out value
         setCurrentText(String.valueOf(mCount));
         setText(String.valueOf(count));
 
         mCount = count;
     }
 
-    public void setCount(int count) {
+    void setCount(int count) {
         setCurrentText(String.valueOf(count));
         mCount = count;
     }
 
     // Alpha animations in editing mode cause action bar corruption on the
     // Nexus 7 (bug 961749). As a workaround, skip these animations in editing
     // mode.
     void onEnterEditingMode() {
--- a/mobile/android/base/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/base/toolbar/ToolbarDisplayLayout.java
@@ -90,17 +90,17 @@ public class ToolbarDisplayLayout extend
     interface OnStopListener {
         public Tab onStop();
     }
 
     interface OnTitleChangeListener {
         public void onTitleChange(CharSequence title);
     }
 
-    final private BrowserApp mActivity;
+    private final BrowserApp mActivity;
 
     private UIMode mUiMode;
 
     private ThemedTextView mTitle;
     private int mTitlePadding;
     private ToolbarTitlePrefs mTitlePrefs;
     private OnTitleChangeListener mTitleChangeListener;
 
--- a/mobile/android/base/toolbar/ToolbarTitlePrefs.java
+++ b/mobile/android/base/toolbar/ToolbarTitlePrefs.java
@@ -6,18 +6,18 @@
 package org.mozilla.gecko.toolbar;
 
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.util.ThreadUtils;
 
 class ToolbarTitlePrefs {
-    public static final String PREF_TITLEBAR_MODE = "browser.chrome.titlebarMode";
-    public static final String PREF_TRIM_URLS = "browser.urlbar.trimURLs";
+    static final String PREF_TITLEBAR_MODE = "browser.chrome.titlebarMode";
+    static final String PREF_TRIM_URLS = "browser.urlbar.trimURLs";
 
     interface OnChangeListener {
         public void onChange();
     }
 
     final String[] prefs = {
         PREF_TITLEBAR_MODE,
         PREF_TRIM_URLS
--- a/mobile/android/chrome/content/aboutReader.html
+++ b/mobile/android/chrome/content/aboutReader.html
@@ -29,15 +29,14 @@
       <li class="dropdown-popup">
         <ul id="font-type-buttons"></ul>
         <hr></hr>
         <ul id="font-size-buttons" class="segmented-button"></ul>
         <hr></hr>
         <ul id="color-scheme-buttons" class="segmented-button"></ul>
       </li>
     </ul>
-    <li><a id="list-button" class="button list-button" href="#"></a></li>
     <li><a id="toggle-button" class="button toggle-button" href="#"></a></li>
   </ul>
 
 </body>
 
 </html>
--- a/mobile/android/chrome/content/aboutReader.js
+++ b/mobile/android/chrome/content/aboutReader.js
@@ -2,19 +2,16 @@
  * 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/. */
 
 let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm")
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-// Panel ID defined in HomeConfig.java.
-const READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
-
 XPCOMUtils.defineLazyGetter(window, "gChromeWin", function ()
   window.QueryInterface(Ci.nsIInterfaceRequestor)
     .getInterface(Ci.nsIWebNavigation)
     .QueryInterface(Ci.nsIDocShellTreeItem)
     .rootTreeItem
     .QueryInterface(Ci.nsIInterfaceRequestor)
     .getInterface(Ci.nsIDOMWindow)
     .QueryInterface(Ci.nsIDOMChromeWindow));
@@ -29,18 +26,16 @@ let AboutReader = function(doc, win) {
   dump("Init()");
 
   this._docRef = Cu.getWeakReference(doc);
   this._winRef = Cu.getWeakReference(win);
 
   Services.obs.addObserver(this, "Reader:FaviconReturn", false);
   Services.obs.addObserver(this, "Reader:Add", false);
   Services.obs.addObserver(this, "Reader:Remove", false);
-  Services.obs.addObserver(this, "Reader:ListCountReturn", false);
-  Services.obs.addObserver(this, "Reader:ListCountUpdated", false);
   Services.obs.addObserver(this, "Reader:ListStatusReturn", false);
 
   this._article = null;
 
   dump("Feching toolbar, header and content notes from about:reader");
   this._headerElementRef = Cu.getWeakReference(doc.getElementById("reader-header"));
   this._domainElementRef = Cu.getWeakReference(doc.getElementById("reader-domain"));
   this._titleElementRef = Cu.getWeakReference(doc.getElementById("reader-title"));
@@ -59,17 +54,16 @@ let AboutReader = function(doc, win) {
 
   win.addEventListener("unload", this, false);
   win.addEventListener("scroll", this, false);
   win.addEventListener("popstate", this, false);
   win.addEventListener("resize", this, false);
 
   this._setupAllDropdowns();
   this._setupButton("toggle-button", this._onReaderToggle.bind(this));
-  this._setupButton("list-button", this._onList.bind(this));
   this._setupButton("share-button", this._onShare.bind(this));
 
   let colorSchemeOptions = [
     { name: gStrings.GetStringFromName("aboutReader.colorSchemeDark"),
       value: "dark"},
     { name: gStrings.GetStringFromName("aboutReader.colorSchemeLight"),
       value: "light"},
     { name: gStrings.GetStringFromName("aboutReader.colorSchemeAuto"),
@@ -122,21 +116,16 @@ let AboutReader = function(doc, win) {
 
   dump("Decoding query arguments");
   let queryArgs = this._decodeQueryString(win.location.href);
 
   // Track status of reader toolbar add/remove toggle button
   this._isReadingListItem = -1;
   this._updateToggleButton();
 
-  // Track status of reader toolbar list button
-  this._readingListCount = -1;
-  this._updateListButton();
-  this._requestReadingListCount();
-
   let url = queryArgs.url;
   let tabId = queryArgs.tabId;
   if (tabId) {
     dump("Loading from tab with ID: " + tabId + ", URL: " + url);
     this._loadFromTab(tabId, url);
   } else {
     dump("Fetching page with URL: " + url);
     this._loadFromURL(url);
@@ -210,37 +199,16 @@ AboutReader.prototype = {
           if (this._isReadingListItem != 0) {
             this._isReadingListItem = 0;
             this._updateToggleButton();
           }
         }
         break;
       }
 
-      case "Reader:ListCountReturn":
-      case "Reader:ListCountUpdated": {
-        let count = parseInt(aData);
-        if (this._readingListCount != count) {
-          let isInitialStateChange = (this._readingListCount == -1);
-          this._readingListCount = count;
-          this._updateListButton();
-
-          // Display the toolbar when all its initial component states are known
-          if (isInitialStateChange) {
-            this._setToolbarVisibility(true);
-          }
-
-          // Initial readinglist count is requested before any page is displayed
-          if (this._article) {
-            this._requestReadingListStatus();
-          }
-        }
-        break;
-      }
-
       case "Reader:ListStatusReturn": {
         let args = JSON.parse(aData);
         if (args.url == this._article.url) {
           if (this._isReadingListItem != args.inReadingList) {
             let isInitialStateChange = (this._isReadingListItem == -1);
             this._isReadingListItem = args.inReadingList;
             this._updateToggleButton();
 
@@ -284,47 +252,31 @@ AboutReader.prototype = {
 
       case "devicelight":
         this._handleDeviceLight(aEvent.value);
         break;
 
       case "unload":
         Services.obs.removeObserver(this, "Reader:Add");
         Services.obs.removeObserver(this, "Reader:Remove");
-        Services.obs.removeObserver(this, "Reader:ListCountReturn");
-        Services.obs.removeObserver(this, "Reader:ListCountUpdated");
         Services.obs.removeObserver(this, "Reader:ListStatusReturn");
         break;
     }
   },
 
   _updateToggleButton: function Reader_updateToggleButton() {
     let classes = this._doc.getElementById("toggle-button").classList;
 
     if (this._isReadingListItem == 1) {
       classes.add("on");
     } else {
       classes.remove("on");
     }
   },
 
-  _updateListButton: function Reader_updateListButton() {
-    let classes = this._doc.getElementById("list-button").classList;
-
-    if (this._readingListCount > 0) {
-      classes.add("on");
-    } else {
-      classes.remove("on");
-    }
-  },
-
-  _requestReadingListCount: function Reader_requestReadingListCount() {
-    gChromeWin.sendMessageToJava({ type: "Reader:ListCountRequest" });
-  },
-
   _requestReadingListStatus: function Reader_requestReadingListStatus() {
     gChromeWin.sendMessageToJava({
       type: "Reader:ListStatusRequest",
       url: this._article.url
     });
   },
 
   _onReaderToggle: function Reader_onToggle() {
@@ -356,23 +308,16 @@ AboutReader.prototype = {
     } else {
       // In addition to removing the article from the cache (handled in
       // browser.js), sending this message will cause the toggle button to be
       // updated (handled in this file).
       Services.obs.notifyObservers(null, "Reader:Remove", this._article.url);
     }
   },
 
-  _onList: function Reader_onList() {
-    if (!this._article || this._readingListCount < 1)
-      return;
-
-    gChromeWin.BrowserApp.loadURI("about:home?panel=" + READING_LIST_PANEL_ID);
-  },
-
   _onShare: function Reader_onShare() {
     if (!this._article)
       return;
 
     gChromeWin.sendMessageToJava({
       type: "Reader:Share",
       url: this._article.url,
       title: this._article.title
@@ -489,17 +434,17 @@ AboutReader.prototype = {
     let win = this._win;
     if (win.history.state)
       win.history.back();
 
     if (!this._toolbarEnabled)
       return;
 
     // Don't allow visible toolbar until banner state is known
-    if (this._readingListCount == -1 || this._isReadingListItem == -1)
+    if (this._isReadingListItem == -1)
       return;
 
     if (this._getToolbarVisibility() === visible)
       return;
 
     this._toolbarElement.classList.toggle("toolbar-hidden");
     this._setSystemUIVisibility(visible);
 
--- a/mobile/android/themes/core/aboutReader.css
+++ b/mobile/android/themes/core/aboutReader.css
@@ -341,17 +341,17 @@ body {
   transition-property: visibility, opacity;
   transition-duration: 0.7s;
   visibility: hidden;
   opacity: 0.0;
 }
 
 .toolbar > * {
   float: right;
-  width: 25%;
+  width: 33%;
 }
 
 .button {
   color: white;
   display: block;
   background-position: center;
   background-size: 30px 24px;
   background-repeat: no-repeat;
@@ -491,24 +491,16 @@ body {
 .toggle-button.on {
   background-image: url('chrome://browser/skin/images/reader-toggle-on-icon-mdpi.png');
 }
 
 .toggle-button {
   background-image: url('chrome://browser/skin/images/reader-toggle-off-icon-mdpi.png');
 }
 
-.list-button.on {
-  background-image: url('chrome://browser/skin/images/reader-list-on-icon-mdpi.png');
-}
-
-.list-button {
-  background-image: url('chrome://browser/skin/images/reader-list-off-icon-mdpi.png');
-}
-
 .share-button {
   background-image: url('chrome://browser/skin/images/reader-share-icon-mdpi.png');
 }
 
 .style-button {
   background-image: url('chrome://browser/skin/images/reader-style-icon-mdpi.png');
 }
 
@@ -528,24 +520,16 @@ body {
   .toggle-button.on {
     background-image: url('chrome://browser/skin/images/reader-toggle-on-icon-hdpi.png');
   }
 
   .toggle-button {
     background-image: url('chrome://browser/skin/images/reader-toggle-off-icon-hdpi.png');
   }
 
-  .list-button.on {
-    background-image: url('chrome://browser/skin/images/reader-list-on-icon-hdpi.png');
-  }
-
-  .list-button {
-    background-image: url('chrome://browser/skin/images/reader-list-off-icon-hdpi.png');
-  }
-
   .share-button {
     background-image: url('chrome://browser/skin/images/reader-share-icon-hdpi.png');
   }
 
   .style-button {
     background-image: url('chrome://browser/skin/images/reader-style-icon-hdpi.png');
   }
 }
@@ -566,24 +550,16 @@ body {
   .toggle-button.on {
     background-image: url('chrome://browser/skin/images/reader-toggle-on-icon-xhdpi.png');
   }
 
   .toggle-button {
     background-image: url('chrome://browser/skin/images/reader-toggle-off-icon-xhdpi.png');
   }
 
-  .list-button.on {
-    background-image: url('chrome://browser/skin/images/reader-list-on-icon-xhdpi.png');
-  }
-
-  .list-button {
-    background-image: url('chrome://browser/skin/images/reader-list-off-icon-xhdpi.png');
-  }
-
   .share-button {
     background-image: url('chrome://browser/skin/images/reader-share-icon-xhdpi.png');
   }
 
   .style-button {
     background-image: url('chrome://browser/skin/images/reader-style-icon-xhdpi.png');
   }
 }
deleted file mode 100644
index 0242843558b138642a03210b3b2d7d12b2e586eb..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index bcc26589ef113a7662b452aa1f0fce1d621000a5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index a356191a2312d7f839fc145ffac7dbc7a2759d82..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 52afbcff61f152fd28fd3a2bc82903a5faca551d..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index c93b4d6fe80011ee1abb0db457db44c4cc7e8e8f..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index f71552d8ffe560ec04f8548a003cd96fecb35b07..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/mobile/android/themes/core/jar.mn
+++ b/mobile/android/themes/core/jar.mn
@@ -74,22 +74,16 @@ chrome.jar:
   skin/images/reader-plus-icon-hdpi.png     (images/reader-plus-icon-hdpi.png)
   skin/images/reader-plus-icon-xhdpi.png    (images/reader-plus-icon-xhdpi.png)
   skin/images/reader-minus-icon-mdpi.png    (images/reader-minus-icon-mdpi.png)
   skin/images/reader-minus-icon-hdpi.png    (images/reader-minus-icon-hdpi.png)
   skin/images/reader-minus-icon-xhdpi.png   (images/reader-minus-icon-xhdpi.png)
   skin/images/reader-dropdown-arrow-mdpi.png     (images/reader-dropdown-arrow-mdpi.png)
   skin/images/reader-dropdown-arrow-hdpi.png     (images/reader-dropdown-arrow-hdpi.png)
   skin/images/reader-dropdown-arrow-xhdpi.png    (images/reader-dropdown-arrow-xhdpi.png)
-  skin/images/reader-list-on-icon-mdpi.png       (images/reader-list-on-icon-mdpi.png)
-  skin/images/reader-list-on-icon-hdpi.png       (images/reader-list-on-icon-hdpi.png)
-  skin/images/reader-list-on-icon-xhdpi.png      (images/reader-list-on-icon-xhdpi.png)
-  skin/images/reader-list-off-icon-mdpi.png      (images/reader-list-off-icon-mdpi.png)
-  skin/images/reader-list-off-icon-hdpi.png      (images/reader-list-off-icon-hdpi.png)
-  skin/images/reader-list-off-icon-xhdpi.png     (images/reader-list-off-icon-xhdpi.png)
   skin/images/reader-toggle-on-icon-mdpi.png     (images/reader-toggle-on-icon-mdpi.png)
   skin/images/reader-toggle-on-icon-hdpi.png     (images/reader-toggle-on-icon-hdpi.png)
   skin/images/reader-toggle-on-icon-xhdpi.png    (images/reader-toggle-on-icon-xhdpi.png)
   skin/images/reader-toggle-off-icon-mdpi.png    (images/reader-toggle-off-icon-mdpi.png)
   skin/images/reader-toggle-off-icon-hdpi.png    (images/reader-toggle-off-icon-hdpi.png)
   skin/images/reader-toggle-off-icon-xhdpi.png   (images/reader-toggle-off-icon-xhdpi.png)
   skin/images/reader-share-icon-mdpi.png         (images/reader-share-icon-mdpi.png)
   skin/images/reader-share-icon-hdpi.png         (images/reader-share-icon-hdpi.png)
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -540,48 +540,55 @@ FxAccountsInternal.prototype = {
    *          kA: An encryption key from the FxA server
    *          kB: An encryption key derived from the user's FxA password
    *          verified: email verification status
    *        }
    *        or null if no user is signed in
    */
   getKeys: function() {
     let currentState = this.currentAccountState;
-    return currentState.getUserAccountData().then((data) => {
-      if (!data) {
+    return currentState.getUserAccountData().then((userData) => {
+      if (!userData) {
         throw new Error("Can't get keys; User is not signed in");
       }
-      if (data.kA && data.kB) {
-        return data;
+      if (userData.kA && userData.kB) {
+        return userData;
       }
       if (!currentState.whenKeysReadyDeferred) {
         currentState.whenKeysReadyDeferred = Promise.defer();
-        this.fetchAndUnwrapKeys(data.keyFetchToken).then(
-          data => {
-            if (!data.kA || !data.kB) {
-              currentState.whenKeysReadyDeferred.reject(
-                new Error("user data missing kA or kB")
-              );
-              return;
+        if (userData.keyFetchToken) {
+          this.fetchAndUnwrapKeys(userData.keyFetchToken).then(
+            (dataWithKeys) => {
+              if (!dataWithKeys.kA || !dataWithKeys.kB) {
+                currentState.whenKeysReadyDeferred.reject(
+                  new Error("user data missing kA or kB")
+                );
+                return;
+              }
+              currentState.whenKeysReadyDeferred.resolve(dataWithKeys);
+            },
+            (err) => {
+              currentState.whenKeysReadyDeferred.reject(err);
             }
-            currentState.whenKeysReadyDeferred.resolve(data);
-          },
-          err => currentState.whenKeysReadyDeferred.reject(err)
-        );
+          );
+        } else {
+          currentState.whenKeysReadyDeferred.reject('No keyFetchToken');
+        }
       }
       return currentState.whenKeysReadyDeferred.promise;
     }).then(result => currentState.resolve(result));
    },
 
   fetchAndUnwrapKeys: function(keyFetchToken) {
     log.debug("fetchAndUnwrapKeys: token: " + keyFetchToken);
     let currentState = this.currentAccountState;
     return Task.spawn(function* task() {
       // Sign out if we don't have a key fetch token.
       if (!keyFetchToken) {
+        log.warn("improper fetchAndUnwrapKeys() call: token missing");
         yield this.signOut();
         return null;
       }
 
       let {kA, wrapKB} = yield this.fetchKeys(keyFetchToken);
 
       let data = yield currentState.getUserAccountData();
 
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js
+++ b/services/fxaccounts/tests/xpcshell/test_accounts.js
@@ -300,34 +300,34 @@ add_test(function test_getKeys() {
           do_test_finished();
           run_next_test();
         });
       });
     });
   });
 });
 
-// getKeys with no keyFetchToken should trigger signOut
-add_test(function test_getKeys_no_token() {
+//  fetchAndUnwrapKeys with no keyFetchToken should trigger signOut
+add_test(function test_fetchAndUnwrapKeys_no_token() {
   do_test_pending();
 
   let fxa = new MockFxAccounts();
   let user = getTestUser("lettuce.protheroe");
   delete user.keyFetchToken
 
   makeObserver(ONLOGOUT_NOTIFICATION, function() {
-    log.debug("test_getKeys_no_token observed logout");
+    log.debug("test_fetchAndUnwrapKeys_no_token observed logout");
     fxa.internal.getUserAccountData().then(user => {
       do_test_finished();
       run_next_test();
     });
   });
 
   fxa.setSignedInUser(user).then((user) => {
-    fxa.internal.getKeys();
+    fxa.internal.fetchAndUnwrapKeys();
   });
 });
 
 // Alice (User A) signs up but never verifies her email.  Then Bob (User B)
 // signs in with a verified email.  Ensure that no sign-in events are triggered
 // on Alice's behalf.  In the end, Bob should be the signed-in user.
 add_test(function test_overlapping_signins() {
   do_test_pending();
--- a/services/sync/tests/unit/test_browserid_identity.js
+++ b/services/sync/tests/unit/test_browserid_identity.js
@@ -555,16 +555,17 @@ add_task(function test_getKeysMissing() 
   _("BrowserIDManager correctly handles getKeys succeeding but not returning keys.");
 
   let browseridManager = new BrowserIDManager();
   let identityConfig = makeIdentityConfig();
   // our mock identity config already has kA and kB - remove them or we never
   // try and fetch them.
   delete identityConfig.fxaccount.user.kA;
   delete identityConfig.fxaccount.user.kB;
+  identityConfig.fxaccount.user.keyFetchToken = 'keyFetchToken';
 
   configureFxAccountIdentity(browseridManager, identityConfig);
 
   // Mock a fxAccounts object that returns no keys
   let fxa = new FxAccounts({
     fetchAndUnwrapKeys: function () {
       return Promise.resolve({});
     },
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -24,17 +24,17 @@ this.EXPORTED_SYMBOLS = ["DebuggerTransp
                          "EnvironmentClient",
                          "ObjectClient"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 
-let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
+let promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js").Promise;
 const { defer, resolve, reject } = promise;
 
 XPCOMUtils.defineLazyServiceGetter(this, "socketTransportService",
                                    "@mozilla.org/network/socket-transport-service;1",
                                    "nsISocketTransportService");
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
                                   "resource://gre/modules/devtools/Console.jsm");
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/deprecated-sync-thenables.js
@@ -0,0 +1,119 @@
+/* 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/. */
+
+/**
+ * THIS MODULE IS DEPRECATED. IMPORT "Promise.jsm" INSTEAD.
+ */
+
+"use strict";
+
+this.Promise = {};
+
+if (typeof(require) === "function") {
+  module.exports = Promise;
+} else {
+  this.EXPORTED_SYMBOLS = ["Promise"];
+}
+
+function fulfilled(value) {
+  return { then: function then(fulfill) { fulfill(value); } };
+}
+
+function rejected(reason) {
+  return { then: function then(fulfill, reject) { reject(reason); } };
+}
+
+function isPromise(value) {
+  return value && typeof(value.then) === 'function';
+}
+
+function defer() {
+  var observers = [];
+  var result = null;
+  var promise = {
+    then: function then(onFulfill, onError) {
+      var deferred = defer();
+
+      function resolve(value) {
+        try {
+          deferred.resolve(onFulfill ? onFulfill(value) : value);
+        } catch (error) {
+          deferred.resolve(rejected(error));
+        }
+      }
+
+      function reject(reason) {
+        try {
+          if (onError) deferred.resolve(onError(reason));
+          else deferred.resolve(rejected(reason));
+        } catch (error) {
+          deferred.resolve(rejected(error));
+        }
+      }
+
+      if (observers) {
+        observers.push({ resolve: resolve, reject: reject });
+      } else {
+        result.then(resolve, reject);
+      }
+
+      return deferred.promise;
+    }
+  };
+
+  var deferred = {
+    promise: promise,
+    resolve: function resolve(value) {
+      if (!result) {
+        result = isPromise(value) ? value : fulfilled(value);
+        while (observers.length) {
+          var observer = observers.shift();
+          result.then(observer.resolve, observer.reject);
+        }
+        observers = null;
+      }
+    },
+    reject: function reject(reason) {
+      deferred.resolve(rejected(reason));
+    }
+  };
+
+  return deferred;
+}
+Promise.defer = defer;
+
+function resolve(value) {
+  var deferred = defer();
+  deferred.resolve(value);
+  return deferred.promise;
+}
+Promise.resolve = resolve;
+
+function reject(reason) {
+  var deferred = defer();
+  deferred.reject(reason);
+  return deferred.promise;
+}
+Promise.reject = reject;
+
+var promised = (function() {
+  var call = Function.call;
+  var concat = Array.prototype.concat;
+  function execute(args) { return call.apply(call, args) }
+  function promisedConcat(promises, unknown) {
+    return promises.then(function(values) {
+      return resolve(unknown).then(function(value) {
+        return values.concat([ value ]);
+      });
+    });
+  }
+  return function promised(f) {
+    return function promised() {
+      return concat.apply([ f, this ], arguments).
+        reduce(promisedConcat, resolve([])).
+        then(execute);
+    };
+  }
+})();
+Promise.all = promised(Array);
--- a/toolkit/devtools/gcli/source/lib/gcli/util/promise.js
+++ b/toolkit/devtools/gcli/source/lib/gcli/util/promise.js
@@ -13,13 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 'use strict';
 
 var Cu = require('chrome').Cu;
 module.exports = exports =
-    Cu.import('resource://gre/modules/commonjs/sdk/core/promise.js', {}).Promise;
+    Cu.import('resource://gre/modules/devtools/deprecated-sync-thenables.js', {}).Promise;
 
 // When we've solved the debugger/sdk/promise/gcli/helpers/overlap problem then
 // we should use this instead:
 // module.exports = exports = require('resource://gre/modules/Promise.jsm').Promise;
--- a/toolkit/devtools/server/actors/childtab.js
+++ b/toolkit/devtools/server/actors/childtab.js
@@ -17,27 +17,28 @@
  *
  * @param connection DebuggerServerConnection
  *        The conection to the client.
  * @param chromeGlobal
  *        The content script global holding |content| and |docShell| properties for a tab.
  */
 function ContentActor(connection, chromeGlobal)
 {
+  this._chromeGlobal = chromeGlobal;
   TabActor.call(this, connection, chromeGlobal);
   this.traits.reconfigure = false;
 }
 
 ContentActor.prototype = Object.create(TabActor.prototype);
 
 ContentActor.prototype.constructor = ContentActor;
 
 Object.defineProperty(ContentActor.prototype, "docShell", {
   get: function() {
-    return this.chromeEventHandler.docShell;
+    return this._chromeGlobal.docShell;
   },
   enumerable: true,
   configurable: false
 });
 
 ContentActor.prototype.exit = function() {
   TabActor.prototype.exit.call(this);
 };
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -771,74 +771,16 @@ let traversalMethod = {
     whatToShow: Option(1)
   },
   response: {
     node: RetVal("nullable:domnode")
   }
 }
 
 /**
- * We need to know when a document is navigating away so that we can kill
- * the nodes underneath it.  We also need to know when a document is
- * navigated to so that we can send a mutation event for the iframe node.
- *
- * The nsIWebProgressListener is the easiest/best way to watch these
- * loads that works correctly with the bfcache.
- *
- * See nsIWebProgressListener for details
- * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIWebProgressListener
- */
-var ProgressListener = Class({
-  extends: Unknown,
-  interfaces: ["nsIWebProgressListener", "nsISupportsWeakReference"],
-
-  initialize: function(tabActor) {
-    Unknown.prototype.initialize.call(this);
-    this.webProgress = tabActor.webProgress;
-    this.webProgress.addProgressListener(
-      this, Ci.nsIWebProgress.NOTIFY_ALL
-    );
-  },
-
-  destroy: function() {
-    try {
-      this.webProgress.removeProgressListener(this);
-    } catch(ex) {
-      // This can throw during browser shutdown.
-    }
-    this.webProgress = null;
-  },
-
-  onStateChange: makeInfallible(function stateChange(progress, request, flags, status) {
-    if (!this.webProgress) {
-      console.warn("got an onStateChange after destruction");
-      return;
-    }
-
-    let isWindow = flags & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
-    let isDocument = flags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
-    if (!(isWindow || isDocument)) {
-      return;
-    }
-
-    if (isDocument && (flags & Ci.nsIWebProgressListener.STATE_START)) {
-      events.emit(this, "windowchange-start", progress.DOMWindow);
-    }
-    if (isWindow && (flags & Ci.nsIWebProgressListener.STATE_STOP)) {
-      events.emit(this, "windowchange-stop", progress.DOMWindow);
-    }
-  }),
-
-  onProgressChange: function() {},
-  onSecurityChange: function() {},
-  onStatusChange: function() {},
-  onLocationChange: function() {},
-});
-
-/**
  * Server side of the DOM walker.
  */
 var WalkerActor = protocol.ActorClass({
   typeName: "domwalker",
 
   events: {
     "new-mutations" : {
       type: "newMutations"
@@ -884,20 +826,18 @@ var WalkerActor = protocol.ActorClass({
     // even when it is orphaned with the `retainNode` method.  This
     // list contains orphaned nodes that were so retained.
     this._retainedOrphans = new Set();
 
     this.onMutations = this.onMutations.bind(this);
     this.onFrameLoad = this.onFrameLoad.bind(this);
     this.onFrameUnload = this.onFrameUnload.bind(this);
 
-    this.progressListener = ProgressListener(tabActor);
-
-    events.on(this.progressListener, "windowchange-start", this.onFrameUnload);
-    events.on(this.progressListener, "windowchange-stop", this.onFrameLoad);
+    events.on(tabActor, "will-navigate", this.onFrameUnload);
+    events.on(tabActor, "navigate", this.onFrameLoad);
 
     // Ensure that the root document node actor is ready and
     // managed.
     this.rootNode = this.document();
   },
 
   // Returns the JSON representation of this object over the wire.
   form: function() {
@@ -910,17 +850,16 @@ var WalkerActor = protocol.ActorClass({
   toString: function() {
     return "[WalkerActor " + this.actorID + "]";
   },
 
   destroy: function() {
     this._hoveredNode = null;
     this.clearPseudoClassLocks();
     this._activePseudoClassLocks = null;
-    this.progressListener.destroy();
     this.rootDoc = null;
     events.emit(this, "destroyed");
     protocol.Actor.prototype.destroy.call(this);
   },
 
   release: method(function() {}, { release: true }),
 
   unmanage: function(actor) {
@@ -1961,27 +1900,26 @@ var WalkerActor = protocol.ActorClass({
         mutation.numChildren = change.target.childNodes.length;
         mutation.removed = removedActors;
         mutation.added = addedActors;
       }
       this.queueMutation(mutation);
     }
   },
 
-  onFrameLoad: function(window) {
-    let frame = this.layoutHelpers.getFrameElement(window);
-    let isTopLevel = this.layoutHelpers.isTopLevelWindow(window);
-    if (!frame && !this.rootDoc && isTopLevel) {
+  onFrameLoad: function({ window, isTopLevel }) {
+    if (!this.rootDoc && isTopLevel) {
       this.rootDoc = window.document;
       this.rootNode = this.document();
       this.queueMutation({
         type: "newRoot",
         target: this.rootNode.form()
       });
     }
+    let frame = this.layoutHelpers.getFrameElement(window);
     let frameActor = this._refMap.get(frame);
     if (!frameActor) {
       return;
     }
 
     this.queueMutation({
       type: "frameLoad",
       target: frameActor.actorID,
@@ -2003,17 +1941,17 @@ var WalkerActor = protocol.ActorClass({
       if (win === window) {
         return true;
       }
       win = this.layoutHelpers.getFrameElement(win);
     }
     return false;
   },
 
-  onFrameUnload: function(window) {
+  onFrameUnload: function({ window }) {
     // Any retained orphans that belong to this document
     // or its children need to be released, and a mutation sent
     // to notify of that.
     let releasedOrphans = [];
 
     for (let retained of this._retainedOrphans) {
       if (Cu.isDeadWrapper(retained.rawNode) ||
           this._childOfWindow(window, retained.rawNode)) {
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -5,16 +5,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 let {Cu} = require("chrome");
 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 
+// Assumptions on events module:
+// events needs to be dispatched synchronously,
+// by calling the listeners in the order or registration.
+XPCOMUtils.defineLazyGetter(this, "events", () => {
+  return devtools.require("sdk/event/core");
+});
+
 /**
  * Browser-specific actors.
  */
 
 /**
  * Yield all windows of type |aWindowType|, from the oldest window to the
  * youngest, using nsIWindowMediator::getEnumerator. We're usually
  * interested in "navigator:browser" windows.
@@ -480,36 +487,34 @@ BrowserTabList.prototype.onCloseWindow =
  * ContentActor. Subclasses are expected to implement a getter
  * the docShell properties.
  *
  * @param aConnection DebuggerServerConnection
  *        The conection to the client.
  * @param aChromeEventHandler
  *        An object on which listen for DOMWindowCreated and pageshow events.
  */
-function TabActor(aConnection, aChromeEventHandler)
+function TabActor(aConnection)
 {
   this.conn = aConnection;
-  this._chromeEventHandler = aChromeEventHandler;
   this._tabActorPool = null;
   // A map of actor names to actor instances provided by extensions.
   this._extraActors = {};
-
-  this._onWindowCreated = this.onWindowCreated.bind(this);
+  this._exited = false;
 
   this.traits = { reconfigure: true };
 }
 
 // XXX (bug 710213): TabActor attach/detach/exit/disconnect is a
 // *complete* mess, needs to be rethought asap.
 
 TabActor.prototype = {
   traits: null,
 
-  get exited() { return !this._chromeEventHandler; },
+  get exited() { return this._exited; },
   get attached() { return !!this._attached; },
 
   _tabPool: null,
   get tabActorPool() { return this._tabPool; },
 
   _contextPool: null,
   get contextActorPool() { return this._contextPool; },
 
@@ -517,17 +522,20 @@ TabActor.prototype = {
 
   // A constant prefix that will be used to form the actor ID by the server.
   actorPrefix: "tab",
 
   /**
    * An object on which listen for DOMWindowCreated and pageshow events.
    */
   get chromeEventHandler() {
-    return this._chromeEventHandler;
+    // TODO: bug 992778, fix docShell.chromeEventHandler in child processes
+    return this.docShell.chromeEventHandler ||
+           this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIContentFrameMessageManager);
   },
 
   /**
    * Getter for the nsIMessageManager associated to the tab.
    */
   get messageManager() {
     return this._chromeEventHandler;
   },
@@ -626,33 +634,33 @@ TabActor.prototype = {
   },
 
   /**
    * Called when the actor is removed from the connection.
    */
   disconnect: function BTA_disconnect() {
     this._detach();
     this._extraActors = null;
-    this._chromeEventHandler = null;
+    this._exited = true;
   },
 
   /**
    * Called by the root actor when the underlying tab is closed.
    */
   exit: function BTA_exit() {
     if (this.exited) {
       return;
     }
 
     if (this._detach()) {
       this.conn.send({ from: this.actorID,
                        type: "tabDetached" });
     }
 
-    this._chromeEventHandler = null;
+    this._exited = true;
   },
 
   /* Support for DebuggerServer.addTabActor. */
   _createExtraActors: CommonCreateExtraActors,
   _appendExtraActors: CommonAppendExtraActors,
 
   /**
    * Does the actual work of attching to a tab.
@@ -665,20 +673,18 @@ TabActor.prototype = {
     // Create a pool for tab-lifetime actors.
     dbg_assert(!this._tabPool, "Shouldn't have a tab pool if we weren't attached.");
     this._tabPool = new ActorPool(this.conn);
     this.conn.addActorPool(this._tabPool);
 
     // ... and a pool for context-lifetime actors.
     this._pushContext();
 
-    // Watch for globals being created in this tab.
-    this.chromeEventHandler.addEventListener("DOMWindowCreated", this._onWindowCreated, true);
-    this.chromeEventHandler.addEventListener("pageshow", this._onWindowCreated, true);
     this._progressListener = new DebuggerProgressListener(this);
+    this._progressListener.watch(this.docShell);
 
     this._attached = true;
   },
 
   /**
    * Creates a thread actor and a pool for context-lifetime actors. It then sets
    * up the content window for debugging.
    */
@@ -710,20 +716,22 @@ TabActor.prototype = {
    *
    * @returns false if the tab wasn't attached or true of detahing succeeds.
    */
   _detach: function BTA_detach() {
     if (!this.attached) {
       return false;
     }
 
-    this._progressListener.destroy();
-
-    this.chromeEventHandler.removeEventListener("DOMWindowCreated", this._onWindowCreated, true);
-    this.chromeEventHandler.removeEventListener("pageshow", this._onWindowCreated, true);
+    // Check for docShell availability, as it can be already gone
+    // during Firefox shutdown.
+    if (this.docShell) {
+      this._progressListener.unwatch(this.docShell);
+    }
+    this._progressListener = null;
 
     this._popContext();
 
     // Shut down actors that belong to this tab's pool.
     this.conn.removeActorPool(this._tabPool);
     this._tabPool = null;
     if (this._tabActorPool) {
       this.conn.removeActorPool(this._tabActorPool);
@@ -904,38 +912,126 @@ TabActor.prototype = {
     }
   },
 
   /**
    * Handle location changes, by clearing the previous debuggees and enabling
    * debugging, which may have been disabled temporarily by the
    * DebuggerProgressListener.
    */
-  onWindowCreated:
-  DevToolsUtils.makeInfallible(function BTA_onWindowCreated(evt) {
-    // pageshow events for non-persisted pages have already been handled by a
-    // prior DOMWindowCreated event.
-    if (!this._attached || (evt.type == "pageshow" && !evt.persisted)) {
-      return;
-    }
-    if (evt.target === this.contentDocument) {
-      this.threadActor.clearDebuggees();
-      if (this.threadActor.dbg) {
-        this.threadActor.dbg.enabled = true;
-        this.threadActor.global = evt.target.defaultView;
-        this.threadActor.maybePauseOnExceptions();
+  _windowReady: function (window) {
+    let isTopLevel = window == this.window;
+    dumpn("window-ready: " + window.location + " isTopLevel:" + isTopLevel);
+
+    events.emit(this, "window-ready", {
+      window: window,
+      isTopLevel: isTopLevel
+    });
+
+    // TODO bug 997119: move that code to ThreadActor by listening to window-ready
+    let threadActor = this.threadActor;
+    if (isTopLevel) {
+      threadActor.clearDebuggees();
+      if (threadActor.dbg) {
+        threadActor.dbg.enabled = true;
+        threadActor.global = window;
+        threadActor.maybePauseOnExceptions();
       }
     }
 
     // Refresh the debuggee list when a new window object appears (top window or
     // iframe).
-    if (this.threadActor.attached) {
-      this.threadActor.findGlobals();
+    if (threadActor.attached) {
+      threadActor.findGlobals();
+    }
+  },
+
+  /**
+   * Start notifying server codebase and client about a new document
+   * being loaded in the currently targeted context.
+   */
+  _willNavigate: function (window, newURI, request) {
+    let isTopLevel = window == this.window;
+
+    // will-navigate event needs to be dispatched synchronously,
+    // by calling the listeners in the order or registration.
+    // This event fires once navigation starts,
+    // (all pending user prompts are dealt with),
+    // but before the first request starts.
+    events.emit(this, "will-navigate", {
+      window: window,
+      isTopLevel: isTopLevel,
+      newURI: newURI,
+      request: request
+    });
+
+
+    // We don't do anything for inner frames in TabActor.
+    // (we will only update thread actor on window-ready)
+    if (!isTopLevel) {
+      return;
+    }
+
+    // Proceed normally only if the debuggee is not paused.
+    // TODO bug 997119: move that code to ThreadActor by listening to will-navigate
+    let threadActor = this.threadActor;
+    if (request && threadActor.state == "paused") {
+      request.suspend();
+      threadActor.onResume();
+      threadActor.dbg.enabled = false;
+      this._pendingNavigation = request;
     }
-  }, "TabActor.prototype.onWindowCreated"),
+    threadActor.disableAllBreakpoints();
+
+    this.conn.send({
+      from: this.actorID,
+      type: "tabNavigated",
+      url: newURI,
+      nativeConsoleAPI: true,
+      state: "start"
+    });
+  },
+
+  /**
+   * Notify server and client about a new document done loading in the current
+   * targeted context.
+   */
+  _navigate: function (window) {
+    let isTopLevel = window == this.window;
+
+    // navigate event needs to be dispatched synchronously,
+    // by calling the listeners in the order or registration.
+    // This event is fired once the document is loaded,
+    // after the load event, it's document ready-state is 'complete'.
+    events.emit(this, "navigate", {
+      window: window,
+      isTopLevel: isTopLevel
+    });
+
+    // We don't do anything for inner frames in TabActor.
+    // (we will only update thread actor on window-ready)
+    if (!isTopLevel) {
+      return;
+    }
+
+    // TODO bug 997119: move that code to ThreadActor by listening to navigate
+    let threadActor = this.threadActor;
+    if (threadActor.state == "running") {
+      threadActor.dbg.enabled = true;
+    }
+
+    this.conn.send({
+      from: this.actorID,
+      type: "tabNavigated",
+      url: this.url,
+      title: this.title,
+      nativeConsoleAPI: this.hasNativeConsoleAPI(this.window),
+      state: "stop"
+    });
+  },
 
   /**
    * Tells if the window.console object is native or overwritten by script in
    * the page.
    *
    * @param nsIDOMWindow aWindow
    *        The window object you want to check.
    * @return boolean
@@ -994,22 +1090,25 @@ Object.defineProperty(BrowserTabActor.pr
     return this._browser.messageManager;
   },
   enumerable: true,
   configurable: false
 });
 
 Object.defineProperty(BrowserTabActor.prototype, "title", {
   get: function() {
-    let title = this.contentDocument.contentTitle;
+    let title = this.contentDocument.title || this._browser.contentTitle;
     // If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a
     // tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label
     // as the title.
     if (!title && this._tabbrowser) {
-      title = this._tabbrowser._getTabForContentWindow(this.window).label;
+      let tab = this._tabbrowser._getTabForContentWindow(this.window);
+      if (tab) {
+        title = tab.label;
+      }
     }
     return title;
   },
   enumerable: true,
   configurable: false
 });
 
 Object.defineProperty(BrowserTabActor.prototype, "browser", {
@@ -1249,87 +1348,91 @@ BrowserAddonActor.prototype.requestTypes
  * navigate away from a paused page, the listener makes sure that the debuggee
  * is resumed before the navigation begins.
  *
  * @param TabActor aTabActor
  *        The tab actor associated with this listener.
  */
 function DebuggerProgressListener(aTabActor) {
   this._tabActor = aTabActor;
-  this._tabActor.webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_ALL);
-  let EventEmitter = devtools.require("devtools/toolkit/event-emitter");
-  EventEmitter.decorate(this);
+  this._onWindowCreated = this.onWindowCreated.bind(this);
 }
 
 DebuggerProgressListener.prototype = {
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIWebProgressListener,
     Ci.nsISupportsWeakReference,
     Ci.nsISupports,
   ]),
 
-  onStateChange:
-  DevToolsUtils.makeInfallible(function DPL_onStateChange(aProgress, aRequest, aFlag, aStatus) {
-    let isStart = aFlag & Ci.nsIWebProgressListener.STATE_START;
-    let isStop = aFlag & Ci.nsIWebProgressListener.STATE_STOP;
-    let isDocument = aFlag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
-    let isNetwork = aFlag & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
-    let isRequest = aFlag & Ci.nsIWebProgressListener.STATE_IS_REQUEST;
-    let isWindow = aFlag & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
+  watch: function DPL_watch(docShell) {
+    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebProgress);
+    webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATUS |
+                                          Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
+                                          Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+
+    // TODO: fix docShell.chromeEventHandler in child processes!
+    let chromeEventHandler = docShell.chromeEventHandler ||
+                             docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                                     .getInterface(Ci.nsIContentFrameMessageManager);
+
+    // Watch for globals being created in this docshell tree.
+    chromeEventHandler.addEventListener("DOMWindowCreated",
+                                        this._onWindowCreated, true);
+    chromeEventHandler.addEventListener("pageshow",
+                                        this._onWindowCreated, true);
+  },
 
-    // Skip non-interesting states.
-    if (!isWindow || !isNetwork ||
-        aProgress.DOMWindow != this._tabActor.window) {
+  unwatch: function DPL_unwatch(docShell) {
+    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebProgress);
+    webProgress.removeProgressListener(this);
+
+    // TODO: fix docShell.chromeEventHandler in child processes!
+    let chromeEventHandler = docShell.chromeEventHandler ||
+                             docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                                     .getInterface(Ci.nsIContentFrameMessageManager);
+    chromeEventHandler.removeEventListener("DOMWindowCreated",
+                                           this._onWindowCreated, true);
+    chromeEventHandler.removeEventListener("pageshow",
+                                           this._onWindowCreated, true);
+  },
+
+  onWindowCreated:
+  DevToolsUtils.makeInfallible(function DPL_onWindowCreated(evt) {
+    // Ignore any event if the tab actor isn't attached.
+    if (!this._tabActor.attached) {
       return;
     }
 
-    if (isStart && aRequest instanceof Ci.nsIChannel) {
-      // Proceed normally only if the debuggee is not paused.
-      if (this._tabActor.threadActor.state == "paused") {
-        aRequest.suspend();
-        this._tabActor.threadActor.onResume();
-        this._tabActor.threadActor.dbg.enabled = false;
-        this._tabActor._pendingNavigation = aRequest;
-      }
-
-      let packet = {
-        from: this._tabActor.actorID,
-        type: "tabNavigated",
-        url: aRequest.URI.spec,
-        nativeConsoleAPI: true,
-        state: "start"
-      };
-      this._tabActor.threadActor.disableAllBreakpoints();
-      this._tabActor.conn.send(packet);
-      this.emit("will-navigate", packet);
-    } else if (isStop) {
-      if (this._tabActor.threadActor.state == "running") {
-        this._tabActor.threadActor.dbg.enabled = true;
-      }
-
-      let window = this._tabActor.window;
-      let packet = {
-        from: this._tabActor.actorID,
-        type: "tabNavigated",
-        url: this._tabActor.url,
-        title: this._tabActor.title,
-        nativeConsoleAPI: this._tabActor.hasNativeConsoleAPI(window),
-        state: "stop"
-      };
-      this._tabActor.conn.send(packet);
-      this.emit("navigate", packet);
-    }
-  }, "DebuggerProgressListener.prototype.onStateChange"),
-
-  /**
-   * Destroy the progress listener instance.
-   */
-  destroy: function DPL_destroy() {
-    try {
-      this._tabActor.webProgress.removeProgressListener(this);
-    } catch (ex) {
-      // This can throw during browser shutdown.
+    // pageshow events for non-persisted pages have already been handled by a
+    // prior DOMWindowCreated event.
+    if (evt.type == "pageshow" && !evt.persisted) {
+      return;
     }
 
-    this._tabActor._progressListener = null;
-    this._tabActor = null;
-  }
+    let window = evt.target.defaultView;
+    this._tabActor._windowReady(window);
+  }, "DebuggerProgressListener.prototype.onWindowCreated"),
+
+  onStateChange:
+  DevToolsUtils.makeInfallible(function DPL_onStateChange(aProgress, aRequest, aFlag, aStatus) {
+    // Ignore any event if the tab actor isn't attached.
+    if (!this._tabActor.attached) {
+      return;
+    }
+
+    let isStart = aFlag & Ci.nsIWebProgressListener.STATE_START;
+    let isStop = aFlag & Ci.nsIWebProgressListener.STATE_STOP;
+    let isDocument = aFlag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
+    let isWindow = aFlag & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
+
+    let window = aProgress.DOMWindow;
+    if (isDocument && isStart) {
+      let newURI = aRequest instanceof Ci.nsIChannel ? aRequest.URI.spec : null;
+      this._tabActor._willNavigate(window, newURI, aRequest);
+    }
+    if (isWindow && isStop) {
+      this._tabActor._navigate(window);
+    }
+  }, "DebuggerProgressListener.prototype.onStateChange")
 };
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -232,18 +232,18 @@ WebConsoleActor.prototype =
   _evalWindow: null,
   get evalWindow() {
     return this._evalWindow || this.window;
   },
 
   set evalWindow(aWindow) {
     this._evalWindow = aWindow;
 
-    if (!this._progressListenerActive && this.parentActor._progressListener) {
-      this.parentActor._progressListener.once("will-navigate", this._onWillNavigate);
+    if (!this._progressListenerActive) {
+      events.on(this.parentActor, "will-navigate", this._onWillNavigate);
       this._progressListenerActive = true;
     }
   },
 
   /**
    * Flag used to track if we are listening for events from the progress
    * listener of the tab actor. We use the progress listener to clear
    * this.evalWindow on page navigation.
@@ -1369,20 +1369,23 @@ WebConsoleActor.prototype =
         break;
     }
   },
 
   /**
    * The "will-navigate" progress listener. This is used to clear the current
    * eval scope.
    */
-  _onWillNavigate: function WCA__onWillNavigate()
+  _onWillNavigate: function WCA__onWillNavigate({ window, isTopLevel })
   {
-    this._evalWindow = null;
-    this._progressListenerActive = false;
+    if (isTopLevel) {
+      this._evalWindow = null;
+      events.off(this.parentActor, "will-navigate", this._onWillNavigate);
+      this._progressListenerActive = false;
+    }
   },
 };
 
 WebConsoleActor.prototype.requestTypes =
 {
   startListeners: WebConsoleActor.prototype.onStartListeners,
   stopListeners: WebConsoleActor.prototype.onStopListeners,
   getCachedMessages: WebConsoleActor.prototype.onGetCachedMessages,
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -42,17 +42,17 @@ Object.defineProperty(this, "Components"
 
 const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties";
 
 const nsFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
 Cu.import("resource://gre/modules/reflect.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 dumpn.wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 
-Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
+Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js");
 
 function loadSubScript(aURL)
 {
   try {
     let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
       .getService(Ci.mozIJSSubScriptLoader);
     loader.loadSubScript(aURL, this);
   } catch(e) {
@@ -61,21 +61,20 @@ function loadSubScript(aURL)
                    e + " - " + e.stack + "\n";
     dump(errorStr);
     Cu.reportError(errorStr);
     throw e;
   }
 }
 
 let events = require("sdk/event/core");
-let {defer, resolve, reject, promised, all} = require("sdk/core/promise");
+let {defer, resolve, reject, all} = require("devtools/toolkit/deprecated-sync-thenables");
 this.defer = defer;
 this.resolve = resolve;
 this.reject = reject;
-this.promised = promised;
 this.all = all;
 
 Cu.import("resource://gre/modules/devtools/SourceMap.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
                                   "resource://gre/modules/devtools/Console.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "NetworkMonitorManager", () => {
--- a/toolkit/devtools/server/protocol.js
+++ b/toolkit/devtools/server/protocol.js
@@ -1,17 +1,17 @@
 /* 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";
 
 let {Cu} = require("chrome");
 let Services = require("Services");
-let promise = require("sdk/core/promise");
+let promise = require("devtools/toolkit/deprecated-sync-thenables");
 let {Class} = require("sdk/core/heritage");
 let {EventTarget} = require("sdk/event/target");
 let events = require("sdk/event/core");
 let object = require("sdk/util/object");
 
 // Waiting for promise.done() to be added, see bug 851321
 function promiseDone(err) {
   console.error(err);
--- a/toolkit/devtools/server/tests/browser/browser.ini
+++ b/toolkit/devtools/server/tests/browser/browser.ini
@@ -3,12 +3,15 @@ skip-if = e10s # Bug ?????? - devtools t
 subsuite = devtools
 support-files =
   head.js
   storage-dynamic-windows.html
   storage-listings.html
   storage-unsecured-iframe.html
   storage-updates.html
   storage-secured-iframe.html
+  navigate-first.html
+  navigate-second.html
 
 [browser_storage_dynamic_windows.js]
 [browser_storage_listings.js]
 [browser_storage_updates.js]
+[browser_navigateEvents.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/browser_navigateEvents.js
@@ -0,0 +1,169 @@
+
+let Cu = Components.utils;
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+
+const URL1 = MAIN_DOMAIN + "navigate-first.html";
+const URL2 = MAIN_DOMAIN + "navigate-second.html";
+
+let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
+let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+
+let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+let events = devtools.require("sdk/event/core");
+
+let client;
+
+// State machine to check events order
+let i = 0;
+function assertEvent(event, data) {
+  let x = 0;
+  switch(i++) {
+    case x++:
+      is(event, "request", "Get first page load");
+      is(data, URL1);
+      break;
+    case x++:
+      is(event, "load-new-document", "Ask to load the second page");
+      break;
+    case x++:
+      is(event, "unload-dialog", "We get the dialog on first page unload");
+      break;
+    case x++:
+      is(event, "will-navigate", "The very first event is will-navigate on server side");
+      is(data.newURI, URL2, "newURI property is correct");
+      break;
+    case x++:
+      is(event, "tabNavigated", "Right after will-navigate, the client receive tabNavigated");
+      is(data.state, "start", "state is start");
+      is(data.url, URL2, "url property is correct");
+      break;
+    case x++:
+      is(event, "request", "Given that locally, the Debugger protocol is sync, the request happens after tabNavigated");
+      is(data, URL2);
+      break;
+    case x++:
+      is(event, "DOMContentLoaded");
+      is(content.document.readyState, "interactive");
+      break;
+    case x++:
+      is(event, "load");
+      is(content.document.readyState, "complete");
+      break;
+    case x++:
+      is(event, "navigate", "Then once the second doc is loaded, we get the navigate event");
+      is(content.document.readyState, "complete", "navigate is emitted only once the document is fully loaded");
+      break;
+    case x++:
+      is(event, "tabNavigated", "Finally, the receive the client event");
+      is(data.state, "stop", "state is stop");
+      is(data.url, URL2, "url property is correct");
+
+      // End of test!
+      cleanup();
+      break;
+  }
+}
+
+function waitForOnBeforeUnloadDialog(browser, callback) {
+  browser.addEventListener("DOMWillOpenModalDialog", function onModalDialog() {
+    browser.removeEventListener("DOMWillOpenModalDialog", onModalDialog, true);
+
+    executeSoon(() => {
+      let stack = browser.parentNode;
+      let dialogs = stack.getElementsByTagName("tabmodalprompt");
+      let {button0, button1} = dialogs[0].ui;
+      callback(button0, button1);
+    });
+  }, true);
+}
+
+let httpObserver = function (subject, topic, state) {
+  let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+  let url = channel.URI.spec;
+  // Only listen for our document request, as many other requests can happen
+  if (url == URL1 || url == URL2) {
+    assertEvent("request", url);
+  }
+};
+Services.obs.addObserver(httpObserver, "http-on-modify-request", false);
+
+function onDOMContentLoaded() {
+  assertEvent("DOMContentLoaded");
+}
+function onLoad() {
+  assertEvent("load");
+}
+
+function getServerTabActor(callback) {
+  // Ensure having a minimal server
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init(function () { return true; });
+    DebuggerServer.addBrowserActors();
+  }
+
+  // Connect to this tab
+  let transport = DebuggerServer.connectPipe();
+  client = new DebuggerClient(transport);
+  client.connect(function onConnect() {
+    client.listTabs(function onListTabs(aResponse) {
+      // Fetch the BrowserTabActor for this tab
+      let actorID = aResponse.tabs[aResponse.selected].actor;
+      client.attachTab(actorID, function(aResponse, aTabClient) {
+        // !Hack! Retrieve a server side object, the BrowserTabActor instance
+        let conn = transport._serverConnection;
+        let tabActor = conn.getActor(actorID);
+        callback(tabActor);
+      });
+    });
+  });
+
+  client.addListener("tabNavigated", function (aEvent, aPacket) {
+    assertEvent("tabNavigated", aPacket);
+  });
+}
+
+function test() {
+  waitForExplicitFinish();
+
+  // Open a test tab
+  addTab(URL1, function(doc) {
+    getServerTabActor(function (tabActor) {
+      // In order to listen to internal will-navigate/navigate events
+      events.on(tabActor, "will-navigate", function (data) {
+        assertEvent("will-navigate", data);
+      });
+      events.on(tabActor, "navigate", function (data) {
+        assertEvent("navigate", data);
+      });
+
+      // Start listening for page load events
+      let browser = gBrowser.selectedTab.linkedBrowser;
+      browser.addEventListener("DOMContentLoaded", onDOMContentLoaded, true);
+      browser.addEventListener("load", onLoad, true);
+
+      // Listen for alert() call being made in navigate-first during unload
+      waitForOnBeforeUnloadDialog(browser, function (btnLeave, btnStay) {
+        assertEvent("unload-dialog");
+        // accept to quit this page to another
+        btnLeave.click();
+      });
+
+      // Load another document in this doc to dispatch these events
+      assertEvent("load-new-document");
+      content.location = URL2;
+    });
+
+  });
+}
+
+function cleanup() {
+  let browser = gBrowser.selectedTab.linkedBrowser;
+  browser.removeEventListener("DOMContentLoaded", onDOMContentLoaded);
+  browser.removeEventListener("load", onLoad);
+  client.close(function () {
+    Services.obs.addObserver(httpObserver, "http-on-modify-request", false);
+    DebuggerServer.destroy();
+    finish();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/navigate-first.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+</head>
+<body>
+First
+<script>
+
+window.onbeforeunload=function(e){
+  e.returnValue="?";
+};
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/navigate-second.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+</head>
+<body>
+Second
+</body>
+</html>
--- a/toolkit/devtools/server/tests/mochitest/inspector-helpers.js
+++ b/toolkit/devtools/server/tests/mochitest/inspector-helpers.js
@@ -56,22 +56,24 @@ function attachURL(url, callback) {
   window.addEventListener("message", function loadListener(event) {
     if (event.data === "ready") {
       client = new DebuggerClient(DebuggerServer.connectPipe());
       client.connect((applicationType, traits) => {
         client.listTabs(response => {
           for (let tab of response.tabs) {
             if (tab.url === url) {
               window.removeEventListener("message", loadListener, false);
-              try {
-                callback(null, client, tab, win.document);
-              } catch(ex) {
-                Cu.reportError(ex);
-                dump(ex);
-              }
+              client.attachTab(tab.actor, function(aResponse, aTabClient) {
+                try {
+                  callback(null, client, tab, win.document);
+                } catch(ex) {
+                  Cu.reportError(ex);
+                  dump(ex);
+                }
+              });
               break;
             }
           }
         });
       });
     }
   }, false);
 
--- a/toolkit/devtools/tests/mochitest/test_eventemitter_basic.html
+++ b/toolkit/devtools/tests/mochitest/test_eventemitter_basic.html
@@ -18,17 +18,17 @@
 
   <body>
 
     <script type="application/javascript;version=1.8">
       "use strict";
 
       const { utils: Cu } = Components;
       const { Promise: promise } =
-        Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+        Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
       const { EventEmitter } =
         Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
       const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 
       SimpleTest.waitForExplicitFinish();
 
       testEmitter();
       testEmitter({});