Merge from mozilla-inbound.
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 07 Feb 2013 11:36:39 +0100
changeset 138188 1e739d9c31800b11d8a8b36cd99c062066b25200
parent 138187 10f51d74f9f35fb8fda2faed89c2664ba99c50a9 (current diff)
parent 131016 cb97c0155552b222453d328bb02f5cd8a2ab0a69 (diff)
child 138189 06ed972b5d4dfb755129bd228f88e7afd07bbbc8
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge from mozilla-inbound.
b2g/chrome/content/UAO_child.js
content/html/content/src/nsHTMLBRElement.cpp
content/html/content/src/nsHTMLMenuElement.cpp
content/html/content/src/nsHTMLMenuElement.h
content/html/content/src/nsHTMLStyleElement.cpp
content/media/webaudio/AudioEventTimeline.h
docshell/test/bug94514-postpage.html
docshell/test/test_bug94514.html
dom/battery/nsIDOMBatteryManager.idl
dom/interfaces/html/nsIUndoManagerTransaction.idl
dom/sms/interfaces/nsIRilSmsDatabaseService.idl
dom/sms/interfaces/nsISmsDatabaseService.idl
dom/sms/src/android/SmsDatabaseService.cpp
dom/sms/src/android/SmsDatabaseService.h
dom/sms/src/fallback/SmsDatabaseService.cpp
dom/sms/src/fallback/SmsDatabaseService.h
dom/sms/src/ril/SmsDatabaseService.js
dom/sms/src/ril/SmsDatabaseService.manifest
dom/telephony/Voicemail.cpp
dom/telephony/Voicemail.h
dom/telephony/VoicemailEvent.cpp
dom/telephony/VoicemailEvent.h
dom/telephony/nsIDOMVoicemail.idl
dom/telephony/nsIDOMVoicemailEvent.idl
dom/telephony/nsIDOMVoicemailStatus.idl
dom/telephony/test/marionette/pdu_builder.js
dom/telephony/test/marionette/test_voicemail_number.js
dom/telephony/test/marionette/test_voicemail_statuschanged.js
dom/telephony/test/marionette/test_voicemail_statuschanged.py
gfx/cairo/libpixman/src/pixman-srgb.c
js/src/Makefile.in
js/src/gc/RootMarking.cpp
js/src/ion/BaselineJIT.cpp
js/src/ion/Ion.cpp
js/src/ion/IonBuilder.cpp
js/src/ion/IonCompartment.h
js/src/ion/VMFunctions.cpp
js/src/ion/shared/Assembler-shared.h
js/src/ion/shared/Assembler-x86-shared.h
js/src/ion/shared/MacroAssembler-x86-shared.h
js/src/jsapi.h
js/src/jscompartment.cpp
js/src/jsfriendapi.h
js/src/jsfun.cpp
js/src/jsinfer.cpp
js/src/jsinterp.cpp
js/src/jsopcode.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsval.h
js/src/shell/js.cpp
js/src/vm/ArgumentsObject.cpp
js/src/vm/Debugger.cpp
js/src/vm/ObjectImpl-inl.h
js/src/vm/ObjectImpl.h
js/src/vm/ScopeObject.cpp
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
layout/xul/base/src/grid/Makefile.in
layout/xul/base/src/grid/crashtests/306911-crash.xul
layout/xul/base/src/grid/crashtests/306911-grid-testcases.xul
layout/xul/base/src/grid/crashtests/306911-grid-testcases2.xul
layout/xul/base/src/grid/crashtests/311710-1.xul
layout/xul/base/src/grid/crashtests/312784-1.xul
layout/xul/base/src/grid/crashtests/313173-1-inner.xul
layout/xul/base/src/grid/crashtests/313173-1.html
layout/xul/base/src/grid/crashtests/321066-1.xul
layout/xul/base/src/grid/crashtests/321073-1.xul
layout/xul/base/src/grid/crashtests/382750-1.xul
layout/xul/base/src/grid/crashtests/400790-1.xul
layout/xul/base/src/grid/crashtests/423802-crash.xul
layout/xul/base/src/grid/crashtests/crashtests.list
layout/xul/base/src/grid/examples/borderedcolumns.xul
layout/xul/base/src/grid/examples/borderedrowscolumns.xul
layout/xul/base/src/grid/examples/borderedrowscolumns2.xul
layout/xul/base/src/grid/examples/borderedrowscolumns3.xul
layout/xul/base/src/grid/examples/bordermargincolumns1.xul
layout/xul/base/src/grid/examples/collapsetest.xul
layout/xul/base/src/grid/examples/divcolumngrid.xul
layout/xul/base/src/grid/examples/divrowgrid.xul
layout/xul/base/src/grid/examples/dynamicgrid.xul
layout/xul/base/src/grid/examples/flexgroupgrid.xul
layout/xul/base/src/grid/examples/javascriptappend.xul
layout/xul/base/src/grid/examples/jumpygrid.xul
layout/xul/base/src/grid/examples/nestedrows.xul
layout/xul/base/src/grid/examples/rowspan.xul
layout/xul/base/src/grid/examples/scrollingcolumns.xul
layout/xul/base/src/grid/examples/scrollingrows.xul
layout/xul/base/src/grid/examples/splitter.xul
layout/xul/base/src/grid/nsGrid.cpp
layout/xul/base/src/grid/nsGrid.h
layout/xul/base/src/grid/nsGridCell.cpp
layout/xul/base/src/grid/nsGridCell.h
layout/xul/base/src/grid/nsGridLayout2.cpp
layout/xul/base/src/grid/nsGridLayout2.h
layout/xul/base/src/grid/nsGridRow.cpp
layout/xul/base/src/grid/nsGridRow.h
layout/xul/base/src/grid/nsGridRowGroupFrame.cpp
layout/xul/base/src/grid/nsGridRowGroupFrame.h
layout/xul/base/src/grid/nsGridRowGroupLayout.cpp
layout/xul/base/src/grid/nsGridRowGroupLayout.h
layout/xul/base/src/grid/nsGridRowLayout.cpp
layout/xul/base/src/grid/nsGridRowLayout.h
layout/xul/base/src/grid/nsGridRowLeafFrame.cpp
layout/xul/base/src/grid/nsGridRowLeafFrame.h
layout/xul/base/src/grid/nsGridRowLeafLayout.cpp
layout/xul/base/src/grid/nsGridRowLeafLayout.h
layout/xul/base/src/grid/nsIGridPart.h
layout/xul/base/src/grid/reftests/column-sizing-1-ref.xul
layout/xul/base/src/grid/reftests/column-sizing-1.xul
layout/xul/base/src/grid/reftests/not-full-basic-ref.xhtml
layout/xul/base/src/grid/reftests/not-full-basic.xul
layout/xul/base/src/grid/reftests/not-full-grid-pack-align.xul
layout/xul/base/src/grid/reftests/not-full-row-group-align-ref.xhtml
layout/xul/base/src/grid/reftests/not-full-row-group-align.xul
layout/xul/base/src/grid/reftests/not-full-row-group-direction-ref.xhtml
layout/xul/base/src/grid/reftests/not-full-row-group-direction.xul
layout/xul/base/src/grid/reftests/not-full-row-group-pack-ref.xhtml
layout/xul/base/src/grid/reftests/not-full-row-group-pack.xul
layout/xul/base/src/grid/reftests/not-full-row-leaf-align.xul
layout/xul/base/src/grid/reftests/not-full-row-leaf-direction.xul
layout/xul/base/src/grid/reftests/not-full-row-leaf-pack-ref.xhtml
layout/xul/base/src/grid/reftests/not-full-row-leaf-pack.xul
layout/xul/base/src/grid/reftests/reftest.list
layout/xul/base/src/grid/reftests/row-or-column-sizing-1.xul
layout/xul/base/src/grid/reftests/row-or-column-sizing-2.xul
layout/xul/base/src/grid/reftests/row-or-column-sizing-3.xul
layout/xul/base/src/grid/reftests/row-or-column-sizing-4.xul
layout/xul/base/src/grid/reftests/row-sizing-1-ref.xul
layout/xul/base/src/grid/reftests/row-sizing-1.xul
layout/xul/base/src/grid/reftests/scrollable-columns-ref.xhtml
layout/xul/base/src/grid/reftests/scrollable-columns.xul
layout/xul/base/src/grid/reftests/scrollable-rows-ref.xhtml
layout/xul/base/src/grid/reftests/scrollable-rows.xul
layout/xul/base/src/grid/reftests/sizing-2d-ref.xul
layout/xul/base/src/grid/reftests/sizing-2d.xul
layout/xul/base/src/grid/reftests/z-order-1-ref.xul
layout/xul/base/src/grid/reftests/z-order-1.xul
layout/xul/base/src/grid/reftests/z-order-2-ref.xul
layout/xul/base/src/grid/reftests/z-order-2.xul
layout/xul/base/src/tree/public/Makefile.in
layout/xul/base/src/tree/public/nsITreeBoxObject.idl
layout/xul/base/src/tree/public/nsITreeColumns.idl
layout/xul/base/src/tree/public/nsITreeContentView.idl
layout/xul/base/src/tree/public/nsITreeSelection.idl
layout/xul/base/src/tree/public/nsITreeView.idl
layout/xul/base/src/tree/src/Makefile.in
layout/xul/base/src/tree/src/crashtests/307298-1.xul
layout/xul/base/src/tree/src/crashtests/309732-1.xul
layout/xul/base/src/tree/src/crashtests/309732-2.xul
layout/xul/base/src/tree/src/crashtests/366583-1.xul
layout/xul/base/src/tree/src/crashtests/380217-1.xul
layout/xul/base/src/tree/src/crashtests/382444-1-inner.html
layout/xul/base/src/tree/src/crashtests/382444-1.html
layout/xul/base/src/tree/src/crashtests/391178-1.xhtml
layout/xul/base/src/tree/src/crashtests/391178-2.xul
layout/xul/base/src/tree/src/crashtests/393665-1.xul
layout/xul/base/src/tree/src/crashtests/399227-1.xul
layout/xul/base/src/tree/src/crashtests/399227-2.xul
layout/xul/base/src/tree/src/crashtests/399692-1.xhtml
layout/xul/base/src/tree/src/crashtests/399715-1.xhtml
layout/xul/base/src/tree/src/crashtests/409807-1.xul
layout/xul/base/src/tree/src/crashtests/414170-1.xul
layout/xul/base/src/tree/src/crashtests/430394-1.xul
layout/xul/base/src/tree/src/crashtests/454186-1.xul
layout/xul/base/src/tree/src/crashtests/479931-1.xhtml
layout/xul/base/src/tree/src/crashtests/509602-1-overlay.xul
layout/xul/base/src/tree/src/crashtests/509602-1.xul
layout/xul/base/src/tree/src/crashtests/601427.html
layout/xul/base/src/tree/src/crashtests/crashtests.list
layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
layout/xul/base/src/tree/src/nsTreeBodyFrame.h
layout/xul/base/src/tree/src/nsTreeBoxObject.cpp
layout/xul/base/src/tree/src/nsTreeBoxObject.h
layout/xul/base/src/tree/src/nsTreeColFrame.cpp
layout/xul/base/src/tree/src/nsTreeColFrame.h
layout/xul/base/src/tree/src/nsTreeColumns.cpp
layout/xul/base/src/tree/src/nsTreeColumns.h
layout/xul/base/src/tree/src/nsTreeContentView.cpp
layout/xul/base/src/tree/src/nsTreeContentView.h
layout/xul/base/src/tree/src/nsTreeImageListener.cpp
layout/xul/base/src/tree/src/nsTreeImageListener.h
layout/xul/base/src/tree/src/nsTreeSelection.cpp
layout/xul/base/src/tree/src/nsTreeSelection.h
layout/xul/base/src/tree/src/nsTreeStyleCache.cpp
layout/xul/base/src/tree/src/nsTreeStyleCache.h
layout/xul/base/src/tree/src/nsTreeUtils.cpp
layout/xul/base/src/tree/src/nsTreeUtils.h
mobile/android/chrome/content/sanitize.js
modules/libpref/src/init/all.js
python/virtualenv/virtualenv_support/distribute-0.6.28.tar.gz
python/virtualenv/virtualenv_support/setuptools-0.6c11-py2.4.egg
services/aitc/Aitc.js
services/aitc/AitcComponents.manifest
services/aitc/Makefile.in
services/aitc/modules/browserid.js
services/aitc/modules/client.js
services/aitc/modules/main.js
services/aitc/modules/manager.js
services/aitc/modules/storage.js
services/aitc/services-aitc.js
services/aitc/tests/Makefile.in
services/aitc/tests/mochitest/browser_id_simple.js
services/aitc/tests/mochitest/file_browser_id_mock.html
services/aitc/tests/mochitest/head.js
services/aitc/tests/unit/test_aitc_client.js
services/aitc/tests/unit/test_aitc_manager.js
services/aitc/tests/unit/test_load_modules.js
services/aitc/tests/unit/test_storage_queue.js
services/aitc/tests/unit/test_storage_registry.js
services/aitc/tests/unit/xpcshell.ini
services/common/modules-testing/aitcserver.js
services/common/tests/run_aitc_server.js
services/common/tests/unit/test_aitc_server.js
services/common/tests/unit/test_utils_json.js
testing/mozbase/README
testing/tools/profiles/createTestingProfile.py
toolkit/components/places/tests/bookmarks/test_onBeforeItemRemoved_observer.js
toolkit/components/places/tests/unit/test_onBeforeDeleteURI_observer.js
--- a/accessible/src/generic/Accessible.cpp
+++ b/accessible/src/generic/Accessible.cpp
@@ -3075,17 +3075,17 @@ Accessible::GetFirstAvailableAccessible(
   nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aStartNode->OwnerDoc());
   NS_ENSURE_TRUE(domDoc, nullptr);
 
   nsCOMPtr<nsIDOMNode> currentNode = do_QueryInterface(aStartNode);
   nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(GetNode());
   nsCOMPtr<nsIDOMTreeWalker> walker;
   domDoc->CreateTreeWalker(rootNode,
                            nsIDOMNodeFilter::SHOW_ELEMENT | nsIDOMNodeFilter::SHOW_TEXT,
-                           nullptr, false, getter_AddRefs(walker));
+                           nullptr, 1, getter_AddRefs(walker));
   NS_ENSURE_TRUE(walker, nullptr);
 
   walker->SetCurrentNode(currentNode);
   while (true) {
     walker->NextNode(getter_AddRefs(currentNode));
     if (!currentNode)
       return nullptr;
 
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -34,17 +34,17 @@ this.AccessFu = {
 
     this.prefsBranch = Cc['@mozilla.org/preferences-service;1']
       .getService(Ci.nsIPrefService).getBranch('accessibility.accessfu.');
     this.prefsBranch.addObserver('activate', this, false);
 
     try {
       Cc['@mozilla.org/android/bridge;1'].
         getService(Ci.nsIAndroidBridge).handleGeckoMessage(
-          JSON.stringify({ gecko: { type: 'Accessibility:Ready' } }));
+          JSON.stringify({ type: 'Accessibility:Ready' }));
       Services.obs.addObserver(this, 'Accessibility:Settings', false);
     } catch (x) {
       // Not on Android
       aWindow.addEventListener(
         'ContentStart',
         (function(event) {
            let content = aWindow.shell.contentBrowser.contentWindow;
            content.addEventListener('mozContentEvent', this, false, true);
@@ -343,17 +343,17 @@ var Output = {
   Android: function Android(aDetails, aBrowser) {
     if (!this._bridge)
       this._bridge = Cc['@mozilla.org/android/bridge;1'].getService(Ci.nsIAndroidBridge);
 
     for each (let androidEvent in aDetails) {
       androidEvent.type = 'Accessibility:Event';
       if (androidEvent.bounds)
         androidEvent.bounds = this._adjustBounds(androidEvent.bounds, aBrowser);
-      this._bridge.handleGeckoMessage(JSON.stringify({gecko: androidEvent}));
+      this._bridge.handleGeckoMessage(JSON.stringify(androidEvent));
     }
   },
 
   Haptic: function Haptic(aDetails, aBrowser) {
     this.chromeWin.navigator.vibrate(aDetails.pattern);
   },
 
   _adjustBounds: function(aJsonBounds, aBrowser) {
@@ -503,17 +503,17 @@ var Input = {
           else
             target.blur();
         }
 
         if (Utils.MozBuildApp == 'mobile/android')
           // Return focus to native Android browser chrome.
           Cc['@mozilla.org/android/bridge;1'].
             getService(Ci.nsIAndroidBridge).handleGeckoMessage(
-              JSON.stringify({ gecko: { type: 'ToggleChrome:Focus' } }));
+              JSON.stringify({ type: 'ToggleChrome:Focus' }));
         break;
       case aEvent.DOM_VK_RETURN:
       case aEvent.DOM_VK_ENTER:
         if (this.editState.editing)
           return;
         this.activateCurrent();
         break;
     default:
--- a/accessible/tests/mochitest/name/markup.js
+++ b/accessible/tests/mochitest/name/markup.js
@@ -266,17 +266,17 @@ function testNameForElmRule(aElm, aRule)
 
       mLocalName: tagname,
       mAttrName: attrname,
       mAttrValue: aElm.getAttribute("id")
     };
 
     var treeWalker = document.createTreeWalker(document.body,
                                                NodeFilter.SHOW_ELEMENT,
-                                               filter, false);
+                                               filter);
     labelElm = treeWalker.nextNode();
 
   } else {
     // if attrname is empty then look for the element in subtree.
     labelElm = aElm.getElementsByTagName(tagname)[0];
     if (!labelElm)
       labelElm = aElm.getElementsByTagName("html:" + tagname)[0];
   }
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -569,17 +569,17 @@ pref("hal.processPriorityManager.gonk.ma
 pref("hal.processPriorityManager.gonk.foregroundNice", 0);
 pref("hal.processPriorityManager.gonk.backgroundNice", 10);
 
 #ifndef DEBUG
 // Enable pre-launching content processes for improved startup time
 // (hiding latency).
 pref("dom.ipc.processPrelaunch.enabled", true);
 // Wait this long before pre-launching a new subprocess.
-pref("dom.ipc.processPrelaunch.delayMs", 1000);
+pref("dom.ipc.processPrelaunch.delayMs", 5000);
 #endif
 
 // Ignore the "dialog=1" feature in window.open.
 pref("dom.disable_window_open_dialog_feature", true);
 
 // Screen reader support
 pref("accessibility.accessfu.activate", 2);
 
@@ -640,8 +640,10 @@ pref("webgl.can-lose-context-in-foregrou
 // Allow nsMemoryInfoDumper to create a fifo in the temp directory.  We use
 // this fifo to trigger about:memory dumps, among other things.
 pref("memory_info_dumper.watch_fifo.enabled", true);
 pref("memory_info_dumper.watch_fifo.directory", "/data/local");
 
 // <input type='file'> implementation is not complete. We have to disable the
 // type to web content to help them do feature detection.
 pref("dom.disable_input_file", true);
+
+pref("general.useragent.enable_overrides", true);
deleted file mode 100644
--- a/b2g/chrome/content/UAO_child.js
+++ /dev/null
@@ -1,2 +0,0 @@
-Components.utils.import('resource://gre/modules/UserAgentOverrides.jsm');
-UserAgentOverrides.init();
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..826e5340843246732f7001c836f4a3ec37681c86
GIT binary patch
literal 3409
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..980e787310a9a1391715ba8d3b9bd9bfbc2b5b5c
GIT binary patch
literal 3382
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
M07*qoM6N<$f>WkX<^TWy
index 0bd1e60e6a3e2d23f43bfbba3e5c86afa71acf00..6daf7cf7188b00fd28c1c8c3a54673b262036a77
GIT binary patch
literal 3217
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
D&kXdV
index e377d321ce03505c9127296eab43b80d3b39f6df..c7837f8229c03de1fcd0c02ef3aa78573e0a69b8
GIT binary patch
literal 3042
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
index a8482eb4dd7ebaf168eda6a8ca82e77efeffb930..fd64f969724698d9694b4effceb3896aaeaa56ac
GIT binary patch
literal 3318
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
A?*IS*
index 49c60505f46523e7c3b0a0e5f052c7b450f26c78..b965b73d55b725a4b69c2a5620b761e7435f5ab1
GIT binary patch
literal 3967
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
Z9RQGDd}f+!o@W36002ovPDHLkV1m^HVb1^n
index 4dbb94f98f0acf7f1a3df73ff3fbf4eb9345f2bf..5de342bda53b604fe2413c8e769e7bbf14e70dc0
GIT binary patch
literal 3259
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
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -64,16 +64,22 @@ XPCOMUtils.defineLazyGetter(this, "ppmm"
 
 #ifdef MOZ_WIDGET_GONK
 XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
   Cu.import("resource://gre/modules/systemlibs.js");
   return libcutils;
 });
 #endif
 
+#ifdef MOZ_SERVICES_CAPTIVEDETECT
+XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector',
+                                  '@mozilla.org/services/captive-detector;1',
+                                  'nsICaptivePortalDetector');
+#endif
+
 function getContentWindow() {
   return shell.contentBrowser.contentWindow;
 }
 
 function debug(str) {
   dump(' -*- Shell.js: ' + str + '\n');
 }
 
@@ -279,16 +285,17 @@ var shell = {
     window.addEventListener('sizemodechange', this);
     this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
 
     CustomEventManager.init();
     WebappsHelper.init();
     AccessFu.attach(window);
     UserAgentOverrides.init();
     IndexedDBPromptHelper.init();
+    CaptivePortalLoginHelper.init();
 
     // XXX could factor out into a settings->pref map.  Not worth it yet.
     SettingsListener.observe("debug.fps.enabled", false, function(value) {
       Services.prefs.setBoolPref("layers.acceleration.draw-fps", value);
     });
     SettingsListener.observe("debug.paint-flashing.enabled", false, function(value) {
       Services.prefs.setBoolPref("nglayout.debug.paint_flashing", value);
     });
@@ -640,16 +647,19 @@ var CustomEventManager = {
         FormsHelper.handleEvent(detail);
         break;
       case 'system-message-listener-ready':
         Services.obs.notifyObservers(null, 'system-message-listener-ready', null);
         break;
       case 'remote-debugger-prompt':
         RemoteDebugger.handleEvent(detail);
         break;
+      case 'captive-portal-login-cancel':
+        CaptivePortalLoginHelper.handleEvent(detail);
+        break;
     }
   }
 }
 
 var AlertsHelper = {
   _listeners: {},
   _count: 0,
 
@@ -832,16 +842,17 @@ var WebappsHelper = {
       case "webapps-launch":
         DOMApplicationRegistry.getManifestFor(json.origin, function(aManifest) {
           if (!aManifest)
             return;
 
           let manifest = new ManifestHelper(aManifest, json.origin);
           shell.sendChromeEvent({
             "type": "webapps-launch",
+            "timestamp": json.timestamp,
             "url": manifest.fullLaunchPath(json.startPoint),
             "manifestURL": json.manifestURL
           });
         });
         break;
       case "webapps-ask-install":
         let id = this.registerInstaller(json);
         shell.sendChromeEvent({
@@ -989,16 +1000,29 @@ window.addEventListener('ContentStart', 
       let props = aSubject.QueryInterface(Ci.nsIPropertyBag2);
       if (props.hasKey("abnormal") && props.hasKey("dumpID")) {
         shell.reportCrash(false, props.getProperty("dumpID"));
       }
     },
     "ipc:content-shutdown", false);
 })();
 
+var CaptivePortalLoginHelper = {
+  init: function init() {
+    Services.obs.addObserver(this, 'captive-portal-login', false);
+    Services.obs.addObserver(this, 'captive-portal-login-abort', false);
+  },
+  handleEvent: function handleEvent(detail) {
+    Services.captivePortalDetector.cancelLogin(detail.id);
+  },
+  observe: function observe(subject, topic, data) {
+    shell.sendChromeEvent(JSON.parse(data));
+  }
+}
+
 // Listen for crashes submitted through the crash reporter UI.
 window.addEventListener('ContentStart', function cr_onContentStart() {
   let content = shell.contentBrowser.contentWindow;
   content.addEventListener("mozContentEvent", function cr_onMozContentEvent(e) {
     if (e.detail.type == "submit-crash" && e.detail.crashID) {
       shell.submitCrash(e.detail.crashID);
     }
   });
--- a/b2g/chrome/content/touchcontrols.css
+++ b/b2g/chrome/content/touchcontrols.css
@@ -22,46 +22,59 @@
   width: 100%;
 }
 
 .controlsSpacer {
   display: none;
   -moz-box-flex: 0;
 }
 
+.fullscreenButton,
 .playButton,
 .muteButton {
   -moz-appearance: none;
   min-height: 42px;
   min-width: 42px;
   border: none !important;
 }
 
+.fullscreenButton {
+  background: url("chrome://browser/content/images/fullscreen-hdpi.png") no-repeat center;
+}
+
+.fullscreenButton[fullscreened="true"] {
+  background: url("chrome://browser/content/images/exitfullscreen-hdpi.png") no-repeat center;
+}
+
 .playButton {
-  -moz-transform: translateX(21px);
   background: url("chrome://browser/content/images/pause-hdpi.png") no-repeat center;
 }
 
+/*
+ * Normally the button bar has fullscreen spacer play spacer mute, but if
+ * this is an audio control rather than a video control, the fullscreen button
+ * is hidden by videocontrols.xml, and that alters the position of the
+ * play button.  This workaround moves it back to center.
+ */
+.controlBar.audio .playButton {
+  transform: translateX(28px);
+}
+
 .playButton[paused="true"] {
   background: url("chrome://browser/content/images/play-hdpi.png") no-repeat center;
 }
 
 .muteButton {
   background: url("chrome://browser/content/images/mute-hdpi.png") no-repeat center;
 }
 
 .muteButton[muted="true"] {
   background: url("chrome://browser/content/images/unmute-hdpi.png") no-repeat center;
 }
 
-/* This button is hidden until bug 704229 is fixed. */
-.fullscreenButton {
-  display: none;
-}
-
 /* bars */
 .scrubberStack {
   width: 100%;
   min-height: 32px;
   max-height: 32px;
   padding: 0px 8px;
   margin: 0px;
 }
@@ -73,16 +86,17 @@
 .scrubber,
 .scrubber .scale-slider,
 .scrubber .scale-thumb {
   -moz-appearance: none;
   border: none;
   padding: 0px;
   margin: 0px;
   background-color: transparent;
+  border-radius: 3px;
 }
 
 .bufferBar {
   border: 1px solid #5e6166;
 }
 
 .bufferBar,
 .progressBar {
@@ -163,27 +177,24 @@
   -moz-transition-delay: 750ms;
 }
 
 .statusOverlay[fadeout] {
   opacity: 0;
 }
 
 .volumeStack,
+.controlBar[firstshow="true"] .fullscreenButton,
 .controlBar[firstshow="true"] .muteButton,
 .controlBar[firstshow="true"] .scrubberStack,
 .controlBar[firstshow="true"] .durationBox,
 .timeLabel {
   display: none;
 }
 
-.controlBar[firstshow="true"] .playButton {
-  -moz-transform: none;
-}
-
 /* Error description formatting */
 .errorLabel {
   font-family: Helvetica, Arial, sans-serif;
   font-size: 11px;
   color: #bbb;
   text-shadow:
     -1px -1px 0 #000,
     1px -1px 0 #000,
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -20,26 +20,26 @@ chrome.jar:
   content/runapp.js                     (content/runapp.js)
 #endif
   content/content.css                   (content/content.css)
   content/touchcontrols.css             (content/touchcontrols.css)
 
   content/payment.js                    (content/payment.js)
   content/identity.js                   (content/identity.js)
 
-  content/UAO_child.js                 (content/UAO_child.js)
-
 % override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
 % override chrome://global/skin/netError.css chrome://browser/content/netError.css
 % override chrome://global/skin/media/videocontrols.css chrome://browser/content/touchcontrols.css
 
   content/netError.xhtml                (content/netError.xhtml)
   content/netError.css                  (content/netError.css)
   content/images/errorpage-larry-black.png (content/images/errorpage-larry-black.png)
   content/images/errorpage-larry-white.png (content/images/errorpage-larry-white.png)
   content/images/errorpage-warning.png (content/images/errorpage-warning.png)
   content/images/scrubber-hdpi.png     (content/images/scrubber-hdpi.png)
   content/images/unmute-hdpi.png       (content/images/unmute-hdpi.png)
   content/images/pause-hdpi.png        (content/images/pause-hdpi.png)
   content/images/play-hdpi.png         (content/images/play-hdpi.png)
   content/images/mute-hdpi.png         (content/images/mute-hdpi.png)
+  content/images/fullscreen-hdpi.png     (content/images/fullscreen-hdpi.png)
+  content/images/exitfullscreen-hdpi.png (content/images/exitfullscreen-hdpi.png)
   content/images/throbber.png          (content/images/throbber.png)
   content/images/error.png             (content/images/error.png)
--- a/b2g/components/ProcessGlobal.js
+++ b/b2g/components/ProcessGlobal.js
@@ -52,24 +52,13 @@ ProcessGlobal.prototype = {
       let message = subject.wrappedJSObject;
       let prefix = ('Content JS ' + message.level.toUpperCase() +
                     ' at ' + message.filename + ':' + message.lineNumber +
                     ' in ' + (message.functionName || 'anonymous') + ': ');
       Services.console.logStringMessage(prefix + Array.join(message.arguments,
                                                             ' '));
       break;
     }
-    case 'remote-browser-frame-shown': {
-      let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
-      let mm = frameLoader.messageManager;
-
-      const kFrameScript = "chrome://browser/content/UAO_child.js";
-      try {
-        mm.loadFrameScript(kFrameScript, true);
-      } catch (e) {
-        dump('Error loading ' + kFrameScript + ' as frame script: ' + e + '\n');
-      }
-    }
     }
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ProcessGlobal]);
--- a/b2g/config/panda/releng-pandaboard.tt
+++ b/b2g/config/panda/releng-pandaboard.tt
@@ -1,8 +1,14 @@
 [
 {
-"size": 676768064,
-"digest": "d51092cc586934f21b84e62447f49d32548cd3f222a092f2b7b8c04ff59231c97969140e0b1d897c22bcb12b8bf6a9c8b24e55891896c7eeb9bc0f190980813d",
+"size": 678628568,
+"digest": "e0b98a75831313171781d91ab4c3b8ae66e3af7fcec433a33d75fbc647cb3bba311f790ccb9cd9c8be8c5549210b759d6b85afe7395ada74c2c319a29556fd8e",
 "algorithm": "sha512",
 "filename": "gonk.tar.xz"
+},
+{
+"size": 2116507,
+"digest": "be67a012963a5c162834f9fcb989bcebd2d047dcb4e17ee23031b694dcf7cdfd6d7a6545d7a1f5e7293b6d24415403972f4ea1ab8c6c78fefcabfaf3f6875214",
+"algorithm": "sha512",
+"filename": "download-panda.tar.bz2"
 }
 ]
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -5,26 +5,27 @@
 MOZ_APP_BASENAME=B2G
 MOZ_APP_VENDOR=Mozilla
 
 MOZ_APP_VERSION=21.0a1
 MOZ_APP_UA_NAME=Firefox
 
 MOZ_UA_OS_AGNOSTIC=1
 
-MOZ_B2G_VERSION=1.0.0.0-prerelease
+MOZ_B2G_VERSION=2.0.0.0-prerelease
 MOZ_B2G_OS_NAME=Boot2Gecko
 
 MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
 MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
 # MOZ_APP_DISPLAYNAME is set by branding/configure.sh
 
 MOZ_SAFE_BROWSING=
 MOZ_SERVICES_COMMON=1
 MOZ_SERVICES_METRICS=1
+MOZ_SERVICES_CAPTIVEDETECT=1
 
 MOZ_WEBSMS_BACKEND=1
 MOZ_DISABLE_DOMCRYPTO=1
 MOZ_APP_STATIC_INI=1
 
 if test "$OS_TARGET" = "Android"; then
 MOZ_CAPTURE=1
 MOZ_RAW=1
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -157,16 +157,17 @@
 @BINPATH@/components/dom.xpt
 @BINPATH@/components/dom_activities.xpt
 @BINPATH@/components/dom_apps.xpt
 @BINPATH@/components/dom_audiochannel.xpt
 @BINPATH@/components/dom_base.xpt
 @BINPATH@/components/dom_system.xpt
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/dom_telephony.xpt
+@BINPATH@/components/dom_voicemail.xpt
 @BINPATH@/components/dom_wifi.xpt
 @BINPATH@/components/dom_system_gonk.xpt
 @BINPATH@/components/dom_icc.xpt
 @BINPATH@/components/dom_cellbroadcast.xpt
 #endif
 #ifdef MOZ_B2G_FM
 @BINPATH@/components/dom_fm.xpt
 #endif
@@ -197,16 +198,17 @@
 #endif
 @BINPATH@/components/dom_browserelement.xpt
 @BINPATH@/components/dom_messages.xpt
 @BINPATH@/components/dom_power.xpt
 @BINPATH@/components/dom_range.xpt
 @BINPATH@/components/dom_settings.xpt
 @BINPATH@/components/dom_permissionsettings.xpt
 @BINPATH@/components/dom_sidebar.xpt
+@BINPATH@/components/dom_mobilemessage.xpt
 @BINPATH@/components/dom_sms.xpt
 @BINPATH@/components/dom_storage.xpt
 @BINPATH@/components/dom_stylesheets.xpt
 @BINPATH@/components/dom_threads.xpt
 @BINPATH@/components/dom_traversal.xpt
 @BINPATH@/components/dom_views.xpt
 @BINPATH@/components/dom_xbl.xpt
 @BINPATH@/components/dom_xpath.xpt
@@ -280,16 +282,19 @@
 @BINPATH@/components/rdf.xpt
 @BINPATH@/components/satchel.xpt
 @BINPATH@/components/saxparser.xpt
 @BINPATH@/components/sessionstore.xpt
 #ifdef MOZ_SERVICES_SYNC
 @BINPATH@/components/services-crypto.xpt
 #endif
 @BINPATH@/components/services-crypto-component.xpt
+#ifdef MOZ_SERVICES_CAPTIVEDETECT
+@BINPATH@/components/services-captivedetect.xpt
+#endif
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
@@ -451,18 +456,18 @@
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/NetworkManager.manifest
 @BINPATH@/components/NetworkManager.js
 @BINPATH@/components/RadioInterfaceLayer.manifest
 @BINPATH@/components/RadioInterfaceLayer.js
 @BINPATH@/components/MmsService.manifest
 @BINPATH@/components/MmsService.js
 @BINPATH@/components/RILContentHelper.js
-@BINPATH@/components/SmsDatabaseService.manifest
-@BINPATH@/components/SmsDatabaseService.js
+@BINPATH@/components/MobileMessageDatabaseService.manifest
+@BINPATH@/components/MobileMessageDatabaseService.js
 @BINPATH@/components/WifiWorker.js
 @BINPATH@/components/WifiWorker.manifest
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 @BINPATH@/components/NetworkStatsManager.js
 @BINPATH@/components/NetworkStatsManager.manifest
 #endif
 #ifdef MOZ_B2G_FM
@@ -486,16 +491,20 @@
 @BINPATH@/components/WeaveCrypto.js
 #endif
 @BINPATH@/components/servicesComponents.manifest
 @BINPATH@/components/cryptoComponents.manifest
 #ifdef MOZ_SERVICES_HEALTHREPORT
 @BINPATH@/components/HealthReportComponents.manifest
 @BINPATH@/components/HealthReportService.js
 #endif
+#ifdef MOZ_SERVICES_CAPTIVEDETECT
+@BINPATH@/components/CaptivePortalDetectComponents.manifest
+@BINPATH@/components/captivedetect.js
+#endif
 @BINPATH@/components/TelemetryPing.js
 @BINPATH@/components/TelemetryPing.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 
 @BINPATH@/components/nsDOMIdentity.js
@@ -583,16 +592,19 @@
 @BINPATH@/defaults/autoconfig/platform.js
 @BINPATH@/defaults/autoconfig/prefcalls.js
 @BINPATH@/defaults/profile/prefs.js
 
 ; Services (gre) prefs
 #ifdef MOZ_SERVICES_SYNC
 @BINPATH@/defaults/pref/services-sync.js
 #endif
+#ifdef MOZ_SERVICES_CAPTIVEDETECT
+@BINPATH@/defaults/pref/services-captivedetect.js
+#endif
 
 ; [Layout Engine Resources]
 ; Style Sheets, Graphics and other Resources used by the layout engine. 
 @BINPATH@/res/EditorOverride.css
 @BINPATH@/res/contenteditable.css
 @BINPATH@/res/designmode.css
 @BINPATH@/res/table-add-column-after-active.gif
 @BINPATH@/res/table-add-column-after-hover.gif
--- a/b2g/test/emulator.manifest
+++ b/b2g/test/emulator.manifest
@@ -1,6 +1,6 @@
 [{
-"size": 631436376,
-"digest": "cea07d65a39a244961f183711b14d054c90690b69a79d89a3783f9a634f9ace7f6e70033e963a4f58ca8482b3aec8d4c5d3227cc7a0bc61e6afeccf2acc1a789",
+"size": 608318343,
+"digest": "9ab6487eccf44b0781cc96c2af9ba497f720a8d289bde40e29417f9db82788d6c8653c7dafa7443069f5898635eef45fa048ee99c03a9d0113c019a2f80f5aa8",
 "algorithm": "sha512",
 "filename": "emulator.zip"
-}]
\ No newline at end of file
+}]
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -325,19 +325,16 @@ pref("browser.download.manager.flashCoun
 pref("browser.download.manager.addToRecentDocs", true);
 pref("browser.download.manager.quitBehavior", 0);
 pref("browser.download.manager.scanWhenDone", true);
 pref("browser.download.manager.resumeOnWakeDelay", 10000);
 
 // This allows disabling the Downloads Panel in favor of the old interface.
 pref("browser.download.useToolkitUI", false);
 
-// This controls retention behavior in the Downloads Panel only.
-pref("browser.download.panel.removeFinishedDownloads", false);
-
 // This records whether or not the panel has been shown at least once.
 pref("browser.download.panel.shown", false);
 
 // This records whether or not at least one session with the Downloads Panel
 // enabled has been completed already.
 pref("browser.download.panel.firstSessionCompleted", false);
 
 // search engines URL
--- a/browser/base/Makefile.in
+++ b/browser/base/Makefile.in
@@ -31,15 +31,15 @@ DEFINES += \
 ifneq (,$(filter windows gtk2 cocoa, $(MOZ_WIDGET_TOOLKIT)))
 DEFINES += -DHAVE_SHELL_SERVICE=1
 endif
 
 ifneq (,$(filter windows cocoa gtk2, $(MOZ_WIDGET_TOOLKIT)))
 DEFINES += -DCONTEXT_COPY_IMAGE_CONTENTS=1
 endif
 
-ifneq (,$(filter windows, $(MOZ_WIDGET_TOOLKIT)))
+ifneq (,$(filter windows cocoa, $(MOZ_WIDGET_TOOLKIT)))
 DEFINES += -DCAN_DRAW_IN_TITLEBAR=1
 endif
 
 ifneq (,$(filter windows gtk2, $(MOZ_WIDGET_TOOLKIT)))
 DEFINES += -DMENUBAR_CAN_AUTOHIDE=1
 endif
--- a/browser/base/content/abouthealthreport/abouthealth.js
+++ b/browser/base/content/abouthealthreport/abouthealth.js
@@ -67,34 +67,35 @@ function refreshDataView(data) {
 /**
  * Ensure the page has the latest version of the uploaded JSON payload.
  */
 function refreshJSONPayload() {
   reporter.getLastPayload().then(refreshDataView);
 }
 
 function onOptInClick() {
-  policy.healthReportUploadEnabled = true;
+  policy.recordHealthReportUploadEnabled(true,
+                                         "Clicked opt in button on about page.");
   refreshWithDataSubmissionFlag(true);
 }
 
 function onOptOutClick() {
   let prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"]
                   .getService(Ci.nsIPromptService);
 
   let messages = document.getElementById("optout-confirmationPrompt");
   let title = messages.getAttribute("confirmationPrompt_title");
   let message = messages.getAttribute("confirmationPrompt_message");
 
   if (!prompts.confirm(window, title, message)) {
     return;
   }
 
-  policy.healthReportUploadEnabled = false;
-  reporter.requestDeleteRemoteData("Clicked opt out button on about page.");
+  policy.recordHealthReportUploadEnabled(false,
+                                         "Clicked opt out button on about page.");
   refreshWithDataSubmissionFlag(false);
   updateView("disabled");
 }
 
 function onShowRawDataClick() {
   updateView("showDetails");
   refreshJSONPayload();
 }
--- a/browser/base/content/browser-data-submission-info-bar.js
+++ b/browser/base/content/browser-data-submission-info-bar.js
@@ -8,17 +8,20 @@
 let gDataNotificationInfoBar = {
   _OBSERVERS: [
     "datareporting:notify-data-policy:request",
     "datareporting:notify-data-policy:close",
   ],
 
   _DATA_REPORTING_NOTIFICATION: "data-reporting",
 
-  _notificationBox: null,
+  get _notificationBox() {
+    delete this._notificationBox;
+    return this._notificationBox = document.getElementById("global-notificationbox");
+  },
 
   get _log() {
     let log4moz = Cu.import("resource://services-common/log4moz.js", {}).Log4Moz;
     delete this._log;
     return this._log = log4moz.repository.getLogger("Services.DataReporting.InfoBar");
   },
 
   init: function() {
@@ -30,44 +33,21 @@ let gDataNotificationInfoBar = {
       }
     }.bind(this), false);
 
     for (let o of this._OBSERVERS) {
       Services.obs.addObserver(this, o, true);
     }
   },
 
-  _ensureNotificationBox: function () {
-    if (this._notificationBox) {
-      return;
-    }
-
-    let nb = document.createElementNS(
-      "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
-      "notificationbox"
-    );
-    nb.id = "data-notification-notify-bar";
-    nb.setAttribute("flex", "1");
-
-    let bottombox = document.getElementById("browser-bottombox");
-    bottombox.insertBefore(nb, bottombox.firstChild);
-
-    this._notificationBox = nb;
-  },
-
   _getDataReportingNotification: function (name=this._DATA_REPORTING_NOTIFICATION) {
-    if (!this._notificationBox) {
-      return undefined;
-    }
     return this._notificationBox.getNotificationWithValue(name);
   },
 
   _displayDataPolicyInfoBar: function (request) {
-    this._ensureNotificationBox();
-
     if (this._getDataReportingNotification()) {
       return;
     }
 
     let policy = Cc["@mozilla.org/datareporting/service;1"]
                    .getService(Ci.nsISupports)
                    .wrappedJSObject
                    .policy;
@@ -110,19 +90,16 @@ let gDataNotificationInfoBar = {
             request.onUserAccept("info-bar-dismissed");
           }
 
           Services.obs.notifyObservers(null, "datareporting:notify-data-policy:close", null);
         }
       }.bind(this)
     );
 
-    // Keep open until user closes it.
-    notification.persistence = -1;
-
     // Tell the notification request we have displayed the notification.
     request.onUserNotifyComplete();
   },
 
   _clearPolicyNotification: function () {
     let notification = this._getDataReportingNotification();
     if (notification) {
       notification.close();
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1075,17 +1075,16 @@ var PlacesStarButton = {
         this._itemIds.push(aItemId);
         this._updateStateInternal();
       }
     }
   },
 
   onBeginUpdateBatch: function () {},
   onEndUpdateBatch: function () {},
-  onBeforeItemRemoved: function () {},
   onItemVisited: function () {},
   onItemMoved: function () {}
 };
 
 
 // This object handles the initialization and uninitialization of the bookmarks
 // toolbar.  updateState is called when the browser window is opened and
 // after closing the toolbar customization dialog.
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -204,17 +204,17 @@ var gPluginHandler = {
         this.addLinkClickCallback(updateLink, "openPluginUpdatePage");
         /* FALLTHRU */
 
       case "PluginVulnerableNoUpdate":
       case "PluginClickToPlay":
         this._handleClickToPlayEvent(plugin);
         let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
         let pluginName = this._getPluginInfo(plugin).pluginName;
-        let messageString = gNavigatorBundle.getFormattedString("PluginClickToPlay", [pluginName]);
+        let messageString = gNavigatorBundle.getFormattedString("PluginClickToActivate", [pluginName]);
         let overlayText = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgClickToPlay");
         overlayText.textContent = messageString;
         if (eventType == "PluginVulnerableUpdatable" ||
             eventType == "PluginVulnerableNoUpdate") {
           let vulnerabilityString = gNavigatorBundle.getString(eventType);
           let vulnerabilityText = doc.getAnonymousElementByAttribute(plugin, "anonid", "vulnerabilityStatus");
           vulnerabilityText.textContent = vulnerabilityString;
         }
@@ -447,17 +447,17 @@ var gPluginHandler = {
       objLoadingContent.playPlugin();
       return;
     }
 
     if (overlay) {
       overlay.addEventListener("click", function(aEvent) {
         // Have to check that the target is not the link to update the plugin
         if (!(aEvent.originalTarget instanceof HTMLAnchorElement) &&
-            !(aEvent.originalTarget instanceof HTMLButtonElement) &&
+            (aEvent.originalTarget.getAttribute('anonid') != 'closeIcon') &&
             aEvent.button == 0 && aEvent.isTrusted) {
           gPluginHandler.activateSinglePlugin(aEvent.target.ownerDocument.defaultView.top, aPlugin);
           aEvent.stopPropagation();
           aEvent.preventDefault();
         }
       }, true);
 
       let closeIcon = doc.getAnonymousElementByAttribute(aPlugin, "anonid", "closeIcon");
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -157,31 +157,31 @@ let SocialUI = {
   _updateActiveUI: function SocialUI_updateActiveUI() {
     // The "active" UI isn't dependent on there being a provider, just on
     // social being "active" (but also chromeless/PB)
     let enabled = Social.active && !this._chromeless &&
                   !PrivateBrowsingUtils.isWindowPrivate(window);
     let broadcaster = document.getElementById("socialActiveBroadcaster");
     broadcaster.hidden = !enabled;
 
-    if (!Social.provider)
-      return;
+    let toggleCommand = document.getElementById("Social:Toggle");
+    toggleCommand.setAttribute("hidden", enabled ? "false" : "true");
 
-    let toggleCommand = document.getElementById("Social:Toggle");
-    // We only need to update the command itself - all our menu items use it.
-    let label = gNavigatorBundle.getFormattedString(Social.provider.enabled ?
-                                                      "social.turnOff.label" :
-                                                      "social.turnOn.label",
-                                                    [Social.provider.name]);
-    let accesskey = gNavigatorBundle.getString(Social.provider.enabled ?
-                                                 "social.turnOff.accesskey" :
-                                                 "social.turnOn.accesskey");
-    toggleCommand.setAttribute("label", label);
-    toggleCommand.setAttribute("accesskey", accesskey);
-    toggleCommand.setAttribute("hidden", enabled ? "false" : "true");
+    if (enabled) {
+      // We only need to update the command itself - all our menu items use it.
+      let label = gNavigatorBundle.getFormattedString(Social.provider.enabled ?
+                                                        "social.turnOff.label" :
+                                                        "social.turnOn.label",
+                                                      [Social.provider.name]);
+      let accesskey = gNavigatorBundle.getString(Social.provider.enabled ?
+                                                   "social.turnOff.accesskey" :
+                                                   "social.turnOn.accesskey");
+      toggleCommand.setAttribute("label", label);
+      toggleCommand.setAttribute("accesskey", accesskey);
+    }
   },
 
   _updateMenuItems: function () {
     if (!Social.provider)
       return;
 
     // The View->Sidebar and Menubar->Tools menu.
     for (let id of ["menu_socialSidebar", "menu_socialAmbientMenu"])
@@ -714,38 +714,38 @@ var SocialMenu = {
   }
 };
 
 // XXX Need to audit that this is being initialized correctly
 var SocialToolbar = {
   // Called once, after window load, when the Social.provider object is
   // initialized.
   init: function SocialToolbar_init() {
-    let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
-    let label = gNavigatorBundle.getFormattedString("social.remove.label",
-                                                    [brandShortName]);
-    let accesskey = gNavigatorBundle.getString("social.remove.accesskey");
-
+    let accesskey = gNavigatorBundle.getString("social.removeProvider.accesskey");
     let removeCommand = document.getElementById("Social:Remove");
-    removeCommand.setAttribute("label", label);
     removeCommand.setAttribute("accesskey", accesskey);
 
     this.updateProvider();
     this._dynamicResizer = new DynamicResizeWatcher();
   },
 
   // Called when the Social.provider changes
   updateProvider: function () {
-    if (!SocialUI.enabled)
-      return;
-    this.button.style.listStyleImage = "url(" + Social.provider.iconURL + ")";
-    this.button.setAttribute("label", Social.provider.name);
-    this.button.setAttribute("tooltiptext", Social.provider.name);
+    if (Social.provider) {
+      let label = gNavigatorBundle.getFormattedString("social.removeProvider.label",
+                                                      [Social.provider.name]);
+      let removeCommand = document.getElementById("Social:Remove");
+      removeCommand.setAttribute("label", label);
+      this.button.setAttribute("label", Social.provider.name);
+      this.button.setAttribute("tooltiptext", Social.provider.name);
+      this.button.style.listStyleImage = "url(" + Social.provider.iconURL + ")";
+
+      this.updateProfile();
+    }
     this.updateButton();
-    this.updateProfile();
     this.populateProviderMenus();
   },
 
   get button() {
     return document.getElementById("social-provider-button");
   },
 
   // Note: this doesn't actually handle hiding the toolbar button,
@@ -792,36 +792,36 @@ var SocialToolbar = {
     userDetailsBroadcaster.setAttribute("value", loggedInStatusValue);
     userDetailsBroadcaster.setAttribute("label", loggedInStatusValue);
   },
 
   // XXX doesn't this need to be called for profile changes, given its use of provider.profile?
   updateButton: function SocialToolbar_updateButton() {
     this.updateButtonHiddenState();
     let provider = Social.provider;
-    let icons = provider.ambientNotificationIcons;
-    let iconNames = Object.keys(icons);
     let panel = document.getElementById("social-notification-panel");
-    panel.hidden = false;
+    panel.hidden = !SocialUI.enabled;
 
     let command = document.getElementById("Social:ToggleNotifications");
     command.setAttribute("checked", Services.prefs.getBoolPref("social.toast-notifications.enabled"));
 
     const CACHE_PREF_NAME = "social.cached.ambientNotificationIcons";
     // provider.profile == undefined means no response yet from the provider
     // to tell us whether the user is logged in or not.
     if (!SocialUI.enabled ||
         (!Social.haveLoggedInUser() && provider.profile !== undefined)) {
       // Either no enabled provider, or there is a provider and it has
       // responded with a profile and the user isn't loggedin.  The icons
       // etc have already been removed by updateButtonHiddenState, so we want
       // to nuke any cached icons we have and get out of here!
       Services.prefs.clearUserPref(CACHE_PREF_NAME);
       return;
     }
+    let icons = provider.ambientNotificationIcons;
+    let iconNames = Object.keys(icons);
     if (Social.provider.profile === undefined) {
       // provider has not told us about the login state yet - see if we have
       // a cached version for this provider.
       let cached;
       try {
         cached = JSON.parse(Services.prefs.getComplexValue(CACHE_PREF_NAME,
                                                            Ci.nsISupportsString).data);
       } catch (ex) {}
@@ -1015,17 +1015,17 @@ var SocialToolbar = {
   _populateProviderMenu: function SocialToolbar_renderProviderMenu(providerMenuSep, providers) {
     let menu = providerMenuSep.parentNode;
     // selectable providers are inserted before the provider-menu seperator,
     // remove any menuitems in that area
     while (providerMenuSep.previousSibling.nodeName == "menuitem") {
       menu.removeChild(providerMenuSep.previousSibling);
     }
     // only show a selection if enabled and there is more than one
-    if (!SocialUI.enabled || Social.providers.length < 2) {
+    if (!SocialUI.enabled || providers.length < 2) {
       providerMenuSep.hidden = true;
       return;
     }
     for (let provider of providers) {
       let menuitem = document.createElement("menuitem");
       menuitem.className = "menuitem-iconic social-provider-menuitem";
       menuitem.setAttribute("image", provider.iconURL);
       menuitem.setAttribute("label", provider.name);
--- a/browser/base/content/browser-thumbnails.js
+++ b/browser/base/content/browser-thumbnails.js
@@ -154,18 +154,24 @@ let gBrowserThumbnails = {
 
     let httpChannel;
     try {
       httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
     } catch (e) { /* Not an HTTP channel. */ }
 
     if (httpChannel) {
       // Continue only if we have a 2xx status code.
-      if (Math.floor(httpChannel.responseStatus / 100) != 2)
+      try {
+        if (Math.floor(httpChannel.responseStatus / 100) != 2)
+          return false;
+      } catch (e) {
+        // Can't get response information from the httpChannel
+        // because mResponseHead is not available.
         return false;
+      }
 
       // Cache-Control: no-store.
       if (httpChannel.isNoStoreResponse())
         return false;
 
       // Don't capture HTTPS pages unless the user explicitly enabled it.
       if (uri.schemeIs("https") && !this._sslDiskCacheEnabled)
         return false;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2995,16 +2995,20 @@ var PrintPreviewListener = {
     this._chromeState.addonBarOpen = !addonBar.collapsed;
     addonBar.collapsed = true;
     gBrowser.updateWindowResizers();
 
     this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden;
     if (gFindBarInitialized)
       gFindBar.close();
 
+    var globalNotificationBox = document.getElementById("global-notificationbox");
+    this._chromeState.globalNotificationsOpen = !globalNotificationBox.notificationsHidden;
+    globalNotificationBox.notificationsHidden = true;
+
     this._chromeState.syncNotificationsOpen = false;
     var syncNotifications = document.getElementById("sync-notifications");
     if (syncNotifications) {
       this._chromeState.syncNotificationsOpen = !syncNotifications.notificationsHidden;
       syncNotifications.notificationsHidden = true;
     }
   },
   _showChrome: function () {
@@ -3014,16 +3018,19 @@ var PrintPreviewListener = {
     if (this._chromeState.addonBarOpen) {
       document.getElementById("addon-bar").collapsed = false;
       gBrowser.updateWindowResizers();
     }
 
     if (this._chromeState.findOpen)
       gFindBar.open();
 
+    if (this._chromeState.globalNotificationsOpen)
+      document.getElementById("global-notificationbox").notificationsHidden = false;
+
     if (this._chromeState.syncNotificationsOpen)
       document.getElementById("sync-notifications").notificationsHidden = false;
   }
 }
 
 function getMarkupDocumentViewer()
 {
   return gBrowser.markupDocumentViewer;
@@ -6201,17 +6208,17 @@ var IndexedDBPromptHelper = {
                                                     [ host, data ]);
       responseTopic = this._quotaResponse;
     }
     else if (topic == this._quotaCancel) {
       responseTopic = this._quotaResponse;
     }
 
     const hiddenTimeoutDuration = 30000; // 30 seconds
-    const firstTimeoutDuration = 360000; // 5 minutes
+    const firstTimeoutDuration = 300000; // 5 minutes
 
     var timeoutId;
 
     var observer = requestor.getInterface(Ci.nsIObserver);
 
     var mainAction = {
       label: gNavigatorBundle.getString("offlineApps.allow"),
       accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -484,24 +484,26 @@
       </popupnotificationcontent>
     </popupnotification>
 
   </popupset>
 
 #ifdef CAN_DRAW_IN_TITLEBAR
 <vbox id="titlebar">
   <hbox id="titlebar-content">
+#ifdef MENUBAR_CAN_AUTOHIDE
     <hbox id="appmenu-button-container">
       <button id="appmenu-button"
               type="menu"
               label="&brandShortName;"
               style="-moz-user-focus: ignore;">
 #include browser-appmenu.inc
       </button>
     </hbox>
+#endif
     <spacer id="titlebar-spacer" flex="1"/>
     <hbox id="titlebar-buttonbox-container" align="start">
       <hbox id="titlebar-buttonbox">
         <toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/>
         <toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/>
         <toolbarbutton class="titlebar-button" id="titlebar-close" command="cmd_closeWindow"/>
       </hbox>
     </hbox>
@@ -565,17 +567,17 @@
                                             this.parentNode.removeAttribute('forwarddisabled');">
           <observes element="Browser:ForwardOrForwardDuplicate" attribute="disabled"/>
         </dummyobservertarget>
       </toolbaritem>
 
       <toolbaritem id="urlbar-container" align="center" flex="400" persist="width" combined="true"
                    title="&locationItem.title;" class="chromeclass-location" removable="true">
         <textbox id="urlbar" flex="1"
-                 placeholder="&urlbar.placeholder;"
+                 placeholder="&urlbar.placeholder2;"
                  type="autocomplete"
                  autocompletesearch="urlinline history"
                  autocompletesearchparam="enable-actions"
                  autocompletepopup="PopupAutoCompleteRichResult"
                  completeselectedindex="true"
                  tabscrolling="true"
                  showcommentcolumn="true"
                  showimagecolumn="true"
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -570,17 +570,17 @@ function goThroughFrames(aDocument, aWin
   }
 }
 
 function processFrames()
 {
   if (gFrameList.length) {
     var doc = gFrameList[0];
     onProcessFrame.forEach(function(func) { func(doc); });
-    var iterator = doc.createTreeWalker(doc, NodeFilter.SHOW_ELEMENT, grabAll, true);
+    var iterator = doc.createTreeWalker(doc, NodeFilter.SHOW_ELEMENT, grabAll);
     gFrameList.shift();
     setTimeout(doGrab, 10, iterator);
     onFinished.push(selectImage);
   }
   else
     onFinished.forEach(function(func) { func(); });
 }
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1420,17 +1420,19 @@
             }
 
             // We start our browsers out as inactive, and then maintain
             // activeness in the tab switcher.
             b.docShellIsActive = false;
 
             // If we just created a new tab that loads the default
             // newtab url, swap in a preloaded page if possible.
-            if (aURI == BROWSER_NEW_TAB_URL) {
+            // Do nothing if we're a private window.
+            if (aURI == BROWSER_NEW_TAB_URL &&
+                !PrivateBrowsingUtils.isWindowPrivate(window)) {
               gBrowserNewTabPreloader.newTab(t);
             }
 
             // Check if we're opening a tab related to the current tab and
             // move it to after the current tab.
             // aReferrerURI is null or undefined if the tab is opened from
             // an external application or bookmark, i.e. somewhere other
             // than the current tab.
--- a/browser/base/content/test/browser_sanitize-timespans.js
+++ b/browser/base/content/test/browser_sanitize-timespans.js
@@ -1,28 +1,29 @@
 // Bug 453440 - Test the timespan-based logic of the sanitizer code
 var now_uSec = Date.now() * 1000;
 
 const dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
-const bhist = Cc["@mozilla.org/browser/global-history;2"].getService(Ci.nsIBrowserHistory);
 const formhist = Cc["@mozilla.org/satchel/form-history;1"].getService(Ci.nsIFormHistory2);
 
 const kUsecPerMin = 60 * 1000000;
 
 let tempScope = {};
 Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
                                            .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
 let Sanitizer = tempScope.Sanitizer;
 
 function test() {
   waitForExplicitFinish();
 
   setupDownloads();
   setupFormHistory();
-  setupHistory(onHistoryReady);
+  setupHistory(function() {
+    Task.spawn(onHistoryReady).then(finish);
+  });
 }
 
 function onHistoryReady() {
   var hoursSinceMidnight = new Date().getHours();
   var minutesSinceMidnight = hoursSinceMidnight * 60 + new Date().getMinutes();
 
   // Should test cookies here, but nsICookieManager/nsICookieService
   // doesn't let us fake creation times.  bug 463127
@@ -40,29 +41,38 @@ function onHistoryReady() {
   itemPrefs.setBoolPref("passwords", false);
   itemPrefs.setBoolPref("sessions", false);
   itemPrefs.setBoolPref("siteSettings", false);
 
   // Clear 10 minutes ago
   s.range = [now_uSec - 10*60*1000000, now_uSec];
   s.sanitize();
   s.range = null;
-  
-  ok(!bhist.isVisited(makeURI("http://10minutes.com")), "10minutes.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://1hour.com")), "Pretend visit to 1hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://1hour10minutes.com/")), "Pretend visit to 1hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://2hour.com")), "Pretend visit to 2hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  
-  if (minutesSinceMidnight > 10)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://10minutes.com"))),
+     "Pretend visit to 10minutes.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://1hour.com"))),
+     "Pretend visit to 1hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+     "Pretend visit to 1hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+     "Pretend visit to 2hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+     "Pretend visit to 2hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (minutesSinceMidnight > 10) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("10minutes"), "10minutes form entry should be deleted");
   ok(formhist.nameExists("1hour"), "1hour form entry should still exist");
   ok(formhist.nameExists("1hour10minutes"), "1hour10minutes form entry should still exist");
   ok(formhist.nameExists("2hour"), "2hour form entry should still exist");
   ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
   ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (minutesSinceMidnight > 10)
@@ -79,28 +89,36 @@ function onHistoryReady() {
   ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
 
   if (minutesSinceMidnight > 10)
     ok(downloadExists(5555554), "'Today' download should still be present");
 
   // Clear 1 hour
   Sanitizer.prefs.setIntPref("timeSpan", 1);
   s.sanitize();
-  
-  ok(!bhist.isVisited(makeURI("http://1hour.com")), "1hour.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://1hour10minutes.com/")), "Pretend visit to 1hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://2hour.com")), "Pretend visit to 2hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  
-  if (hoursSinceMidnight > 1)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://1hour.com"))),
+     "Pretend visit to 1hour.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+     "Pretend visit to 1hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+     "Pretend visit to 2hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+     "Pretend visit to 2hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (hoursSinceMidnight > 1) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("1hour"), "1hour form entry should be deleted");
   ok(formhist.nameExists("1hour10minutes"), "1hour10minutes form entry should still exist");
   ok(formhist.nameExists("2hour"), "2hour form entry should still exist");
   ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
   ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (hoursSinceMidnight > 1)
     ok(formhist.nameExists("today"), "today form entry should still exist");
@@ -116,26 +134,34 @@ function onHistoryReady() {
 
   if (hoursSinceMidnight > 1)
     ok(downloadExists(5555554), "'Today' download should still be present");
   
   // Clear 1 hour 10 minutes
   s.range = [now_uSec - 70*60*1000000, now_uSec];
   s.sanitize();
   s.range = null;
-  
-  ok(!bhist.isVisited(makeURI("http://1hour10minutes.com")), "Pretend visit to 1hour10minutes.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://2hour.com")), "Pretend visit to 2hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  if (minutesSinceMidnight > 70)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+     "Pretend visit to 1hour10minutes.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+     "Pretend visit to 2hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+     "Pretend visit to 2hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (minutesSinceMidnight > 70) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("1hour10minutes"), "1hour10minutes form entry should be deleted");
   ok(formhist.nameExists("2hour"), "2hour form entry should still exist");
   ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
   ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (minutesSinceMidnight > 70)
     ok(formhist.nameExists("today"), "today form entry should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
@@ -147,25 +173,32 @@ function onHistoryReady() {
   ok(downloadExists(5555553), "<4 hour old download should still be present");
   ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
   if (minutesSinceMidnight > 70)
     ok(downloadExists(5555554), "'Today' download should still be present");
 
   // Clear 2 hours
   Sanitizer.prefs.setIntPref("timeSpan", 2);
   s.sanitize();
-  
-  ok(!bhist.isVisited(makeURI("http://2hour.com")), "Pretend visit to 2hour.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  if (hoursSinceMidnight > 2)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+     "Pretend visit to 2hour.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+     "Pretend visit to 2hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (hoursSinceMidnight > 2) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("2hour"), "2hour form entry should be deleted");
   ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
   ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (hoursSinceMidnight > 2)
     ok(formhist.nameExists("today"), "today form entry should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
 
@@ -177,24 +210,30 @@ function onHistoryReady() {
   ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
   if (hoursSinceMidnight > 2)
     ok(downloadExists(5555554), "'Today' download should still be present");
   
   // Clear 2 hours 10 minutes
   s.range = [now_uSec - 130*60*1000000, now_uSec];
   s.sanitize();
   s.range = null;
-  
-  ok(!bhist.isVisited(makeURI("http://2hour10minutes.com")), "Pretend visit to 2hour10minutes.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  if (minutesSinceMidnight > 130)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+     "Pretend visit to 2hour10minutes.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (minutesSinceMidnight > 130) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should be deleted");
   ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (minutesSinceMidnight > 130)
     ok(formhist.nameExists("today"), "today form entry should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
 
   ok(!downloadExists(5555557), "2 hour 10 minute old download should now be deleted");
@@ -202,44 +241,53 @@ function onHistoryReady() {
   ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
   ok(downloadExists(5555550), "Year old download should still be present");
   if (minutesSinceMidnight > 130)
     ok(downloadExists(5555554), "'Today' download should still be present");
 
   // Clear 4 hours
   Sanitizer.prefs.setIntPref("timeSpan", 3);
   s.sanitize();
-  
-  ok(!bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  if (hoursSinceMidnight > 4)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (hoursSinceMidnight > 4) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("4hour"), "4hour form entry should be deleted");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (hoursSinceMidnight > 4)
     ok(formhist.nameExists("today"), "today form entry should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
 
   ok(!downloadExists(5555553), "<4 hour old download should now be deleted");
   ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
   ok(downloadExists(5555550), "Year old download should still be present");
   if (hoursSinceMidnight > 4)
     ok(downloadExists(5555554), "'Today' download should still be present");
 
   // Clear 4 hours 10 minutes
   s.range = [now_uSec - 250*60*1000000, now_uSec];
   s.sanitize();
   s.range = null;
-  
-  ok(!bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should now be deleted");
-  if (minutesSinceMidnight > 250)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should now be deleted");
+  if (minutesSinceMidnight > 250) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
   
   ok(!formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should be deleted");
   if (minutesSinceMidnight > 250)
     ok(formhist.nameExists("today"), "today form entry should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
 
   ok(!downloadExists(5555558), "4 hour 10 minute download should now be deleted");
   ok(downloadExists(5555550), "Year old download should still be present");
@@ -252,36 +300,37 @@ function onHistoryReady() {
 
   // Be careful.  If we add our objectss just before midnight, and sanitize
   // runs immediately after, they won't be expired.  This is expected, but
   // we should not test in that case.  We cannot just test for opposite
   // condition because we could cross midnight just one moment after we
   // cache our time, then we would have an even worse random failure.
   var today = isToday(new Date(now_uSec/1000));
   if (today) {
-    ok(!bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should now be deleted");
+    ok(!(yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should now be deleted");
     ok(!formhist.nameExists("today"), "today form entry should be deleted");
     ok(!downloadExists(5555554), "'Today' download should now be deleted");
   }
 
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+     "Pretend visit to before-today.com should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
   ok(downloadExists(5555550), "Year old download should still be present");
 
   // Choose everything
   Sanitizer.prefs.setIntPref("timeSpan", 0);
   s.sanitize();
 
-  ok(!bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should now be deleted");
+  ok(!(yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+     "Pretend visit to before-today.com should now be deleted");
 
   ok(!formhist.nameExists("b4today"), "b4today form entry should be deleted");
 
   ok(!downloadExists(5555550), "Year old download should now be deleted");
-
-  finish();
 }
 
 function setupHistory(aCallback) {
   let places = [];
 
   function addPlace(aURI, aTitle, aVisitDate) {
     places.push({
       uri: aURI,
--- a/browser/base/content/test/browser_sanitizeDialog.js
+++ b/browser/base/content/test/browser_sanitizeDialog.js
@@ -72,20 +72,21 @@ var gAllTests = [
         // Show details
         this.toggleDetails();
         this.checkDetails(true);
 
         // Hide details
         this.toggleDetails();
         this.checkDetails(false);
         this.cancelDialog();
-
-        ensureHistoryClearedState(uris, false);
+      };
+      wh.onunload = function () {
+        yield promiseHistoryClearedState(uris, false);
         blankSlate();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
       };
       wh.open();
     });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox clears both history
    * visits and downloads when checked; the dialog respects simple timespan.
@@ -131,28 +132,29 @@ var gAllTests = [
                   "timeSpan pref should be hour after accepting dialog with " +
                   "hour selected");
         boolPrefIs("cpd.history", true,
                    "history pref should be true after accepting dialog with " +
                    "history checkbox checked");
         boolPrefIs("cpd.downloads", true,
                    "downloads pref should be true after accepting dialog with " +
                    "history checkbox checked");
-
+      };
+      wh.onunload = function () {
         // History visits and downloads within one hour should be cleared.
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
         ensureDownloadsClearedState(downloadIDs, true);
 
         // Visits and downloads > 1 hour should still exist.
-        ensureHistoryClearedState(olderURIs, false);
+        yield promiseHistoryClearedState(olderURIs, false);
         ensureDownloadsClearedState(olderDownloadIDs, false);
 
         // OK, done, cleanup after ourselves.
         blankSlate();
-        ensureHistoryClearedState(olderURIs, true);
+        yield promiseHistoryClearedState(olderURIs, true);
         ensureDownloadsClearedState(olderDownloadIDs, true);
       };
       wh.open();
     });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox removes neither
@@ -195,25 +197,26 @@ var gAllTests = [
                   "timeSpan pref should be hour after accepting dialog with " +
                   "hour selected");
         boolPrefIs("cpd.history", false,
                    "history pref should be false after accepting dialog with " +
                    "history checkbox unchecked");
         boolPrefIs("cpd.downloads", false,
                    "downloads pref should be false after accepting dialog with " +
                    "history checkbox unchecked");
-
+      };
+      wh.onunload = function () {
         // Of the three only form entries should be cleared.
-        ensureHistoryClearedState(uris, false);
+        yield promiseHistoryClearedState(uris, false);
         ensureDownloadsClearedState(downloadIDs, false);
         ensureFormEntriesClearedState(formEntries, true);
 
         // OK, done, cleanup after ourselves.
         blankSlate();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
         ensureDownloadsClearedState(downloadIDs, true);
       };
       wh.open();
     });
   },
 
   /**
    * Ensures that the "Everything" duration option works.
@@ -248,17 +251,19 @@ var gAllTests = [
         this.toggleDetails();
         this.checkDetails(true);
 
         this.acceptDialog();
 
         intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
                   "timeSpan pref should be everything after accepting dialog " +
                   "with everything selected");
-        ensureHistoryClearedState(uris, true);
+      };
+      wh.onunload = function () {
+        yield promiseHistoryClearedState(uris, true);
       };
       wh.open();
     });
   },
 
   /**
    * Ensures that the "Everything" warning is visible on dialog open after
    * the previous test.
@@ -283,17 +288,19 @@ var gAllTests = [
            "with clearing everything");
         this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
         this.checkPrefCheckbox("history", true);
         this.acceptDialog();
 
         intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
                   "timeSpan pref should be everything after accepting dialog " +
                   "with everything selected");
-        ensureHistoryClearedState(uris, true);
+      };
+      wh.onunload = function () {
+        yield promiseHistoryClearedState(uris, true);
       };
       wh.open();
     });
   },
 
   /**
    * The next three tests checks that when a certain history item cannot be
    * cleared then the checkbox should be both disabled and unchecked.
@@ -316,18 +323,19 @@ var gAllTests = [
 
         var cb = this.win.document.querySelectorAll(
                    "#itemList > [preference='privacy.cpd.history']");
         ok(cb.length == 1 && !cb[0].disabled, "There is history, checkbox to " +
            "clear history should be enabled.");
 
         this.checkAllCheckboxes();
         this.acceptDialog();
-
-        ensureHistoryClearedState(uris, true);
+      };
+      wh.onunload = function () {
+        yield promiseHistoryClearedState(uris, true);
         ensureFormEntriesClearedState(formEntries, true);
       };
       wh.open();
     });
   },
   function () {
     let wh = new WindowHelper();
     wh.onload = function() {
@@ -368,16 +376,18 @@ var gAllTests = [
 
       var cb = this.win.document.querySelectorAll(
                  "#itemList > [preference='privacy.cpd.formdata']");
       ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
          "There exists formEntries so the checkbox should be in sync with " +
          "the pref.");
 
       this.acceptDialog();
+    };
+    wh.onunload = function () {
       ensureFormEntriesClearedState(formEntries, true);
     };
     wh.open();
   },
 
 
   /**
    * These next six tests together ensure that toggling details persists
@@ -778,19 +788,23 @@ WindowHelper.prototype = {
 
         win.removeEventListener("unload", onunload, false);
         wh.win = win;
 
         executeSoon(function () {
           // Some exceptions that reach here don't reach the test harness, but
           // ok()/is() do...
           try {
-            if (wh.onunload)
-              wh.onunload();
-            waitForAsyncUpdates(doNextTest);
+            if (wh.onunload) {
+              Task.spawn(wh.onunload).then(function() {
+                waitForAsyncUpdates(doNextTest);
+              });
+            } else {
+              waitForAsyncUpdates(doNextTest);
+            }
           }
           catch (exc) {
             win.close();
             ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
             finish();
           }
         });
       }, false);
@@ -897,55 +911,16 @@ function addFormEntryWithMinutesAgo(aMin
  */
 function blankSlate() {
   PlacesUtils.bhistory.removeAllPages();
   dm.cleanUp();
   formhist.removeAllEntries();
 }
 
 /**
- * Waits for all pending async statements on the default connection, before
- * proceeding with aCallback.
- *
- * @param aCallback
- *        Function to be called when done.
- * @param aScope
- *        Scope for the callback.
- * @param aArguments
- *        Arguments array for the callback.
- *
- * @note The result is achieved by asynchronously executing a query requiring
- *       a write lock.  Since all statements on the same connection are
- *       serialized, the end of this write operation means that all writes are
- *       complete.  Note that WAL makes so that writers don't block readers, but
- *       this is a problem only across different connections.
- */
-function waitForAsyncUpdates(aCallback, aScope, aArguments)
-{
-  let scope = aScope || this;
-  let args = aArguments || [];
-  let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
-                              .DBConnection;
-  let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
-  begin.executeAsync();
-  begin.finalize();
-
-  let commit = db.createAsyncStatement("COMMIT");
-  commit.executeAsync({
-    handleResult: function() {},
-    handleError: function() {},
-    handleCompletion: function(aReason)
-    {
-      aCallback.apply(scope, args);
-    }
-  });
-  commit.finalize();
-}
-
-/**
  * Ensures that the given pref is the expected value.
  *
  * @param aPrefName
  *        The pref's sub-branch under the privacy branch
  * @param aExpectedVal
  *        The pref's expected value
  * @param aMsg
  *        Passed to is()
@@ -1019,32 +994,16 @@ function ensureFormEntriesClearedState(a
   let niceStr = aShouldBeCleared ? "no longer" : "still";
   aFormEntries.forEach(function (entry) {
     is(formhist.nameExists(entry), !aShouldBeCleared,
        "form entry " + entry + " should " + niceStr + " exist");
   });
 }
 
 /**
- * Ensures that the specified URIs are either cleared or not.
- *
- * @param aURIs
- *        Array of page URIs
- * @param aShouldBeCleared
- *        True if each visit to the URI should be cleared, false otherwise
- */
-function ensureHistoryClearedState(aURIs, aShouldBeCleared) {
-  let niceStr = aShouldBeCleared ? "no longer" : "still";
-  aURIs.forEach(function (aURI) {
-    is(PlacesUtils.bhistory.isVisited(aURI), !aShouldBeCleared,
-       "history visit " + aURI.spec + " should " + niceStr + " exist");
-  });
-}
-
-/**
  * Ensures that the given pref is the expected value.
  *
  * @param aPrefName
  *        The pref's sub-branch under the privacy branch
  * @param aExpectedVal
  *        The pref's expected value
  * @param aMsg
  *        Passed to is()
--- a/browser/base/content/test/browser_sanitizeDialog_treeView.js
+++ b/browser/base/content/test/browser_sanitizeDialog_treeView.js
@@ -72,21 +72,21 @@ var gAllTests = [
         wh.checkGrippy("Grippy should remain at last row after trying to move " +
                        "it down",
                        wh.getRowCount() - 1);
 
         // Cancel the dialog, make sure history visits are not cleared.
         wh.checkPrefCheckbox("history", false);
 
         wh.cancelDialog();
-        ensureHistoryClearedState(uris, false);
+        yield promiseHistoryClearedState(uris, false);
 
         // OK, done, cleanup after ourselves.
         blankSlate();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
       });
     });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox clears both history
    * visits and downloads when checked; the dialog respects simple timespan.
    */
@@ -128,26 +128,26 @@ var gAllTests = [
         wh.checkGrippy("Grippy should be at proper row after selecting HOUR " +
                        "duration",
                        uris.length);
 
         // Accept the dialog, make sure history visits and downloads within one
         // hour are cleared.
         wh.checkPrefCheckbox("history", true);
         wh.acceptDialog();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
         ensureDownloadsClearedState(downloadIDs, true);
 
         // Make sure visits and downloads > 1 hour still exist.
-        ensureHistoryClearedState(olderURIs, false);
+        yield promiseHistoryClearedState(olderURIs, false);
         ensureDownloadsClearedState(olderDownloadIDs, false);
 
         // OK, done, cleanup after ourselves.
         blankSlate();
-        ensureHistoryClearedState(olderURIs, true);
+        yield promiseHistoryClearedState(olderURIs, true);
         ensureDownloadsClearedState(olderDownloadIDs, true);
       });
     });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox removes neither
    * history visits nor downloads when not checked.
@@ -182,23 +182,23 @@ var gAllTests = [
                        wh.getRowCount() - 1);
 
         // Remove only form entries, leave history (including downloads).
         wh.checkPrefCheckbox("history", false);
         wh.checkPrefCheckbox("formdata", true);
         wh.acceptDialog();
 
         // Of the three only form entries should be cleared.
-        ensureHistoryClearedState(uris, false);
+        yield promiseHistoryClearedState(uris, false);
         ensureDownloadsClearedState(downloadIDs, false);
         ensureFormEntriesClearedState(formEntries, true);
 
         // OK, done, cleanup after ourselves.
         blankSlate();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
         ensureDownloadsClearedState(downloadIDs, true);
       });
     });
   },
 
   /**
    * Ensures that the "Everything" duration option works.
    */
@@ -217,17 +217,17 @@ var gAllTests = [
     addVisits(places, function() {
 
       // Open the dialog and do our tests.
       openWindow(function (aWin) {
         let wh = new WindowHelper(aWin);
         wh.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
         wh.checkPrefCheckbox("history", true);
         wh.acceptDialog();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
       });
     });
   }
 ];
 
 // Used as the download database ID for a new download.  Incremented for each
 // new download.  See addDownloadWithMinutesAgo().
 var gDownloadId = 5555551;
@@ -498,55 +498,16 @@ function addFormEntryWithMinutesAgo(aMin
  */
 function blankSlate() {
   PlacesUtils.bhistory.removeAllPages();
   dm.cleanUp();
   formhist.removeAllEntries();
 }
 
 /**
- * Waits for all pending async statements on the default connection, before
- * proceeding with aCallback.
- *
- * @param aCallback
- *        Function to be called when done.
- * @param aScope
- *        Scope for the callback.
- * @param aArguments
- *        Arguments array for the callback.
- *
- * @note The result is achieved by asynchronously executing a query requiring
- *       a write lock.  Since all statements on the same connection are
- *       serialized, the end of this write operation means that all writes are
- *       complete.  Note that WAL makes so that writers don't block readers, but
- *       this is a problem only across different connections.
- */
-function waitForAsyncUpdates(aCallback, aScope, aArguments)
-{
-  let scope = aScope || this;
-  let args = aArguments || [];
-  let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
-                              .DBConnection;
-  let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
-  begin.executeAsync();
-  begin.finalize();
-
-  let commit = db.createAsyncStatement("COMMIT");
-  commit.executeAsync({
-    handleResult: function() {},
-    handleError: function() {},
-    handleCompletion: function(aReason)
-    {
-      aCallback.apply(scope, args);
-    }
-  });
-  commit.finalize();
-}
-
-/**
  * Checks to see if the download with the specified ID exists.
  *
  * @param  aID
  *         The ID of the download to check
  * @return True if the download exists, false otherwise
  */
 function downloadExists(aID)
 {
@@ -606,32 +567,16 @@ function ensureFormEntriesClearedState(a
   let niceStr = aShouldBeCleared ? "no longer" : "still";
   aFormEntries.forEach(function (entry) {
     is(formhist.nameExists(entry), !aShouldBeCleared,
        "form entry " + entry + " should " + niceStr + " exist");
   });
 }
 
 /**
- * Ensures that the specified URIs are either cleared or not.
- *
- * @param aURIs
- *        Array of page URIs
- * @param aShouldBeCleared
- *        True if each visit to the URI should be cleared, false otherwise
- */
-function ensureHistoryClearedState(aURIs, aShouldBeCleared) {
-  let niceStr = aShouldBeCleared ? "no longer" : "still";
-  aURIs.forEach(function (aURI) {
-    is(PlacesUtils.bhistory.isVisited(aURI), !aShouldBeCleared,
-       "history visit " + aURI.spec + " should " + niceStr + " exist");
-  });
-}
-
-/**
  * Opens the sanitize dialog and runs a callback once it's finished loading.
  * 
  * @param aOnloadCallback
  *        A function that will be called once the dialog has loaded
  */
 function openWindow(aOnloadCallback) {
   function windowObserver(aSubject, aTopic, aData) {
     if (aTopic != "domwindowopened")
@@ -640,18 +585,21 @@ function openWindow(aOnloadCallback) {
     Services.ww.unregisterNotification(windowObserver);
     let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
     win.addEventListener("load", function onload(event) {
       win.removeEventListener("load", onload, false);
       executeSoon(function () {
         // Some exceptions that reach here don't reach the test harness, but
         // ok()/is() do...
         try {
-          aOnloadCallback(win);
-          waitForAsyncUpdates(doNextTest);
+          Task.spawn(function() {
+            aOnloadCallback(win);
+          }).then(function() {
+            waitForAsyncUpdates(doNextTest);
+          });
         }
         catch (exc) {
           win.close();
           ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
           finish();
         }
       });
     }, false);
--- a/browser/base/content/test/head.js
+++ b/browser/base/content/test/head.js
@@ -1,8 +1,18 @@
+netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/commonjs/sdk/core/promise.js");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+  "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+  "resource://gre/modules/PlacesUtils.jsm");
+
 function whenDelayedStartupFinished(aWindow, aCallback) {
   Services.obs.addObserver(function observer(aSubject, aTopic) {
     if (aWindow == aSubject) {
       Services.obs.removeObserver(observer, aTopic);
       executeSoon(aCallback);
     }
   }, "browser-delayed-startup-finished", false);
 }
@@ -127,16 +137,71 @@ function resetBlocklist() {
 function whenNewWindowLoaded(aOptions, aCallback) {
   let win = OpenBrowserWindow(aOptions);
   win.addEventListener("load", function onLoad() {
     win.removeEventListener("load", onLoad, false);
     aCallback(win);
   }, false);
 }
 
+/**
+ * Waits for all pending async statements on the default connection, before
+ * proceeding with aCallback.
+ *
+ * @param aCallback
+ *        Function to be called when done.
+ * @param aScope
+ *        Scope for the callback.
+ * @param aArguments
+ *        Arguments array for the callback.
+ *
+ * @note The result is achieved by asynchronously executing a query requiring
+ *       a write lock.  Since all statements on the same connection are
+ *       serialized, the end of this write operation means that all writes are
+ *       complete.  Note that WAL makes so that writers don't block readers, but
+ *       this is a problem only across different connections.
+ */
+function waitForAsyncUpdates(aCallback, aScope, aArguments) {
+  let scope = aScope || this;
+  let args = aArguments || [];
+  let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+                              .DBConnection;
+  let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
+  begin.executeAsync();
+  begin.finalize();
+
+  let commit = db.createAsyncStatement("COMMIT");
+  commit.executeAsync({
+    handleResult: function() {},
+    handleError: function() {},
+    handleCompletion: function(aReason) {
+      aCallback.apply(scope, args);
+    }
+  });
+  commit.finalize();
+}
+
+/**
+ * Asynchronously check a url is visited.
+
+ * @param aURI The URI.
+ * @param aExpectedValue The expected value.
+ * @return {Promise}
+ * @resolves When the check has been added successfully.
+ * @rejects JavaScript exception.
+ */
+function promiseIsURIVisited(aURI, aExpectedValue) {
+  let deferred = Promise.defer();
+  PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
+    deferred.resolve(aIsVisited);
+  });
+
+  return deferred.promise;
+}
+
 function addVisits(aPlaceInfo, aCallback) {
   let places = [];
   if (aPlaceInfo instanceof Ci.nsIURI) {
     places.push({ uri: aPlaceInfo });
   } else if (Array.isArray(aPlaceInfo)) {
     places = places.concat(aPlaceInfo);
   } else {
     places.push(aPlaceInfo);
@@ -165,8 +230,35 @@ function addVisits(aPlaceInfo, aCallback
       handleResult: function () {},
       handleCompletion: function UP_handleCompletion() {
         if (aCallback)
           aCallback();
       }
     }
   );
 }
+
+/**
+ * Ensures that the specified URIs are either cleared or not.
+ *
+ * @param aURIs
+ *        Array of page URIs
+ * @param aShouldBeCleared
+ *        True if each visit to the URI should be cleared, false otherwise
+ */
+function promiseHistoryClearedState(aURIs, aShouldBeCleared) {
+  let deferred = Promise.defer();
+  let callbackCount = 0;
+  let niceStr = aShouldBeCleared ? "no longer" : "still";
+  function callbackDone() {
+    if (++callbackCount == aURIs.length)
+      deferred.resolve();
+  }
+  aURIs.forEach(function (aURI) {
+    PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
+      is(aIsVisited, !aShouldBeCleared,
+         "history visit " + aURI.spec + " should " + niceStr + " exist");
+      callbackDone();
+    });
+  });
+
+  return deferred.promise;
+}
--- a/browser/base/content/test/social/browser_social_chatwindow.js
+++ b/browser/base/content/test/social/browser_social_chatwindow.js
@@ -186,17 +186,17 @@ var tests = {
         case "got-chatbox-message":
           ok(true, "got a chat window opened");
           let chats = document.getElementById("pinnedchats");
           ok(chats.selectedChat.minimized, "chatbox from worker opened as minimized");
           while (chats.selectedChat) {
             chats.selectedChat.close();
           }
           ok(!chats.selectedChat, "chats are all closed");
-          ensureSocialUrlNotRemembered(chatUrl);
+          gURLsNotRemembered.push(chatUrl);
           port.close();
           next();
           break;
       }
     }
     port.postMessage({topic: "test-worker-chat", data: chatUrl});
   },
   testCloseSelf: function(next) {
@@ -451,28 +451,27 @@ var tests = {
       let url = chatUrl + "?" + (chatId++);
       port.postMessage({topic: "test-worker-chat", data: url});
     }
 
     // open a chat (it will open in the main window)
     ok(!window.SocialChatBar.hasChats, "first window should start with no chats");
     openChat(function() {
       ok(window.SocialChatBar.hasChats, "first window has the chat");
-      // create a second window - although this will be the "most recent",
-      // the fact the first window has a chat open means the first will be targetted.
+      // create a second window - this will be the "most recent" and will
+      // therefore be the window that hosts the new chat (see bug 835111)
       let secondWindow = OpenBrowserWindow();
       secondWindow.addEventListener("load", function loadListener() {
         secondWindow.removeEventListener("load", loadListener);
         ok(!secondWindow.SocialChatBar.hasChats, "second window has no chats");
         openChat(function() {
-          ok(!secondWindow.SocialChatBar.hasChats, "second window still has no chats");
-          is(window.SocialChatBar.chatbar.childElementCount, 2, "first window now has 2 chats");
+          ok(secondWindow.SocialChatBar.hasChats, "second window now has chats");
+          is(window.SocialChatBar.chatbar.childElementCount, 1, "first window still has 1 chat");
           window.SocialChatBar.chatbar.removeAll();
-          // now open another chat - it should open in the second window (as
-          // it is the "most recent" and no other windows have chats)
+          // now open another chat - it should still open in the second.
           openChat(function() {
             ok(!window.SocialChatBar.hasChats, "first window has no chats");
             ok(secondWindow.SocialChatBar.hasChats, "second window has a chat");
             secondWindow.close();
             next();
           });
         });
       })
--- a/browser/base/content/test/social/browser_social_mozSocial_API.js
+++ b/browser/base/content/test/social/browser_social_mozSocial_API.js
@@ -48,17 +48,17 @@ var tests = {
       switch (topic) {
         case "test-init-done":
           iconsReady = true;
           checkNext();
           break;
         case "got-panel-message":
           ok(true, "got panel message");
           // Check the panel isn't in our history.
-          ensureSocialUrlNotRemembered(e.data.location);
+          gURLsNotRemembered.push(e.data.location);
           break;
         case "got-social-panel-visibility":
           if (e.data.result == "shown") {
             ok(true, "panel shown");
             let panel = document.getElementById("social-notification-panel");
             panel.hidePopup();
           } else if (e.data.result == "hidden") {
             ok(true, "panel hidden");
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -1,12 +1,21 @@
 /* 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/. */
 
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/commonjs/sdk/core/promise.js");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+  "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+  "resource://gre/modules/PlacesUtils.jsm");
+
 function waitForCondition(condition, nextTest, errorMsg) {
   var tries = 0;
   var interval = setInterval(function() {
     if (tries >= 30) {
       ok(false, errorMsg);
       moveOn();
     }
     if (condition()) {
@@ -14,46 +23,56 @@ function waitForCondition(condition, nex
     }
     tries++;
   }, 100);
   var moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
 // Check that a specified (string) URL hasn't been "remembered" (ie, is not
 // in history, will not appear in about:newtab or auto-complete, etc.)
-function ensureSocialUrlNotRemembered(url) {
-  let gh = Cc["@mozilla.org/browser/global-history;2"]
-           .getService(Ci.nsIGlobalHistory2);
+function promiseSocialUrlNotRemembered(url) {
+  let deferred = Promise.defer();
   let uri = Services.io.newURI(url, null, null);
-  ok(!gh.isVisited(uri), "social URL " + url + " should not be in global history");
+  PlacesUtils.asyncHistory.isURIVisited(uri, function(aURI, aIsVisited) {
+    ok(!aIsVisited, "social URL " + url + " should not be in global history");
+    deferred.resolve();
+  });
+  return deferred.promise;
 }
 
+let gURLsNotRemembered = [];
+
 function runSocialTestWithProvider(manifest, callback) {
   let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
   let manifests = Array.isArray(manifest) ? manifest : [manifest];
 
   // Check that none of the provider's content ends up in history.
-  registerCleanupFunction(function () {
-    manifests.forEach(function (m) {
+  function finishCleanUp() {
+    for (let i = 0; i < manifests.length; i++) {
+      let m = manifests[i];
       for (let what of ['sidebarURL', 'workerURL', 'iconURL']) {
         if (m[what]) {
-          ensureSocialUrlNotRemembered(m[what]);
+          yield promiseSocialUrlNotRemembered(m[what]);
         }
-      }
-    });
-  });
+      };
+    }
+    for (let i = 0; i < gURLsNotRemembered.length; i++) {
+      yield promiseSocialUrlNotRemembered(gURLsNotRemembered[i]);
+    }
+    gURLsNotRemembered = [];
+  }
 
   info("runSocialTestWithProvider: " + manifests.toSource());
 
   let finishCount = 0;
   function finishIfDone(callFinish) {
     finishCount++;
     if (finishCount == manifests.length)
-      finish();
+      Task.spawn(finishCleanUp).then(finish);
   }
   function removeAddedProviders(cleanup) {
     manifests.forEach(function (m) {
       // If we're "cleaning up", don't call finish when done.
       let callback = cleanup ? function () {} : finishIfDone;
       // Similarly, if we're cleaning up, catch exceptions from removeProvider
       let removeProvider = SocialService.removeProvider.bind(SocialService);
       if (cleanup) {
@@ -162,22 +181,21 @@ function checkSocialUI(win) {
   isbool(win.SocialSidebar.canShow, enabled, "social sidebar active?");
   if (enabled)
     isbool(win.SocialSidebar.opened, enabled, "social sidebar open?");
   isbool(win.SocialChatBar.isAvailable, enabled && Social.haveLoggedInUser(), "chatbar available?");
   isbool(!win.SocialChatBar.chatbar.hidden, enabled && Social.haveLoggedInUser(), "chatbar visible?");
   isbool(!win.SocialShareButton.shareButton.hidden, enabled && provider.recommendInfo, "share button visible?");
   isbool(!doc.getElementById("social-toolbar-item").hidden, enabled, "toolbar items visible?");
   if (enabled)
-    todo_is(win.SocialToolbar.button.style.listStyleImage, 'url("' + provider.iconURL + '")', "Bug 821262 - toolbar button has provider icon");
+    is(win.SocialToolbar.button.style.listStyleImage, 'url("' + provider.iconURL + '")', "toolbar button has provider icon");
 
   // and for good measure, check all the social commands.
   // Social:Remove - never disabled directly but parent nodes are
   isbool(!doc.getElementById("Social:Toggle").hidden, enabled, "Social:Toggle visible?");
-  // Until bug 821262 is fixed, ToggleNotifications might not be updated correctly...
-  // isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Bug 821262 - Social:ToggleNotifications visible?");
+  isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?");
   isbool(!doc.getElementById("Social:FocusChat").hidden, enabled && Social.haveLoggedInUser(), "Social:FocusChat visible?");
   isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?");
   is(doc.getElementById("Social:SharePage").getAttribute("disabled"), enabled && provider.recommendInfo ? "false" : "true", "Social:SharePage visible?");
 
   // broadcasters.
   isbool(!doc.getElementById("socialActiveBroadcaster").hidden, enabled, "socialActiveBroadcaster hidden?");
 }
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -1032,18 +1032,18 @@ DownloadsPlacesView.prototype = {
       this._removeElement(shell.element);
       shells.delete(shell);
       if (shells.size == 0)
         this._downloadElementsShellsForURI.delete(aDataItem.uri);
     }
     else {
       shell.dataItem = null;
       // Move it below the session-download items;
-      if (this._lastSessionDownloadElement == shell.dataItem) {
-        this._lastSessionDownloadElement = shell.dataItem.previousSibling;
+      if (this._lastSessionDownloadElement == shell.element) {
+        this._lastSessionDownloadElement = shell.element.previousSibling;
       }
       else {
         let before = this._lastSessionDownloadElement ?
           this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild;
         this._richlistbox.insertBefore(shell.element, before);
       }
     }
   },
@@ -1235,17 +1235,16 @@ DownloadsPlacesView.prototype = {
     this._forEachDownloadElementShellForURI(aNode.uri, function(aDownloadElementShell) {
       aDownloadElementShell.placesNodeTitleChanged();
     });
   },
 
   nodeKeywordChanged: function() {},
   nodeDateAddedChanged: function() {},
   nodeLastModifiedChanged: function() {},
-  nodeReplaced: function() {},
   nodeHistoryDetailsChanged: function() {},
   nodeTagsChanged: function() {},
   sortingChanged: function() {},
   nodeMoved: function() {},
   nodeURIChanged: function() {},
   batching: function() {},
 
   get controller() this._richlistbox.controller,
--- a/browser/components/downloads/content/downloads.css
+++ b/browser/components/downloads/content/downloads.css
@@ -93,8 +93,26 @@ richlistitem[type="download"]:not([selec
 
 #downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress,
 #downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails,
 #downloadsFooter[showingsummary] > #downloadsHistory,
 #downloadsFooter:not([showingsummary]) > #downloadsSummary
 {
   display: none;
 }
+
+/* Hacks for toolbar full and text modes, until bug 573329 removes them */
+
+toolbar[mode="text"] > #downloads-indicator {
+  display: -moz-box;
+  -moz-box-orient: vertical;
+  -moz-box-pack: center;
+}
+
+toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-text {
+  -moz-box-ordinal-group: 1;
+}
+
+toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-icon {
+  display: -moz-box;
+  -moz-box-ordinal-group: 2;
+  visibility: collapse;
+}
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -108,25 +108,23 @@
         <hbox id="downloadsSummary"
               align="center"
               orient="horizontal"
               onkeydown="DownloadsSummary.onKeyDown(event);"
               onclick="DownloadsSummary.onClick(event);">
           <image class="downloadTypeIcon" />
           <vbox>
             <description id="downloadsSummaryDescription"
-                         class="downloadTarget"
                          style="min-width: &downloadsSummary.minWidth2;"/>
             <progressmeter id="downloadsSummaryProgress"
                            class="downloadProgress"
                            min="0"
                            max="100"
                            mode="normal" />
             <description id="downloadsSummaryDetails"
-                         class="downloadDetails"
                          style="width: &downloadDetails.width;"
                          crop="end"/>
           </vbox>
         </hbox>
 
         <button id="downloadsHistory"
                 class="plain"
                 label="&downloadsHistory.label;"
--- a/browser/components/downloads/content/indicatorOverlay.xul
+++ b/browser/components/downloads/content/indicatorOverlay.xul
@@ -48,11 +48,13 @@
           <progressmeter id="downloads-indicator-progress"
                          class="plain"
                          min="0"
                          max="100"/>
         </vbox>
         <vbox id="downloads-indicator-icon"/>
         <vbox id="downloads-indicator-notification"/>
       </stack>
+      <label class="toolbarbutton-text" crop="right" flex="1"
+             value="&downloads.label;"/>
     </toolbarbutton>
   </popupset>
 </overlay>
--- a/browser/components/downloads/src/DownloadsStartup.js
+++ b/browser/components/downloads/src/DownloadsStartup.js
@@ -118,26 +118,20 @@ DownloadsStartup.prototype = {
         // Since this notification is generated during the getService call and
         // we need to get the Download Manager service ourselves, we must post
         // the handler on the event queue to be executed later.
         Services.tm.mainThread.dispatch(this._ensureDataLoaded.bind(this),
                                         Ci.nsIThread.DISPATCH_NORMAL);
         break;
 
       case "download-manager-change-retention":
-        // When the panel interface is enabled, we use a different preference to
-        // determine whether downloads should be removed from view as soon as
-        // they are finished.  We do this to allow proper migration to the new
-        // feature when using the same profile on multiple versions of the
-        // product (bug 697678).
+        // If we're using the Downloads Panel, we override the retention
+        // preference to always retain downloads on completion.
         if (!DownloadsCommon.useToolkitUI) {
-          let removeFinishedDownloads = Services.prefs.getBoolPref(
-                            "browser.download.panel.removeFinishedDownloads");
-          aSubject.QueryInterface(Ci.nsISupportsPRInt32)
-                  .data = removeFinishedDownloads ? 0 : 2;
+          aSubject.QueryInterface(Ci.nsISupportsPRInt32).data = 2;
         }
         break;
 
       case "browser-lastwindow-close-granted":
         // When using the panel interface, downloads that are already completed
         // should be removed when the last full browser window is closed.  This
         // event is invoked only if the application is not shutting down yet.
         // If the Download Manager service is not initialized, we don't want to
--- a/browser/components/migration/tests/unit/test_IE_bookmarks.js
+++ b/browser/components/migration/tests/unit/test_IE_bookmarks.js
@@ -30,17 +30,16 @@ function run_test() {
         do_check_neq(index, -1);
         expectedParents.splice(index, 1);
         if (expectedParents.length == 0)
           PlacesUtils.bookmarks.removeObserver(this);
       }
     },
     onBeginUpdateBatch: function () {},
     onEndUpdateBatch: function () {},
-    onBeforeItemRemoved: function () {},
     onItemRemoved: function () {},
     onItemChanged: function () {},
     onItemVisited: function () {},
     onItemMoved: function () {},
   }, false);
 
   // Wait for migration.
   Services.obs.addObserver(function onMigrationEnded() {
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -541,36 +541,16 @@ PlacesViewBase.prototype = {
       // Figure out if we need to show the "<Empty>" menu-item.
       // TODO Bug 517701: This doesn't seem to handle the case of an empty
       // root.
       if (parentElt._startMarker.nextSibling == parentElt._endMarker)
         this._setEmptyPopupStatus(parentElt, true);
     }
   },
 
-  nodeReplaced:
-  function PVB_nodeReplaced(aParentPlacesNode, aOldPlacesNode, aNewPlacesNode, aIndex) {
-    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
-    if (parentElt._built) {
-      let elt = this._getDOMNodeForPlacesNode(aOldPlacesNode);
-
-      // Here we need the <menu>.
-      if (elt.localName == "menupopup")
-        elt = elt.parentNode;
-
-      parentElt.removeChild(elt);
-
-      // No worries: If elt is the last item (i.e. no nextSibling),
-      // _insertNewItem/_insertNewItemToPopup will insert the new element as
-      // the last item.
-      let nextElt = elt.nextSibling;
-      this._insertNewItemToPopup(aNewPlacesNode, parentElt, nextElt);
-    }
-  },
-
   nodeHistoryDetailsChanged:
   function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) {
     if (aPlacesNode.parent &&
         this.controller.hasCachedLivemarkInfo(aPlacesNode.parent)) {
       // Find the node in the parent.
       let popup = this._getDOMNodeForPlacesNode(aPlacesNode.parent);
       for (let child = popup._startMarker.nextSibling;
            child != popup._endMarker;
@@ -1276,41 +1256,16 @@ PlacesToolbar.prototype = {
       elt = elt.parentNode;
 
     if (elt.parentNode == this._rootElt) {
       // Node is on the toolbar
       this.updateChevron();
     }
   },
 
-  nodeReplaced:
-  function PT_nodeReplaced(aParentPlacesNode,
-                           aOldPlacesNode, aNewPlacesNode, aIndex) {
-    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
-    if (parentElt == this._rootElt) {
-      let elt = this._getDOMNodeForPlacesNode(aOldPlacesNode);
-
-      // Here we need the <menu>.
-      if (elt.localName == "menupopup")
-        elt = elt.parentNode;
-
-      this._removeChild(elt);
-
-      // No worries: If elt is the last item (i.e. no nextSibling),
-      // _insertNewItem/_insertNewItemToPopup will insert the new element as
-      // the last item.
-      let next = elt.nextSibling;
-      this._insertNewItem(aNewPlacesNode, next);
-      this.updateChevron();
-      return;
-    }
-
-    PlacesViewBase.prototype.nodeReplaced.apply(this, arguments);
-  },
-
   invalidateContainer: function PT_invalidateContainer(aPlacesNode) {
     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
     if (elt == this._rootElt) {
       // Container is the toolbar itself.
       this._rebuild();
       return;
     }
 
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -1021,11 +1021,10 @@ var gEditItemOverlay = {
   onItemAdded: function EIO_onItemAdded(aItemId, aParentId, aIndex, aItemType,
                                         aURI) {
     this._lastNewItem = aItemId;
   },
 
   onItemRemoved: function() { },
   onBeginUpdateBatch: function() { },
   onEndUpdateBatch: function() { },
-  onBeforeItemRemoved: function() { },
   onItemVisited: function() { },
 };
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -1246,20 +1246,27 @@ let ContentArea = {
         typeof aView != "object" && typeof aView != "function")
       throw new Error("Invalid arguments");
 
     this._specialViews.set(aQueryString, { view: aView,
                                            options: aOptions || new Object() });
   },
 
   get currentView() PlacesUIUtils.getViewForNode(this._deck.selectedPanel),
-  set currentView(aView) {
-    if (this.currentView != aView)
-      this._deck.selectedPanel = aView.associatedElement;
-    return aView;
+  set currentView(aNewView) {
+    let oldView = this.currentView;
+    if (oldView != aNewView) {
+      this._deck.selectedPanel = aNewView.associatedElement;
+
+      // If the content area inactivated view was focused, move focus
+      // to the new view.
+      if (document.activeElement == oldView.associatedElement)
+        aNewView.associatedElement.focus();
+    }
+    return aNewView;
   },
 
   get currentPlace() this.currentView.place,
   set currentPlace(aQueryString) {
     let oldView = this.currentView;
     let newView = this.getContentViewForQueryString(aQueryString);
     newView.place = aQueryString;
     if (oldView != newView) {
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -758,34 +758,16 @@ PlacesTreeView.prototype = {
 
     // Restore selection.
     if (nodesToReselect.length > 0) {
       this._restoreSelection(nodesToReselect, aNewParent);
       this.selection.selectEventsSuppressed = false;
     }
   },
 
-  /**
-   * Be careful, the parameter 'aIndex' here specifies the node's index in the
-   * parent node, not the visible index.
-   */
-  nodeReplaced:
-  function PTV_nodeReplaced(aParentNode, aOldNode, aNewNode, aIndexDoNotUse) {
-    NS_ASSERT(this._result, "Got a notification but have no result!");
-    if (!this._tree || !this._result)
-      return;
-
-    // Nothing to do if the replaced node was not set.
-    let row = this._getRowForNode(aOldNode);
-    if (row != -1) {
-      this._rows[row] = aNewNode;
-      this._tree.invalidateRow(row);
-    }
-  },
-
   _invalidateCellValue: function PTV__invalidateCellValue(aNode,
                                                           aColumnType) {
     NS_ASSERT(this._result, "Got a notification but have no result!");
     if (!this._tree || !this._result)
       return;
 
     // Nothing to do for the root node.
     if (aNode == this._rootNode)
--- a/browser/components/places/tests/browser/browser_410196_paste_into_tags.js
+++ b/browser/components/places/tests/browser/browser_410196_paste_into_tags.js
@@ -64,20 +64,17 @@ let tests = {
 
   makeHistVisit: function(aCallback) {
     // need to add a history object
     let testURI1 = NetUtil.newURI(MOZURISPEC);
     isnot(testURI1, null, "testURI is not null");
     addVisits(
       {uri: testURI1, transition: PlacesUtils.history.TRANSITION_TYPED},
       window,
-      function() {
-        ok(PlacesUtils.ghistory2.isVisited(testURI1), MOZURISPEC + " is a visited url.");
-        aCallback();
-      });
+      aCallback);
   },
 
   makeTag: function() {
     // create an initial tag to work with
     let bmId = add_bookmark(NetUtil.newURI(TEST_URL));
     ok(bmId > 0, "A bookmark was added");
     PlacesUtils.tagging.tagURI(NetUtil.newURI(TEST_URL), ["foo"]);
     let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(TEST_URL));
--- a/browser/components/places/tests/browser/browser_library_infoBox.js
+++ b/browser/components/places/tests/browser/browser_library_infoBox.js
@@ -17,19 +17,16 @@ var gLibrary;
 gTests.push({
   desc: "Bug 430148 - Remove or hide the more/less button in details pane...",
   run: function() {
     var PO = gLibrary.PlacesOrganizer;
     let ContentTree = gLibrary.ContentTree;
     var infoBoxExpanderWrapper = getAndCheckElmtById("infoBoxExpanderWrapper");
 
     function addVisitsCallback() {
-      var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
-      ok(bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been added.");
-
       // open all bookmarks node
       PO.selectLeftPaneQuery("AllBookmarks");
       isnot(PO._places.selectedNode, null,
             "Correctly selected all bookmarks node.");
       checkInfoBoxSelected(PO);
       ok(infoBoxExpanderWrapper.hidden,
          "Expander button is hidden for all bookmarks node.");
       checkAddInfoFieldsCollapsed(PO);
--- a/browser/components/places/tests/browser/browser_library_left_pane_commands.js
+++ b/browser/components/places/tests/browser/browser_library_left_pane_commands.js
@@ -14,20 +14,16 @@ var gTests = [];
 var gLibrary;
 
 //------------------------------------------------------------------------------
 
 gTests.push({
   desc: "Bug 489351 - Date containers under History in Library cannot be deleted/cut",
   run: function() {
     function addVisitsCallback() {
-      var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
-      // Add a visit.
-      ok(bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been added");
-
       // Select and open the left pane "History" query.
       var PO = gLibrary.PlacesOrganizer;
       PO.selectLeftPaneQuery('History');
       isnot(PO._places.selectedNode, null, "We correctly selected History");
 
       // Check that both delete and cut commands are disabled.
       ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
          "Cut command is disabled");
@@ -51,26 +47,30 @@ gTests.push({
       // Check that delete command is enabled but cut command is disabled.
       ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
          "Cut command is disabled");
       ok(PO._places.controller.isCommandEnabled("cmd_delete"),
          "Delete command is enabled");
 
       // Execute the delete command and check visit has been removed.
       PO._places.controller.doCommand("cmd_delete");
-      ok(!bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been removed");
 
       // Test live update of "History" query.
       is(historyNode.childCount, 0, "History node has no more children");
 
       historyNode.containerOpen = false;
-      nextTest();
+
+      let testURI = NetUtil.newURI(TEST_URI);
+      PlacesUtils.asyncHistory.isURIVisited(testURI, function(aURI, aIsVisited) {
+        ok(!aIsVisited, "Visit has been removed");
+        nextTest();
+      });
     }
     addVisits(
-      {uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
+      {uri: NetUtil.newURI(TEST_URI), visitDate: Date.now() * 1000,
         transition: PlacesUtils.history.TRANSITION_TYPED},
       window,
       addVisitsCallback);
   }
 });
 
 //------------------------------------------------------------------------------
 
@@ -92,17 +92,17 @@ gTests.push({
        "Delete command is disabled");
 
     var toolbarNode = PO._places.selectedNode
                         .QueryInterface(Ci.nsINavHistoryContainerResultNode);
     toolbarNode.containerOpen = true;
 
     // Add an History query to the toolbar.
     PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
-                                         PlacesUtils._uri("place:sort=4"),
+                                         NetUtil.newURI("place:sort=4"),
                                          0, // Insert at start.
                                          "special_query");
     // Get first child and check it is the "Most Visited" smart bookmark.
     ok(toolbarNode.childCount > 0, "Toolbar node has children");
     var queryNode = toolbarNode.getChild(0);
     is(queryNode.title, "special_query", "Query node is correctly selected");
 
     // Select query node.
--- a/browser/components/places/tests/browser/browser_library_views_liveupdate.js
+++ b/browser/components/places/tests/browser/browser_library_views_liveupdate.js
@@ -215,17 +215,16 @@ var bookmarksObserver = {
       default:
         isnot(node, null, "Found new Places node in left pane");
         ok(index >= 0, "Node is at index " + index);
     }
   },
 
   onBeginUpdateBatch: function PSB_onBeginUpdateBatch() {},
   onEndUpdateBatch: function PSB_onEndUpdateBatch() {},
-  onBeforeItemRemoved: function PSB_onBeforeItemRemoved(aItemId) {},
   onItemVisited: function() {},
   onItemChanged: function PSB_onItemChanged(aItemId, aProperty,
                                             aIsAnnotationProperty, aNewValue) {
     if (aProperty == "title") {
       let validator = function(aTreeRowIndex) {
         let tree = gLibrary.PlacesOrganizer._places;
         let cellText = tree.view.getCellText(aTreeRowIndex,
                                              tree.columns.getColumnAt(0));
--- a/browser/components/places/tests/browser/browser_views_liveupdate.js
+++ b/browser/components/places/tests/browser/browser_views_liveupdate.js
@@ -232,17 +232,16 @@ var bookmarksObserver = {
       [node, index] = searchItemInView(aItemId, views[i]);
       isnot(node, null, "Found new Places node in " + views[i]);
       is(index, aNewIndex, "Node is at index " + index);
     }
   },
 
   onBeginUpdateBatch: function PSB_onBeginUpdateBatch() {},
   onEndUpdateBatch: function PSB_onEndUpdateBatch() {},
-  onBeforeItemRemoved: function PSB_onBeforeItemRemoved(aItemId) {},
   onItemVisited: function() {},
 
   onItemChanged: function PSB_onItemChanged(aItemId, aProperty,
                                             aIsAnnotationProperty, aNewValue,
                                             aLastModified, aItemType,
                                             aParentId) {
     if (aProperty !== "title")
       return;
--- a/browser/components/places/tests/unit/test_PUIU_makeTransaction.js
+++ b/browser/components/places/tests/unit/test_PUIU_makeTransaction.js
@@ -23,18 +23,16 @@ function waitForBookmarkNotification(aNo
                                       aURI, aTitle)
     {
       return this.validate(arguments.callee.name, { id: aItemId,
                                                     index: aIndex,
                                                     type: aItemType,
                                                     url: aURI ? aURI.spec : null,
                                                     title: aTitle });
     },
-    onBeforeItemRemoved: function onBeforeItemRemoved()
-      this.validate(arguments.callee.name, arguments),
     onItemRemoved: function onItemRemoved()
       this.validate(arguments.callee.name, arguments),
     onItemChanged: function onItemChanged(aItemId, aProperty, aIsAnno,
                                           aNewValue, aLastModified, aItemType)
     {
       return this.validate(arguments.callee.name,
                            { id: aItemId,
                              get index() PlacesUtils.bookmarks.getItemIndex(this.id),
--- a/browser/components/places/tests/unit/test_browserGlue_corrupt.js
+++ b/browser/components/places/tests/unit/test_browserGlue_corrupt.js
@@ -22,17 +22,16 @@ let bookmarksObserver = {
   onBeginUpdateBatch: function() {},
   onEndUpdateBatch: function() {
     let itemId = bs.getIdForItemAt(bs.toolbarFolder, 0);
     do_check_neq(itemId, -1);
     if (anno.itemHasAnnotation(itemId, "Places/SmartBookmark"))
       continue_test();
   },
   onItemAdded: function() {},
-  onBeforeItemRemoved: function(id) {},
   onItemRemoved: function(id, folder, index, itemType) {},
   onItemChanged: function() {},
   onItemVisited: function(id, visitID, time) {},
   onItemMoved: function() {},
   QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
 };
 
 function run_test() {
--- a/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup.js
+++ b/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup.js
@@ -22,17 +22,16 @@ let bookmarksObserver = {
   onBeginUpdateBatch: function() {},
   onEndUpdateBatch: function() {
     let itemId = bs.getIdForItemAt(bs.toolbarFolder, 0);
     do_check_neq(itemId, -1);
     if (anno.itemHasAnnotation(itemId, "Places/SmartBookmark"))
       continue_test();
   },
   onItemAdded: function() {},
-  onBeforeItemRemoved: function(id) {},
   onItemRemoved: function(id, folder, index, itemType) {},
   onItemChanged: function() {},
   onItemVisited: function(id, visitID, time) {},
   onItemMoved: function() {},
   QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
 };
 
 function run_test() {
--- a/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup_default.js
+++ b/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup_default.js
@@ -22,17 +22,16 @@ let bookmarksObserver = {
   onBeginUpdateBatch: function() {},
   onEndUpdateBatch: function() {
     let itemId = bs.getIdForItemAt(bs.toolbarFolder, 0);
     do_check_neq(itemId, -1);
     if (anno.itemHasAnnotation(itemId, "Places/SmartBookmark"))
       continue_test();
   },
   onItemAdded: function() {},
-  onBeforeItemRemoved: function(id) {},
   onItemRemoved: function(id, folder, index, itemType) {},
   onItemChanged: function() {},
   onItemVisited: function(id, visitID, time) {},
   onItemMoved: function() {},
   QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
 };
 
 function run_test() {
--- a/browser/components/places/tests/unit/test_browserGlue_migrate.js
+++ b/browser/components/places/tests/unit/test_browserGlue_migrate.js
@@ -47,17 +47,16 @@ function run_test() {
         PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, 0);
       do_check_neq(itemId, -1);
       if (PlacesUtils.annotations
                      .itemHasAnnotation(itemId, "Places/SmartBookmark")) {
         do_execute_soon(onSmartBookmarksCreation);
       }
     },
     onItemAdded: function() {},
-    onBeforeItemRemoved: function(id) {},
     onItemRemoved: function(id, folder, index, itemType) {},
     onItemChanged: function() {},
     onItemVisited: function(id, visitID, time) {},
     onItemMoved: function() {},
     QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
   };
   // The test will continue once import has finished and smart bookmarks
   // have been created.
--- a/browser/components/places/tests/unit/test_browserGlue_restore.js
+++ b/browser/components/places/tests/unit/test_browserGlue_restore.js
@@ -22,17 +22,16 @@ let bookmarksObserver = {
   onBeginUpdateBatch: function() {},
   onEndUpdateBatch: function() {
     let itemId = bs.getIdForItemAt(bs.toolbarFolder, 0);
     do_check_neq(itemId, -1);
     if (anno.itemHasAnnotation(itemId, "Places/SmartBookmark"))
       continue_test();
   },
   onItemAdded: function() {},
-  onBeforeItemRemoved: function(id) {},
   onItemRemoved: function(id, folder, index, itemType) {},
   onItemChanged: function() {},
   onItemVisited: function(id, visitID, time) {},
   onItemMoved: function() {},
   QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
 };
 
 function run_test() {
--- a/browser/components/preferences/advanced.js
+++ b/browser/components/preferences/advanced.js
@@ -233,17 +233,18 @@ var gAdvancedPane = {
                                    .wrappedJSObject
                                    .policy;
 
     if (!policy) {
       return;
     }
 
     let checkbox = document.getElementById("submitHealthReportBox");
-    policy.healthReportUploadEnabled = checkbox.checked;
+    policy.recordHealthReportUploadEnabled(checkbox.checked,
+                                           "Checkbox from preferences pane");
   },
 #endif
 
   // NETWORK TAB
 
   /*
    * Preferences:
    *
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -222,17 +222,18 @@ var gAdvancedPane = {
                                    .wrappedJSObject
                                    .policy;
 
     if (!policy) {
       return;
     }
 
     let checkbox = document.getElementById("submitHealthReportBox");
-    policy.healthReportUploadEnabled = checkbox.checked;
+    policy.recordHealthReportUploadEnabled(checkbox.checked,
+                                           "Checkbox from preferences pane");
   },
 #endif
 
   // NETWORK TAB
 
   /*
    * Preferences:
    *
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -119,30 +119,16 @@ var gPrivacyPane = {
     case "custom":
       selectedIndex = 2;
       break;
     }
     document.getElementById("historyPane").selectedIndex = selectedIndex;
   },
 
   /**
-   * Open up the DNT "learn more" link.
-   */
-  openTrackingInfoSite: function PPP_openTrackingInfoSite()
-  {
-    let thisDocEl = document.documentElement,
-        openerDocEl = window.opener && window.opener.document.documentElement,
-        url = "https://www.mozilla.org/dnt";
-    if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
-      openUILinkIn(url, "window");
-    else
-      openUILinkIn(url, "tab");
-  },
-
-  /**
    * Update the Tracking preferences based on controls.
    */
   setTrackingPrefs: function PPP_setTrackingPrefs()
   {
     let dntRadioGroup = document.getElementById("doNotTrackSelection"),
         dntValuePref = document.getElementById("privacy.donottrackheader.value"),
         dntEnabledPref = document.getElementById("privacy.donottrackheader.enabled");
 
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -81,18 +81,18 @@
     <radio id="dntnotrack" value="1" label="&dntTrackingNotOkay.label;"
             accesskey="&dntTrackingNotOkay.accesskey;" />
     <radio id="dntdotrack" value="0" label="&dntTrackingOkay.label;"
             accesskey="&dntTrackingOkay.accesskey;" />
     <radio id="dntnopref" value="-1" label="&dntTrackingNopref.label;"
             accesskey="&dntTrackingNopref.accesskey;" />
   </radiogroup>
   <label class="text-link" id="doNotTrackInfo"
-          onclick="event.stopPropagation();gPrivacyPane.openTrackingInfoSite();"
-          value="&doNotTrackInfo.label;"/>
+         href="https://www.mozilla.org/dnt"
+         value="&doNotTrackInfo.label;"/>
 </groupbox>
 
 <!-- History -->
 <groupbox id="historyGroup" data-category="panePrivacy" hidden="true">
   <caption label="&history.label;"/>
   <hbox align="center">
     <label id="historyModeLabel"
            control="historyMode"
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -122,30 +122,16 @@ var gPrivacyPane = {
     case "custom":
       selectedIndex = 2;
       break;
     }
     document.getElementById("historyPane").selectedIndex = selectedIndex;
   },
 
   /**
-   * Open up the DNT "learn more" link.
-   */
-  openTrackingInfoSite: function PPP_openTrackingInfoSite()
-  {
-    let thisDocEl = document.documentElement,
-        openerDocEl = window.opener && window.opener.document.documentElement,
-        url = "https://www.mozilla.org/dnt";
-    if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
-      openUILinkIn(url, "window");
-    else
-      openUILinkIn(url, "tab");
-  },
-
-  /**
    * Update the Tracking preferences based on controls.
    */
   setTrackingPrefs: function PPP_setTrackingPrefs()
   {
     let dntRadioGroup = document.getElementById("doNotTrackSelection"),
         dntValuePref = document.getElementById("privacy.donottrackheader.value"),
         dntEnabledPref = document.getElementById("privacy.donottrackheader.enabled");
 
--- a/browser/components/preferences/privacy.xul
+++ b/browser/components/preferences/privacy.xul
@@ -92,17 +92,17 @@
         <radio id="dntnotrack" value="1" label="&dntTrackingNotOkay.label;"
                 accesskey="&dntTrackingNotOkay.accesskey;" />
         <radio id="dntdotrack" value="0" label="&dntTrackingOkay.label;"
                 accesskey="&dntTrackingOkay.accesskey;" />
         <radio id="dntnopref" value="-1" label="&dntTrackingNopref.label;"
                 accesskey="&dntTrackingNopref.accesskey;" />
       </radiogroup>
       <label class="text-link" id="doNotTrackInfo"
-             onclick="event.stopPropagation();gPrivacyPane.openTrackingInfoSite();"
+             href="https://www.mozilla.org/dnt"
              value="&doNotTrackInfo.label;"/>
 
     </groupbox>
 
     <!-- History -->
     <groupbox id="historyGroup">
       <caption label="&history.label;"/>
 
--- a/browser/components/privatebrowsing/test/browser/perwindow/Makefile.in
+++ b/browser/components/privatebrowsing/test/browser/perwindow/Makefile.in
@@ -10,32 +10,30 @@ relativesrcdir  = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_BROWSER_FILES =  \
 		head.js \
 		browser_privatebrowsing_certexceptionsui.js \
 		browser_privatebrowsing_concurrent.js \
 		browser_privatebrowsing_concurrent_page.html \
-		browser_privatebrowsing_cookieacceptdialog.js \
-		browser_privatebrowsing_cookieacceptdialog.html \
-		browser_privatebrowsing_crh.js \
 		browser_privatebrowsing_downloadLastDir.js \
 		browser_privatebrowsing_downloadLastDir_c.js \
 		browser_privatebrowsing_downloadLastDir_toggle.js \
 		browser_privatebrowsing_DownloadLastDirWithCPS.js \
 		browser_privatebrowsing_geoprompt.js \
 		browser_privatebrowsing_geoprompt_page.html \
 		browser_privatebrowsing_lastpbcontextexited.js \
 		browser_privatebrowsing_localStorage.js \
 		browser_privatebrowsing_localStorage_before_after.js \
 		browser_privatebrowsing_localStorage_before_after_page.html \
 		browser_privatebrowsing_localStorage_before_after_page2.html \
 		browser_privatebrowsing_localStorage_page1.html \
 		browser_privatebrowsing_localStorage_page2.html \
+		browser_privatebrowsing_nonbrowser.js \
 		browser_privatebrowsing_opendir.js \
 		browser_privatebrowsing_openlocation.js \
 		browser_privatebrowsing_openLocationLastURL.js \
 		browser_privatebrowsing_placestitle.js \
 		browser_privatebrowsing_placesTitleNoUpdate.js \
 		browser_privatebrowsing_placesTitleNoUpdate.html \
 		browser_privatebrowsing_popupblocker.js \
 		browser_privatebrowsing_protocolhandler.js \
@@ -46,9 +44,18 @@ MOCHITEST_BROWSER_FILES =  \
 		browser_privatebrowsing_windowtitle.js \
 		browser_privatebrowsing_windowtitle_page.html \
 		browser_privatebrowsing_zoom.js \
 		browser_privatebrowsing_zoomrestore.js \
 		popup.html \
 		title.sjs \
 		$(NULL)
 
+# Temporarily disabled on OS X for bug 822284
+ifneq ($(MOZ_WIDGET_TOOLKIT),cocoa)
+MOCHITEST_BROWSER_FILES += \
+		browser_privatebrowsing_cookieacceptdialog.js \
+		browser_privatebrowsing_cookieacceptdialog.html \
+		browser_privatebrowsing_crh.js \
+		$(NULL)
+endif
+
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/perwindow/browser_privatebrowsing_nonbrowser.js
@@ -0,0 +1,33 @@
+/* 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/. */
+
+function test() {
+  waitForExplicitFinish();
+
+  let windowsToClose = [];
+  registerCleanupFunction(function() {
+    windowsToClose.forEach(function(win) {
+      win.close();
+    });
+  });
+
+  let win = OpenBrowserWindow({private: true});
+  win.addEventListener("load", function onLoad() {
+    win.removeEventListener("load", onLoad, false);
+    let consoleWin = win.open("chrome://global/content/console.xul", "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
+    consoleWin.addEventListener("load", function consoleLoad() {
+      consoleWin.removeEventListener("load", consoleLoad, false);
+      win.close();
+    }, false);
+    windowsToClose.push(consoleWin);
+  }, false);
+
+  let observer = function() {
+    is(true, true, "observer fired");
+    Services.obs.removeObserver(observer, "last-pb-context-exited");
+    executeSoon(finish);
+  };
+  Services.obs.addObserver(observer, "last-pb-context-exited", false);
+  windowsToClose.push(win);
+}
\ No newline at end of file
--- a/browser/components/privatebrowsing/test/browser/perwindow/browser_privatebrowsing_placesTitleNoUpdate.js
+++ b/browser/components/privatebrowsing/test/browser/perwindow/browser_privatebrowsing_placesTitleNoUpdate.js
@@ -41,17 +41,16 @@ function test() {
           case 2:
             afterUpdateVisit();
           break;
         }
       },
       onBeginUpdateBatch: function () {},
       onEndUpdateBatch: function () {},
       onVisit: function () {},
-      onBeforeDeleteURI: function () {},
       onDeleteURI: function () {},
       onClearHistory: function () {},
       onPageChanged: function () {},
       onDeleteVisits: function() {},
       QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
     };
     PlacesUtils.history.addObserver(historyObserver, false);
 
--- a/browser/components/privatebrowsing/test/browser/perwindow/browser_privatebrowsing_placestitle.js
+++ b/browser/components/privatebrowsing/test/browser/perwindow/browser_privatebrowsing_placestitle.js
@@ -58,17 +58,16 @@ function test() {
           // title change.
           ok(false, "Title changed. Unexpected pass: " + testNumber);
       }
     },
 
     onBeginUpdateBatch: function () {},
     onEndUpdateBatch: function () {},
     onVisit: function () {},
-    onBeforeDeleteURI: function () {},
     onDeleteURI: function () {},
     onClearHistory: function () {},
     onPageChanged: function () {},
     onDeleteVisits: function() {},
     QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
   };
   PlacesUtils.history.addObserver(historyObserver, false);
 
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -1447,17 +1447,19 @@ let SessionStoreInternal = {
     tabState.index += aDelta;
     tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
     tabState.pinned = false;
 
     this._setWindowStateBusy(aWindow);
     let newTab = aTab == aWindow.gBrowser.selectedTab ?
       aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab}) :
       aWindow.gBrowser.addTab();
-    this.restoreHistoryPrecursor(aWindow, [newTab], [tabState], 0, 0, 0);
+
+    this.restoreHistoryPrecursor(aWindow, [newTab], [tabState], 0, 0, 0,
+                                 true /* Load this tab right away. */);
 
     return newTab;
   },
 
   getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
     if (!aWindow.__SSi && aWindow.__SS_dyingCache)
       return aWindow.__SS_dyingCache._closedTabs.length;
     if (!aWindow.__SSi)
@@ -1669,23 +1671,16 @@ let SessionStoreInternal = {
 
     // We want to re-use the last opened window instead of opening a new one in
     // the case where it's "empty" and not associated with a window in the session.
     // We will do more processing via _prepWindowToRestoreInto if we need to use
     // the lastWindow.
     let lastWindow = this._getMostRecentBrowserWindow();
     let canUseLastWindow = lastWindow &&
                            !lastWindow.__SS_lastSessionWindowID;
-    let lastSessionFocusedWindow = null;
-    this.windowToFocus = lastWindow;
-
-    // move the last focused window to the start of the array so that we
-    // minimize window movement (see bug 669272)
-    lastSessionState.windows.unshift(
-      lastSessionState.windows.splice(lastSessionState.selectedWindow - 1, 1)[0]);
 
     // Restore into windows or open new ones as needed.
     for (let i = 0; i < lastSessionState.windows.length; i++) {
       let winState = lastSessionState.windows[i];
       let lastSessionWindowID = winState.__lastSessionWindowID;
       // delete lastSessionWindowID so we don't add that to the window again
       delete winState.__lastSessionWindowID;
 
@@ -1713,28 +1708,19 @@ let SessionStoreInternal = {
         // Restore into that window - pretend it's a followup since we'll already
         // have a focused window.
         //XXXzpao This is going to merge extData together (taking what was in
         //        winState over what is in the window already. The hack we have
         //        in _preWindowToRestoreInto will prevent most (all?) Panorama
         //        weirdness but we will still merge other extData.
         //        Bug 588217 should make this go away by merging the group data.
         this.restoreWindow(windowToUse, { windows: [winState] }, canOverwriteTabs, true);
-        if (i == 0)
-          lastSessionFocusedWindow = windowToUse;
-
-        // if we overwrote the tabs for our last focused window, we should
-        // give focus to the window that had it in the previous session
-        if (canOverwriteTabs && windowToUse == lastWindow)
-          this.windowToFocus = lastSessionFocusedWindow;
       }
       else {
-        let win = this._openWindowWithState({ windows: [winState] });
-        if (i == 0)
-          lastSessionFocusedWindow = win;
+        this._openWindowWithState({ windows: [winState] });
       }
     }
 
     // Merge closed windows from this session with ones from last session
     if (lastSessionState._closedWindows) {
       this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows);
       this._capClosedWindows();
     }
@@ -2647,27 +2633,27 @@ let SessionStoreInternal = {
     this._setWindowStateBusy(aWindow);
 
     if (root._closedWindows)
       this._closedWindows = root._closedWindows;
 
     var winData;
     if (!root.selectedWindow || root.selectedWindow > root.windows.length) {
       root.selectedWindow = 0;
-    } else {
-      // put the selected window at the beginning of the array to ensure that
-      // it gets restored first
-      root.windows.unshift(root.windows.splice(root.selectedWindow - 1, 1)[0]);
     }
+
     // open new windows for all further window entries of a multi-window session
     // (unless they don't contain any tab data)
     for (var w = 1; w < root.windows.length; w++) {
       winData = root.windows[w];
       if (winData && winData.tabs && winData.tabs[0]) {
         var window = this._openWindowWithState({ windows: [winData] });
+        if (w == root.selectedWindow - 1) {
+          this.windowToFocus = window;
+        }
       }
     }
     winData = root.windows[0];
     if (!winData.tabs) {
       winData.tabs = [];
     }
     // don't restore a single blank tab when we've had an external
     // URL passed in for loading at startup (cf. bug 357419)
@@ -2883,33 +2869,38 @@ let SessionStoreInternal = {
    * @param aTabData
    *        Array of tab data
    * @param aSelectTab
    *        Index of selected tab
    * @param aIx
    *        Index of the next tab to check readyness for
    * @param aCount
    *        Counter for number of times delaying b/c browser or history aren't ready
+   * @param aRestoreImmediately
+   *        Flag to indicate whether the given set of tabs aTabs should be
+   *        restored/loaded immediately even if restore_on_demand = true
    */
   restoreHistoryPrecursor:
-    function ssi_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, aIx, aCount) {
+    function ssi_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab,
+                                         aIx, aCount, aRestoreImmediately = false) {
     var tabbrowser = aWindow.gBrowser;
 
     // make sure that all browsers and their histories are available
     // - if one's not, resume this check in 100ms (repeat at most 10 times)
     for (var t = aIx; t < aTabs.length; t++) {
       try {
         if (!tabbrowser.getBrowserForTab(aTabs[t]).webNavigation.sessionHistory) {
           throw new Error();
         }
       }
       catch (ex) { // in case browser or history aren't ready yet
         if (aCount < 10) {
           var restoreHistoryFunc = function(self) {
-            self.restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, aIx, aCount + 1);
+            self.restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab,
+                                         aIx, aCount + 1, aRestoreImmediately);
           }
           aWindow.setTimeout(restoreHistoryFunc, 100, this);
           return;
         }
       }
     }
 
     if (!this._isWindowLoaded(aWindow)) {
@@ -2998,32 +2989,37 @@ let SessionStoreInternal = {
         }
       }
     }
 
     // helper hashes for ensuring unique frame IDs and unique document
     // identifiers.
     var idMap = { used: {} };
     var docIdentMap = {};
-    this.restoreHistory(aWindow, aTabs, aTabData, idMap, docIdentMap);
+    this.restoreHistory(aWindow, aTabs, aTabData, idMap, docIdentMap,
+                        aRestoreImmediately);
   },
 
   /**
    * Restore history for a window
    * @param aWindow
    *        Window reference
    * @param aTabs
    *        Array of tab references
    * @param aTabData
    *        Array of tab data
    * @param aIdMap
    *        Hash for ensuring unique frame IDs
+   * @param aRestoreImmediately
+   *        Flag to indicate whether the given set of tabs aTabs should be
+   *        restored/loaded immediately even if restore_on_demand = true
    */
   restoreHistory:
-    function ssi_restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap) {
+    function ssi_restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap,
+                                aRestoreImmediately) {
     var _this = this;
     // if the tab got removed before being completely restored, then skip it
     while (aTabs.length > 0 && !(this._canRestoreTabHistory(aTabs[0]))) {
       aTabs.shift();
       aTabData.shift();
     }
     if (aTabs.length == 0) {
       // At this point we're essentially ready for consumers to read/write data
@@ -3080,22 +3076,23 @@ let SessionStoreInternal = {
 
     // notify the tabbrowser that the tab chrome has been restored
     var event = aWindow.document.createEvent("Events");
     event.initEvent("SSTabRestoring", true, false);
     tab.dispatchEvent(event);
 
     // Restore the history in the next tab
     aWindow.setTimeout(function(){
-      _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap);
+      _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap,
+                           aRestoreImmediately);
     }, 0);
 
     // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
     // it ensures each window will have its selected tab loaded.
-    if (aWindow.gBrowser.selectedBrowser == browser) {
+    if (aRestoreImmediately || aWindow.gBrowser.selectedBrowser == browser) {
       this.restoreTab(tab);
     }
     else {
       // Put the tab into the right bucket
       if (tabData.pinned)
         this._tabsToRestore.priority.push(tab);
       else if (tabData.hidden)
         this._tabsToRestore.hidden.push(tab);
@@ -3611,20 +3608,22 @@ let SessionStoreInternal = {
    *        Bool update all windows
    */
   saveState: function ssi_saveState(aUpdateAll) {
     // If crash recovery is disabled, we only want to resume with pinned tabs
     // if we crash.
     let pinnedOnly = this._loadState == STATE_RUNNING && !this._resume_from_crash;
 
     TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_DATA_MS");
+    TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
 
     var oState = this._getCurrentState(aUpdateAll, pinnedOnly);
     if (!oState) {
       TelemetryStopwatch.cancel("FX_SESSION_RESTORE_COLLECT_DATA_MS");
+      TelemetryStopwatch.cancel("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
       return;
     }
 
     // Forget about private windows.
     for (let i = oState.windows.length - 1; i >= 0; i--) {
       if (oState.windows[i].isPrivate) {
         oState.windows.splice(i, 1);
         if (oState.selectedWindow >= i) {
@@ -3668,27 +3667,30 @@ let SessionStoreInternal = {
       }
     }
 
     // Persist the last session if we deferred restoring it
     if (this._lastSessionState)
       oState.lastSessionState = this._lastSessionState;
 
     TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_DATA_MS");
+    TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
 
     this._saveStateObject(oState);
   },
 
   /**
    * write a state object to disk
    */
   _saveStateObject: function ssi_saveStateObject(aStateObj) {
     TelemetryStopwatch.start("FX_SESSION_RESTORE_SERIALIZE_DATA_MS");
+    TelemetryStopwatch.start("FX_SESSION_RESTORE_SERIALIZE_DATA_LONGEST_OP_MS");
     let data = this._toJSONString(aStateObj);
     TelemetryStopwatch.finish("FX_SESSION_RESTORE_SERIALIZE_DATA_MS");
+    TelemetryStopwatch.finish("FX_SESSION_RESTORE_SERIALIZE_DATA_LONGEST_OP_MS");
 
     let stateString = this._createSupportsString(data);
     Services.obs.notifyObservers(stateString, "sessionstore-state-write", "");
     data = stateString.data;
 
     // Don't touch the file if an observer has deleted all state data.
     if (!data) {
       return;
--- a/browser/components/thumbnails/PageThumbs.jsm
+++ b/browser/components/thumbnails/PageThumbs.jsm
@@ -508,14 +508,13 @@ let PageThumbsHistoryObserver = {
   onClearHistory: function Thumbnails_onClearHistory() {
     PageThumbsStorage.wipe();
   },
 
   onTitleChanged: function () {},
   onBeginUpdateBatch: function () {},
   onEndUpdateBatch: function () {},
   onVisit: function () {},
-  onBeforeDeleteURI: function () {},
   onPageChanged: function () {},
   onDeleteVisits: function () {},
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
 };
--- a/browser/config/mozconfigs/linux32/debug-asan
+++ b/browser/config/mozconfigs/linux32/debug-asan
@@ -9,9 +9,12 @@ ac_add_options --enable-valgrind
 . $topsrcdir/build/unix/mozconfig.asan
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux32/nightly-asan
+++ b/browser/config/mozconfigs/linux32/nightly-asan
@@ -11,9 +11,12 @@ ac_add_options --enable-codesighs
 . $topsrcdir/build/unix/mozconfig.asan
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux64/debug-asan
+++ b/browser/config/mozconfigs/linux64/debug-asan
@@ -9,9 +9,12 @@ ac_add_options --enable-valgrind
 . $topsrcdir/build/unix/mozconfig.asan
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux64/nightly-asan
+++ b/browser/config/mozconfigs/linux64/nightly-asan
@@ -11,9 +11,12 @@ ac_add_options --enable-codesighs
 . $topsrcdir/build/unix/mozconfig.asan
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/macosx64/debug-asan
+++ b/browser/config/mozconfigs/macosx64/debug-asan
@@ -5,9 +5,12 @@ ac_add_options --enable-debug
 ac_add_options --enable-optimize="-O1"
 ac_add_options --enable-accessibility
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
 ac_add_options --with-macbundlename-prefix=Firefox
 
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -20,17 +20,16 @@ if test "$OS_ARCH" = "WINNT"; then
         MOZ_STUB_INSTALLER=1
       fi
     fi
   fi
 fi
 
 MOZ_CHROME_FILE_FORMAT=omni
 MOZ_SAFE_BROWSING=1
-MOZ_SERVICES_AITC=1
 MOZ_SERVICES_COMMON=1
 MOZ_SERVICES_CRYPTO=1
 MOZ_SERVICES_HEALTHREPORT=1
 MOZ_SERVICES_METRICS=1
 MOZ_SERVICES_SYNC=1
 MOZ_APP_VERSION=$FIREFOX_VERSION
 MOZ_EXTENSIONS_DEFAULT=" gio"
 # MOZ_APP_DISPLAYNAME will be set by branding/configure.sh
--- a/browser/devtools/markupview/MarkupView.jsm
+++ b/browser/devtools/markupview/MarkupView.jsm
@@ -116,18 +116,17 @@ MarkupView.prototype = {
     let walker = this.doc.createTreeWalker(
       aStart || this._elt,
       Ci.nsIDOMNodeFilter.SHOW_ELEMENT,
       function(aElement) {
         if (aElement.container && aElement.container.visible) {
           return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
         }
         return Ci.nsIDOMNodeFilter.FILTER_SKIP;
-      },
-      false
+      }
     );
     walker.currentNode = this._selectedContainer.elt;
     return walker;
   },
 
   /**
    * Key handling.
    */
@@ -778,16 +777,24 @@ function MarkupContainer(aMarkupView, aN
   }.bind(this));
 
   this.codeBox.insertBefore(this.editor.elt, this.children);
 
   this.editor.elt.addEventListener("mousedown", function(evt) {
     this.markup.navigate(this);
   }.bind(this), false);
 
+  if (this.editor.summaryElt) {
+    this.editor.summaryElt.addEventListener("click", function(evt) {
+      this.markup.navigate(this);
+      this.markup.expandNode(this.node);
+    }.bind(this), false);
+    this.codeBox.appendChild(this.editor.summaryElt);
+  }
+
   if (this.editor.closeElt) {
     this.editor.closeElt.addEventListener("mousedown", function(evt) {
       this.markup.navigate(this);
     }.bind(this), false);
     this.codeBox.appendChild(this.editor.closeElt);
   }
 
 }
@@ -818,19 +825,25 @@ MarkupContainer.prototype = {
   get expanded() {
     return this.children.hasAttribute("expanded");
   },
 
   set expanded(aValue) {
     if (aValue) {
       this.expander.setAttribute("expanded", "");
       this.children.setAttribute("expanded", "");
+      if (this.editor.summaryElt) {
+        this.editor.summaryElt.setAttribute("expanded", "");
+      }
     } else {
       this.expander.removeAttribute("expanded");
       this.children.removeAttribute("expanded");
+      if (this.editor.summaryElt) {
+        this.editor.summaryElt.removeAttribute("expanded");
+      }
     }
   },
 
   /**
    * True if the container is visible in the markup tree.
    */
   get visible()
   {
@@ -982,21 +995,27 @@ function ElementEditor(aContainer, aNode
 
   this.attrs = [];
 
   // The templates will fill the following properties
   this.elt = null;
   this.tag = null;
   this.attrList = null;
   this.newAttr = null;
+  this.summaryElt = null;
   this.closeElt = null;
 
   // Create the main editor
   this.template("element", this);
 
+  if (this.node.firstChild || this.node.textContent.length > 0) {
+    // Create the summary placeholder
+    this.template("elementContentSummary", this);
+  }
+
   // Create the closing tag
   this.template("elementClose", this);
 
   // Make the tag name editable (unless this is a document element)
   if (aNode != aNode.ownerDocument.documentElement) {
     this.tag.setAttribute("tabindex", "0");
     _editableField({
       element: this.tag,
@@ -1291,34 +1310,33 @@ ElementEditor.prototype = {
 
 RootContainer.prototype = {
   hasChildren: true,
   expanded: true,
   update: function RC_update() {}
 };
 
 function documentWalker(node) {
-  return new DocumentWalker(node, Ci.nsIDOMNodeFilter.SHOW_ALL, whitespaceTextFilter, false);
+  return new DocumentWalker(node, Ci.nsIDOMNodeFilter.SHOW_ALL, whitespaceTextFilter);
 }
 
 function nodeDocument(node) {
   return node.ownerDocument || (node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ? node : null);
 }
 
 /**
  * Similar to a TreeWalker, except will dig in to iframes and it doesn't
  * implement the good methods like previousNode and nextNode.
  *
  * See TreeWalker documentation for explanations of the methods.
  */
-function DocumentWalker(aNode, aShow, aFilter, aExpandEntityReferences)
+function DocumentWalker(aNode, aShow, aFilter)
 {
   let doc = nodeDocument(aNode);
-  this.walker = doc.createTreeWalker(nodeDocument(aNode),
-    aShow, aFilter, aExpandEntityReferences);
+  this.walker = doc.createTreeWalker(nodeDocument(aNode), aShow, aFilter);
   this.walker.currentNode = aNode;
   this.filter = aFilter;
 }
 
 DocumentWalker.prototype = {
   get node() this.walker.node,
   get whatToShow() this.walker.whatToShow,
   get expandEntityReferences() this.walker.expandEntityReferences,
--- a/browser/devtools/markupview/markup-view.css
+++ b/browser/devtools/markupview/markup-view.css
@@ -15,16 +15,24 @@ ul.children:not([expanded]) {
 }
 
 .newattr {
   display: inline-block;
   width: 1em;
   height: 1ex;
 }
 
+.summary {
+  cursor: pointer;
+}
+
+.summary[expanded] {
+  display: none;
+}
+
 #root {
   margin-right: 80px;
 }
 
 /* Preview */
 
 #previewbar {
   position: fixed;
--- a/browser/devtools/markupview/markup-view.xhtml
+++ b/browser/devtools/markupview/markup-view.xhtml
@@ -27,16 +27,18 @@
     <span id="template-text" save="${elt}" class="editor text">
       <pre save="${value}" style="display:inline-block;" tabindex="0"></pre>
     </span>
 
     <span id="template-comment" save="${elt}" class="editor comment devtools-theme-comment">
       <span>&lt;!--</span><pre save="${value}" style="display:inline-block;" tabindex="0"></pre><span>--&gt;</span>
     </span>
 
+    <span id="template-elementContentSummary" save="${summaryElt}" class="summary"> … </span>
+
     <span id="template-elementClose" save="${closeElt}">&lt;/<span save="${closeTag}" class="tagname devtools-theme-tagname"></span>&gt;</span>
    </div>
    <div id="previewbar" class="disabled">
      <div id="preview"/>
      <div id="viewbox"/>
    </div>
 </body>
 </html>
--- a/browser/devtools/markupview/test/browser_inspector_markup_mutation.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_mutation.js
@@ -26,17 +26,17 @@ function test() {
 
   let inspector;
 
   // Strip whitespace from a node and its children.
   function stripWhitespace(node)
   {
     node.normalize();
     let iter = node.ownerDocument.createNodeIterator(node, NodeFilter.SHOW_TEXT + NodeFilter.SHOW_COMMENT,
-      null, false);
+      null);
 
     while ((node = iter.nextNode())) {
       node.nodeValue = node.nodeValue.replace(/\s+/g, '');
       if (node.nodeType == Node.TEXT_NODE &&
         !/[^\s]/.exec(node.nodeValue)) {
         node.parentNode.removeChild(node);
       }
     }
--- a/browser/devtools/shared/DOMHelpers.jsm
+++ b/browser/devtools/shared/DOMHelpers.jsm
@@ -91,17 +91,17 @@ DOMHelpers.prototype = {
 
     return null;  // we have no children worth showing.
   },
 
   getFirstChild: function Helpers_getFirstChild(node)
   {
     let SHOW_ALL = Components.interfaces.nsIDOMNodeFilter.SHOW_ALL;
     this.treeWalker = node.ownerDocument.createTreeWalker(node,
-      SHOW_ALL, null, false);
+      SHOW_ALL, null);
     return this.treeWalker.firstChild();
   },
 
   getNextSibling: function Helpers_getNextSibling(node)
   {
     let next = this.treeWalker.nextSibling();
 
     if (!next)
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -874,17 +874,17 @@ TextProperty.prototype = {
  * @constructor
  */
 this.CssRuleView = function CssRuleView(aDoc, aStore)
 {
   this.doc = aDoc;
   this.store = aStore;
   this.element = this.doc.createElementNS(XUL_NS, "vbox");
   this.element.setAttribute("tabindex", "0");
-  this.element.classList.add("ruleview");
+  this.element.className = "ruleview devtools-monospace";
   this.element.flex = 1;
 
   this._boundCopy = this._onCopy.bind(this);
   this.element.addEventListener("copy", this._boundCopy);
 
   this._createContextMenu();
   this._showEmpty();
 }
--- a/browser/devtools/styleinspector/csshtmltree.xul
+++ b/browser/devtools/styleinspector/csshtmltree.xul
@@ -1,16 +1,17 @@
 <?xml version="1.0"?>
 <!-- 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/. -->
 
 <?xml-stylesheet href="chrome://global/skin/global.css"?>
 <?xml-stylesheet href="chrome://browser/content/devtools/styleinspector.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/csshtmltree.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
 
 <!DOCTYPE window [
   <!ENTITY % inspectorDTD SYSTEM "chrome://browser/locale/devtools/styleinspector.dtd">
   %inspectorDTD;
   <!ELEMENT loop ANY>
   <!ATTLIST li foreach CDATA #IMPLIED>
   <!ATTLIST div foreach CDATA #IMPLIED>
   <!ATTLIST loop foreach CDATA #IMPLIED>
@@ -35,25 +36,25 @@
   window.onunload = function() {
     if (this.computedview) {
       this.computedview.destroy();
     }
   }
 </script>
 
 <!-- The output from #templateRoot (below) is inserted here. -->
-<div id="root"></div>
+<div id="root" class="devtools-monospace"></div>
 
 <!-- When no properties are found the following block is displayed. -->
 <div id="noResults" hidden="">
   &noPropertiesFound;
 </div>
 
 <!-- The output from #templateProperty (below) is appended here. -->
-<table id="propertyContainer">
+<table id="propertyContainer" class="devtools-monospace">
 </table>
 
 <xul:hbox id="footer">
   <xul:label class="legendKey bestmatch">&bestMatch;</xul:label>
   <xul:label class="legendKey matched">&matched;</xul:label>
   <xul:label class="legendKey parentmatch">&parentMatch;</xul:label>
 </xul:hbox>
 <!--
--- a/browser/devtools/styleinspector/cssruleview.xul
+++ b/browser/devtools/styleinspector/cssruleview.xul
@@ -1,15 +1,17 @@
 <?xml version="1.0"?>
 <!-- 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/. -->
 <?xml-stylesheet href="chrome://global/skin/global.css"?>
 <?xml-stylesheet href="chrome://browser/content/devtools/styleinspector.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/csshtmltree.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
+
 <!DOCTYPE window [
   <!ENTITY % inspectorDTD SYSTEM "chrome://browser/locale/devtools/styleinspector.dtd">
   %inspectorDTD;
 ]>
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="&ruleViewTitle;">
   <script type="application/javascript;version=1.8">
     window.setPanel = function(panel, iframe) {
       Components.utils.import("resource:///modules/devtools/StyleInspector.jsm");
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 0.7.82
+Current extension version is: 0.7.180
 
--- a/browser/extensions/pdfjs/components/PdfStreamConverter.js
+++ b/browser/extensions/pdfjs/components/PdfStreamConverter.js
@@ -196,19 +196,20 @@ PdfDataListener.prototype = {
     }
     if (this.errorCode) {
       value(null, this.errorCode);
     }
   }
 };
 
 // All the priviledged actions.
-function ChromeActions(domWindow, dataListener) {
+function ChromeActions(domWindow, dataListener, contentDispositionFilename) {
   this.domWindow = domWindow;
   this.dataListener = dataListener;
+  this.contentDispositionFilename = contentDispositionFilename;
 }
 
 ChromeActions.prototype = {
   isInPrivateBrowsing: function() {
     let docIsPrivate;
     try {
       docIsPrivate = PrivateBrowsingUtils.isWindowPrivate(this.domWindow);
     } catch (x) {
@@ -227,16 +228,17 @@ ChromeActions.prototype = {
     }
     // caching the result
     this.isInPrivateBrowsing = function isInPrivateBrowsingCached() {
       return docIsPrivate;
     };
     return docIsPrivate;
   },
   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 blobUri = data.blobUrl ? NetUtil.newURI(data.blobUrl) : originalUri;
     var extHelperAppSvc =
           Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
              getService(Ci.nsIExternalHelperAppService);
@@ -254,19 +256,23 @@ ChromeActions.prototype = {
         if (sendResponse)
           sendResponse(true);
         return;
       }
       // Create a nsIInputStreamChannel so we can set the url on the channel
       // so the filename will be correct.
       let channel = Cc['@mozilla.org/network/input-stream-channel;1'].
                        createInstance(Ci.nsIInputStreamChannel);
+      channel.QueryInterface(Ci.nsIChannel);
+      channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT;
+      if (self.contentDispositionFilename) {
+        channel.contentDispositionFilename = self.contentDispositionFilename;
+      }
       channel.setURI(originalUri);
       channel.contentStream = aInputStream;
-      channel.QueryInterface(Ci.nsIChannel);
       if ('nsIPrivateBrowsingChannel' in Ci &&
           channel instanceof Ci.nsIPrivateBrowsingChannel) {
         channel.setPrivate(docIsPrivate);
       }
 
       var listener = {
         extListener: null,
         onStartRequest: function(aRequest, aContext) {
@@ -578,16 +584,20 @@ PdfStreamConverter.prototype = {
 
   // nsIRequestObserver::onStartRequest
   onStartRequest: function(aRequest, aContext) {
     // Setup the request so we can use it below.
     aRequest.QueryInterface(Ci.nsIChannel);
     // Creating storage for PDF data
     var contentLength = aRequest.contentLength;
     var dataListener = new PdfDataListener(contentLength);
+    var contentDispositionFilename;
+    try {
+      contentDispositionFilename = aRequest.contentDispositionFilename;
+    } catch (e) {}
     this.dataListener = dataListener;
     this.binaryStream = Cc['@mozilla.org/binaryinputstream;1']
                         .createInstance(Ci.nsIBinaryInputStream);
 
     // Change the content type so we don't get stuck in a loop.
     aRequest.contentType = 'text/html';
 
     // Create a new channel that is viewer loaded as a resource.
@@ -608,17 +618,18 @@ PdfStreamConverter.prototype = {
         listener.onDataAvailable(aRequest, context, inputStream, offset, count);
       },
       onStopRequest: function(request, context, statusCode) {
         // We get the DOM window here instead of before the request since it
         // may have changed during a redirect.
         var domWindow = getDOMWindow(channel);
         // Double check the url is still the correct one.
         if (domWindow.document.documentURIObject.equals(aRequest.URI)) {
-          let actions = new ChromeActions(domWindow, dataListener);
+          let actions = new ChromeActions(domWindow, dataListener,
+                                          contentDispositionFilename);
           let requestListener = new RequestListener(actions);
           domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {
             requestListener.receive(event);
           }, false, true);
           if (actions.supportsIntegratedFind()) {
             var chromeWindow = getChromeWindow(domWindow);
             var findEventManager = new FindEventManager(chromeWindow.gFindBar,
                                                         domWindow,
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -11,18 +11,18 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 var PDFJS = {};
-PDFJS.version = '0.7.82';
-PDFJS.build = 'd467790';
+PDFJS.version = '0.7.180';
+PDFJS.build = '3699c31';
 
 (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
@@ -34,16 +34,20 @@ PDFJS.build = 'd467790';
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+/* globals assertWellFormed, calculateMD5, Catalog, error, info, isArray,
+           isArrayBuffer, isDict, isName, isStream, isString, Lexer,
+           Linearization, NullStream, PartialEvaluator, shadow, Stream,
+           StreamsSequenceStream, stringToPDFString, TODO, Util, warn, XRef */
 
 'use strict';
 
 var globalScope = (typeof window === 'undefined') ? this : window;
 
 var isWorker = (typeof window == 'undefined');
 
 var ERRORS = 0, WARNINGS = 1, INFOS = 5;
@@ -92,17 +96,17 @@ function getPdf(arg, callback) {
   var calledErrorBack = false;
 
   if ('error' in params) {
     xhr.onerror = function errorBack() {
       if (!calledErrorBack) {
         calledErrorBack = true;
         params.error();
       }
-    }
+    };
   }
 
   xhr.onreadystatechange = function getPdfOnreadystatechange(e) {
     if (xhr.readyState === 4) {
       if (xhr.status === xhr.expected) {
         var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
                     xhr.responseArrayBuffer || xhr.response);
         callback(data);
@@ -172,17 +176,17 @@ var Page = (function PageClosure() {
       return shadow(this, 'view', cropBox);
     },
     get annotations() {
       return shadow(this, 'annotations', this.inheritPageProp('Annots'));
     },
     get rotate() {
       var rotate = this.inheritPageProp('Rotate') || 0;
       // Normalize rotation so it's a multiple of 90 and between 0 and 270
-      if (rotate % 90 != 0) {
+      if (rotate % 90 !== 0) {
         rotate = 0;
       } else if (rotate >= 360) {
         rotate = rotate % 360;
       } else if (rotate < 0) {
         // The spec doesn't cover negatives, assume its counterclockwise
         // rotation. The following is the other implementation of modulo.
         rotate = ((rotate % 360) + 360) % 360;
       }
@@ -551,17 +555,18 @@ var PDFDocument = (function PDFDocumentC
     get numPages() {
       var linearization = this.linearization;
       var num = linearization ? linearization.numPages : this.catalog.numPages;
       // shadow the prototype getter
       return shadow(this, 'numPages', num);
     },
     getDocumentInfo: function PDFDocument_getDocumentInfo() {
       var docInfo = {
-        PDFFormatVersion: this.pdfFormatVersion
+        PDFFormatVersion: this.pdfFormatVersion,
+        IsAcroFormPresent: !!this.acroForm
       };
       if (this.xref.trailer.has('Info')) {
         var infoDict = this.xref.trailer.get('Info');
 
         var validEntries = DocumentInfoValidators.entries;
         // Only fill the document info with valid entries from the spec.
         for (var key in validEntries) {
           if (infoDict.has(key)) {
@@ -762,16 +767,28 @@ var InvalidPDFException = (function Inva
   }
 
   InvalidPDFException.prototype = new Error();
   InvalidPDFException.constructor = InvalidPDFException;
 
   return InvalidPDFException;
 })();
 
+var MissingPDFException = (function MissingPDFExceptionClosure() {
+  function MissingPDFException(msg) {
+    this.name = 'MissingPDFException';
+    this.message = msg;
+  }
+
+  MissingPDFException.prototype = new Error();
+  MissingPDFException.constructor = MissingPDFException;
+
+  return MissingPDFException;
+})();
+
 function bytesToString(bytes) {
   var str = '';
   var length = bytes.length;
   for (var n = 0; n < length; ++n)
     str += String.fromCharCode(bytes[n]);
   return str;
 }
 
@@ -828,42 +845,42 @@ var Util = PDFJS.Util = (function UtilCl
   // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i],
   // with v as [X,Y,Z]
   Util.apply3dTransform = function Util_apply3dTransform(m, v) {
     return [
       m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
       m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
       m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
     ];
-  }
+  };
 
   // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)
   // For coordinate systems whose origin lies in the bottom-left, this
   // means normalization to (BL,TR) ordering. For systems with origin in the
   // top-left, this means (TL,BR) ordering.
   Util.normalizeRect = function Util_normalizeRect(rect) {
     var r = rect.slice(0); // clone rect
     if (rect[0] > rect[2]) {
       r[0] = rect[2];
       r[2] = rect[0];
     }
     if (rect[1] > rect[3]) {
       r[1] = rect[3];
       r[3] = rect[1];
     }
     return r;
-  }
+  };
 
   // Returns a rectangle [x1, y1, x2, y2] corresponding to the
   // intersection of rect1 and rect2. If no intersection, returns 'false'
   // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2]
   Util.intersect = function Util_intersect(rect1, rect2) {
     function compare(a, b) {
       return a - b;
-    };
+    }
 
     // Order points along the axes
     var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare),
         orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare),
         result = [];
 
     rect1 = Util.normalizeRect(rect1);
     rect2 = Util.normalizeRect(rect2);
@@ -913,25 +930,25 @@ var PageViewport = PDFJS.PageViewport = 
       case -270:
       case 90:
         rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0;
         break;
       case -90:
       case 270:
         rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0;
         break;
-      case 360:
-      case 0:
+      //case 360:
+      //case 0:
       default:
         rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1;
         break;
     }
     var offsetCanvasX, offsetCanvasY;
     var width, height;
-    if (rotateA == 0) {
+    if (rotateA === 0) {
       offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
       offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
       width = Math.abs(viewBox[3] - viewBox[1]) * scale;
       height = Math.abs(viewBox[2] - viewBox[0]) * scale;
     } else {
       offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
       offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
       width = Math.abs(viewBox[2] - viewBox[0]) * scale;
@@ -1043,21 +1060,23 @@ function isDict(v, type) {
   return isName(dictType) && dictType.name == type;
 }
 
 function isArray(v) {
   return v instanceof Array;
 }
 
 function isStream(v) {
-  return typeof v == 'object' && v != null && ('getChar' in v);
+  return typeof v == 'object' && v !== null && v !== undefined &&
+         ('getChar' in v);
 }
 
 function isArrayBuffer(v) {
-  return typeof v == 'object' && v != null && ('byteLength' in v);
+  return typeof v == 'object' && v !== null && v !== undefined &&
+         ('byteLength' in v);
 }
 
 function isRef(v) {
   return v instanceof Ref;
 }
 
 function isPDFFunction(v) {
   var fnDict;
@@ -1090,29 +1109,30 @@ var Promise = PDFJS.Promise = (function 
   /**
    * If `data` is passed in this constructor, the promise is created resolved.
    * If there isn't data, it isn't resolved at the beginning.
    */
   function Promise(name, data) {
     this.name = name;
     this.isRejected = false;
     this.error = null;
+    this.exception = null;
     // If you build a promise and pass in some data it's already resolved.
-    if (data != null) {
+    if (data !== null && data !== undefined) {
       this.isResolved = true;
       this._data = data;
       this.hasData = true;
     } else {
       this.isResolved = false;
       this._data = EMPTY_PROMISE;
     }
     this.callbacks = [];
     this.errbacks = [];
     this.progressbacks = [];
-  };
+  }
   /**
    * Builds a promise that is resolved when all the passed in promises are
    * resolved.
    * @param {Promise[]} promises Array of promises to wait for.
    * @return {Promise} New dependant promise.
    */
   Promise.all = function Promise_all(promises) {
     var deferred = new Promise();
@@ -1198,16 +1218,17 @@ var Promise = PDFJS.Promise = (function 
         error('A Promise can be rejected only once ' + this.name);
       }
       if (this.isResolved) {
         error('The Promise was already resolved ' + this.name);
       }
 
       this.isRejected = true;
       this.error = reason || null;
+      this.exception = exception || null;
       var errbacks = this.errbacks;
 
       for (var i = 0, ii = errbacks.length; i < ii; i++) {
         errbacks[i].call(null, reason, exception);
       }
     },
 
     then: function Promise_then(callback, errback, progressback) {
@@ -1216,17 +1237,18 @@ var Promise = PDFJS.Promise = (function 
       }
 
       // If the promise is already resolved, call the callback directly.
       if (this.isResolved) {
         var data = this.data;
         callback.call(null, data);
       } else if (this.isRejected && errback) {
         var error = this.error;
-        errback.call(null, error);
+        var exception = this.exception;
+        errback.call(null, error, exception);
       } else {
         this.callbacks.push(callback);
         if (errback)
           this.errbacks.push(errback);
       }
 
       if (progressback)
         this.progressbacks.push(progressback);
@@ -1559,29 +1581,29 @@ var PDFPageProxy = (function PDFPageProx
           delete self.operatorList;
           self.objs.clear();
         }
 
         if (error)
           promise.reject(error);
         else
           promise.resolve();
-      };
+      }
       var continueCallback = params.continueCallback;
 
       // Once the operatorList and fonts are loaded, do the actual rendering.
       this.displayReadyPromise.then(
         function pageDisplayReadyPromise() {
           if (self.destroyed) {
             complete();
             return;
           }
 
           var gfx = new CanvasGraphics(params.canvasContext, this.commonObjs,
-            this.objs, params.textLayer);
+            this.objs, !this.pageInfo.disableTextLayer && params.textLayer);
           try {
             this.display(gfx, params.viewport, complete, continueCallback);
           } catch (e) {
             complete(e);
           }
         }.bind(this),
         function pageDisplayReadPromiseError(reason) {
           complete(reason);
@@ -1660,17 +1682,17 @@ var PDFPageProxy = (function PDFPageProx
           globalScope['StepperManager'].enabled) {
         stepper = globalScope['StepperManager'].create(this.pageNumber - 1);
         stepper.init(operatorList);
         stepper.nextBreakPoint = stepper.getNextBreakPoint();
       }
 
       var continueWrapper;
       if (continueCallback)
-        continueWrapper = function() { continueCallback(next); }
+        continueWrapper = function() { continueCallback(next); };
       else
         continueWrapper = next;
 
       var self = this;
       function next() {
         startIdx = gfx.executeOperatorList(operatorList, startIdx,
                                            continueWrapper, stepper);
         if (startIdx == length) {
@@ -1824,16 +1846,20 @@ var WorkerTransport = (function WorkerTr
       messageHandler.on('IncorrectPassword', function transportBadPass(data) {
         this.workerReadyPromise.reject(data.exception.message, data.exception);
       }, this);
 
       messageHandler.on('InvalidPDF', function transportInvalidPDF(data) {
         this.workerReadyPromise.reject(data.exception.name, data.exception);
       }, this);
 
+      messageHandler.on('MissingPDF', function transportMissingPDF(data) {
+        this.workerReadyPromise.reject(data.exception.message, data.exception);
+      }, this);
+
       messageHandler.on('UnknownError', function transportUnknownError(data) {
         this.workerReadyPromise.reject(data.exception.message, data.exception);
       }, this);
 
       messageHandler.on('GetPage', function transportPage(data) {
         var pageInfo = data.pageInfo;
         var page = new PDFPageProxy(pageInfo, this);
         this.pageCache[pageInfo.pageIndex] = page;
@@ -2017,24 +2043,26 @@ function createScratchCanvas(width, heig
   canvas.height = height;
   return canvas;
 }
 
 function addContextCurrentTransform(ctx) {
   // If the context doesn't expose a `mozCurrentTransform`, add a JS based on.
   if (!ctx.mozCurrentTransform) {
     // Store the original context
+    ctx._scaleX = ctx._scaleX || 1.0;
+    ctx._scaleY = ctx._scaleY || 1.0;
     ctx._originalSave = ctx.save;
     ctx._originalRestore = ctx.restore;
     ctx._originalRotate = ctx.rotate;
     ctx._originalScale = ctx.scale;
     ctx._originalTranslate = ctx.translate;
     ctx._originalTransform = ctx.transform;
 
-    ctx._transformMatrix = [1, 0, 0, 1, 0, 0];
+    ctx._transformMatrix = [ctx._scaleX, 0, 0, ctx._scaleY, 0, 0];
     ctx._transformStack = [];
 
     Object.defineProperty(ctx, 'mozCurrentTransform', {
       get: function getCurrentTransform() {
         return this._transformMatrix;
       }
     });
 
@@ -2207,17 +2235,17 @@ var CanvasGraphics = (function CanvasGra
     var bufferPos = 3; // alpha component offset
     for (i = 0; i < height; i++) {
       mask = 0;
       for (j = 0; j < width; j++) {
         if (!mask) {
           buf = imgArray[imgArrayPos++];
           mask = 128;
         }
-        if (!(buf & mask) == inverseDecode) {
+        if (!(buf & mask) === inverseDecode) {
           buffer[bufferPos] = 0;
         }
         bufferPos += 4;
         mask >>= 1;
       }
     }
   }
 
@@ -2901,17 +2929,17 @@ var CanvasGraphics = (function CanvasGra
         ctx.restore();
       } else {
         ctx.save();
         this.applyTextTransforms();
 
         var lineWidth = current.lineWidth;
         var a1 = current.textMatrix[0], b1 = current.textMatrix[1];
         var scale = Math.sqrt(a1 * a1 + b1 * b1);
-        if (scale == 0 || lineWidth == 0)
+        if (scale === 0 || lineWidth === 0)
           lineWidth = this.getSinglePixelWidth();
         else
           lineWidth /= scale;
 
         if (textSelection)
           geom = this.createTextGeometry();
 
         if (fontSizeScale != 1.0) {
@@ -3516,17 +3544,17 @@ var Dict = (function DictClosure() {
       return key in map;
     };
 
     this.forEach = function Dict_forEach(callback) {
       for (var key in map) {
         callback(key, this.get(key));
       }
     };
-  };
+  }
 
   return Dict;
 })();
 
 var Ref = (function RefClosure() {
   function Ref(num, gen) {
     this.num = num;
     this.gen = gen;
@@ -3858,17 +3886,17 @@ var XRef = (function XRefClosure() {
             !isInt(generationFieldWidth)) {
           error('Invalid XRef entry fields length: ' + first + ', ' + n);
         }
         for (i = 0; i < n; ++i) {
           var type = 0, offset = 0, generation = 0;
           for (j = 0; j < typeFieldWidth; ++j)
             type = (type << 8) | stream.getByte();
           // if type field is absent, its default value = 1
-          if (typeFieldWidth == 0)
+          if (typeFieldWidth === 0)
             type = 1;
           for (j = 0; j < offsetFieldWidth; ++j)
             offset = (offset << 8) | stream.getByte();
           for (j = 0; j < generationFieldWidth; ++j)
             generation = (generation << 8) | stream.getByte();
           var entry = {};
           entry.offset = offset;
           entry.gen = generation;
@@ -4094,17 +4122,17 @@ var XRef = (function XRefClosure() {
         var obj3 = parser.getObj();
         if (!isInt(obj1) || obj1 != num ||
             !isInt(obj2) || obj2 != gen ||
             !isCmd(obj3)) {
           error('bad XRef entry');
         }
         if (!isCmd(obj3, 'obj')) {
           // some bad pdfs use "obj1234" and really mean 1234
-          if (obj3.cmd.indexOf('obj') == 0) {
+          if (obj3.cmd.indexOf('obj') === 0) {
             num = parseInt(obj3.cmd.substring(3), 10);
             if (!isNaN(num))
               return num;
           }
           error('bad XRef entry');
         }
         if (this.encrypt && !suppressEncryption) {
           try {
@@ -4348,17 +4376,17 @@ var PDFFunction = (function PDFFunctionC
       var type = IR[0];
       switch (type) {
         case CONSTRUCT_SAMPLED:
           return this.constructSampledFromIR(IR);
         case CONSTRUCT_INTERPOLATED:
           return this.constructInterpolatedFromIR(IR);
         case CONSTRUCT_STICHED:
           return this.constructStichedFromIR(IR);
-        case CONSTRUCT_POSTSCRIPT:
+        //case CONSTRUCT_POSTSCRIPT:
         default:
           return this.constructPostScriptFromIR(IR);
       }
     },
 
     parse: function PDFFunction_parse(xref, fn) {
       var IR = this.getIR(xref, fn);
       return this.fromIR(IR);
@@ -4501,17 +4529,17 @@ var PDFFunction = (function PDFFunctionC
           //                    Decode_2j, Decode_2j+1)
           rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
 
           // y_j = min(max(r_j, range_2j), range_2j+1)
           y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]);
         }
 
         return y;
-      }
+      };
     },
 
     constructInterpolated: function PDFFunction_constructInterpolated(str,
                                                                       dict) {
       var c0 = dict.get('C0') || [0];
       var c1 = dict.get('C1') || [1];
       var n = dict.get('N');
 
@@ -4538,17 +4566,17 @@ var PDFFunction = (function PDFFunctionC
         var x = n == 1 ? args[0] : Math.pow(args[0], n);
 
         var out = [];
         for (var j = 0; j < length; ++j)
           out.push(c0[j] + (x * diff[j]));
 
         return out;
 
-      }
+      };
     },
 
     constructStiched: function PDFFunction_constructStiched(fn, dict, xref) {
       var domain = dict.get('Domain');
 
       if (!domain)
         error('No domain');
 
@@ -5273,17 +5301,18 @@ var CIDToUnicodeMaps = {
     [700, 8217], 92, [699, 8216], 124, [126, 8764], {f: 3, c: 161}, 8260, 402,
     0, 164, 8220, 171, {f: 2, c: 8249}, {f: 2, c: 64257}, [8210, 8211], 0, 0,
     [183, 8729], 0, 8226, 8218, 8222, 8221, 187, 0, 0, 191, {f: 2, c: 769},
     [175, 772], {f: 3, c: 774}, 778, [184, 807], 779, 808, 780, [822, 8212],
     198, 170, 321, 216, 338, 186, 230, 305, 322, 248, 339, 223, 173, 169, 172,
     174, 0, 0, {f: 2, c: 178}, 181, 185, {f: 3, c: 188}, {f: 6, c: 192},
     {f: 16, c: 199}, 0, {f: 6, c: 217}, {f: 6, c: 224}, {f: 16, c: 231}, 0,
     {f: 7, c: 249}, 352, 376, 381, [773, 8254], 353, 8482, 382, 0, 8194,
-    {s: 91}, 65512, {s: 3}, {f: 63, c: 65377}, {s: 243}, [8195, 12288],
+    {f: 59, c: 33}, 165, {f: 31, c: 93}, 65512, {f: 2, c: 125}, 0,
+    {f: 63, c: 65377}, {s: 243}, [8195, 12288],
     {f: 2, c: 12289}, 65292, 65294, 12539, {f: 2, c: 65306}, 65311, 65281,
     {f: 2, c: 12443}, 180, 65344, 168, 65342, 65507, 65343, {f: 2, c: 12541},
     {f: 2, c: 12445}, 12291, 20189, {f: 3, c: 12293}, 12540, 8213, 8208, 65295,
     65340, [12316, 65374], 8214, 65372, 8230, 8229, {s: 4}, {f: 2, c: 65288},
     {f: 2, c: 12308}, 65339, 65341, 65371, 65373, {f: 10, c: 12296}, 65291,
     [8722, 65293], 177, 215, 247, 65309, 8800, 65308, 65310, {f: 2, c: 8806},
     8734, 8756, 9794, 9792, 176, {f: 2, c: 8242}, 8451, 65509, 65284,
     {f: 2, c: 65504}, 65285, 65283, 65286, 65290, 65312, 167, 9734, 9733, 9675,
@@ -6025,19 +6054,19 @@ var CIDToUnicodeMaps = {
     64035, 36559, 0, 64037, 36967, 37086, 64038, 37141, 37159, 37338, 37335,
     37342, {f: 2, c: 37357}, {f: 2, c: 37348}, 37382, 37392, 37386, 37434,
     37440, 37436, 37454, 37465, 37457, 37433, 37479, 37543, {f: 2, c: 37495},
     37607, 37591, 37593, 37584, 64039, 37589, 37600, 37587, 37669, 37665,
     37627, 64040, 37662, 37631, 37661, 37634, 37744, 37719, 37796, 37830,
     37854, 37880, 37937, 37957, 37960, 38290, 0, 64041, 38557, 38575, 38707,
     38715, 38723, 38733, 38735, [12205, 38737], 0, 38999, 39013,
     {f: 2, c: 64042}, 39207, 64044, 39326, 39502, 39641, 39644, 39797, 39794,
-    39823, 39857, 39867, 39936, 40304, 40299, 64045, 40473, 40657, {s: 636},
-    8364, 8486, 0, 0, 64256, {f: 2, c: 64259}, 257, 299, 363, 275, 333, 256,
-    298, 362, 274, 332, {f: 4, c: 8539}, {f: 2, c: 8531}, 8304,
+    39823, 39857, 39867, 39936, 40304, 40299, 64045, 40473, 40657, 0, 92,
+    {s: 634}, 8364, 8486, 0, 0, 64256, {f: 2, c: 64259}, 257, 299, 363, 275,
+    333, 256, 298, 362, 274, 332, {f: 4, c: 8539}, {f: 2, c: 8531}, 8304,
     {f: 6, c: 8308}, {f: 10, c: 8320}, 461, 282, 0, 7868, 463, 0, 296, 465, 0,
     467, 366, 360, 462, 283, 0, 7869, 464, 0, 297, 466, 0, 468, 367, 361, 593,
     8049, 8048, 509, 0, 596, 0, 0, 601, 0, 0, 602, 0, 0, 603, 8051, 8050, 0,
     331, 629, 652, 0, 0, 658, 643, 720, {s: 682}, {f: 10, c: 12832}, {s: 108},
     {f: 4, c: 12892}, {f: 15, c: 12977}, {s: 50}, {f: 26, c: 9424},
     {f: 26, c: 9398}, {s: 48}, {f: 47, c: 13008}, 0, {f: 10, c: 12928}, 12944,
     {f: 6, c: 12938}, 0, 12959, {s: 6}, {f: 2, c: 12960}, 12955, 12954, 12963,
     12962, 12951, 0, 12956, 12949, {s: 6}, 9676, {s: 11}, 10111,
@@ -12415,17 +12444,17 @@ var ColorSpace = (function ColorSpaceClo
     if (!decode)
       return true;
 
     if (n * 2 !== decode.length) {
       warn('The decode map is not the correct length');
       return true;
     }
     for (var i = 0, ii = decode.length; i < ii; i += 2) {
-      if (decode[i] != 0 || decode[i + 1] != 1)
+      if (decode[i] !== 0 || decode[i + 1] != 1)
         return false;
     }
     return true;
   };
 
   return ColorSpace;
 })();
 
@@ -13463,17 +13492,17 @@ var LabCS = (function LabCSClosure() {
 
     if (this.amin > this.amax || this.bmin > this.bmax) {
       info('Invalid Range, falling back to defaults');
       this.amin = -100;
       this.amax = 100;
       this.bmin = -100;
       this.bmax = 100;
     }
-  };
+  }
 
   // Function g(x) from spec
   function fn_g(x) {
     if (x >= 6 / 29)
       return x * x * x;
     else
       return (108 / 841) * (x - 4 / 29);
   }
@@ -13897,17 +13926,17 @@ var AES128Cipher = (function AES128Ciphe
       result.push(plain);
       buffer = new Uint8Array(16);
       bufferLength = 0;
     }
     // saving incomplete buffer
     this.buffer = buffer;
     this.bufferLength = bufferLength;
     this.iv = iv;
-    if (result.length == 0)
+    if (result.length === 0)
       return new Uint8Array([]);
     if (result.length == 1)
       return result[0];
     // combining plain text blocks into one
     var output = new Uint8Array(16 * result.length);
     for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16)
       output.set(result[i], j);
     return output;
@@ -14077,22 +14106,23 @@ var CipherTransformFactory = (function C
     this.dict = dict;
     var algorithm = dict.get('V');
     if (!isInt(algorithm) ||
       (algorithm != 1 && algorithm != 2 && algorithm != 4))
       error('unsupported encryption algorithm');
     this.algorithm = algorithm;
     var keyLength = dict.get('Length') || 40;
     if (!isInt(keyLength) ||
-      keyLength < 40 || (keyLength % 8) != 0)
+      keyLength < 40 || (keyLength % 8) !== 0)
       error('invalid key length');
     // prepare keys
     var ownerPassword = stringToBytes(dict.get('O'));
     var userPassword = stringToBytes(dict.get('U'));
     var flags = dict.get('P');
+    this.disableTextLayer = !(flags & 16);
     var revision = dict.get('R');
     var encryptMetadata = algorithm == 4 &&  // meaningful when V is 4
       dict.get('EncryptMetadata') !== false; // makes true as default value
     this.encryptMetadata = encryptMetadata;
 
     var fileIdBytes = stringToBytes(fileId);
     var passwordBytes;
     if (password)
@@ -14142,17 +14172,17 @@ var CipherTransformFactory = (function C
     }
     var hash = calculateMD5(key, 0, i);
     return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
   }
 
   function buildCipherConstructor(cf, name, num, gen, key) {
     var cryptFilter = cf.get(name.name);
     var cfm;
-    if (cryptFilter != null)
+    if (cryptFilter !== null && cryptFilter !== undefined)
       cfm = cryptFilter.get('CFM');
     if (!cfm || cfm.name == 'None') {
       return function cipherTransformFactoryBuildCipherConstructorNone() {
         return new NullCipher();
       };
     }
     if ('V2' == cfm.name) {
       return function cipherTransformFactoryBuildCipherConstructorV2() {
@@ -14713,17 +14743,17 @@ var PartialEvaluator = (function Partial
               );
               args = [gsStateObj];
               break;
           } // switch
 
           fnArray.push(fn);
           argsArray.push(args);
           args = [];
-        } else if (obj != null) {
+        } else if (obj !== null && obj !== undefined) {
           args.push(obj instanceof Dict ? obj.getAll() : obj);
           assertWellFormed(args.length <= 33, 'Too many arguments');
         }
       }
 
       return queue;
     },
 
@@ -14899,17 +14929,17 @@ var PartialEvaluator = (function Partial
                     chunk += ' ';
                   }
                 }
               }
               break;
             case 'Tj':
               chunk += fontCharsToUnicode(args[0], font);
               break;
-            case "'":
+            case '\'':
               // For search, adding a extra white space for line breaks would be
               // better here, but that causes too much spaces in the
               // text-selection divs.
               chunk += fontCharsToUnicode(args[0], font);
               break;
             case '"':
               // Note comment in "'"
               chunk += fontCharsToUnicode(args[2], font);
@@ -14967,17 +14997,17 @@ var PartialEvaluator = (function Partial
 
           if (chunk !== '') {
             bidiTexts.push(PDFJS.bidi(chunk, -1));
 
             chunk = '';
           }
 
           args = [];
-        } else if (obj != null) {
+        } else if (obj !== null && obj !== undefined) {
           assertWellFormed(args.length <= 33, 'Too many arguments');
           args.push(obj);
         }
       } // while
 
       return state;
     },
 
@@ -15181,17 +15211,17 @@ var PartialEvaluator = (function Partial
     readCidToGidMap: function PartialEvaluator_readCidToGidMap(cidToGidStream) {
       // Extract the encoding from the CIDToGIDMap
       var glyphsData = cidToGidStream.getBytes();
 
       // Set encoding 0 to later verify the font has an encoding
       var result = [];
       for (var j = 0, jj = glyphsData.length; j < jj; j++) {
         var glyphID = (glyphsData[j++] << 8) | glyphsData[j];
-        if (glyphID == 0)
+        if (glyphID === 0)
           continue;
 
         var code = j >> 1;
         result[code] = glyphID;
       }
       return result;
     },
 
@@ -15201,30 +15231,26 @@ var PartialEvaluator = (function Partial
                                                    properties) {
       var glyphsWidths = [];
       var defaultWidth = 0;
       if (properties.composite) {
         defaultWidth = dict.get('DW') || 1000;
 
         var widths = dict.get('W');
         if (widths) {
-          var start = 0, end = 0;
           for (var i = 0, ii = widths.length; i < ii; i++) {
+            var start = widths[i++];
             var code = xref.fetchIfRef(widths[i]);
             if (isArray(code)) {
               for (var j = 0, jj = code.length; j < jj; j++)
                 glyphsWidths[start++] = code[j];
-              start = 0;
-            } else if (start) {
+            } else {
               var width = widths[++i];
               for (var j = start; j <= code; j++)
                 glyphsWidths[j] = width;
-              start = 0;
-            } else {
-              start = code;
             }
           }
         }
       } else {
         var firstChar = properties.firstChar;
         var widths = dict.get('Widths');
         if (widths) {
           var j = firstChar;
@@ -15384,16 +15410,23 @@ var PartialEvaluator = (function Partial
       if (isString(fontName)) {
         fontName = new Name(fontName);
       }
       if (isString(baseFont)) {
         baseFont = new Name(baseFont);
       }
 
       var fontNameStr = fontName && fontName.name;
+      // 9.7.6.1
+      if (type.name == 'CIDFontType0') {
+        var cidEncoding = baseDict.get('Encoding');
+        if (isName(cidEncoding)) {
+          fontNameStr = fontNameStr + '-' + cidEncoding.name;
+        }
+      }
       var baseFontStr = baseFont && baseFont.name;
       if (fontNameStr !== baseFontStr) {
         warn('The FontDescriptor\'s FontName is "' + fontNameStr +
             '" but should be the same as the Font\'s BaseFont "' +
             baseFontStr + '"');
       }
       fontName = fontName || baseFont;
 
@@ -15477,17 +15510,18 @@ var EvalState = (function EvalStateClosu
 var CMAP_GLYPH_OFFSET = 0xE000;
 var GLYPH_AREA_SIZE = 0x1900;
 var SYMBOLIC_FONT_GLYPH_OFFSET = 0xF000;
 
 // PDF Glyph Space Units are one Thousandth of a TextSpace Unit
 // except for Type 3 fonts
 var PDF_GLYPH_SPACE_UNITS = 1000;
 
-// Until hinting is fully supported this constant can be used
+// Hinting is currently disabled due to unknown problems on windows
+// in tracemonkey and various other pdfs with type1 fonts.
 var HINTING_ENABLED = false;
 
 var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
 
 var FontFlags = {
   FixedPitch: 1,
   Serif: 2,
   Symbolic: 4,
@@ -15860,21 +15894,37 @@ var symbolsFonts = {
 var CMapConverterList = {
   'H': jis7ToUnicode,
   'V': jis7ToUnicode,
   'EUC-H': eucjpToUnicode,
   'EUC-V': eucjpToUnicode,
   '90ms-RKSJ-H': sjisToUnicode,
   '90ms-RKSJ-V': sjisToUnicode,
   '90msp-RKSJ-H': sjisToUnicode,
-  '90msp-RKSJ-V': sjisToUnicode
+  '90msp-RKSJ-V': sjisToUnicode,
+  'GBK-EUC-H': gbkToUnicode
+};
+
+// CMaps using Hankaku (Halfwidth) Latin glyphs instead of proportional one.
+// We need to distinguish them to get correct widths from CIDFont dicts.
+var HalfwidthCMaps = {
+  'H': true,
+  'V': true,
+  'EUC-H': true,
+  'EUC-V': true,
+  '90ms-RKSJ-H': true,
+  '90ms-RKSJ-V': true,
+  'UniJIS-UCS2-HW-H': true,
+  'UniJIS-UCS2-HW-V': true
 };
 
 var decodeBytes;
 if (typeof TextDecoder !== 'undefined') {
+  // The encodings supported by TextDecoder can be found at:
+  // http://encoding.spec.whatwg.org/#concept-encoding-get
   decodeBytes = function(bytes, encoding) {
     return new TextDecoder(encoding).decode(bytes);
   };
 } else if (typeof FileReaderSync !== 'undefined') {
   decodeBytes = function(bytes, encoding) {
     return new FileReaderSync().readAsText(new Blob([bytes]), encoding);
   };
 } else {
@@ -15894,16 +15944,20 @@ function jis7ToUnicode(str) {
 function eucjpToUnicode(str) {
   return decodeBytes(stringToBytes(str), 'euc-jp');
 }
 
 function sjisToUnicode(str) {
   return decodeBytes(stringToBytes(str), 'shift_jis');
 }
 
+function gbkToUnicode(str) {
+  return decodeBytes(stringToBytes(str), 'gbk');
+}
+
 // Some characters, e.g. copyrightserif, mapped to the private use area and
 // might not be displayed using standard fonts. Mapping/hacking well-known chars
 // to the similar equivalents in the normal characters range.
 function mapPrivateUseChars(code) {
   switch (code) {
     case 0xF8E9: // copyrightsans
     case 0xF6D9: // copyrightserif
       return 0x00A9; // copyright
@@ -17669,80 +17723,80 @@ var Font = (function FontClosure() {
 
     // Transfer some properties again that could change during font conversion
     this.fontMatrix = properties.fontMatrix;
     this.widths = properties.widths;
     this.defaultWidth = properties.defaultWidth;
     this.encoding = properties.baseEncoding;
 
     this.loading = true;
-  };
+  }
 
   var numFonts = 0;
   function getUniqueName() {
     return 'pdfFont' + numFonts++;
   }
 
   function stringToArray(str) {
     var array = [];
     for (var i = 0, ii = str.length; i < ii; ++i)
       array[i] = str.charCodeAt(i);
 
     return array;
-  };
+  }
 
   function arrayToString(arr) {
     var str = '';
     for (var i = 0, ii = arr.length; i < ii; ++i)
       str += String.fromCharCode(arr[i]);
 
     return str;
-  };
+  }
 
   function int16(bytes) {
     return (bytes[0] << 8) + (bytes[1] & 0xff);
-  };
+  }
 
   function int32(bytes) {
     return (bytes[0] << 24) + (bytes[1] << 16) +
            (bytes[2] << 8) + (bytes[3] & 0xff);
-  };
+  }
 
   function getMaxPower2(number) {
     var maxPower = 0;
     var value = number;
     while (value >= 2) {
       value /= 2;
       maxPower++;
     }
 
     value = 2;
     for (var i = 1; i < maxPower; i++)
       value *= 2;
     return value;
-  };
+  }
 
   function string16(value) {
     return String.fromCharCode((value >> 8) & 0xff) +
            String.fromCharCode(value & 0xff);
-  };
+  }
 
   function safeString16(value) {
     // clamp value to the 16-bit int range
     value = value > 0x7FFF ? 0x7FFF : value < -0x8000 ? -0x8000 : value;
     return String.fromCharCode((value >> 8) & 0xff) +
            String.fromCharCode(value & 0xff);
-  };
+  }
 
   function string32(value) {
     return String.fromCharCode((value >> 24) & 0xff) +
            String.fromCharCode((value >> 16) & 0xff) +
            String.fromCharCode((value >> 8) & 0xff) +
            String.fromCharCode(value & 0xff);
-  };
+  }
 
   function createOpenTypeHeader(sfnt, file, numTables) {
     // Windows hates the Mac TrueType sfnt version number
     if (sfnt == 'true')
       sfnt = string32(0x00010000);
 
     // sfnt version (4 bytes)
     var header = sfnt;
@@ -17758,17 +17812,17 @@ var Font = (function FontClosure() {
     // entrySelector (2 bytes)
     header += string16(Math.log(tablesMaxPower2) / Math.log(2));
 
     // rangeShift (2 bytes)
     header += string16(numTables * 16 - searchRange);
 
     file.file += header;
     file.virtualOffset += header.length;
-  };
+  }
 
   function createTableEntry(file, tag, data) {
     // offset
     var offset = file.virtualOffset;
 
     // length
     var length = data.length;
 
@@ -17784,17 +17838,17 @@ var Font = (function FontClosure() {
     for (var i = 0; i < n; i += 4)
       checksum = (checksum + int32([data[i], data[i + 1], data[i + 2],
                                     data[i + 3]])) | 0;
 
     var tableEntry = (tag + string32(checksum) +
                       string32(offset) + string32(length));
     file.file += tableEntry;
     file.virtualOffset += data.length;
-  };
+  }
 
   function getRanges(glyphs) {
     // Array.sort() sorts by characters, not numerically, so convert to an
     // array of characters.
     var codes = [];
     var length = glyphs.length;
     for (var n = 0; n < length; ++n)
       codes.push({ unicode: glyphs[n].unicode, code: n });
@@ -17813,19 +17867,19 @@ var Font = (function FontClosure() {
         codeIndices.push(codes[n].code);
         ++end;
         ++n;
       }
       ranges.push([start, end, codeIndices]);
     }
 
     return ranges;
-  };
-
-  function createCMapTable(glyphs, deltas) {
+  }
+
+  function createCmapTable(glyphs, deltas) {
     var ranges = getRanges(glyphs);
 
     var numTables = 1;
     var cmap = '\x00\x00' + // version
                string16(numTables) +  // numTables
                '\x00\x03' + // platformID
                '\x00\x01' + // encodingID
                string32(4 + numTables * 8); // start of the table record
@@ -17890,17 +17944,17 @@ var Font = (function FontClosure() {
                     string16(rangeShift) +
                     endCount + '\x00\x00' + startCount +
                     idDeltas + idRangeOffsets + glyphsIds;
 
     return stringToArray(cmap +
                          '\x00\x04' + // format
                          string16(format314.length + 4) + // length
                          format314);
-  };
+  }
 
   function validateOS2Table(os2) {
     var stream = new Stream(os2.data);
     var version = int16(stream.getBytes(2));
     // TODO verify all OS/2 tables fields, but currently we validate only those
     // that give us issues
     stream.getBytes(60); // skipping type, misc sizes, panose, unicode ranges
     var selection = int16(stream.getBytes(2));
@@ -18013,30 +18067,30 @@ var Font = (function FontClosure() {
            string16(winDescent) + // usWinDescent
            '\x00\x00\x00\x00' + // ulCodePageRange1 (Bits 0-31)
            '\x00\x00\x00\x00' + // ulCodePageRange2 (Bits 32-63)
            string16(properties.xHeight) + // sxHeight
            string16(properties.capHeight) + // sCapHeight
            string16(0) + // usDefaultChar
            string16(firstCharIndex || properties.firstChar) + // usBreakChar
            '\x00\x03';  // usMaxContext
-  };
+  }
 
   function createPostTable(properties) {
     var angle = Math.floor(properties.italicAngle * (Math.pow(2, 16)));
     return '\x00\x03\x00\x00' + // Version number
            string32(angle) + // italicAngle
            '\x00\x00' + // underlinePosition
            '\x00\x00' + // underlineThickness
            string32(properties.fixedPitch) + // isFixedPitch
            '\x00\x00\x00\x00' + // minMemType42
            '\x00\x00\x00\x00' + // maxMemType42
            '\x00\x00\x00\x00' + // minMemType1
            '\x00\x00\x00\x00';  // maxMemType1
-  };
+  }
 
   function createNameTable(name, proto) {
     if (!proto) {
       proto = [[], []]; // no strings and unicode strings
     }
 
     var strings = [
       proto[0][0] || 'Original licence',  // 0.Copyright
@@ -18091,16 +18145,47 @@ var Font = (function FontClosure() {
         strOffset += str.length;
       }
     }
 
     nameTable += strings.join('') + stringsUnicode.join('');
     return nameTable;
   }
 
+  // Normalize the charcodes in the cmap table into unicode values
+  // that will work with the (3, 1) cmap table we will write out.
+  function cmapCharcodeToUnicode(charcode, symbolic, platformId, encodingId) {
+    var unicode;
+    if (symbolic) {
+      // These codes will be shifted into the range
+      // SYMBOLIC_FONT_GLYPH_OFFSET to (SYMBOLIC_FONT_GLYPH_OFFSET + 0xFF)
+      // so that they are not in the control character range that could
+      // be displayed as spaces by browsers.
+      if (platformId === 3 && encodingId === 0 ||
+          platformId === 1 && encodingId === 0) {
+        unicode = SYMBOLIC_FONT_GLYPH_OFFSET | (charcode & 0xFF);
+      }
+    } else {
+      if (platformId === 3 && encodingId === 1) {
+        // A (3, 1) table is alredy unicode (Microsoft Unicode format)
+        unicode = charcode;
+      } else if (platformId === 1 && encodingId === 0) {
+        // TODO(mack): Should apply the changes to convert the
+        // MacRomanEncoding to Mac OS Roman encoding in 9.6.6.4
+        // table 115 of the pdf spec
+        var glyphName = Encodings.MacRomanEncoding[charcode];
+        if (glyphName) {
+          unicode = GlyphsUnicode[glyphName];
+        }
+      }
+    }
+    return unicode;
+  }
+
+
   Font.prototype = {
     name: null,
     font: null,
     mimetype: null,
     encoding: null,
 
     exportData: function Font_exportData() {
       var data = {};
@@ -18138,27 +18223,27 @@ var Font = (function FontClosure() {
 
         return {
           tag: tag,
           checksum: checksum,
           length: length,
           offset: offset,
           data: data
         };
-      };
+      }
 
       function readOpenTypeHeader(ttf) {
         return {
           version: arrayToString(ttf.getBytes(4)),
           numTables: int16(ttf.getBytes(2)),
           searchRange: int16(ttf.getBytes(2)),
           entrySelector: int16(ttf.getBytes(2)),
           rangeShift: int16(ttf.getBytes(2))
         };
-      };
+      }
 
       function createGlyphNameMap(glyphs, ids, properties) {
         var glyphNames = properties.glyphNames;
         if (!glyphNames) {
           properties.glyphNameMap = {};
           return;
         }
         var glyphsLength = glyphs.length;
@@ -18173,213 +18258,264 @@ var Font = (function FontClosure() {
           var code = glyphs[i].code;
           encoding[code] = glyphName;
         }
         properties.glyphNameMap = glyphNameMap;
         if (!properties.hasEncoding)
           properties.baseEncoding = encoding;
       }
 
-      function readCMapTable(cmap, font) {
+      /**
+       * Read the appropriate subtable from the cmap according to 9.6.6.4 from
+       * PDF spec
+       */
+      function readCmapTable(cmap, font, hasEncoding, isSymbolicFont) {
         var start = (font.start ? font.start : 0) + cmap.offset;
         font.pos = start;
 
         var version = int16(font.getBytes(2));
-        var numRecords = int16(font.getBytes(2));
-
-        var records = [];
-        for (var i = 0; i < numRecords; i++) {
-          records.push({
-            platformID: int16(font.getBytes(2)),
-            encodingID: int16(font.getBytes(2)),
-            offset: int32(font.getBytes(4))
-          });
-        }
-
-        // Check that table are sorted by platformID then encodingID,
-        records.sort(function fontReadCMapTableSort(a, b) {
-          return ((a.platformID << 16) + a.encodingID) -
-                 ((b.platformID << 16) + b.encodingID);
-        });
-
-        var tables = [records[0]];
-        for (var i = 1; i < numRecords; i++) {
-          // The sanitizer will drop the font if 2 tables have the same
-          // platformID and the same encodingID, this will be correct for
-          // most cases but if the font has been made for Mac it could
-          // exist a few platformID: 1, encodingID: 0 but with a different
-          // language field and that's correct. But the sanitizer does not
-          // seem to support this case.
-          var current = records[i];
-          var previous = records[i - 1];
-          if (((current.platformID << 16) + current.encodingID) <=
-             ((previous.platformID << 16) + previous.encodingID))
-                continue;
-          tables.push(current);
-        }
-
-        var missing = numRecords - tables.length;
-        if (missing) {
-          numRecords = tables.length;
-          var data = string16(version) + string16(numRecords);
-
-          for (var i = 0; i < numRecords; i++) {
-            var table = tables[i];
-            data += string16(table.platformID) +
-                    string16(table.encodingID) +
-                    string32(table.offset);
-          }
-
-          for (var i = 0, ii = data.length; i < ii; i++)
-            cmap.data[i] = data.charCodeAt(i);
-        }
-
-        for (var i = 0; i < numRecords; i++) {
-          var table = tables[i];
-          font.pos = start + table.offset;
-
-          var format = int16(font.getBytes(2));
-          var length = int16(font.getBytes(2));
-          var language = int16(font.getBytes(2));
-
-          if (format == 0) {
-            // Characters below 0x20 are controls characters that are hardcoded
-            // into the platform so if some characters in the font are assigned
-            // under this limit they will not be displayed so let's rewrite the
-            // CMap.
-            var glyphs = [];
-            var ids = [];
-            for (var j = 0; j < 256; j++) {
-              var index = font.getByte();
-              if (index) {
-                glyphs.push({ unicode: j, code: j });
-                ids.push(index);
-              }
-            }
-            return {
-              glyphs: glyphs,
-              ids: ids,
-              hasShortCmap: true
+        var numTables = int16(font.getBytes(2));
+
+        var potentialTable;
+        var foundPreferredTable;
+        // There's an order of preference in terms of which cmap subtable we
+        // want to use. So scan through them to find our preferred table.
+        for (var i = 0; i < numTables; i++) {
+          var platformId = int16(font.getBytes(2));
+          var encodingId = int16(font.getBytes(2));
+          var offset = int32(font.getBytes(4));
+          var useTable = false;
+          var canBreak = false;
+
+          // The following block implements the following from the spec:
+          //
+          //   When the font has no Encoding entry, or the font descriptor’s
+          //   Symbolic flag is set (in which case the Encoding entry
+          //   is ignored), this shall occur:
+          //      - If the font contains a (3, 0) subtable, the range of
+          //      - Otherwise, the (1, 0) subtable will be used.
+          //   Otherwise, if the font does have an encoding:
+          //      - Use the (3, 1) cmap subtable
+          //      - Otherwise, use the (1, 0) subtable if present
+          //
+          // The following diverges slightly from the above spec in order
+          // to handle the case that hasEncoding and isSymbolicFont are both
+          // true. In this, based on the ordering of the rules in the spec,
+          // my interpretation is that we should be acting as if the font is
+          // symbolic.
+          //
+          // However, in this case, the test pdf 'preistabelle.pdf'
+          // is interpreting this case as a non-symbolic font. In this case
+          // though, 'presitabelle.pdf' does contain a (3, 1) table and does
+          // not contain a (3, 0) table which indicates it is non-symbolic.
+          //
+          // Thus, I am using this heurisitic of looking at which table is
+          // found to truly determine whether or not the font is symbolic.
+          // That is, if the specific symbolic/non-symbolic font specific
+          // tables (3, 0) or (3, 1) is found, that information is used for
+          // deciding if the font is symbolic or not.
+          //
+          // TODO(mack): This section needs some more thought on whether the
+          // heuristic is good enough. For now, it passes all the regression
+          // tests.
+          if (isSymbolicFont && platformId === 3 && encodingId === 0) {
+            useTable = true;
+            canBreak = true;
+            foundPreferredTable = true;
+          } else if (hasEncoding && platformId === 3 && encodingId === 1) {
+            useTable = true;
+            canBreak = true;
+            foundPreferredTable = true;
+            // Update the isSymbolicFont based on this heuristic
+            isSymbolicFont = false;
+          } else if (platformId === 1 && encodingId === 0 &&
+              !foundPreferredTable) {
+            useTable = true;
+            foundPreferredTable = true;
+          } else if (!potentialTable) {
+            // We will use an arbitrary table if we cannot find a preferred
+            // table
+            useTable = true;
+          }
+
+          if (useTable) {
+            potentialTable = {
+              platformId: platformId,
+              encodingId: encodingId,
+              offset: offset,
+              isSymbolicFont: isSymbolicFont
             };
-          } else if (format == 4) {
-            // re-creating the table in format 4 since the encoding
-            // might be changed
-            var segCount = (int16(font.getBytes(2)) >> 1);
-            font.getBytes(6); // skipping range fields
-            var segIndex, segments = [];
-            for (segIndex = 0; segIndex < segCount; segIndex++) {
-              segments.push({ end: int16(font.getBytes(2)) });
-            }
-            font.getBytes(2);
-            for (segIndex = 0; segIndex < segCount; segIndex++) {
-              segments[segIndex].start = int16(font.getBytes(2));
-            }
-
-            for (segIndex = 0; segIndex < segCount; segIndex++) {
-              segments[segIndex].delta = int16(font.getBytes(2));
-            }
-
-            var offsetsCount = 0;
-            for (segIndex = 0; segIndex < segCount; segIndex++) {
-              var segment = segments[segIndex];
-              var rangeOffset = int16(font.getBytes(2));
-              if (!rangeOffset) {
-                segment.offsetIndex = -1;
+          }
+          if (canBreak) {
+            break;
+          }
+        }
+
+        if (!potentialTable) {
+          error('Could not find a cmap table');
+          return;
+        }
+
+        if (!foundPreferredTable) {
+          warn('Did not find a cmap of suitable format. Interpreting (' +
+               potentialTable.platformId + ', ' + potentialTable.encodingId +
+               ') as (3, 1) table');
+          potentialTable.platformId = 3;
+          potentialTable.encodingId = 1;
+        }
+
+        font.pos = start + potentialTable.offset;
+        var format = int16(font.getBytes(2));
+        var length = int16(font.getBytes(2));
+        var language = int16(font.getBytes(2));
+
+        var hasShortCmap = false;
+        var mappings = [];
+
+        // TODO(mack): refactor this cmap subtable reading logic out
+        if (format === 0) {
+          for (var j = 0; j < 256; j++) {
+            var index = font.getByte();
+            if (!index) {
+              continue;
+            }
+            mappings.push({
+              charcode: j,
+              glyphId: index
+            });
+          }
+          hasShortCmap = true;
+        } else if (format === 4) {
+          // re-creating the table in format 4 since the encoding
+          // might be changed
+          var segCount = (int16(font.getBytes(2)) >> 1);
+          font.getBytes(6); // skipping range fields
+          var segIndex, segments = [];
+          for (segIndex = 0; segIndex < segCount; segIndex++) {
+            segments.push({ end: int16(font.getBytes(2)) });
+          }
+          font.getBytes(2);
+          for (segIndex = 0; segIndex < segCount; segIndex++) {
+            segments[segIndex].start = int16(font.getBytes(2));
+          }
+
+          for (segIndex = 0; segIndex < segCount; segIndex++) {
+            segments[segIndex].delta = int16(font.getBytes(2));
+          }
+
+          var offsetsCount = 0;
+          for (segIndex = 0; segIndex < segCount; segIndex++) {
+            var segment = segments[segIndex];
+            var rangeOffset = int16(font.getBytes(2));
+            if (!rangeOffset) {
+              segment.offsetIndex = -1;
+              continue;
+            }
+
+            var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);
+            segment.offsetIndex = offsetIndex;
+            offsetsCount = Math.max(offsetsCount, offsetIndex +
+              segment.end - segment.start + 1);
+          }
+
+          var offsets = [];
+          for (var j = 0; j < offsetsCount; j++) {
+            offsets.push(int16(font.getBytes(2)));
+          }
+
+          for (segIndex = 0; segIndex < segCount; segIndex++) {
+            var segment = segments[segIndex];
+            var start = segment.start, end = segment.end;
+            var delta = segment.delta, offsetIndex = segment.offsetIndex;
+
+            for (var j = start; j <= end; j++) {
+              if (j == 0xFFFF) {
                 continue;
               }
 
-              var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);
-              segment.offsetIndex = offsetIndex;
-              offsetsCount = Math.max(offsetsCount, offsetIndex +
-                segment.end - segment.start + 1);
-            }
-
-            var offsets = [];
-            for (var j = 0; j < offsetsCount; j++)
-              offsets.push(int16(font.getBytes(2)));
-
-            var glyphs = [], ids = [];
-
-            for (segIndex = 0; segIndex < segCount; segIndex++) {
-              var segment = segments[segIndex];
-              var start = segment.start, end = segment.end;
-              var delta = segment.delta, offsetIndex = segment.offsetIndex;
-
-              for (var j = start; j <= end; j++) {
-                if (j == 0xFFFF)
-                  continue;
-
-                var glyphCode = offsetIndex < 0 ? j :
-                  offsets[offsetIndex + j - start];
-                glyphCode = (glyphCode + delta) & 0xFFFF;
-                if (glyphCode == 0)
-                  continue;
-
-                glyphs.push({ unicode: j, code: j });
-                ids.push(glyphCode);
-              }
-            }
-
-            return {
-              glyphs: glyphs,
-              ids: ids
-            };
-          } else if (format == 6) {
-            // 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 = int16(font.getBytes(2));
-            var entryCount = int16(font.getBytes(2));
-
-            var glyphs = [];
-            var ids = [];
-            for (var j = 0; j < entryCount; j++) {
-              var glyphCode = int16(font.getBytes(2));
-              var code = firstCode + j;
-
-              glyphs.push({ unicode: code, code: code });
-              ids.push(glyphCode);
-            }
-
-            return {
-              glyphs: glyphs,
-              ids: ids
-            };
-          }
-        }
-        error('Unsupported cmap table format');
-      };
+              var glyphId = offsetIndex < 0 ? j :
+                offsets[offsetIndex + j - start];
+              glyphId = (glyphId + delta) & 0xFFFF;
+              if (glyphId === 0) {
+                continue;
+              }
+              mappings.push({
+                charcode: j,
+                glyphId: glyphId
+              });
+            }
+          }
+        } else if (format == 6) {
+          // 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 = int16(font.getBytes(2));
+          var entryCount = int16(font.getBytes(2));
+
+          var glyphs = [];
+          var ids = [];
+          for (var j = 0; j < entryCount; j++) {
+            var glyphId = int16(font.getBytes(2));
+            var charcode = firstCode + j;
+
+            mappings.push({
+              charcode: charcode,
+              glyphId: glyphId
+            });
+          }
+        } else {
+          error('cmap table has unsupported format: ' + format);
+        }
+
+        return {
+          platformId: potentialTable.platformId,
+          encodingId: potentialTable.encodingId,
+          isSymbolicFont: potentialTable.isSymbolicFont,
+          mappings: mappings,
+          hasShortCmap: hasShortCmap
+        };
+      }
 
       function sanitizeMetrics(font, header, metrics, numGlyphs) {
         if (!header) {
           if (metrics) {
             metrics.data = null;
           }
           return;
         }
 
         font.pos = (font.start ? font.start : 0) + header.offset;
         font.pos += header.length - 2;
         var numOfMetrics = int16(font.getBytes(2));
 
+        if (numOfMetrics > numGlyphs) {
+          info('The numOfMetrics (' + numOfMetrics + ') should not be ' +
+               'greater than the numGlyphs (' + numGlyphs + ')');
+          // Reduce numOfMetrics if it is greater than numGlyphs
+          numOfMetrics = numGlyphs;
+          header.data[34] = (numOfMetrics & 0xff00) >> 8;
+          header.data[35] = numOfMetrics & 0x00ff;
+        }
+
         var numOfSidebearings = numGlyphs - numOfMetrics;
         var numMissing = numOfSidebearings -
-          ((hmtx.length - numOfMetrics * 4) >> 1);
+          ((metrics.length - numOfMetrics * 4) >> 1);
+
         if (numMissing > 0) {
           font.pos = (font.start ? font.start : 0) + metrics.offset;
           var entries = '';
-          for (var i = 0, ii = hmtx.length; i < ii; i++)
+          for (var i = 0, ii = metrics.length; i < ii; i++)
             entries += String.fromCharCode(font.getByte());
           for (var i = 0; i < numMissing; i++)
             entries += '\x00\x00';
           metrics.data = stringToArray(entries);
         }
-      };
+      }
 
       function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart) {
         if (sourceEnd - sourceStart <= 12) {
           // glyph with data less than 12 is invalid one
           return 0;
         }
         var glyf = source.subarray(sourceStart, sourceEnd);
         var contoursCount = (glyf[0] << 8) | glyf[1];
@@ -18426,16 +18562,60 @@ var Font = (function FontClosure() {
           dest.set(glyf.subarray(0, glyphDataLength), destStart);
           return glyphDataLength;
         }
         // glyph data is fine
         dest.set(glyf, destStart);
         return glyf.length;
       }
 
+      function sanitizeHead(head, numGlyphs, locaLength) {
+        var data = head.data;
+
+        // Validate version:
+        // Should always be 0x00010000
+        var version = int32([data[0], data[1], data[2], data[3]]);
+        if (version >> 16 !== 1) {
+          info('Attempting to fix invalid version in head table: ' + version);
+          data[0] = 0;
+          data[1] = 1;
+          data[2] = 0;
+          data[3] = 0;
+        }
+
+        var indexToLocFormat = int16([data[50], data[51]]);
+        if (indexToLocFormat < 0 || indexToLocFormat > 1) {
+          info('Attempting to fix invalid indexToLocFormat in head table: ' +
+               indexToLocFormat);
+
+          // The value of indexToLocFormat should be 0 if the loca table
+          // consists of short offsets, and should be 1 if the loca table
+          // consists of long offsets.
+          //
+          // The number of entries in the loca table should be numGlyphs + 1.
+          //
+          // Using this information, we can work backwards to deduce if the
+          // size of each offset in the loca table, and thus figure out the
+          // appropriate value for indexToLocFormat.
+
+          var numGlyphsPlusOne = numGlyphs + 1;
+          if (locaLength === numGlyphsPlusOne << 1) {
+            // 0x0000 indicates the loca table consists of short offsets
+            data[50] = 0;
+            data[51] = 0;
+          } else if (locaLength === numGlyphsPlusOne << 2) {
+            // 0x0001 indicates the loca table consists of long offsets
+            data[50] = 0;
+            data[51] = 1;
+          } else {
+            warn('Could not fix indexToLocFormat: ' + indexToLocFormat);
+          }
+        }
+      }
+
       function sanitizeGlyphLocations(loca, glyf, numGlyphs,
                                       isGlyphLocationsLong) {
         var itemSize, itemDecode, itemEncode;
         if (isGlyphLocationsLong) {
           itemSize = 4;
           itemDecode = function fontItemDecodeLong(data, offset) {
             return (data[offset] << 24) | (data[offset + 1] << 16) |
                    (data[offset + 2] << 8) | data[offset + 3];
@@ -18475,17 +18655,17 @@ var Font = (function FontClosure() {
 
           var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset,
                                         newGlyfData, writeOffset);
           writeOffset += newLength;
           itemEncode(locaData, j, writeOffset);
           startOffset = endOffset;
         }
 
-        if (writeOffset == 0) {
+        if (writeOffset === 0) {
           // glyf table cannot be empty -- redoing the glyf and loca tables
           // to have single glyph with one point
           var simpleGlyph = new Uint8Array(
             [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
           for (var i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize)
             itemEncode(locaData, j, simpleGlyph.length);
           glyf.data = simpleGlyph;
           return;
@@ -18602,17 +18782,17 @@ var Font = (function FontClosure() {
             platform: int16(font.getBytes(2)),
             encoding: int16(font.getBytes(2)),
             language: int16(font.getBytes(2)),
             name: int16(font.getBytes(2)),
             length: int16(font.getBytes(2)),
             offset: int16(font.getBytes(2))
           };
           // using only Macintosh and Windows platform/encoding names
-          if ((r.platform == 1 && r.encoding == 0 && r.language == 0) ||
+          if ((r.platform == 1 && r.encoding === 0 && r.language === 0) ||
               (r.platform == 3 && r.encoding == 1 && r.language == 0x409)) {
             records.push(r);
           }
         }
         for (var i = 0, ii = records.length; i < ii; i++) {
           var record = records[i];
           var pos = start + stringsStart + record.offset;
           if (pos + record.length > end) {
@@ -18634,17 +18814,17 @@ var Font = (function FontClosure() {
         }
         return names;
       }
 
       function isOS2Valid(os2Table) {
         var data = os2Table.data;
         // usWinAscent == 0 makes font unreadable by windows
         var usWinAscent = (data[74] << 8) | data[75];
-        if (usWinAscent == 0)
+        if (usWinAscent === 0)
           return false;
 
         return true;
       }
 
       var TTOpsStackDeltas = [
         0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5,
         -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1,
@@ -18850,28 +19030,32 @@ var Font = (function FontClosure() {
       font.pos = (font.start || 0) + maxp.offset;
       var version = int16(font.getBytes(4));
       var numGlyphs = int16(font.getBytes(2));
 
       sanitizeMetrics(font, hhea, hmtx, numGlyphs);
 
       sanitizeTTPrograms(fpgm, prep);
 
+      if (head) {
+        sanitizeHead(head, numGlyphs, loca.length);
+      }
+
       var isGlyphLocationsLong = int16([head.data[50], head.data[51]]);
       if (head && loca && glyf) {
         sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong);
       }
 
       var emptyGlyphIds = [];
       if (glyf)
         findEmptyGlyphs(loca, isGlyphLocationsLong, emptyGlyphIds);
 
       // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
       // Sometimes it's 0. That needs to be fixed
-      if (hhea.data[10] == 0 && hhea.data[11] == 0) {
+      if (hhea.data[10] === 0 && hhea.data[11] === 0) {
         hhea.data[10] = 0xFF;
         hhea.data[11] = 0xFF;
       }
 
       // The 'post' table has glyphs names.
       if (post) {
         var valid = readPostScriptTable(post, properties, numGlyphs);
         if (!valid) {
@@ -18943,198 +19127,154 @@ var Font = (function FontClosure() {
             break;
           var unicode = unusedUnicode++;
           this.toFontChar[cid] = unicode;
           usedUnicodes[unicode] = true;
           glyphs.push({ unicode: unicode, code: cid });
           ids.push(i);
         }
       } else {
-        var cmapTable = readCMapTable(cmap, font);
-
-        glyphs = cmapTable.glyphs;
-        ids = cmapTable.ids;
-
-        var hasShortCmap = !!cmapTable.hasShortCmap;
+        this.useToFontChar = true;
+        // Most of the following logic in this code branch is based on the
+        // 9.6.6.4 of the PDF spec.
+
+        // TODO(mack):
+        // We are using this.hasEncoding to mean that the encoding is either
+        // MacRomanEncoding or WinAnsiEncoding (following spec in 9.6.6.4),
+        // but this.hasEncoding is currently true for any encodings on the
+        // Encodings object (e.g. MacExpertEncoding). So should consider using
+        // better check for this.
+        var cmapTable = readCmapTable(cmap, font, this.hasEncoding,
+            this.isSymbolicFont);
+
+        // TODO(mack): If the (3, 0) cmap table used, then the font is
+        // symbolic. The range of charcodes in the cmap table should be
+        // one of the following:
+        //   -> 0x0000 - 0x00FF
+        //   -> 0xF000 - 0xF0FF
+        //   -> 0xF100 - 0xF1FF
+        //   -> 0xF200 - 0xF2FF
+        // If it is not, we should change not consider this a symbolic font
+        this.isSymbolicFont = cmapTable.isSymbolicFont;
+
+        var cmapPlatformId = cmapTable.platformId;
+        var cmapEncodingId = cmapTable.encodingId;
+        var cmapMappings = cmapTable.mappings;
+        var cmapMappingsLength = cmapMappings.length;
+        var glyphs = [];
+        var ids = [];
+        for (var i = 0; i < cmapMappingsLength; ++i) {
+          var cmapMapping = cmapMappings[i];
+          var charcode = cmapMapping.charcode;
+          var unicode = cmapCharcodeToUnicode(charcode, this.isSymbolicFont,
+              cmapPlatformId, cmapEncodingId);
+
+          if (!unicode) {
+            // TODO(mack): gotta check if skipping mappings where we cannot find
+            // a unicode is the correct behaviour
+            continue;
+          }
+          glyphs.push({
+            code: charcode,
+            unicode: unicode
+          });
+          ids.push(cmapMapping.glyphId);
+        }
+
+        var hasShortCmap = cmapTable.hasShortCmap;
         var toFontChar = this.toFontChar;
 
         if (hasShortCmap && ids.length == numGlyphs) {
           // Fixes the short cmap tables -- some generators use incorrect
           // glyph id.
-          for (var i = 0, ii = ids.length; i < ii; i++)
+          for (var i = 0, ii = ids.length; i < ii; i++) {
             ids[i] = i;
-        }
-
-        var unusedUnicode = CMAP_GLYPH_OFFSET;
-        var glyphNames = properties.glyphNames || [];
-        var encoding = properties.baseEncoding;
-        var differences = properties.differences;
-        if (toFontChar && toFontChar.length > 0) {
-          // checking if cmap is just identity map
-          var isIdentity = true;
-          for (var i = 0, ii = glyphs.length; i < ii; i++) {
-            if (glyphs[i].unicode != i + 1) {
-              isIdentity = false;
-              break;
-            }
-          }
-          // if it is, replacing with meaningful toUnicode values
-          if (isIdentity && !this.isSymbolicFont) {
-            var usedUnicodes = [], unassignedUnicodeItems = [];
-            for (var i = 0, ii = glyphs.length; i < ii; i++) {
-              var unicode = toFontChar[i + 1];
-              if (!unicode || typeof unicode !== 'number' ||
-                  unicode in usedUnicodes) {
-                unassignedUnicodeItems.push(i);
-                continue;
-              }
-              glyphs[i].unicode = unicode;
-              usedUnicodes[unicode] = true;
-            }
-            for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; j++) {
-              var i = unassignedUnicodeItems[j];
-              while (unusedUnicode in usedUnicodes)
-                unusedUnicode++;
-              var cid = i + 1;
-              // override only if unicode mapping is not specified
-              if (!(cid in toFontChar))
-                toFontChar[cid] = unusedUnicode;
-              glyphs[i].unicode = unusedUnicode++;
-            }
-            this.useToFontChar = true;
-          }
-        }
-
-        // remove glyph references outside range of avaialable glyphs or empty
-        var glyphsRemoved = 0;
-        for (var i = ids.length - 1; i >= 0; i--) {
-          if (ids[i] < numGlyphs &&
-              (!emptyGlyphIds[ids[i]] || this.isSymbolicFont))
-            continue;
-          ids.splice(i, 1);
-          glyphs.splice(i, 1);
-          glyphsRemoved++;
-        }
-
-        // checking if it's a "true" symbolic font
-        if (this.isSymbolicFont) {
-          var minUnicode = 0xFFFF, maxUnicode = 0;
-          for (var i = 0, ii = glyphs.length; i < ii; i++) {
-            var unicode = glyphs[i].unicode;
-            minUnicode = Math.min(minUnicode, unicode);
-            maxUnicode = Math.max(maxUnicode, unicode);
-          }
-          // high byte must be the same for min and max unicodes
-          if ((maxUnicode & 0xFF00) != (minUnicode & 0xFF00))
-            this.isSymbolicFont = false;
-        }
-
-        // heuristics: if removed more than 5 glyphs encoding WinAnsiEncoding
-        // does not set properly (broken PDFs have about 100 removed glyphs)
-        if (glyphsRemoved > 5) {
-          warn('Switching TrueType encoding to MacRomanEncoding for ' +
-               this.name + ' font');
-          encoding = Encodings.MacRomanEncoding;
-        }
-
-        if (hasShortCmap && this.hasEncoding && !this.isSymbolicFont) {
-          // Re-encode short map encoding to unicode -- that simplifies the
-          // resolution of MacRoman encoded glyphs logic for TrueType fonts:
-          // copying all characters to private use area, all mapping all known
-          // glyphs to the unicodes. The glyphs and ids arrays will grow.
-          var usedUnicodes = [];
-          for (var i = 0, ii = glyphs.length; i < ii; i++) {
-            var code = glyphs[i].unicode;
-            var gid = ids[i];
-            glyphs[i].unicode += CMAP_GLYPH_OFFSET;
-            toFontChar[code] = glyphs[i].unicode;
-
-            var glyphName = glyphNames[gid] || encoding[code];
-            if (glyphName in GlyphsUnicode) {
-              var unicode = GlyphsUnicode[glyphName];
-              if (unicode in usedUnicodes)
-                continue;
-
-              usedUnicodes[unicode] = true;
-              glyphs.push({
-                unicode: unicode,
-                code: glyphs[i].code
-              });
-              ids.push(gid);
-              toFontChar[code] = unicode;
-            }
-          }
-          this.useToFontChar = true;
-        } else if (!this.isSymbolicFont && (this.hasEncoding ||
-                    properties.glyphNames || differences.length > 0)) {
-          // Re-encode cmap encoding to unicode, based on the 'post' table data
-          // diffrence array or base encoding
-          var reverseMap = [];
-          for (var i = 0, ii = glyphs.length; i < ii; i++)
-            reverseMap[glyphs[i].unicode] = i;
-
-          var newGlyphUnicodes = [];
-          for (var i = 0, ii = glyphs.length; i < ii; i++) {
-            var code = glyphs[i].unicode;
-            var changeCode = false;
-            var gid = ids[i];
-
-            var glyphName = glyphNames[gid];
-            if (!glyphName) {
-              glyphName = differences[code] || encoding[code];
-              changeCode = true;
-            }
-            if (glyphName in GlyphsUnicode) {
-              var unicode = GlyphsUnicode[glyphName];
-              if (!unicode || reverseMap[unicode] === i)
-                continue; // unknown glyph name or in its own place
-
-              newGlyphUnicodes[i] = unicode;
-              if (changeCode)
-                toFontChar[code] = unicode;
-              delete reverseMap[code];
-            }
-          }
-          for (var index in newGlyphUnicodes) {
-            if (newGlyphUnicodes.hasOwnProperty(index)) {
-              var unicode = newGlyphUnicodes[index];
-              if (reverseMap[unicode]) {
-                // avoiding assigning to the same unicode
-                glyphs[index].unicode = unusedUnicode++;
-                continue;
-              }
-              glyphs[index].unicode = unicode;
-              reverseMap[unicode] = index;
-            }
-          }
-          this.useToFontChar = true;
-        }
-
-        // Moving all symbolic font glyphs into 0xF000 - 0xF0FF range.
+          }
+        }
+
+        // Rewrite the whole toFontChar dictionary with a new one using the
+        // information from the mappings in the cmap table.
+        var newToFontChar = [];
         if (this.isSymbolicFont) {
           for (var i = 0, ii = glyphs.length; i < ii; i++) {
-            var code = glyphs[i].unicode & 0xFF;
-            var fontCharCode = SYMBOLIC_FONT_GLYPH_OFFSET | code;
-            glyphs[i].unicode = toFontChar[code] = fontCharCode;
-          }
-          this.useToFontChar = true;
-        }
+            var glyph = glyphs[i];
+            // For (3, 0) cmap tables:
+            // The charcode key being stored in toFontChar is the lower byte
+            // of the two-byte charcodes of the cmap table since according to
+            // the spec: 'each byte from the string shall be prepended with the
+            // high byte of the range [of charcodes in the cmap table], to form
+            // a two-byte character, which shall be used to select the
+            // associated glyph description from the subtable'.
+            //
+            // For (1, 0) cmap tables:
+            // 'single bytes from the string shall be used to look up the
+            // associated glyph descriptions from the subtable'. This means
+            // charcodes in the cmap will be single bytes, so no-op since
+            // glyph.code & 0xFF === glyph.code
+            newToFontChar[glyph.code & 0xFF] = glyph.unicode;
+          }
+        } else {
+
+          var encoding = properties.baseEncoding;
+          var differences = properties.differences;
+
+          // TODO(mack): check if it is necessary to shift control characters
+          // for non-symbolic fonts so that browsers dont't render them using
+          // space characters
+
+          var glyphCodeMapping = cmapTable.glyphCodeMapping;
+          for (var charcode = 0; charcode < encoding.length; ++charcode) {
+            if (!encoding.hasOwnProperty(charcode)) {
+              continue;
+            }
+
+            // Since the cmap table that we will be writing out is a (3, 1)
+            // unicode table, in this section we will rewrites the charcodes
+            // in the pdf into unicodes
+
+            var glyphName = encoding[charcode];
+            // A nonsymbolic font should not have a Differences array, but
+            // if it does have one, we should still use it
+            if (charcode in differences) {
+              glyphName = differences[charcode];
+            }
+
+            // Finally, any undefined entries in the table shall be filled
+            // using StandardEncoding
+            if (!glyphName) {
+              glyphName = Encodings.StandardEncoding[charcode];
+            }
+
+            // TODO(mack): Handle the case that the glyph name cannot be
+            // mapped as specified, in which case the glyph name shall be
+            // looked up in the font program's 'post' table (if one is
+            // present) and the associated glyph id shall be used.
+            //
+            // For now, we're just using the '.notdef' glyph name in this
+            // case.
+            glyphName = glyphName || '.notdef';
+
+            var unicode = GlyphsUnicode[glyphName];
+            newToFontChar[charcode] = unicode;
+          }
+        }
+        this.toFontChar = toFontChar = newToFontChar;
 
         createGlyphNameMap(glyphs, ids, properties);
         this.glyphNameMap = properties.glyphNameMap;
       }
 
       if (glyphs.length === 0) {
         // defines at least one glyph
         glyphs.push({ unicode: 0xF000, code: 0xF000, glyph: '.notdef' });
         ids.push(0);
       }
 
       // Converting glyphs and ids into font's cmap table
-      cmap.data = createCMapTable(glyphs, ids);
+      cmap.data = createCmapTable(glyphs, ids);
       var unicodeIsEnabled = [];
       for (var i = 0, ii = glyphs.length; i < ii; i++) {
         unicodeIsEnabled[glyphs[i].unicode] = true;
       }
       this.unicodeIsEnabled = unicodeIsEnabled;
 
       if (os2 && !validateOS2Table(os2)) {
         tables.splice(tables.indexOf(os2), 1);
@@ -19268,17 +19408,17 @@ var Font = (function FontClosure() {
       var fields = {
         // PostScript Font Program
         'CFF ': font.data,
 
         // OS/2 and Windows Specific metrics
         'OS/2': stringToArray(createOS2Table(properties, charstrings)),
 
         // Character to glyphs mapping
-        'cmap': createCMapTable(charstrings.slice(),
+        'cmap': createCmapTable(charstrings.slice(),
                                 ('glyphIds' in font) ? font.glyphIds : null),
 
         // Font header
         'head': (function fontFieldsHead() {
           return stringToArray(
               '\x00\x01\x00\x00' + // Version number
               '\x00\x00\x10\x00' + // fontRevision
               '\x00\x00\x00\x00' + // checksumAdjustement
@@ -19369,17 +19509,17 @@ var Font = (function FontClosure() {
       }
       return result;
     },
 
     rebuildToUnicode: function Font_rebuildToUnicode(properties) {
       var firstChar = properties.firstChar, lastChar = properties.lastChar;
       var map = [];
       if (properties.composite) {
-        var isIdentityMap = this.cidToUnicode.length == 0;
+        var isIdentityMap = this.cidToUnicode.length === 0;
         for (var i = firstChar, ii = lastChar; i <= ii; i++) {
           // TODO missing map the character according font's CMap
           var cid = i;
           map[i] = isIdentityMap ? cid : this.cidToUnicode[cid];
         }
       } else {
         for (var i = firstChar, ii = lastChar; i <= ii; i++) {
           var glyph = properties.differences[i];
@@ -19405,67 +19545,78 @@ var Font = (function FontClosure() {
       if (cidSystemInfo) {
         cidToUnicode = CIDToUnicodeMaps[
           cidSystemInfo.registry + '-' + cidSystemInfo.ordering];
       }
 
       if (!cidToUnicode)
         return; // identity encoding
 
+      var cidEncoding = properties.cidEncoding;
+      var overwrite = HalfwidthCMaps[cidEncoding];
       var cid = 1, i, j, k, ii;
       for (i = 0, ii = cidToUnicode.length; i < ii; ++i) {
         var unicode = cidToUnicode[i];
         if (isArray(unicode)) {
           var length = unicode.length;
           for (j = 0; j < length; j++) {
-            cidToUnicodeMap[cid] = unicode[j];
-            unicodeToCIDMap[unicode[j]] = cid;
+            cidToUnicodeMap[cid] = k = unicode[j];
+            if (!unicodeToCIDMap[k] || overwrite) {
+              unicodeToCIDMap[k] = cid;
+            }
           }
           cid++;
         } else if (typeof unicode === 'object') {
           var fillLength = unicode.f;
           if (fillLength) {
             k = unicode.c;
             for (j = 0; j < fillLength; ++j) {
               cidToUnicodeMap[cid] = k;
-              unicodeToCIDMap[k] = cid;
+              if (!unicodeToCIDMap[k] || overwrite) {
+                unicodeToCIDMap[k] = cid;
+              }
               cid++;
               k++;
             }
           } else
             cid += unicode.s;
         } else if (unicode) {
           cidToUnicodeMap[cid] = unicode;
-          unicodeToCIDMap[unicode] = cid;
+          if (!unicodeToCIDMap[unicode] || overwrite) {
+            unicodeToCIDMap[unicode] = cid;
+          }
           cid++;
         } else
           cid++;
       }
 
-      var cidEncoding = properties.cidEncoding;
-      if (cidEncoding && cidEncoding.indexOf('Identity-') !== 0) {
+      if (!cidEncoding) {
+        return;
+      }
+      if (cidEncoding.indexOf('Identity-') !== 0) {
         // input is already Unicode for non-Identity CMap encodings.
-        // However, Unicode-to-CID conversion is needed
-        // regardless of the CMap encoding. So we can't reset
-        // unicodeToCID.
         this.cidToUnicode = [];
+      } else {
+        // We don't have to do reverse conversions if the string is
+        // already CID.
+        this.unicodeToCID = [];
       }
     },
 
     bindDOM: function Font_bindDOM() {
       if (!this.data)
         return null;
 
       var data = bytesToString(this.data);
       var fontName = this.loadedName;
 
       // Add the font-face rule to the document
       var url = ('url(data:' + this.mimetype + ';base64,' +
                  window.btoa(data) + ');');
-      var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}';
+      var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}';
 
       var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
       if (!styleElement) {
           styleElement = document.createElement('style');
           styleElement.id = 'PDFJS_FONT_STYLE_TAG';
           document.documentElement.getElementsByTagName('head')[0].appendChild(
             styleElement);
       }
@@ -19499,17 +19650,17 @@ var Font = (function FontClosure() {
         // finding the charcode via unicodeToCID map
         var charcode = 0;
         if (this.composite)
           charcode = this.unicodeToCID[glyphUnicode];
         // ... via toUnicode map
         if (!charcode && 'toUnicode' in this)
           charcode = this.toUnicode.indexOf(glyphUnicode);
         // setting it to unicode if negative or undefined
-        if (!(charcode > 0))
+        if (charcode <= 0)
           charcode = glyphUnicode;
         // trying to get width via charcode
         width = this.widths[charcode];
         if (width)
           break; // the non-zero width found
       }
       width = width || this.defaultWidth;
       // Do not shadow the property here. See discussion:
@@ -19676,22 +19827,333 @@ var ErrorFont = (function ErrorFontClosu
     exportData: function ErrorFont_exportData() {
       return {error: this.error};
     }
   };
 
   return ErrorFont;
 })();
 
-var CallothersubrCmd = (function CallothersubrCmdClosure() {
-  function CallothersubrCmd(index) {
-    this.index = index;
-  }
-
-  return CallothersubrCmd;
+/*
+ * CharStrings are encoded following the the CharString Encoding sequence
+ * describe in Chapter 6 of the "Adobe Type1 Font Format" specification.
+ * The value in a byte indicates a command, a number, or subsequent bytes
+ * that are to be interpreted in a special way.
+ *
+ * CharString Number Encoding:
+ *  A CharString byte containing the values from 32 through 255 inclusive
+ *  indicate an integer. These values are decoded in four ranges.
+ *
+ * 1. A CharString byte containing a value, v, between 32 and 246 inclusive,
+ * indicate the integer v - 139. Thus, the integer values from -107 through
+ * 107 inclusive may be encoded in single byte.
+ *
+ * 2. A CharString byte containing a value, v, between 247 and 250 inclusive,
+ * indicates an integer involving the next byte, w, according to the formula:
+ * [(v - 247) x 256] + w + 108
+ *
+ * 3. A CharString byte containing a value, v, between 251 and 254 inclusive,
+ * indicates an integer involving the next byte, w, according to the formula:
+ * -[(v - 251) * 256] - w - 108
+ *
+ * 4. A CharString containing the value 255 indicates that the next 4 bytes
+ * are a two complement signed integer. The first of these bytes contains the
+ * highest order bits, the second byte contains the next higher order bits
+ * and the fourth byte contain the lowest order bits.
+ *
+ *
+ * CharString Command Encoding:
+ *  CharStrings commands are encoded in 1 or 2 bytes.
+ *
+ *  Single byte commands are encoded in 1 byte that contains a value between
+ *  0 and 31 inclusive.
+ *  If a command byte contains the value 12, then the value in the next byte
+ *  indicates a command. This "escape" mechanism allows many extra commands
+ * to be encoded and this encoding technique helps to minimize the length of
+ * the charStrings.
+ */
+var Type1CharString = (function Type1CharStringClosure() {
+  var COMMAND_MAP = {
+    'hstem': [1],
+    'vstem': [3],
+    'vmoveto': [4],
+    'rlineto': [5],
+    'hlineto': [6],
+    'vlineto': [7],
+    'rrcurveto': [8],
+    'callsubr': [10],
+    'flex': [12, 35],
+    'drop' : [12, 18],
+    'endchar': [14],
+    'rmoveto': [21],
+    'hmoveto': [22],
+    'vhcurveto': [30],
+    'hvcurveto': [31]
+  };
+
+  function Type1CharString() {
+    this.width = 0;
+    this.lsb = 0;
+    this.flexing = false;
+    this.output = [];
+    this.stack = [];
+  }
+
+  Type1CharString.prototype = {
+    convert: function Type1CharString_convert(encoded, subrs) {
+      var count = encoded.length;
+      var error = false;
+      for (var i = 0; i < count; i++) {
+        var value = encoded[i];
+        if (value < 32) {
+          if (value === 12) {
+            value = (value << 8) + encoded[++i];
+          }
+          switch (value) {
+            case 1: // hstem
+              if (!HINTING_ENABLED) {
+                this.stack = [];
+                break;
+              }
+              error = this.executeCommand(2, COMMAND_MAP.hstem);
+              break;
+            case 3: // vstem
+              if (!HINTING_ENABLED) {
+                this.stack = [];
+                break;
+              }
+              error = this.executeCommand(2, COMMAND_MAP.vstem);
+              break;
+            case 4: // vmoveto
+              if (this.flexing) {
+                if (this.stack.length < 1) {
+                  error = true;
+                  break;
+                }
+                // Add the dx for flex and but also swap the values so they are
+                // the right order.
+                var dy = this.stack.pop();
+                this.stack.push(0, dy);
+                break;
+              }
+              error = this.executeCommand(1, COMMAND_MAP.vmoveto);
+              break;
+            case 5: // rlineto
+              error = this.executeCommand(2, COMMAND_MAP.rlineto);
+              break;
+            case 6: // hlineto
+              error = this.executeCommand(1, COMMAND_MAP.hlineto);
+              break;
+            case 7: // vlineto
+              error = this.executeCommand(1, COMMAND_MAP.vlineto);
+              break;
+            case 8: // rrcurveto
+              error = this.executeCommand(6, COMMAND_MAP.rrcurveto);
+              break;
+            case 9: // closepath
+              // closepath is a Type1 command that does not take argument and is
+              // useless in Type2 and it can simply be ignored.
+              this.stack = [];
+              break;
+            case 10: // callsubr
+              if (this.stack.length < 1) {
+                error = true;
+                break;
+              }
+              var subrNumber = this.stack.pop();
+              error = this.convert(subrs[subrNumber], subrs);
+              break;
+            case 11: // return
+              return error;
+            case 13: // hsbw
+              if (this.stack.length < 2) {
+                error = true;
+                break;
+              }
+              // To convert to type2 we have to move the width value to the
+              // first part of the charstring and then use hmoveto with lsb.
+              var wx = this.stack.pop();
+              var sbx = this.stack.pop();
+              this.lsb = sbx;
+              this.width = wx;
+              this.stack.push(sbx);
+              error = this.executeCommand(1, COMMAND_MAP.hmoveto);
+              break;
+            case 14: // endchar
+              this.output.push(COMMAND_MAP.endchar[0]);
+              break;
+            case 21: // rmoveto
+              if (this.flexing) {
+                break;
+              }
+              error = this.executeCommand(2, COMMAND_MAP.rmoveto);
+              break;
+            case 22: // hmoveto
+              if (this.flexing) {
+                // Add the dy for flex.
+                this.stack.push(0);
+                break;
+              }
+              error = this.executeCommand(1, COMMAND_MAP.hmoveto);
+              break;
+            case 30: // vhcurveto
+              error = this.executeCommand(4, COMMAND_MAP.vhcurveto);
+              break;
+            case 31: // hvcurveto
+              error = this.executeCommand(4, COMMAND_MAP.hvcurveto);
+              break;
+            case (12 << 8) + 0: // dotsection
+              // dotsection is a Type1 command to specify some hinting feature
+              // for dots that do not take a parameter and it can safely be
+              // ignored for Type2.
+              this.stack = [];
+              break;
+            case (12 << 8) + 1: // vstem3
+              if (!HINTING_ENABLED) {
+                this.stack = [];
+                break;
+              }
+              // [vh]stem3 are Type1 only and Type2 supports [vh]stem with
+              // multiple parameters, so instead of returning [vh]stem3 take a
+              // shortcut and return [vhstem] instead.
+              error = this.executeCommand(2, COMMAND_MAP.vstem);
+              break;
+            case (12 << 8) + 2: // hstem3
+              if (!HINTING_ENABLED) {
+                 this.stack = [];
+                break;
+              }
+              // See vstem3.
+              error = this.executeCommand(2, COMMAND_MAP.hstem);
+              break;
+            case (12 << 8) + 6: // seac
+              // seac is like type 2's special endchar but it doesn't use the
+              // first argument asb, so remove it.
+              error = this.executeCommand(4, COMMAND_MAP.endchar);
+              break;
+            case (12 << 8) + 7: // sbw
+              if (this.stack.length < 4) {
+                error = true;
+                break;
+              }
+              // To convert to type2 we have to move the width value to the
+              // first part of the charstring and then use rmoveto with
+              // (dx, dy). The height argument will not be used for vmtx and
+              // vhea tables reconstruction -- ignoring it.
+              var wy = this.stack.pop();
+              var wx = this.stack.pop();
+              var sby = this.stack.pop();
+              var sbx = this.stack.pop();
+              this.lsb = sbx;
+              this.width = wx;
+              this.stack.push(sbx, sby);
+              error = this.executeCommand(2, COMMAND_MAP.rmoveto);
+              break;
+            case (12 << 8) + 12: // div
+              if (this.stack.length < 2) {
+                error = true;
+                break;
+              }
+              var num2 = this.stack.pop();
+              var num1 = this.stack.pop();
+              this.stack.push(num1 / num2);
+              break;
+            case (12 << 8) + 16: // callothersubr
+              if (this.stack.length < 2) {
+                error = true;
+                break;
+              }
+              var subrNumber = this.stack.pop();
+              var numArgs = this.stack.pop();
+              if (subrNumber === 0 && numArgs === 3) {
+                var flexArgs = this.stack.splice(this.stack.length - 17, 17);
+                this.stack.push(
+                  flexArgs[2] + flexArgs[0], // bcp1x + rpx
+                  flexArgs[3] + flexArgs[1], // bcp1y + rpy
+                  flexArgs[4], // bcp2x
+                  flexArgs[5], // bcp2y
+                  flexArgs[6], // p2x
+                  flexArgs[7], // p2y
+                  flexArgs[8], // bcp3x
+                  flexArgs[9], // bcp3y
+                  flexArgs[10], // bcp4x
+                  flexArgs[11], // bcp4y
+                  flexArgs[12], // p3x
+                  flexArgs[13], // p3y
+                  flexArgs[14] // flexDepth
+                  // 15 = finalx unused by flex
+                  // 16 = finaly unused by flex
+                );
+                error = this.executeCommand(13, COMMAND_MAP.flex, true);
+                this.flexing = false;
+                this.stack.push(flexArgs[15], flexArgs[16]);
+              } else if (subrNumber === 1 && numArgs === 0) {
+                this.flexing = true;
+              }
+              break;
+            case (12 << 8) + 17: // pop
+              // Ignore this since it is only used with othersubr.
+              break;
+            case (12 << 8) + 33: // setcurrentpoint
+              // Ignore for now.
+              this.stack = [];
+              break;
+            default:
+              warn('Unknown type 1 charstring command of "' + value + '"');
+              break;
+          }
+          if (error) {
+            break;
+          }
+          continue;
+        } else if (value <= 246) {
+          value = value - 139;
+        } else if (value <= 250) {
+          value = ((value - 247) * 256) + encoded[++i] + 108;
+        } else if (value <= 254) {
+          value = -((value - 251) * 256) - encoded[++i] - 108;
+        } else {
+          value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 |
+                  (encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0;
+        }
+        this.stack.push(value);
+      }
+      return error;
+    },
+
+    executeCommand: function(howManyArgs, command, keepStack) {
+      var stackLength = this.stack.length;
+      if (howManyArgs > stackLength) {
+        return true;
+      }
+      var start = stackLength - howManyArgs;
+      for (var i = start; i < stackLength; i++) {
+        var value = this.stack[i];
+        if (value === (value | 0)) { // int
+          this.output.push(28, (value >> 8) & 0xff, value & 0xff);
+        } else { // fixed point
+          value = (65536 * value) | 0;
+          this.output.push(255,
+                           (value >> 24) & 0xFF,
+                           (value >> 16) & 0xFF,
+                           (value >> 8) & 0xFF,
+                           value & 0xFF);
+        }
+      }
+      this.output.push.apply(this.output, command);
+      if (keepStack) {
+        this.stack.splice(start, howManyArgs);
+      } else {
+        this.stack = [];
+      }
+      return false;
+    }
+  };
+
+  return Type1CharString;
 })();
 
 /*
  * Type1Parser encapsulate the needed code for parsing a Type1 font
  * program. Some of its logic depends on the Type2 charstrings
  * structure.
  */
 var Type1Parser = function type1Parser() {
@@ -19713,348 +20175,16 @@ var Type1Parser = function type1Parser()
       value = stream[i];
       decryptedString[i] = value ^ (r >> 8);
       r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
     }
     return decryptedString.slice(discardNumber);
   }
 
   /*
-   * CharStrings are encoded following the the CharString Encoding sequence
-   * describe in Chapter 6 of the "Adobe Type1 Font Format" specification.
-   * The value in a byte indicates a command, a number, or subsequent bytes
-   * that are to be interpreted in a special way.
-   *
-   * CharString Number Encoding:
-   *  A CharString byte containing the values from 32 through 255 inclusive
-   *  indicate an integer. These values are decoded in four ranges.
-   *
-   * 1. A CharString byte containing a value, v, between 32 and 246 inclusive,
-   * indicate the integer v - 139. Thus, the integer values from -107 through
-   * 107 inclusive may be encoded in single byte.
-   *
-   * 2. A CharString byte containing a value, v, between 247 and 250 inclusive,
-   * indicates an integer involving the next byte, w, according to the formula:
-   * [(v - 247) x 256] + w + 108
-   *
-   * 3. A CharString byte containing a value, v, between 251 and 254 inclusive,
-   * indicates an integer involving the next byte, w, according to the formula:
-   * -[(v - 251) * 256] - w - 108
-   *
-   * 4. A CharString containing the value 255 indicates that the next 4 bytes
-   * are a two complement signed integer. The first of these bytes contains the
-   * highest order bits, the second byte contains the next higher order bits
-   * and the fourth byte contain the lowest order bits.
-   *
-   *
-   * CharString Command Encoding:
-   *  CharStrings commands are encoded in 1 or 2 bytes.
-   *
-   *  Single byte commands are encoded in 1 byte that contains a value between
-   *  0 and 31 inclusive.
-   *  If a command byte contains the value 12, then the value in the next byte
-   *  indicates a command. This "escape" mechanism allows many extra commands
-   * to be encoded and this encoding technique helps to minimize the length of
-   * the charStrings.
-   */
-  var charStringDictionary = {
-    '1': 'hstem',
-    '3': 'vstem',
-    '4': 'vmoveto',
-    '5': 'rlineto',
-    '6': 'hlineto',
-    '7': 'vlineto',
-    '8': 'rrcurveto',
-
-    // closepath is a Type1 command that do not take argument and is useless
-    // in Type2 and it can simply be ignored.
-    '9': null, // closepath
-
-    '10': 'callsubr',
-
-    // return is normally used inside sub-routines to tells to the execution
-    // flow that it can be back to normal.
-    // During the translation process Type1 charstrings will be flattened and
-    // sub-routines will be embedded directly into the charstring directly, so
-    // this can be ignored safely.
-    '11': 'return',
-
-    '12': {
-      // dotsection is a Type1 command to specify some hinting feature for dots
-      // that do not take a parameter and it can safely be ignored for Type2.
-      '0': null, // dotsection
-
-      // [vh]stem3 are Type1 only and Type2 supports [vh]stem with multiple
-      // parameters, so instead of returning [vh]stem3 take a shortcut and
-      // return [vhstem] instead.
-      '1': 'vstem',
-      '2': 'hstem',
-
-      '6': 'endchar', // seac
-      // Type1 only command with command not (yet) built-in ,throw an error
-      '7': -1, // sbw
-
-      '10': 'add',
-      '11': 'sub',
-      '12': 'div',
-
-      // callothersubr is a mechanism to make calls on the postscript
-      // interpreter, this is not supported by Type2 charstring but hopefully
-      // most of the default commands can be ignored safely.
-      '16': 'callothersubr',
-
-      '17': 'pop',
-
-      // setcurrentpoint sets the current point to x, y without performing a
-      // moveto (this is a one shot positionning command). This is used only
-      // with the return of an OtherSubrs call.
-      // TODO Implement the OtherSubrs charstring embedding and replace this
-      // call by a no-op, like 2 'pop' commands for example.
-      '33': null // setcurrentpoint
-    },
-    '13': 'hsbw',
-    '14': 'endchar',
-    '21': 'rmoveto',
-    '22': 'hmoveto',
-    '30': 'vhcurveto',
-    '31': 'hvcurveto'
-  };
-
-  var ESCAPE_CMD = 12;
-
-  // Breaks up the stack by arguments and also calculates the value.
-  function breakUpArgs(stack, numArgs) {
-    var args = [];
-    var index = stack.length - 1;
-    for (var i = 0; i < numArgs; i++) {
-      if (index < 0) {
-        args.unshift({ arg: [0],
-                       value: 0,
-                       offset: 0 });
-        warn('Malformed charstring stack: not enough values on stack.');
-        continue;
-      }
-      var token = stack[index];
-      if (token === 'div') {
-        var a = stack[index - 2];
-        var b = stack[index - 1];
-        if (!isInt(a) || !isInt(b)) {
-          warn('Malformed charsting stack: expected ints on stack for div.');
-          a = 0;
-          b = 1;
-        }
-        args.unshift({ arg: [a, b, 'div'],
-                       value: a / b,
-                       offset: index - 2 });
-        index -= 3;
-      } else if (isInt(token)) {
-        args.unshift({ arg: stack.slice(index, index + 1),
-                       value: token,
-                       offset: index });
-        index--;
-      } else {
-        warn('Malformed charsting stack: found bad token ' + token + '.');
-      }
-    }
-    return args;
-  }
-
-  function decodeCharString(array) {
-    var charstring = [];
-    var lsb = 0;
-    var width = 0;
-    var flexing = false;
-
-    var value = '';
-    var count = array.length;
-    for (var i = 0; i < count; i++) {
-      value = array[i];
-
-      if (value < 32) {
-        var command = null;
-        if (value == ESCAPE_CMD) {
-          var escape = array[++i];
-
-          // TODO Clean this code
-          if (escape == 16) {
-            var index = charstring.pop();
-            var argc = charstring.pop();
-            for (var j = 0; j < argc; j++)
-              charstring.push('drop');
-
-            // If the flex mechanism is not used in a font program, Adobe
-            // states that entries 0, 1 and 2 can simply be replaced by
-            // {}, which means that we can simply ignore them.
-            if (index < 3) {
-              continue;
-            }
-
-            // This is the same things about hint replacement, if it is not used
-            // entry 3 can be replaced by {3}
-            // TODO support hint replacment
-            if (index == 3) {
-              charstring.push(3);
-              i++;
-              continue;
-            }
-
-            assert(argc == 0, 'callothersubr with arguments is not supported');
-            charstring.push(new CallothersubrCmd(index));
-            continue;
-          } else if (escape == 7) { // sbw
-            var args = breakUpArgs(charstring, 4);
-            var arg0 = args[0];
-            var arg1 = args[1];
-            var arg2 = args[2];
-            lsb = arg0.value;
-            width = arg2.value;
-            // To convert to type2 we have to move the width value to the first
-            // part of the charstring and then use rmoveto with (dx, dy).
-            // The height argument will not be used for vmtx and vhea tables
-            // reconstruction -- ignoring it.
-            charstring = arg2.arg;
-            charstring = charstring.concat(arg0.arg, arg1.arg);
-            charstring.push('rmoveto');
-            continue;
-          } else if (escape == 17 || escape == 33) {
-            // pop or setcurrentpoint commands can be ignored
-            // since we are not doing callothersubr
-            continue;
-          } else if (escape == 6) {
-            // seac is like type 2's special endchar but it doesn't use the
-            // first argument asb, so remove it.
-            var args = breakUpArgs(charstring, 5);
-            var arg0 = args[0];
-            charstring.splice(arg0.offset, arg0.arg.length);
-          } else if (!HINTING_ENABLED && (escape == 1 || escape == 2)) {
-            charstring.push('drop', 'drop', 'drop', 'drop', 'drop', 'drop');
-            continue;
-          }
-
-          command = charStringDictionary['12'][escape];
-        } else {
-          if (value == 13) { // hsbw
-            var args = breakUpArgs(charstring, 2);
-            var arg0 = args[0];
-            var arg1 = args[1];
-            lsb = arg0.value;
-            width = arg1.value;
-            // To convert to type2 we have to move the width value to the first
-            // part of the charstring and then use hmoveto with lsb.
-            charstring = arg1.arg;
-            charstring = charstring.concat(arg0.arg);
-            charstring.push('hmoveto');
-            continue;
-          } else if (value == 10) { // callsubr
-            if (charstring[charstring.length - 1] < 3) { // subr #0..2
-              // XXX: According to the spec if flex or hinting is not used then
-              // subroutines 0-3 can actually be anything defined by the font,
-              // so we really shouldn't be doing flex here but when
-              // callothersubr 0-2 is used. There hasn't been a real world
-              // example of this yet so we'll keep doing it here.
-              var subrNumber = charstring.pop();
-              switch (subrNumber) {
-                case 1:
-                  flexing = true; // prepare for flex coordinates
-                  break;
-                case 0:
-                  var flexArgs = breakUpArgs(charstring, 17);
-
-                  // removing all flex arguments from the stack
-                  charstring.splice(flexArgs[0].offset,
-                                    charstring.length - flexArgs[0].offset);
-
-                  charstring = charstring.concat(
-                    flexArgs[0].arg, // bcp1x +
-                    flexArgs[2].arg, // rpx
-                    ['add'],
-                    flexArgs[1].arg, // bcp1y +
-                    flexArgs[3].arg, // rpy
-                    ['add'],
-                    flexArgs[4].arg, // bcp2x
-                    flexArgs[5].arg, // bcp2y
-                    flexArgs[6].arg, // p2x
-                    flexArgs[7].arg, // p2y
-                    flexArgs[8].arg, // bcp3x
-                    flexArgs[9].arg, // bcp3y
-                    flexArgs[10].arg, // bcp4x
-                    flexArgs[11].arg, // bcp4y
-                    flexArgs[12].arg, // p3x
-                    flexArgs[13].arg, // p3y
-                    flexArgs[14].arg, // flexDepth
-                    // 15 = finalx unused by flex
-                    // 16 = finaly unused by flex
-                    ['flex']
-                  );
-
-                  flexing = false;
-                  break;
-              }
-              continue;
-            }
-          } else if (value == 21 && flexing) { // rmoveto
-            continue; // ignoring rmoveto
-          } else if (value == 22 && flexing) { // hmoveto
-            // Add the dy for flex.
-            charstring.push(0);
-            continue; // ignoring hmoveto
-          } else if (value == 4 && flexing) { // vmoveto
-            // Insert the dx for flex before dy.
-            var flexArgs = breakUpArgs(charstring, 1);
-            charstring.splice(flexArgs[0].offset, 0, 0);
-            continue; // ignoring vmoveto
-          } else if (!HINTING_ENABLED && (value == 1 || value == 3)) {
-            charstring.push('drop', 'drop');
-            continue;
-          }
-          command = charStringDictionary[value];
-        }
-
-        // Some charstring commands are meaningless in Type2 and will return
-        // a null, let's just ignored them
-        if (!command && i < count) {
-          continue;
-        } else if (!command) {
-          break;
-        } else if (command == -1) {
-          warn('Support for Type1 command ' + value +
-                ' (' + escape + ') is not implemented in charstring: ' +
-                charstring);
-          if (value == 12) {
-            // we know how to ignore only some the Type1 commands
-            switch (escape) {
-              case 7:
-                charstring.push('drop', 'drop', 'drop', 'drop');
-                continue;
-              case 8:
-                charstring.push('drop');
-                continue;
-            }
-          }
-        }
-
-        value = command;
-      } else if (value <= 246) {
-        value = value - 139;
-      } else if (value <= 250) {
-        value = ((value - 247) * 256) + array[++i] + 108;
-      } else if (value <= 254) {
-        value = -((value - 251) * 256) - array[++i] - 108;
-      } else {
-        value = (array[++i] & 0xff) << 24 | (array[++i] & 0xff) << 16 |
-                (array[++i] & 0xff) << 8 | (array[++i] & 0xff) << 0;
-      }
-
-      charstring.push(value);
-    }
-
-    return { charstring: charstring, width: width, lsb: lsb };
-  }
-
-  /*
    * Returns an object containing a Subrs array and a CharStrings
    * array extracted from and eexec encrypted block of data
    */
   function readNumberArray(str, index) {
     var start = index;
     while (str[index++] != '[')
       start++;
     start++;
@@ -20111,16 +20241,17 @@ var Type1Parser = function type1Parser()
 
   this.extractFontProgram = function Type1Parser_extractFontProgram(stream) {
     var eexec = decrypt(stream, EEXEC_ENCRYPT_KEY, 4);
     var eexecStr = '';
     for (var i = 0, ii = eexec.length; i < ii; i++)
       eexecStr += String.fromCharCode(eexec[i]);
 
     var glyphsSection = false, subrsSection = false;
+    var subrs = [], charstrings = [];
     var program = {
       subrs: [],
       charstrings: [],
       properties: {
         'privateData': {
           'lenIV': 4
         }
       }
@@ -20146,27 +20277,24 @@ var Type1Parser = function type1Parser()
       var c = eexecStr[i];
 
       if ((glyphsSection || subrsSection) &&
           (token == 'RD' || token == '-|')) {
         i++;
         var data = eexec.slice(i, i + length);
         var lenIV = program.properties.privateData['lenIV'];
         var encoded = decrypt(data, CHAR_STRS_ENCRYPT_KEY, lenIV);
-        var str = decodeCharString(encoded);
 
         if (glyphsSection) {
-          program.charstrings.push({
+          charstrings.push({
             glyph: glyph,
-            data: str.charstring,
-            lsb: str.lsb,
-            width: str.width
+            encoded: encoded
           });
         } else {
-          program.subrs.push(str.charstring);
+          subrs.push(encoded);
         }
         i += length;
         token = '';
       } else if (isSeparator(c)) {
         // Use '| 0' to prevent setting a double into length such as the double
         // does not flow into the loop variable.
         length = parseInt(token, 10) | 0;
         token = '';
@@ -20188,32 +20316,31 @@ var Type1Parser = function type1Parser()
                 var index = parseInt(getToken(), 10);
                 if (index > j)
                   j = index;
                 var length = parseInt(getToken(), 10);
                 getToken(); // read in 'RD'
                 var data = eexec.slice(i + 1, i + 1 + length);
                 var lenIV = program.properties.privateData['lenIV'];
                 var encoded = decrypt(data, CHAR_STRS_ENCRYPT_KEY, lenIV);
-                var str = decodeCharString(encoded);
                 i = i + 1 + length;
                 t = getToken(); // read in 'NP'
                 if (t == 'noaccess')
                   getToken(); // read in 'put'
-                program.subrs[index] = str.charstring;
+                subrs[index] = encoded;
               }
               break;
             case '/BlueValues':
             case '/OtherBlues':
             case '/FamilyBlues':
             case '/FamilyOtherBlues':
               var blueArray = readNumberArray(eexecStr, i + 1);
               // *Blue* values may contain invalid data: disables reading of
               // those values when hinting is disabled.
-              if (blueArray.length > 0 && (blueArray.length % 2) == 0 &&
+              if (blueArray.length > 0 && (blueArray.length % 2) === 0 &&
                   HINTING_ENABLED) {
                 program.properties.privateData[token.substring(1)] = blueArray;
               }
               break;
             case '/StemSnapH':
             case '/StemSnapV':
               program.properties.privateData[token.substring(1)] =
                 readNumberArray(eexecStr, i + 1);
@@ -20240,16 +20367,36 @@ var Type1Parser = function type1Parser()
         } else if (c == '/') {
           token = glyph = '';
           while ((c = eexecStr[++i]) != ' ')
             glyph += c;
         }
       }
     }
 
+    for (var i = 0; i < charstrings.length; i++) {
+      var glyph = charstrings[i].glyph;
+      var encoded = charstrings[i].encoded;
+      var charString = new Type1CharString();
+      var error = charString.convert(encoded, subrs);
+      var output = charString.output;
+      if (error) {
+        // It seems when FreeType encounters an error while evaluating a glyph
+        // that it completely ignores the glyph so we'll mimic that behaviour
+        // here and put an endchar to make the validator happy.
+        output = [14];
+      }
+      program.charstrings.push({
+        glyph: glyph,
+        data: output,
+        lsb: charString.lsb,
+        width: charString.width
+      });
+    }
+
     return program;
   };
 
   this.extractFontHeader = function Type1Parser_extractFontHeader(stream,
                                                                   properties) {
     var headerString = '';
     for (var i = 0, ii = stream.length; i < ii; i++)
       headerString += String.fromCharCode(stream[i]);
@@ -20431,24 +20578,21 @@ Type1Font.prototype = {
 
     charstrings.sort(function charstrings_sort(a, b) {
       return a.unicode - b.unicode;
     });
     return charstrings;
   },
 
   getType2Charstrings: function Type1Font_getType2Charstrings(
-                                  type1Subrs) {
+                                  type1Charstrings) {
     var type2Charstrings = [];
-    var count = type1Subrs.length;
-    var type1Charstrings = [];
-    for (var i = 0; i < count; i++)
-      type1Charstrings.push(type1Subrs[i].charstring.slice());
-    for (var i = 0; i < count; i++)
-      type2Charstrings.push(this.flattenCharstring(type1Charstrings, i));
+    for (var i = 0, ii = type1Charstrings.length; i < ii; i++) {
+      type2Charstrings.push(type1Charstrings[i].charstring);
+    }
     return type2Charstrings;
   },
 
   getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) {
     var bias = 0;
     var count = type1Subrs.length;
     if (count < 1133)
       bias = 107;
@@ -20458,103 +20602,36 @@ Type1Font.prototype = {
       bias = 32768;
 
     // Add a bunch of empty subrs to deal with the Type2 bias
     var type2Subrs = [];
     for (var i = 0; i < bias; i++)
       type2Subrs.push([0x0B]);
 
     for (var i = 0; i < count; i++) {
-      type2Subrs.push(this.flattenCharstring(type1Subrs, i));
+      type2Subrs.push(type1Subrs[i]);
     }
 
     return type2Subrs;
   },
 
-  /*
-   * Flatten the commands by interpreting the postscript code and replacing
-   * every 'callsubr', 'callothersubr' by the real commands.
-   */
-  commandsMap: {
-    'hstem': 1,
-    'vstem': 3,
-    'vmoveto': 4,
-    'rlineto': 5,
-    'hlineto': 6,
-    'vlineto': 7,
-    'rrcurveto': 8,
-    'callsubr': 10,
-    'return': 11,
-    'add': [12, 10],
-    'sub': [12, 11],
-    'div': [12, 12],
-    'exch': [12, 28],
-    'flex': [12, 35],
-    'drop' : [12, 18],
-    'endchar': 14,
-    'rmoveto': 21,
-    'hmoveto': 22,
-    'vhcurveto': 30,
-    'hvcurveto': 31
-  },
-
-  flattenCharstring: function Type1Font_flattenCharstring(charstrings, index) {
-    var charstring = charstrings[index];
-    if (!charstring)
-      return [0x0B];
-    var map = this.commandsMap;
-    // charstring changes size - can't cache .length in loop
-    for (var i = 0; i < charstring.length; i++) {
-      var command = charstring[i];
-      if (typeof command === 'string') {
-        var cmd = map[command];
-        assert(cmd, 'Unknow command: ' + command);
-
-        if (isArray(cmd))
-          charstring.splice(i++, 1, cmd[0], cmd[1]);
-        else
-          charstring[i] = cmd;
-      } else if (command instanceof CallothersubrCmd) {
-        var otherSubrCharstring = charstrings[command.index];
-        if (otherSubrCharstring) {
-          var lastCommand = otherSubrCharstring.indexOf('return');
-          if (lastCommand >= 0)
-            otherSubrCharstring = otherSubrCharstring.slice(0, lastCommand);
-          charstring.splice.apply(charstring,
-                                  [i, 1].concat(otherSubrCharstring));
-        } else
-          charstring.splice(i, 1); // ignoring empty subr call
-        i--;
-      } else {
-        // Type1 charstring use a division for number above 32000
-        if (command > 32000) {
-          var divisor = charstring[i + 1];
-          command /= divisor;
-          charstring.splice(i, 3, 28, (command >> 8) & 0xff, command & 0xff);
-        } else {
-          charstring.splice(i, 1, 28, (command >> 8) & 0xff, command & 0xff);
-        }
-        i += 2;
-      }
-    }
-    return charstring;
-  },
-
   wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) {
     var cff = new CFF();
     cff.header = new CFFHeader(1, 0, 4, 4);
 
     cff.names = [name];
 
     var topDict = new CFFTopDict();
-    topDict.setByName('version', 0);
-    topDict.setByName('Notice', 1);
-    topDict.setByName('FullName', 2);
-    topDict.setByName('FamilyName', 3);
-    topDict.setByName('Weight', 4);
+    // CFF strings IDs 0...390 are predefined names, so refering
+    // to entries in our own String INDEX starts at SID 391.
+    topDict.setByName('version', 391);
+    topDict.setByName('Notice', 392);
+    topDict.setByName('FullName', 393);
+    topDict.setByName('FamilyName', 394);
+    topDict.setByName('Weight', 395);
     topDict.setByName('Encoding', null); // placeholder
     topDict.setByName('FontMatrix', properties.fontMatrix);
     topDict.setByName('FontBBox', properties.bbox);
     topDict.setByName('charset', null); // placeholder
     topDict.setByName('CharStrings', null); // placeholder
     topDict.setByName('Private', null); // placeholder
     cff.topDict = topDict;
 
@@ -20627,17 +20704,17 @@ Type1Font.prototype = {
   }
 };
 
 var CFFFont = (function CFFFontClosure() {
   function CFFFont(file, properties) {
     this.properties = properties;
 
     var parser = new CFFParser(file, properties);
-    var cff = parser.parse(true);
+    var cff = parser.parse();
     var compiler = new CFFCompiler(cff);
     this.readExtra(cff);
     try {
       this.data = compiler.compile();
     } catch (e) {
       warn('Failed to compile font ' + properties.loadedName);
       // There may have just been an issue with the compiler, set the data
       // anyway and hope the font loaded.
@@ -20714,55 +20791,55 @@ var CFFFont = (function CFFFontClosure()
   };
 
   return CFFFont;
 })();
 
 var CFFParser = (function CFFParserClosure() {
   var CharstringValidationData = [
     null,
-    { id: 'hstem', min: 2, resetStack: true },
+    { id: 'hstem', min: 2, resetStack: true, stem: true },
     null,
-    { id: 'vstem', min: 2, resetStack: true },
+    { id: 'vstem', min: 2, resetStack: true, stem: true },
     { id: 'vmoveto', min: 1, resetStack: true },
     { id: 'rlineto', min: 2, resetStack: true },
     { id: 'hlineto', min: 1, resetStack: true },
     { id: 'vlineto', min: 1, resetStack: true },
     { id: 'rrcurveto', min: 6, resetStack: true },
     null,
     { id: 'callsubr', min: 1, undefStack: true },
-    { id: 'return', min: 0, resetStack: true },
+    { id: 'return', min: 0, undefStack: true },
     null, // 12
     null,
     null, // endchar
     null,
     null,
     null,
-    { id: 'hstemhm', min: 2, resetStack: true },
+    { id: 'hstemhm', min: 2, resetStack: true, stem: true },
     null, // hintmask
     null, // cntrmask
     { id: 'rmoveto', min: 2, resetStack: true },
     { id: 'hmoveto', min: 1, resetStack: true },
-    { id: 'vstemhm', min: 2, resetStack: true },
+    { id: 'vstemhm', min: 2, resetStack: true, stem: true },
     { id: 'rcurveline', min: 8, resetStack: true },
     { id: 'rlinecurve', min: 8, resetStack: true },
     { id: 'vvcurveto', min: 4, resetStack: true },
     { id: 'hhcurveto', min: 4, resetStack: true },
     null, // shortint
     { id: 'callgsubr', min: 1, undefStack: true },
     { id: 'vhcurveto', min: 4, resetStack: true },
     { id: 'hvcurveto', min: 4, resetStack: true }
   ];
   var CharstringValidationData12 = [
     null,
     null,
     null,
     { id: 'and', min: 2, stackDelta: -1 },
     { id: 'or', min: 2, stackDelta: -1 },
-    { id: 'not', min: 2, stackDelta: -1 },
+    { id: 'not', min: 1, stackDelta: 0 },
     null,
     null,
     null,
     { id: 'abs', min: 1, stackDelta: 0 },
     { id: 'add', min: 2, stackDelta: -1 },
     { id: 'sub', min: 2, stackDelta: -1 },
     { id: 'div', min: 2, stackDelta: -1 },
     null,
@@ -20792,17 +20869,17 @@ var CFFParser = (function CFFParserClosu
     { id: 'flex1', min: 11, resetStack: true }
   ];
 
   function CFFParser(file, properties) {
     this.bytes = file.getBytes();
     this.properties = properties;
   }
   CFFParser.prototype = {
-    parse: function CFFParser_parse(normalizeCIDData) {
+    parse: function CFFParser_parse() {
       var properties = this.properties;
       var cff = new CFF();
       this.cff = cff;
 
       // The first five sections must be in order, all the others are reached
       // via offsets contained in one of the below.
       var header = this.parseHeader();
       var nameIndex = this.parseIndex(header.endPos);
@@ -20860,50 +20937,26 @@ var CFFParser = (function CFFParserClosu
                                      cff.charStrings.count, cff.strings, false);
         encoding = this.parseEncoding(topDict.getByName('Encoding'),
                                       properties,
                                       cff.strings, charset.charset);
       }
       cff.charset = charset;
       cff.encoding = encoding;
 
-      if (!cff.isCIDFont || !normalizeCIDData)
-        return cff;
-
-      // DirectWrite does not like CID fonts data. Trying to convert/flatten
-      // the font data and remove CID properties.
-      if (cff.fdArray.length !== 1) {
-        warn('Unable to normalize CID font in CFF data -- using font as is');
-        return cff;
-      }
-
-      var fontDict = cff.fdArray[0];
-      // Make the sanitizer happy and remove anything that is only for CID
-      // fonts.
-      fontDict.setByKey(17, topDict.getByName('CharStrings'));
-      fontDict.removeByName('CIDFontVersion');
-      fontDict.removeByName('CIDFontRevision');
-      fontDict.removeByName('CIDFontType');
-      fontDict.removeByName('CIDCount');
-      fontDict.removeByName('UIDBase');
-      cff.topDict = fontDict;
-      cff.isCIDFont = false;
-      delete cff.fdArray;
-      delete cff.fdSelect;
-
       return cff;
     },
     parseHeader: function CFFParser_parseHeader() {
       var bytes = this.bytes;
       var offset = 0;
 
       while (bytes[offset] != 1)
         ++offset;
 
-      if (offset != 0) {
+      if (offset !== 0) {
         info('cff data is shifted');
         bytes = bytes.subarray(offset);
         this.bytes = bytes;
       }
       var major = bytes[0];
       var minor = bytes[1];
       var hdrSize = bytes[2];
       var offSize = bytes[3];
@@ -20983,17 +21036,17 @@ var CFFParser = (function CFFParserClosu
     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;
 
-      if (count != 0) {
+      if (count !== 0) {
         var offsetSize = bytes[pos++];
         // add 1 for offset to determine size of last object
         var startPos = pos + ((count + 1) * offsetSize) - 1;
 
         for (var i = 0, ii = count + 1; i < ii; ++i) {
           var offset = 0;
           for (var j = 0; j < offsetSize; ++j) {
             offset <<= 8;
@@ -21040,18 +21093,18 @@ var CFFParser = (function CFFParserClosu
     parseStringIndex: function CFFParser_parseStringIndex(index) {
       var strings = new CFFStrings();
       for (var i = 0, ii = index.count; i < ii; ++i) {
         var data = index.get(i);
         strings.add(String.fromCharCode.apply(null, data));
       }
       return strings;
     },
-    createDict: function CFFParser_createDict(type, dict, strings) {
-      var cffDict = new type(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);
       }
@@ -21069,17 +21122,17 @@ var CFFParser = (function CFFParserClosu
         var valid = true;
         var data = charstring;
         var length = data.length;
         for (var j = 0; j < length;) {
           var value = data[j++];
           var validationCommand = null;
           if (value == 12) {
             var q = data[j++];
-            if (q == 0) {
+            if (q === 0) {
               // The CFF specification state that the 'dotsection' command
               // (12, 0) is deprecated and treated as a no-op, but all Type2
               // charstrings processors should support them. Unfortunately
               // the font sanitizer don't. As a workaround the sequence (12, 0)
               // is replaced by a useless (0, hmoveto).
               data[j - 2] = 139;
               data[j - 1] = 22;
               stackSize = 0;
@@ -21096,27 +21149,27 @@ var CFFParser = (function CFFParserClosu
           } else if (value >= 32 && value <= 246) {  // number
             stackSize++;
           } else if (value >= 247 && value <= 254) {  // number (+1 bytes)
             j++;
             stackSize++;
           } else if (value == 255) {  // number (32 bit)
             j += 4;
             stackSize++;
-          } else if (value == 18 || value == 23) {
-            hints += stackSize >> 1;
-            validationCommand = CharstringValidationData[value];
           } else if (value == 19 || value == 20) {
             hints += stackSize >> 1;
             j += (hints + 7) >> 3; // skipping right amount of hints flag data
             stackSize = 0;
           } else {
             validationCommand = CharstringValidationData[value];
           }
           if (validationCommand) {
+            if (validationCommand.stem) {
+              hints += stackSize >> 1;
+            }
             if ('min' in validationCommand) {
               if (!undefStack && stackSize < validationCommand.min) {
                 warn('Not enough parameters for ' + validationCommand.id +
                      '; actual: ' + stackSize +
                      ', expected: ' + validationCommand.min);
                 valid = false;
                 break;
               }
@@ -21173,17 +21226,17 @@ var CFFParser = (function CFFParserClosu
       if (subrsOffset === 0 || relativeOffset >= this.bytes.length) {
         privateDict.removeByName('Subrs');
         return;
       }
       var subrsIndex = this.parseIndex(relativeOffset);
       privateDict.subrsIndex = subrsIndex.obj;
     },
     parseCharsets: function CFFParser_parseCharsets(pos, length, strings, cid) {
-      if (pos == 0) {
+      if (pos === 0) {
         return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE,
                               ISOAdobeCharset);
       } else if (pos == 1) {
         return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT,
                               ExpertCharset);
       } else if (pos == 2) {
         return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT_SUBSET,
                               ExpertSubsetCharset);
@@ -21244,17 +21297,17 @@ var CFFParser = (function CFFParserClosu
         var supplementsCount = bytes[pos++];
         for (var i = 0; i < supplementsCount; i++) {
           var code = bytes[pos++];
           var sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff);
           encoding[code] = properties.differences.indexOf(strings.get(sid));
         }
       }
 
-      if (pos == 0 || pos == 1) {
+      if (pos === 0 || pos == 1) {
         predefined = true;
         format = pos;
         var baseEncoding = pos ? Encodings.ExpertEncoding :
                                  Encodings.StandardEncoding;
         for (var i = 0, ii = charset.length; i < ii; i++) {
           var index = baseEncoding.indexOf(charset[i]);
           if (index != -1) {
             encoding[index] = i;
@@ -21497,36 +21550,39 @@ var CFFTopDict = (function CFFTopDictClo
     [4, 'Weight', 'sid', null],
     [[12, 1], 'isFixedPitch', 'num', 0],
     [[12, 2], 'ItalicAngle', 'num', 0],
     [[12, 3], 'UnderlinePosition', 'num', -100],
     [[12, 4], 'UnderlineThickness', 'num', 50],
     [[12, 5], 'PaintType', 'num', 0],
     [[12, 6], 'CharstringType', 'num', 2],
     [[12, 7], 'FontMatrix', ['num', 'num', 'num', 'num', 'num', 'num'],
-                            [.001, 0, 0, .001, 0, 0]],
+                            [0.001, 0, 0, 0.001, 0, 0]],
     [13, 'UniqueID', 'num', null],
     [5, 'FontBBox', ['num', 'num', 'num', 'num'], [0, 0, 0, 0]],
     [[12, 8], 'StrokeWidth', 'num', 0],
     [14, 'XUID', 'array', null],
     [15, 'charset', 'offset', 0],
     [16, 'Encoding', 'offset', 0],
     [17, 'CharStrings', 'offset', 0],
     [18, 'Private', ['offset', 'offset'], null],
     [[12, 21], 'PostScript', 'sid', null],
     [[12, 22], 'BaseFontName', 'sid', null],
     [[12, 23], 'BaseFontBlend', 'delta', null],
     [[12, 31], 'CIDFontVersion', 'num', 0],
     [[12, 32], 'CIDFontRevision', 'num', 0],
     [[12, 33], 'CIDFontType', 'num', 0],
     [[12, 34], 'CIDCount', 'num', 8720],
     [[12, 35], 'UIDBase', 'num', null],
-    [[12, 36], 'FDArray', 'offset', null],
+    // XXX: CID Fonts on DirectWrite 6.1 only seem to work if FDSelect comes
+    // before FDArray.
     [[12, 37], 'FDSelect', 'offset', null],
-    [[12, 38], 'FontName', 'sid', null]];
+    [[12, 36], 'FDArray', 'offset', null],
+    [[12, 38], 'FontName', 'sid', null]
+  ];
   var tables = null;
   function CFFTopDict(strings) {
     if (tables === null)
       tables = CFFDict.createTables(layout);
     CFFDict.call(this, tables, strings);
     this.privateDict = null;
   }
   CFFTopDict.prototype = Object.create(CFFDict.prototype);
@@ -21664,17 +21720,17 @@ var CFFOffsetTracker = (function CFFOffs
 // Takes a CFF and converts it to the binary representation.
 var CFFCompiler = (function CFFCompilerClosure() {
   function stringToArray(str) {
     var array = [];
     for (var i = 0, ii = str.length; i < ii; ++i)
       array[i] = str.charCodeAt(i);
 
     return array;
-  };
+  }
   function CFFCompiler(cff) {
     this.cff = cff;
   }
   CFFCompiler.prototype = {
     compile: function CFFCompiler_compile() {
       var cff = this.cff;
       var output = {
         data: [],
@@ -21687,17 +21743,19 @@ var CFFCompiler = (function CFFCompilerC
 
       // Compile the five entries that must be in order.
       var header = this.compileHeader(cff.header);
       output.add(header);
 
       var nameIndex = this.compileNameIndex(cff.names);
       output.add(nameIndex);
 
-      var compiled = this.compileTopDicts([cff.topDict], output.length);
+      var compiled = this.compileTopDicts([cff.topDict],
+                                          output.length,
+                                          cff.isCIDFont);
       output.add(compiled.output);
       var topDictTracker = compiled.trackers[0];
 
       var stringIndex = this.compileStringIndex(cff.strings.strings);
       output.add(stringIndex);
 
       var globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
       output.add(globalSubrIndex);
@@ -21730,31 +21788,32 @@ var CFFCompiler = (function CFFCompilerC
       output.add(charStrings);
 
       if (cff.isCIDFont) {
         // For some reason FDSelect must be in front of FDArray on windows. OSX
         // and linux don't seem to care.
         topDictTracker.setEntryLocation('FDSelect', [output.length], output);
         var fdSelect = this.compileFDSelect(cff.fdSelect.raw);
         output.add(fdSelect);
-
-        var compiled = this.compileTopDicts(cff.fdArray, output.length);
+        // It is unclear if the sub font dictionary can have CID related
+        // dictionary keys, but the sanitizer doesn't like them so remove them.
+        var compiled = this.compileTopDicts(cff.fdArray, output.length, true);
         topDictTracker.setEntryLocation('FDArray', [output.length], output);
         output.add(compiled.output);
         var fontDictTrackers = compiled.trackers;
 
         this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output);
       }
 
       this.compilePrivateDicts([cff.topDict], [topDictTracker], output);
 
       return output.data;
     },
     encodeNumber: function CFFCompiler_encodeNumber(value) {
-      if (parseFloat(value) == parseInt(value) && !isNaN(value)) // isInt
+      if (parseFloat(value) == parseInt(value, 10) && !isNaN(value)) // isInt
         return this.encodeInteger(value);
       else
         return this.encodeFloat(value);
     },
     encodeFloat: function CFFCompiler_encodeFloat(num) {
       var value = num.toString();
       var nibbles = '';
       for (var i = 0, ii = value.length; i < ii; ++i) {
@@ -21806,21 +21865,30 @@ var CFFCompiler = (function CFFCompilerC
       ];
     },
     compileNameIndex: function CFFCompiler_compileNameIndex(names) {
       var nameIndex = new CFFIndex();
       for (var i = 0, ii = names.length; i < ii; ++i)
         nameIndex.add(stringToArray(names[i]));
       return this.compileIndex(nameIndex);
     },
-    compileTopDicts: function CFFCompiler_compileTopDicts(dicts, length) {
+    compileTopDicts: function CFFCompiler_compileTopDicts(dicts,
+                                                          length,
+                                                          removeCidKeys) {
       var fontDictTrackers = [];
       var fdArrayIndex = new CFFIndex();
       for (var i = 0, ii = dicts.length; i < ii; ++i) {
         var fontDict = dicts[i];
+        if (removeCidKeys) {
+          fontDict.removeByName('CIDFontVersion');
+          fontDict.removeByName('CIDFontRevision');
+          fontDict.removeByName('CIDFontType');
+          fontDict.removeByName('CIDCount');
+          fontDict.removeByName('UIDBase');
+        }
         var fontDictTracker = new CFFOffsetTracker();
         var fontDictData = this.compileDict(fontDict, fontDictTracker);
         fontDictTrackers.push(fontDictTracker);
         fdArrayIndex.add(fontDictData);
         fontDictTracker.offset(length);
       }
       fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers);
       return {
@@ -21935,17 +22003,17 @@ var CFFCompiler = (function CFFCompilerC
     compileIndex: function CFFCompiler_compileIndex(index, trackers) {
       trackers = trackers || [];
       var objects = index.objects;
       // First 2 bytes contains the number of objects contained into this index
       var count = objects.length;
 
       // If there is no object, just create an index. This technically
       // should just be [0, 0] but OTS has an issue with that.
-      if (count == 0)
+      if (count === 0)
         return [0, 0, 0];
 
       var data = [(count >> 8) & 0xFF, count & 0xff];
 
       var lastOffset = 1;
       for (var i = 0; i < count; ++i)
         lastOffset += objects[i].length;
 
@@ -26456,17 +26524,17 @@ var PDFImage = (function PDFImageClosure
         if (decodeMap) {
           valueZero = decodeMap[0] ? 1 : 0;
           valueOne = decodeMap[1] ? 1 : 0;
         }
         var mask = 0;
         var buf = 0;
 
         for (var i = 0, ii = length; i < ii; ++i) {
-          if (i % rowComps == 0) {
+          if (i % rowComps === 0) {
             mask = 0;
             buf = 0;
           } else {
             mask >>= 1;
           }
 
           if (mask <= 0) {
             buf = buffer[bufferPos++];
@@ -26474,17 +26542,17 @@ var PDFImage = (function PDFImageClosure
           }
 
           output[i] = !(buf & mask) ? valueZero : valueOne;
         }
       } else {
         // The general case that handles all other bpc values.
         var bits = 0, buf = 0;
         for (var i = 0, ii = length; i < ii; ++i) {
-          if (i % rowComps == 0) {
+          if (i % rowComps === 0) {
             buf = 0;
             bits = 0;
           }
 
           while (bits < bpc) {
             buf = (buf << 8) | buffer[bufferPos++];
             bits += 8;
           }
@@ -26570,17 +26638,17 @@ var PDFImage = (function PDFImageClosure
       var bufferPos = 3; // alpha component offset
       for (i = 0; i < height; i++) {
         mask = 0;
         for (j = 0; j < width; j++) {
           if (!mask) {
             buf = imgArray[imgArrayPos++];
             mask = 128;
           }
-          if (!(buf & mask) == inverseDecode) {
+          if (!(buf & mask) === inverseDecode) {
             buffer[bufferPos] = 0;
           }
           bufferPos += 4;
           mask >>= 1;
         }
       }
     },
     fillRgbaBuffer: function PDFImage_fillRgbaBuffer(buffer, width, height) {
@@ -29719,17 +29787,18 @@ var Parser = (function ParserClosure() {
         dict.set(key, this.getObj(cipherTransform));
       }
 
       // parse image stream
       var startPos = stream.pos;
 
       // searching for the /EI\s/
       var state = 0, ch;
-      while (state != 4 && (ch = stream.getByte()) != null) {
+      while (state != 4 &&
+             (ch = stream.getByte()) !== null && ch !== undefined) {
         switch (ch) {
           case 0x20:
           case 0x0D:
           case 0x0A:
             state = state === 3 ? 4 : 0;
             break;
           case 0x45:
             state = 2;
@@ -29950,17 +30019,17 @@ var Lexer = (function LexerClosure() {
             warn('Unterminated string');
             done = true;
             break;
           case '(':
             ++numParen;
             str += ch;
             break;
           case ')':
-            if (--numParen == 0) {
+            if (--numParen === 0) {
               done = true;
             } else {
               str += ch;
             }
             break;
           case '\\':
             ch = stream.getChar();
             switch (ch) {
@@ -30062,23 +30131,23 @@ var Lexer = (function LexerClosure() {
         } else if (ch === '>') {
           break;
         } else if (specialChars[ch.charCodeAt(0)] === 1) {
           continue;
         } else {
           if (isFirstHex) {
             firstDigit = toHexDigit(ch);
             if (firstDigit === -1) {
-              warn("Ignoring invalid character '" + ch + "' in hex string");
+              warn('Ignoring invalid character "' + ch + '" in hex string');
               continue;
             }
           } else {
             secondDigit = toHexDigit(ch);
             if (secondDigit === -1) {
-              warn("Ignoring invalid character '" + ch + "' in hex string");
+              warn('Ignoring invalid character "' + ch + '" in hex string');
               continue;
             }
             str += String.fromCharCode((firstDigit << 4) | secondDigit);
           }
           isFirstHex = !isFirstHex;
         }
       }
       return str;
@@ -30126,16 +30195,17 @@ var Lexer = (function LexerClosure() {
           return this.getHexString(ch);
         // dict punctuation
         case '>':
           ch = stream.lookChar();
           if (ch == '>') {
             stream.skip();
             return Cmd.get('>>');
           }
+          return Cmd.get(ch);
         case '{':
         case '}':
           return Cmd.get(ch);
         // fall through
         case ')':
           error('Illegal character: ' + ch);
       }
 
@@ -30849,17 +30919,17 @@ var StreamsSequenceStream = (function St
   }
 
   StreamsSequenceStream.prototype = Object.create(DecodeStream.prototype);
 
   StreamsSequenceStream.prototype.readBlock =
     function streamSequenceStreamReadBlock() {
 
     var streams = this.streams;
-    if (streams.length == 0) {
+    if (streams.length === 0) {
       this.eof = true;
       return;
     }
     var stream = streams.shift();
     var chunk = stream.getBytes();
     var bufferLength = this.bufferLength;
     var newLength = bufferLength + chunk.length;
     var buffer = this.ensureBuffer(newLength);
@@ -30969,17 +31039,17 @@ var FlateStream = (function FlateStreamC
 
     this.dict = stream.dict;
     var cmf = bytes[bytesPos++];
     var flg = bytes[bytesPos++];
     if (cmf == -1 || flg == -1)
       error('Invalid header in flate stream: ' + cmf + ', ' + flg);
     if ((cmf & 0x0f) != 0x08)
       error('Unknown compression method in flate stream: ' + cmf + ', ' + flg);
-    if ((((cmf << 8) + flg) % 31) != 0)
+    if ((((cmf << 8) + flg) % 31) !== 0)
       error('Bad FCHECK in flate stream: ' + cmf + ', ' + flg);
     if (flg & 0x20)
       error('FDICT bit set in flate stream: ' + cmf + ', ' + flg);
 
     this.bytes = bytes;
     this.bytesPos = bytesPos;
 
     this.codeSize = 0;
@@ -31023,17 +31093,17 @@ var FlateStream = (function FlateStreamC
       if (typeof (b = bytes[bytesPos++]) == 'undefined')
         error('Bad encoding in flate stream');
       codeBuf |= (b << codeSize);
       codeSize += 8;
     }
     var code = codes[codeBuf & ((1 << maxLen) - 1)];
     var codeLen = code >> 16;
     var codeVal = code & 0xffff;
-    if (codeSize == 0 || codeSize < codeLen || codeLen == 0)
+    if (codeSize === 0 || codeSize < codeLen || codeLen === 0)
       error('Bad encoding in flate stream');
     this.codeBuf = (codeBuf >> codeLen);
     this.codeSize = (codeSize - codeLen);
     this.bytesPos = bytesPos;
     return codeVal;
   };
 
   FlateStream.prototype.generateHuffmanTable =
@@ -31077,17 +31147,17 @@ var FlateStream = (function FlateStreamC
 
   FlateStream.prototype.readBlock = function FlateStream_readBlock() {
     // read block header
     var hdr = this.getBits(3);
     if (hdr & 1)
       this.eof = true;
     hdr >>= 1;
 
-    if (hdr == 0) { // uncompressed block
+    if (hdr === 0) { // uncompressed block
       var bytes = this.bytes;
       var bytesPos = this.bytesPos;
       var b;
 
       if (typeof (b = bytes[bytesPos++]) == 'undefined')
         error('Bad block header in flate stream');
       var blockLen = b;
       if (typeof (b = bytes[bytesPos++]) == 'undefined')
@@ -31310,17 +31380,17 @@ var PredictorStream = (function Predicto
 
     var predictor = this.stream.getByte();
     var rawBytes = this.stream.getBytes(rowBytes);
 
     var bufferLength = this.bufferLength;
     var buffer = this.ensureBuffer(bufferLength + rowBytes);
 
     var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
-    if (prevRow.length == 0)
+    if (prevRow.length === 0)
       prevRow = new Uint8Array(rowBytes);
 
     var j = bufferLength;
     switch (predictor) {
       case 0:
         for (var i = 0; i < rowBytes; ++i)
           buffer[j++] = rawBytes[i];
         break;
@@ -31395,20 +31465,20 @@ var PredictorStream = (function Predicto
  * DecodeStreams.
  */
 var JpegStream = (function JpegStreamClosure() {
   function isAdobeImage(bytes) {
     var maxBytesScanned = Math.max(bytes.length - 16, 1024);
     // Looking for APP14, 'Adobe'
     for (var i = 0; i < maxBytesScanned; ++i) {
       if (bytes[i] == 0xFF && bytes[i + 1] == 0xEE &&
-          bytes[i + 2] == 0x00 && bytes[i + 3] == 0x0E &&
+          bytes[i + 2] === 0x00 && bytes[i + 3] == 0x0E &&
           bytes[i + 4] == 0x41 && bytes[i + 5] == 0x64 &&
           bytes[i + 6] == 0x6F && bytes[i + 7] == 0x62 &&
-          bytes[i + 8] == 0x65 && bytes[i + 9] == 0x00)
+          bytes[i + 8] == 0x65 && bytes[i + 9] === 0x00)
           return true;
       // scanning until frame tag
       if (bytes[i] == 0xFF && bytes[i + 1] == 0xC0)
         break;
     }
     return false;
   }
 
@@ -31655,17 +31725,17 @@ var DecryptStream = (function DecryptStr
   }
 
   var chunkSize = 512;
 
   DecryptStream.prototype = Object.create(DecodeStream.prototype);
 
   DecryptStream.prototype.readBlock = function DecryptStream_readBlock() {
     var chunk = this.str.getBytes(chunkSize);
-    if (!chunk || chunk.length == 0) {
+    if (!chunk || chunk.length === 0) {
       this.eof = true;
       return;
     }
     var decrypt = this.decrypt;
     chunk = decrypt(chunk);
 
     var bufferLength = this.bufferLength;
     var i, n = chunk.length;
@@ -32297,17 +32367,17 @@ var CCITTFaxStream = (function CCITTFaxS
     params = params || new Dict();
 
     this.encoding = params.get('K') || 0;
     this.eoline = params.get('EndOfLine') || false;
     this.byteAlign = params.get('EncodedByteAlign') || false;
     this.columns = params.get('Columns') || 1728;
     this.rows = params.get('Rows') || 0;
     var eoblock = params.get('EndOfBlock');
-    if (eoblock == null)
+    if (eoblock === null || eoblock === undefined)
       eoblock = true;
     this.eoblock = eoblock;
     this.black = params.get('BlackIs1') || false;
 
     this.codingLine = new Uint32Array(this.columns + 1);
     this.refLine = new Uint32Array(this.columns + 2);
 
     this.codingLine[0] = this.columns;
@@ -32316,17 +32386,17 @@ var CCITTFaxStream = (function CCITTFaxS
     this.row = 0;
     this.nextLine2D = this.encoding < 0;
     this.inputBits = 0;
     this.inputBuf = 0;
     this.outputBits = 0;
     this.buf = EOF;
 
     var code1;
-    while ((code1 = this.lookBits(12)) == 0) {
+    while ((code1 = this.lookBits(12)) === 0) {
       this.eatBits(1);
     }
     if (code1 == 1) {
       this.eatBits(12);
     }
     if (this.encoding > 0) {
       this.nextLine2D = !this.lookBits(1);
       this.eatBits(1);
@@ -32400,17 +32470,17 @@ var CCITTFaxStream = (function CCITTFaxS
       return this.buf;
 
     var refLine = this.refLine;
     var codingLine = this.codingLine;
     var columns = this.columns;
 
     var refPos, blackPixels, bits;
 
-    if (this.outputBits == 0) {
+    if (this.outputBits === 0) {
       if (this.eof)
         return null;
 
       this.err = false;
 
       var code1, code2, code3;
       if (this.nextLine2D) {
         for (var i = 0; codingLine[i] < columns; ++i)
@@ -32574,17 +32644,17 @@ var CCITTFaxStream = (function CCITTFaxS
         this.inputBits &= ~7;
 
       var gotEOL = false;
 
       if (!this.eoblock && this.row == this.rows - 1) {
         this.eof = true;
       } else {
         code1 = this.lookBits(12);
-        while (code1 == 0) {
+        while (code1 === 0) {
           this.eatBits(1);
           code1 = this.lookBits(12);
         }
         if (code1 == 1) {
           this.eatBits(12);
           gotEOL = true;
         } else if (code1 == EOF) {
           this.eof = true;
@@ -32642,17 +32712,17 @@ var CCITTFaxStream = (function CCITTFaxS
       else
         this.outputBits = codingLine[this.codingPos = 1];
       this.row++;
     }
 
     if (this.outputBits >= 8) {
       this.buf = (this.codingPos & 1) ? 0 : 0xFF;
       this.outputBits -= 8;
-      if (this.outputBits == 0 && codingLine[this.codingPos] < columns) {
+      if (this.outputBits === 0 && codingLine[this.codingPos] < columns) {
         this.codingPos++;
         this.outputBits = (codingLine[this.codingPos] -
                            codingLine[this.codingPos - 1]);
       }
     } else {
       var bits = 8;
       this.buf = 0;
       do {
@@ -32741,17 +32811,17 @@ var CCITTFaxStream = (function CCITTFaxS
     var code = 0;
     var p;
     var n;
     if (this.eoblock) {
       code = this.lookBits(12);
       if (code == EOF)
         return 1;
 
-      if ((code >> 5) == 0)
+      if ((code >> 5) === 0)
         p = whiteTable1[code];
       else
         p = whiteTable2[code >> 3];
 
       if (p[0] > 0) {
         this.eatBits(p[0]);
         return p[1];
       }
@@ -32772,19 +32842,19 @@ var CCITTFaxStream = (function CCITTFaxS
   CCITTFaxStream.prototype.getBlackCode =
     function ccittFaxStreamGetBlackCode() {
 
     var code, p;
     if (this.eoblock) {
       code = this.lookBits(13);
       if (code == EOF)
         return 1;
-      if ((code >> 7) == 0)
+      if ((code >> 7) === 0)
         p = blackTable1[code];
-      else if ((code >> 9) == 0 && (code >> 7) != 0)
+      else if ((code >> 9) === 0 && (code >> 7) !== 0)
         p = blackTable2[(code >> 1) - 64];
       else
         p = blackTable3[code >> 7];
 
       if (p[0] > 0) {
         this.eatBits(p[0]);
         return p[1];
       }
@@ -32804,18 +32874,18 @@ var CCITTFaxStream = (function CCITTFaxS
     info('bad black code');
     this.eatBits(1);
     return 1;
   };
 
   CCITTFaxStream.prototype.lookBits = function CCITTFaxStream_lookBits(n) {
     var c;
     while (this.inputBits < n) {
-      if ((c = this.str.getByte()) == null) {
-        if (this.inputBits == 0)
+      if ((c = this.str.getByte()) === null || c === undefined) {
+        if (this.inputBits === 0)
           return EOF;
         return ((this.inputBuf << (n - this.inputBits)) &
                 (0xFFFF >> (16 - n)));
       }
       this.inputBuf = (this.inputBuf << 8) + c;
       this.inputBits += 8;
     }
     return (this.inputBuf >> (this.inputBits - n)) & (0xFFFF >> (16 - n));
@@ -32858,17 +32928,17 @@ var LZWStream = (function LZWStreamClosu
 
   LZWStream.prototype = Object.create(DecodeStream.prototype);
 
   LZWStream.prototype.readBits = function LZWStream_readBits(n) {
     var bitsCached = this.bitsCached;
     var cachedData = this.cachedData;
     while (bitsCached < n) {
       var c = this.str.getByte();
-      if (c == null) {
+      if (c === null || c === undefined) {
         this.eof = true;
         return null;
       }
       cachedData = (cachedData << 8) | c;
       bitsCached += 8;
     }
     this.bitsCached = (bitsCached -= n);
     this.cachedData = cachedData;
@@ -33078,16 +33148,22 @@ var WorkerMessageHandler = {
 
           return;
         } else if (e instanceof InvalidPDFException) {
           handler.send('InvalidPDF', {
             exception: e
           });
 
           return;
+        } else if (e instanceof MissingPDFException) {
+          handler.send('MissingPDF', {
+            exception: e
+          });
+
+          return;
         } else {
           handler.send('UnknownError', {
             exception: new UnknownErrorException(e.message, e.toString())
           });
 
           return;
         }
       }
@@ -33132,34 +33208,43 @@ var WorkerMessageHandler = {
           url: source.url,
           progress: function getPDFProgress(evt) {
             handler.send('DocProgress', {
               loaded: evt.loaded,
               total: evt.lengthComputable ? evt.total : void(0)
             });
           },
           error: function getPDFError(e) {
-            handler.send('DocError', 'Unexpected server response of ' +
-                         e.target.status + '.');
+            if (e.target.status == 404) {
+              handler.send('MissingPDF', {
+                exception: new MissingPDFException(
+                  'Missing PDF \"' + source.url + '\".')});
+            } else {
+              handler.send('DocError', 'Unexpected server response (' +
+                            e.target.status + ') while retrieving PDF \"' +
+                            source.url + '\".');
+            }
           },
           headers: source.httpHeaders
         },
         function getPDFLoad(data) {
           loadDocument(data, source);
         });
     });
 
     handler.on('GetPageRequest', function wphSetupGetPage(data) {
       var pageNumber = data.pageIndex + 1;
       var pdfPage = pdfModel.getPage(pageNumber);
+      var encrypt = pdfModel.xref.encrypt;
       var page = {
         pageIndex: data.pageIndex,
         rotate: pdfPage.rotate,
         ref: pdfPage.ref,
-        view: pdfPage.view
+        view: pdfPage.view,
+        disableTextLayer: encrypt ? encrypt.disableTextLayer : false
       };
       handler.send('GetPage', {pageInfo: page});
     });
 
     handler.on('GetData', function wphSetupGetData(data, promise) {
       promise.resolve(pdfModel.stream.bytes);
     });
 
@@ -33186,49 +33271,51 @@ var WorkerMessageHandler = {
       try {
         var page = pdfModel.getPage(pageNum);
         // Pre compile the pdf page and fetch the fonts/images.
         operatorList = page.getOperatorList(handler, dependency);
       } catch (e) {
         var minimumStackMessage =
             'worker.js: while trying to getPage() and getOperatorList()';
 
+        var wrappedException;
+
         // Turn the error into an obj that can be serialized
         if (typeof e === 'string') {
-          e = {
+          wrappedException = {
             message: e,
             stack: minimumStackMessage
           };
         } else if (typeof e === 'object') {
-          e = {
+          wrappedException = {
             message: e.message || e.toString(),
             stack: e.stack || minimumStackMessage
           };
         } else {
-          e = {
+          wrappedException = {
             message: 'Unknown exception type: ' + (typeof e),
             stack: minimumStackMessage
           };
         }
 
         handler.send('PageError', {
           pageNum: pageNum,
-          error: e
+          error: wrappedException
         });
         return;
       }
 
       log('page=%d - getOperatorList: time=%dms, len=%d', pageNum,
                               Date.now() - start, operatorList.fnArray.length);
 
       // Filter the dependecies for fonts.
       var fonts = {};
       for (var i = 0, ii = dependency.length; i < ii; i++) {
         var dep = dependency[i];
-        if (dep.indexOf('g_font_') == 0) {
+        if (dep.indexOf('g_font_') === 0) {
           fonts[dep] = true;
         }
       }
       handler.send('RenderPage', {
         pageIndex: data.pageIndex,
         operatorList: operatorList,
         depFonts: Object.keys(fonts)
       });
@@ -33275,17 +33362,17 @@ var workerConsole = {
   },
 
   time: function time(name) {
     consoleTimer[name] = Date.now();
   },
 
   timeEnd: function timeEnd(name) {
     var time = consoleTimer[name];
-    if (time == null) {
+    if (!time) {
       error('Unkown timer name ' + name);
     }
     this.log('Timer:', name, Date.now() - time);
   }
 };
 
 // Worker thread?
 if (typeof window === 'undefined') {
@@ -33328,34 +33415,34 @@ var JpxImage = (function JpxImageClosure
         var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
         this.parse(data);
         if (this.onload)
           this.onload();
       }).bind(this);
       xhr.send(null);
     },
     parse: function JpxImage_parse(data) {
-      function ReadUint(data, offset, bytes) {
+      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 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 = readUint(data, position, 4);
+        var tbox = readUint(data, position + 4, 4);
         position += headerSize;
         if (lbox == 1) {
-          lbox = ReadUint(data, position, 8);
+          lbox = readUint(data, position, 8);
           position += 8;
           headerSize += 8;
         }
-        if (lbox == 0)
+        if (lbox === 0)
           lbox = length - position + headerSize;
         if (lbox < headerSize)
           error('JPX error: Invalid box field size');
         var dataLength = lbox - headerSize;
         var jumpDataLength = true;
         switch (tbox) {
           case 0x6A501A1A: // 'jP\032\032'
             // TODO
@@ -33574,28 +33661,28 @@ var JpxImage = (function JpxImageClosure
               var tile = {};
               tile.index = readUint16(data, position + 2);
               tile.length = readUint32(data, position + 4);
               tile.dataEnd = tile.length + position - 2;
               tile.partIndex = data[position + 8];
               tile.partsCount = data[position + 9];
 
               context.mainHeader = false;
-              if (tile.partIndex == 0) {
+              if (tile.partIndex === 0) {
                 // reset component specific settings
                 tile.COD = context.COD;
                 tile.COC = context.COC.slice(0); // clone of the global COC
                 tile.QCD = context.QCD;
                 tile.QCC = context.QCC.slice(0); // clone of the global COC
               }
               context.currentTile = tile;
               break;
             case 0xFF93: // Start of data (SOD)
               var tile = context.currentTile;
-              if (tile.partIndex == 0) {
+              if (tile.partIndex === 0) {
                 initializeTile(context, tile.index);
                 buildPackets(context);
               }
 
               // moving to the end of the data
               length = tile.dataEnd - position;
 
               parseTilePackets(context, data, position, length);
@@ -33916,17 +34003,17 @@ var JpxImage = (function JpxImageClosure
         resolution.trx0 = Math.ceil(component.tcx0 / scale);
         resolution.try0 = Math.ceil(component.tcy0 / scale);
         resolution.trx1 = Math.ceil(component.tcx1 / scale);
         resolution.try1 = Math.ceil(component.tcy1 / scale);
         buildPrecincts(context, resolution, blocksDimensions);
         resolutions.push(resolution);
 
         var subband;
-        if (r == 0) {
+        if (r === 0) {
           // one sub-band (LL) with last decomposition
           subband = {};
           subband.type = 'LL';
           subband.tbx0 = Math.ceil(component.tcx0 / scale);
           subband.tby0 = Math.ceil(component.tcy0 / scale);
           subband.tbx1 = Math.ceil(component.tcx1 / scale);
           subband.tby1 = Math.ceil(component.tcy1 / scale);
           subband.resolution = resolution;
@@ -34018,17 +34105,17 @@ var JpxImage = (function JpxImageClosure
       bufferSize = 0;
       if (skipNextBit) {
         position++;
         skipNextBit = false;
       }
     }
     function readCodingpasses() {
       var value = readBits(1);
-      if (value == 0)
+      if (value === 0)
         return 1;
       value = (value << 1) | readBits(1);
       if (value == 0x02)
         return 2;
       value = (value << 2) | readBits(2);
       if (value <= 0x0E)
         return (value & 0x03) + 3;
       value = (value << 5) | readBits(5);
@@ -34138,17 +34225,17 @@ var JpxImage = (function JpxImageClosure
   function copyCoefficients(coefficients, x0, y0, width, height,
                             delta, mb, codeblocks, transformation,
                             segmentationSymbolUsed) {
     var r = 0.5; // formula (E-6)
     for (var i = 0, ii = codeblocks.length; i < ii; ++i) {
       var codeblock = codeblocks[i];
       var blockWidth = codeblock.tbx1_ - codeblock.tbx0_;
       var blockHeight = codeblock.tby1_ - codeblock.tby0_;
-      if (blockWidth == 0 || blockHeight == 0)
+      if (blockWidth === 0 || blockHeight === 0)
         continue;
       if (!('data' in codeblock))
         continue;
 
       var bitModel, currentCodingpassType;
       bitModel = new BitModel(blockWidth, blockHeight, codeblock.subbandType,
         codeblock.zeroBitPlanes);
       currentCodingpassType = 2; // first bit plane starts from cleanup
@@ -34190,17 +34277,17 @@ var JpxImage = (function JpxImageClosure
 
       var offset = (codeblock.tbx0_ - x0) + (codeblock.tby0_ - y0) * width;
       var position = 0;
       for (var j = 0; j < blockHeight; j++) {
         for (var k = 0; k < blockWidth; k++) {
           var n = (bitModel.coefficentsSign[position] ? -1 : 1) *
             bitModel.coefficentsMagnitude[position];
           var nb = bitModel.bitsDecoded[position], correction;
-          if (transformation == 0 || mb > nb) {
+          if (transformation === 0 || mb > nb) {
             // use r only if transformation is irreversible or
             // not all bitplanes were decoded for reversible transformation
             n += n < 0 ? n - r : n > 0 ? n + r : 0;
             correction = 1 << (mb - nb);
           } else
             correction = 1;
           coefficients[offset++] = n * correction * delta;
           position++;
@@ -34259,17 +34346,17 @@ var JpxImage = (function JpxImageClosure
           items: coefficients
         });
 
         b++;
       }
     }
 
     var transformation = codingStyleParameters.transformation;
-    var transform = transformation == 0 ? new IrreversibleTransform() :
+    var transform = transformation === 0 ? new IrreversibleTransform() :
       new ReversibleTransform();
     var result = transform.calculate(subbandCoefficients,
       component.tcx0, component.tcy0);
     return {
       left: component.tcx0,
       top: component.tcy0,
       width: result.width,
       height: result.height,
@@ -34589,35 +34676,35 @@ var JpxImage = (function JpxImageClosure
         this.a -= qeIcx;
 
         if (this.chigh < qeIcx) {
           var d = this.exchangeLps(cx);
           this.renormD();
           return d;
         } else {
           this.chigh -= qeIcx;
-          if ((this.a & 0x8000) == 0) {
+          if ((this.a & 0x8000) === 0) {
             var d = this.exchangeMps(cx);
             this.renormD();
             return d;
           } else {
             return cx.mps;
           }
         }
       },
       renormD: function ArithmeticDecoder_renormD() {
         do {
-          if (this.ct == 0)
+          if (this.ct === 0)
             this.byteIn();
 
           this.a <<= 1;
           this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1);
           this.clow = (this.clow << 1) & 0xFFFF;
           this.ct--;
-        } while ((this.a & 0x8000) == 0);
+        } while ((this.a & 0x8000) === 0);
       },
       exchangeMps: function ArithmeticDecoder_exchangeMps(cx) {
         var d;
         var qeTableIcx = QeTable[cx.index];
         if (this.a < qeTableIcx.qe) {
           d = 1 - cx.mps;
 
           if (qeTableIcx.switchFlag == 1) {
@@ -34843,22 +34930,22 @@ var JpxImage = (function JpxImageClosure
             for (var i1 = 0; i1 < 4; i1++) {
               var i = i0 + i1;
               if (i >= height)
                 break;
               var index = i * width + j;
 
               // significant but not those that have just become
               if (!coefficentsMagnitude[index] ||
-                (processingFlags[index] & processedMask) != 0)
+                (processingFlags[index] & processedMask) !== 0)
                 continue;
 
               var contextLabel = 16;
               if ((processingFlags[index] &
-                firstMagnitudeBitMask) != 0) {
+                firstMagnitudeBitMask) !== 0) {
                 processingFlags[i * width + j] ^= firstMagnitudeBitMask;
                 // first refinement
                 var significance = neighborsSignificance[index];
                 var sumOfSignificance = (significance & 3) +
                   ((significance >> 2) & 3) + ((significance >> 4) & 7);
                 contextLabel = sumOfSignificance >= 1 ? 15 : 14;
               }
 
@@ -34889,24 +34976,24 @@ var JpxImage = (function JpxImageClosure
         var twoRowsDown = width * 2;
         var threeRowsDown = width * 3;
         for (var i0 = 0; i0 < height; i0 += 4) {
           for (var j = 0; j < width; j++) {
             var index0 = i0 * width + j;
             // using the property: labels[neighborsSignificance[index]] == 0
             // when neighborsSignificance[index] == 0
             var allEmpty = i0 + 3 < height &&
-              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;
+              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 cx, i;
             if (allEmpty) {
               cx = this.runLengthContext;
               var hasSignificantCoefficent = decoder.readBit(cx);
               if (!hasSignificantCoefficent) {
                 bitsDecoded[index0]++;
                 bitsDecoded[index0 + oneRowDown]++;
@@ -34932,17 +35019,17 @@ var JpxImage = (function JpxImageClosure
               i1++;
             }
             for (; i1 < 4; i1++, index += width) {