merge mozilla-inbound to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sat, 10 Jun 2017 11:18:21 +0200
changeset 363340 91dc9525c422f11041da33b008b14a8117ed9a40
parent 363339 a305f9e66fa5b6faec4804ace67bb69f3c5de2d3 (current diff)
parent 363306 f4b350008c452a890b207afac42fbc530e1e8ec0 (diff)
child 363341 b7e57100a79cc71dba99280b3a2b4aba719d8565
child 363355 49c375200aabad932ccfad27986a6fb7b79e9db8
child 363359 158930f1c0d31038b5015cdf60221e50bebcfd89
push id91290
push userarchaeopteryx@coole-files.de
push dateSat, 10 Jun 2017 09:21:29 +0000
treeherdermozilla-inbound@b7e57100a79c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone55.0a1
first release with
nightly linux32
91dc9525c422 / 55.0a1 / 20170610100201 / files
nightly linux64
91dc9525c422 / 55.0a1 / 20170610100201 / files
nightly mac
91dc9525c422 / 55.0a1 / 20170610030207 / files
nightly win32
91dc9525c422 / 55.0a1 / 20170610030207 / files
nightly win64
91dc9525c422 / 55.0a1 / 20170610030207 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central. r=merge a=merge MozReview-Commit-ID: FeTjZsgM7om
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -669,40 +669,46 @@ pref("accessibility.loadedInLastSession"
 
 pref("plugins.click_to_play", true);
 pref("plugins.testmode", false);
 
 // Should plugins that are hidden show the infobar UI?
 pref("plugins.show_infobar", true);
 
 // Should dismissing the hidden plugin infobar suppress it permanently?
-pref("plugins.remember_infobar_dismissal", false);
+pref("plugins.remember_infobar_dismissal", true);
 
 pref("plugin.default.state", 1);
 
 // Plugins bundled in XPIs are enabled by default.
 pref("plugin.defaultXpi.state", 2);
 
 // Java is Click-to-Activate by default on all channels.
 pref("plugin.state.java", 1);
 
-// Flash is Click-to-Activate by default on Nightly,
-// Always-Activate on other channels.
+// Flash is Click-to-Activate by default on Nightly.
+// On other channels, it will be controlled by a
+// rollout system addon.
 #ifdef NIGHTLY_BUILD
+pref("plugin.state.flash", 1);
+#else
+pref("plugin.state.flash", 2);
+#endif
+
+// Enables the download and use of the flash blocklists.
 pref("plugins.flashBlock.enabled", true);
-pref("plugin.state.flash", 1);
 
 // Prefer HTML5 video over Flash content, and don't
 // load plugin instances with no src declared.
 // These prefs are documented in details on all.js.
+// With the "follow-ctp" setting, this will only
+// apply to users that have plugin.state.flash = 1.
 pref("plugins.favorfallback.mode", "follow-ctp");
 pref("plugins.favorfallback.rules", "nosrc,video");
-#else
-pref("plugin.state.flash", 2);
-#endif
+
 
 #ifdef XP_WIN
 pref("browser.preferences.instantApply", false);
 #else
 pref("browser.preferences.instantApply", true);
 #endif
 
 // Toggling Search bar on and off in about:preferences
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -46,21 +46,23 @@
 .tab-label-container {
   overflow: hidden;
 }
 
 .tab-label-container[pinned] {
   width: 0;
 }
 
-.tab-label-container[textoverflow]:not([pinned]) {
+.tab-label-container[textoverflow][dir=ltr]:not([pinned]) {
+  direction: ltr;
   mask-image: linear-gradient(to left, transparent, black 2em);
 }
 
-.tab-label-container[textoverflow]:not([pinned]):-moz-locale-dir(rtl) {
+.tab-label-container[textoverflow][dir=rtl]:not([pinned]) {
+  direction: rtl;
   mask-image: linear-gradient(to right, transparent, black 2em);
 }
 
 .tab-stack {
   vertical-align: top; /* for pinned tabs */
 }
 
 tabpanels {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1510,17 +1510,22 @@
         <parameter name="aLabel"/>
         <parameter name="aOptions"/>
         <body>
           <![CDATA[
             if (!aLabel || aTab.getAttribute("label") == aLabel) {
               return false;
             }
 
+            let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIDOMWindowUtils);
+            let isRTL = dwu.getDirectionFromText(aLabel) == Ci.nsIDOMWindowUtils.DIRECTION_RTL;
+
             aTab.setAttribute("label", aLabel);
+            aTab.setAttribute("dir", isRTL ? "rtl" : "ltr");
             aTab._labelIsContentTitle = aOptions && aOptions.isContentTitle;
 
             // Dispatch TabAttrModified event unless we're setting the label
             // before the TabOpen event was dispatched.
             if (!aOptions || !aOptions.beforeTabOpen) {
               this._tabAttrModified(aTab, ["label"]);
             }
 
@@ -7232,17 +7237,17 @@
                      anonid="sharing-icon"
                      class="tab-sharing-icon-overlay"
                      role="presentation"/>
           <xul:image xbl:inherits="crashed,busy,soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected"
                      anonid="overlay-icon"
                      class="tab-icon-overlay"
                      role="presentation"/>
           <xul:hbox class="tab-label-container"
-                    xbl:inherits="pinned,selected=visuallyselected"
+                    xbl:inherits="pinned,selected=visuallyselected,dir"
                     onoverflow="this.setAttribute('textoverflow', 'true');"
                     onunderflow="this.removeAttribute('textoverflow');"
                     flex="1">
             <xul:label class="tab-text tab-label"
                        xbl:inherits="xbl:text=label,accesskey,fadein,pinned,selected=visuallyselected,attention"
                        role="presentation"/>
           </xul:hbox>
           <xul:image xbl:inherits="soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected"
--- a/browser/base/content/test/general/browser_storagePressure_notification.js
+++ b/browser/base/content/test/general/browser_storagePressure_notification.js
@@ -6,24 +6,51 @@ function notifyStoragePressure(usage = 1
   let notifyPromise = TestUtils.topicObserved("QuotaManager::StoragePressure", () => true);
   let usageWrapper = Cc["@mozilla.org/supports-PRUint64;1"]
                      .createInstance(Ci.nsISupportsPRUint64);
   usageWrapper.data = usage;
   Services.obs.notifyObservers(usageWrapper, "QuotaManager::StoragePressure");
   return notifyPromise;
 }
 
-function privacyAboutPrefPromise() {
+function openAboutPrefPromise() {
+  let useOldOrganization = Services.prefs.getBoolPref("browser.preferences.useOldOrganization");
+  let targetURL = useOldOrganization ? "about:preferences#advanced" : "about:preferences#privacy";
   let promises = [
-    BrowserTestUtils.waitForLocationChange(gBrowser, "about:preferences#privacy"),
+    BrowserTestUtils.waitForLocationChange(gBrowser, targetURL),
     TestUtils.topicObserved("advanced-pane-loaded", () => true)
   ];
   return Promise.all(promises);
 }
 
+async function testOverUsageThresholdNotification() {
+  await SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
+  await SpecialPowers.pushPrefEnv({set: [["browser.storageManager.pressureNotification.minIntervalMS", 0]]});
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com");
+
+  const BYTES_IN_GIGABYTE = 1073741824;
+  const USAGE_THRESHOLD_BYTES = BYTES_IN_GIGABYTE *
+    Services.prefs.getIntPref("browser.storageManager.pressureNotification.usageThresholdGB");
+  await notifyStoragePressure(USAGE_THRESHOLD_BYTES);
+  let notificationbox = document.getElementById("high-priority-global-notificationbox");
+  let notification = notificationbox.getNotificationWithValue("storage-pressure-notification");
+  ok(notification instanceof XULElement, "Should display storage pressure notification");
+
+  let prefBtn = notification.getElementsByTagName("button")[1];
+  let aboutPrefPromise = openAboutPrefPromise();
+  prefBtn.doCommand();
+  await aboutPrefPromise;
+  let aboutPrefTab = gBrowser.selectedTab;
+  let prefDoc = gBrowser.selectedBrowser.contentDocument;
+  let siteDataGroup = prefDoc.getElementById("siteDataGroup");
+  is_element_visible(siteDataGroup, "Should open to the siteDataGroup section in about:preferences");
+  await BrowserTestUtils.removeTab(aboutPrefTab);
+  await BrowserTestUtils.removeTab(tab);
+}
+
 // Test only displaying notification once within the given interval
 add_task(async function() {
   const TEST_NOTIFICATION_INTERVAL_MS = 2000;
   await SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
   await SpecialPowers.pushPrefEnv({set: [["browser.storageManager.pressureNotification.minIntervalMS", TEST_NOTIFICATION_INTERVAL_MS]]});
 
   await notifyStoragePressure();
   let notificationbox = document.getElementById("high-priority-global-notificationbox");
@@ -37,29 +64,17 @@ add_task(async function() {
 
   await new Promise(resolve => setTimeout(resolve, TEST_NOTIFICATION_INTERVAL_MS + 1));
   await notifyStoragePressure();
   notification = notificationbox.getNotificationWithValue("storage-pressure-notification");
   ok(notification instanceof XULElement, "Should display storage pressure notification after the given interval");
   notification.close();
 });
 
-// Test guiding user to about:preferences when usage exceeds the given threshold
+// Test guiding user to the about:preferences when usage exceeds the given threshold
 add_task(async function() {
-  await SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
-  await SpecialPowers.pushPrefEnv({set: [["browser.storageManager.pressureNotification.minIntervalMS", 0]]});
-
-  const BYTES_IN_GIGABYTE = 1073741824;
-  const USAGE_THRESHOLD_BYTES = BYTES_IN_GIGABYTE *
-    Services.prefs.getIntPref("browser.storageManager.pressureNotification.usageThresholdGB");
-  await notifyStoragePressure(USAGE_THRESHOLD_BYTES);
-  let notificationbox = document.getElementById("high-priority-global-notificationbox");
-  let notification = notificationbox.getNotificationWithValue("storage-pressure-notification");
-  ok(notification instanceof XULElement, "Should display storage pressure notification");
-
-  let prefBtn = notification.getElementsByTagName("button")[1];
-  let aboutPrefPromise = privacyAboutPrefPromise();
-  prefBtn.doCommand();
-  await aboutPrefPromise;
-  let prefDoc = gBrowser.selectedBrowser.contentDocument;
-  let siteDataGroup = prefDoc.getElementById("siteDataGroup");
-  is_element_visible(siteDataGroup, "Should open the Network tab in about:preferences#privacy");
+  // Test for the old about:preferences
+  await SpecialPowers.pushPrefEnv({set: [["browser.preferences.useOldOrganization", true]]});
+  await testOverUsageThresholdNotification();
+  // Test for the new about:preferences
+  await SpecialPowers.pushPrefEnv({set: [["browser.preferences.useOldOrganization", false]]});
+  await testOverUsageThresholdNotification();
 });
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -149,12 +149,13 @@ skip-if = debug || asan # Bug 1354681
 [browser_ext_webNavigation_urlbar_transitions.js]
 [browser_ext_windows.js]
 [browser_ext_windows_create.js]
 tags = fullscreen
 [browser_ext_windows_create_params.js]
 [browser_ext_windows_create_tabId.js]
 [browser_ext_windows_create_url.js]
 [browser_ext_windows_events.js]
+skip-if = os == 'mac' && debug # bug 1308068
 [browser_ext_windows_size.js]
 skip-if = os == 'mac' # Fails when windows are randomly opened in fullscreen mode
 [browser_ext_windows_update.js]
 tags = fullscreen
new file mode 100644
--- /dev/null
+++ b/browser/extensions/clicktoplay-rollout/bootstrap.js
@@ -0,0 +1,144 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/TelemetryEnvironment.jsm");
+
+// The amount of people to be part of the rollout
+const TEST_THRESHOLD = {
+  "beta": 0.5,  // 50%
+  "release": 0.05,  // 5%
+};
+
+if (AppConstants.RELEASE_OR_BETA) {
+  // The rollout is controlled by the channel name, which
+  // is the only way to distinguish between Beta and Release.
+  // However, non-official release builds (like the ones done by distros
+  // to ship Firefox on their package managers) do not set a value
+  // for the release channel, which gets them to the default value
+  // of.. (drumroll) "default".
+  // But we can't just always configure the same settings for the
+  // "default" channel because that's also the name that a locally
+  // built Firefox gets, and CTP is already directly set there
+  // through an #ifdef in firefox.js
+  TEST_THRESHOLD.default = TEST_THRESHOLD.release;
+}
+
+const PREF_COHORT_SAMPLE       = "plugins.ctprollout.cohortSample";
+const PREF_COHORT_NAME         = "plugins.ctprollout.cohort";
+const PREF_FLASH_STATE         = "plugin.state.flash";
+
+function startup() {
+  defineCohort();
+}
+
+function defineCohort() {
+  let updateChannel = UpdateUtils.getUpdateChannel(false);
+  if (!(updateChannel in TEST_THRESHOLD)) {
+    return;
+  }
+
+  let cohort = Preferences.get(PREF_COHORT_NAME);
+
+  if (!cohort) {
+    // The cohort has not been defined yet: this is the first
+    // time that we're running. Let's see if the user has
+    // a non-default setting to avoid changing it.
+    let currentPluginState = Preferences.get(PREF_FLASH_STATE);
+    switch (currentPluginState) {
+      case Ci.nsIPluginTag.STATE_CLICKTOPLAY:
+        cohort = "early-adopter-ctp";
+        break;
+
+      case Ci.nsIPluginTag.STATE_DISABLED:
+        cohort = "early-adopter-disabled";
+        break;
+
+      default:
+        // intentionally missing from the list is STATE_ENABLED,
+        // which will keep cohort undefined.
+        break;
+    }
+  }
+
+  switch (cohort) {
+    case undefined:
+    case "test":
+    case "control":
+    {
+      // If it's either test/control, the cohort might have changed
+      // if the desired sampling has been changed.
+      let testThreshold = TEST_THRESHOLD[updateChannel];
+      let testGroup = (getUserSample(false) < testThreshold);
+
+      if (testGroup) {
+        cohort = "test";
+        let defaultPrefs = new Preferences({defaultBranch: true});
+        defaultPrefs.set(PREF_FLASH_STATE, Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+      } else {
+        cohort = "control";
+      }
+
+      setCohort(cohort);
+      watchForPrefChanges();
+      break;
+    }
+
+    case "early-adopter-ctp":
+    case "early-adopter-disabled":
+    default:
+      // "user-changed-from-*" will fall into this default case and
+      // not do anything special.
+      setCohort(cohort);
+      break;
+  }
+}
+
+function getUserSample() {
+  let prefValue = Preferences.get(PREF_COHORT_SAMPLE, undefined);
+  let value = 0.0;
+
+  if (typeof(prefValue) == "string") {
+    value = parseFloat(prefValue, 10);
+    return value;
+  }
+
+  value = Math.random();
+  Preferences.set(PREF_COHORT_SAMPLE, value.toString().substr(0, 8));
+  return value;
+}
+
+function setCohort(cohortName) {
+  Preferences.set(PREF_COHORT_NAME, cohortName);
+  TelemetryEnvironment.setExperimentActive("clicktoplay-rollout", cohortName);
+
+  try {
+    if (Ci.nsICrashReporter) {
+      Services.appinfo.QueryInterface(Ci.nsICrashReporter).annotateCrashReport("CTPCohort", cohortName);
+    }
+  } catch (e) {}
+}
+
+function watchForPrefChanges() {
+  Preferences.observe(PREF_FLASH_STATE, function() {
+    let currentCohort = Preferences.get(PREF_COHORT_NAME, "unknown");
+    setCohort(`user-changed-from-${currentCohort}`);
+  });
+}
+
+function install() {
+}
+
+function shutdown(data, reason) {
+}
+
+function uninstall() {
+}
copy from browser/extensions/e10srollout/install.rdf.in
copy to browser/extensions/clicktoplay-rollout/install.rdf.in
--- a/browser/extensions/e10srollout/install.rdf.in
+++ b/browser/extensions/clicktoplay-rollout/install.rdf.in
@@ -4,29 +4,29 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
   <Description about="urn:mozilla:install-manifest">
-    <em:id>e10srollout@mozilla.org</em:id>
-    <em:version>1.50</em:version>
+    <em:id>clicktoplay-rollout@mozilla.org</em:id>
+    <em:version>1.0</em:version>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
 
     <!-- Target Application this theme can install into,
         with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
         <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
       </Description>
     </em:targetApplication>
 
     <!-- Front End MetaData -->
-    <em:name>Multi-process staged rollout</em:name>
-    <em:description>Staged rollout of Firefox multi-process feature.</em:description>
+    <em:name>Click-to-Play staged rollout</em:name>
+    <em:description>Staged rollout for Click-to-Play Flash.</em:description>
   </Description>
 </RDF>
copy from browser/extensions/e10srollout/moz.build
copy to browser/extensions/clicktoplay-rollout/moz.build
--- a/browser/extensions/e10srollout/moz.build
+++ b/browser/extensions/clicktoplay-rollout/moz.build
@@ -2,15 +2,15 @@
 # vim: set filetype=python:
 # 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/.
 
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
 
-FINAL_TARGET_FILES.features['e10srollout@mozilla.org'] += [
+FINAL_TARGET_FILES.features['clicktoplay-rollout@mozilla.org'] += [
   'bootstrap.js'
 ]
 
-FINAL_TARGET_PP_FILES.features['e10srollout@mozilla.org'] += [
+FINAL_TARGET_PP_FILES.features['clicktoplay-rollout@mozilla.org'] += [
   'install.rdf.in'
 ]
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DIRS += [
     'aushelper',
+    'clicktoplay-rollout',
     'e10srollout',
     'followonsearch',
     'pdfjs',
     'pocket',
     'screenshots',
     'shield-recipe-client',
     'webcompat',
 ]
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,5 +1,5 @@
 This is the PDF.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.8.423
+Current extension version is: 1.8.432
 
-Taken from upstream commit: 8654635b
+Taken from upstream commit: 93420545
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -63,17 +63,18 @@ function getIntPref(aPref, aDefaultValue
     return Services.prefs.getIntPref(aPref);
   } catch (ex) {
     return aDefaultValue;
   }
 }
 
 function isDefaultHandler() {
   if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
-    throw new Error("isDefaultHandler should only get called in the parent process.");
+    throw new Error("isDefaultHandler should only get called in the parent " +
+                    "process.");
   }
   return PdfjsChromeUtils.isDefaultHandlerApp();
 }
 
 function initializeDefaultPreferences() {
   var DEFAULT_PREFERENCES =
 {
   "showPreviousViewOnLoad": true,
@@ -264,18 +265,20 @@ var PdfJs = {
     categoryManager.getService(Ci.nsICategoryManager).
                     deleteCategoryEntry("Gecko-Content-Viewers",
                                         PDF_CONTENT_TYPE,
                                         false);
   },
 
   // nsIObserver
   observe: function observe(aSubject, aTopic, aData) {
-    if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
-      throw new Error("Only the parent process should be observing PDF handler changes.");
+    if (Services.appinfo.processType !==
+        Services.appinfo.PROCESS_TYPE_DEFAULT) {
+      throw new Error("Only the parent process should be observing PDF " +
+                      "handler changes.");
     }
 
     this.updateRegistration();
     let jsm = "resource://pdf.js/PdfjsChromeUtils.jsm";
     let PdfjsChromeUtils = Components.utils.import(jsm, {}).PdfjsChromeUtils;
     PdfjsChromeUtils.notifyChildOfSettingsChange(this.enabled);
   },
 
--- a/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
+++ b/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
@@ -127,17 +127,18 @@ var PdfjsChromeUtils = {
   notifyChildOfSettingsChange(enabled) {
     if (Services.appinfo.processType ===
         Services.appinfo.PROCESS_TYPE_DEFAULT && this._ppmm) {
       // XXX kinda bad, we want to get the parent process mm associated
       // with the content process. _ppmm is currently the global process
       // manager, which means this is going to fire to every child process
       // we have open. Unfortunately I can't find a way to get at that
       // process specific mm from js.
-      this._ppmm.broadcastAsyncMessage("PDFJS:Child:updateSettings", {enabled});
+      this._ppmm.broadcastAsyncMessage("PDFJS:Child:updateSettings",
+                                       { enabled, });
     }
   },
 
   /*
    * Events
    */
 
   observe(aSubject, aTopic, aData) {
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -992,69 +992,94 @@ var createObjectURL = function createObj
           d2 = (b1 & 3) << 4 | b2 >> 4;
       var d3 = i + 1 < ii ? (b2 & 0xF) << 2 | b3 >> 6 : 64;
       var d4 = i + 2 < ii ? b3 & 0x3F : 64;
       buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
     }
     return buffer;
   };
 }();
+function resolveCall(fn, args, thisArg = null) {
+  if (!fn) {
+    return Promise.resolve(undefined);
+  }
+  return new Promise((resolve, reject) => {
+    resolve(fn.apply(thisArg, args));
+  });
+}
+function resolveOrReject(capability, success, reason) {
+  if (success) {
+    capability.resolve();
+  } else {
+    capability.reject(reason);
+  }
+}
+function finalize(promise) {
+  return Promise.resolve(promise).catch(() => {});
+}
 function MessageHandler(sourceName, targetName, comObj) {
   this.sourceName = sourceName;
   this.targetName = targetName;
   this.comObj = comObj;
-  this.callbackIndex = 1;
+  this.callbackId = 1;
+  this.streamId = 1;
   this.postMessageTransfers = true;
-  var callbacksCapabilities = this.callbacksCapabilities = Object.create(null);
-  var ah = this.actionHandler = Object.create(null);
+  this.streamSinks = Object.create(null);
+  this.streamControllers = Object.create(null);
+  let callbacksCapabilities = this.callbacksCapabilities = Object.create(null);
+  let ah = this.actionHandler = Object.create(null);
   this._onComObjOnMessage = event => {
-    var data = event.data;
+    let data = event.data;
     if (data.targetName !== this.sourceName) {
       return;
     }
-    if (data.isReply) {
-      var callbackId = data.callbackId;
+    if (data.stream) {
+      this._processStreamMessage(data);
+    } else if (data.isReply) {
+      let callbackId = data.callbackId;
       if (data.callbackId in callbacksCapabilities) {
-        var callback = callbacksCapabilities[callbackId];
+        let callback = callbacksCapabilities[callbackId];
         delete callbacksCapabilities[callbackId];
         if ('error' in data) {
           callback.reject(data.error);
         } else {
           callback.resolve(data.data);
         }
       } else {
         error('Cannot resolve callback ' + callbackId);
       }
     } else if (data.action in ah) {
-      var action = ah[data.action];
+      let action = ah[data.action];
       if (data.callbackId) {
-        var sourceName = this.sourceName;
-        var targetName = data.sourceName;
+        let sourceName = this.sourceName;
+        let targetName = data.sourceName;
         Promise.resolve().then(function () {
           return action[0].call(action[1], data.data);
-        }).then(function (result) {
+        }).then(result => {
           comObj.postMessage({
             sourceName,
             targetName,
             isReply: true,
             callbackId: data.callbackId,
             data: result
           });
-        }, function (reason) {
+        }, reason => {
           if (reason instanceof Error) {
             reason = reason + '';
           }
           comObj.postMessage({
             sourceName,
             targetName,
             isReply: true,
             callbackId: data.callbackId,
             error: reason
           });
         });
+      } else if (data.streamId) {
+        this._createStreamSink(data);
       } else {
         action[0].call(action[1], data.data);
       }
     } else {
       error('Unknown action from worker: ' + data.action);
     }
   };
   comObj.addEventListener('message', this._onComObjOnMessage);
@@ -1072,33 +1097,231 @@ MessageHandler.prototype = {
       sourceName: this.sourceName,
       targetName: this.targetName,
       action: actionName,
       data
     };
     this.postMessage(message, transfers);
   },
   sendWithPromise(actionName, data, transfers) {
-    var callbackId = this.callbackIndex++;
+    var callbackId = this.callbackId++;
     var message = {
       sourceName: this.sourceName,
       targetName: this.targetName,
       action: actionName,
       data,
       callbackId
     };
     var capability = createPromiseCapability();
     this.callbacksCapabilities[callbackId] = capability;
     try {
       this.postMessage(message, transfers);
     } catch (e) {
       capability.reject(e);
     }
     return capability.promise;
   },
+  sendWithStream(actionName, data, queueingStrategy, transfers) {
+    let streamId = this.streamId++;
+    let sourceName = this.sourceName;
+    let targetName = this.targetName;
+    return new _streamsLib.ReadableStream({
+      start: controller => {
+        let startCapability = createPromiseCapability();
+        this.streamControllers[streamId] = {
+          controller,
+          startCall: startCapability
+        };
+        this.postMessage({
+          sourceName,
+          targetName,
+          action: actionName,
+          streamId,
+          data,
+          desiredSize: controller.desiredSize
+        });
+        return startCapability.promise;
+      },
+      pull: controller => {
+        let pullCapability = createPromiseCapability();
+        this.streamControllers[streamId].pullCall = pullCapability;
+        this.postMessage({
+          sourceName,
+          targetName,
+          stream: 'pull',
+          streamId,
+          desiredSize: controller.desiredSize
+        });
+        return pullCapability.promise;
+      },
+      cancel: reason => {
+        let cancelCapability = createPromiseCapability();
+        this.streamControllers[streamId].cancelCall = cancelCapability;
+        this.postMessage({
+          sourceName,
+          targetName,
+          stream: 'cancel',
+          reason,
+          streamId
+        });
+        return cancelCapability.promise;
+      }
+    }, queueingStrategy);
+  },
+  _createStreamSink(data) {
+    let self = this;
+    let action = this.actionHandler[data.action];
+    let streamId = data.streamId;
+    let desiredSize = data.desiredSize;
+    let sourceName = this.sourceName;
+    let targetName = data.sourceName;
+    let capability = createPromiseCapability();
+    let sendStreamRequest = ({ stream, chunk, success, reason }) => {
+      this.comObj.postMessage({
+        sourceName,
+        targetName,
+        stream,
+        streamId,
+        chunk,
+        success,
+        reason
+      });
+    };
+    let streamSink = {
+      enqueue(chunk, size = 1) {
+        let lastDesiredSize = this.desiredSize;
+        this.desiredSize -= size;
+        if (lastDesiredSize > 0 && this.desiredSize <= 0) {
+          this.sinkCapability = createPromiseCapability();
+          this.ready = this.sinkCapability.promise;
+        }
+        sendStreamRequest({
+          stream: 'enqueue',
+          chunk
+        });
+      },
+      close() {
+        sendStreamRequest({ stream: 'close' });
+        delete self.streamSinks[streamId];
+      },
+      error(reason) {
+        sendStreamRequest({
+          stream: 'error',
+          reason
+        });
+      },
+      sinkCapability: capability,
+      onPull: null,
+      onCancel: null,
+      desiredSize,
+      ready: null
+    };
+    streamSink.sinkCapability.resolve();
+    streamSink.ready = streamSink.sinkCapability.promise;
+    this.streamSinks[streamId] = streamSink;
+    resolveCall(action[0], [data.data, streamSink], action[1]).then(() => {
+      sendStreamRequest({
+        stream: 'start_complete',
+        success: true
+      });
+    }, reason => {
+      sendStreamRequest({
+        stream: 'start_complete',
+        success: false,
+        reason
+      });
+    });
+  },
+  _processStreamMessage(data) {
+    let sourceName = this.sourceName;
+    let targetName = data.sourceName;
+    let streamId = data.streamId;
+    let sendStreamResponse = ({ stream, success, reason }) => {
+      this.comObj.postMessage({
+        sourceName,
+        targetName,
+        stream,
+        success,
+        streamId,
+        reason
+      });
+    };
+    let deleteStreamController = () => {
+      Promise.all([this.streamControllers[data.streamId].startCall, this.streamControllers[data.streamId].pullCall, this.streamControllers[data.streamId].cancelCall].map(function (capability) {
+        return capability && finalize(capability.promise);
+      })).then(() => {
+        delete this.streamControllers[data.streamId];
+      });
+    };
+    switch (data.stream) {
+      case 'start_complete':
+        resolveOrReject(this.streamControllers[data.streamId].startCall, data.success, data.reason);
+        break;
+      case 'pull_complete':
+        resolveOrReject(this.streamControllers[data.streamId].pullCall, data.success, data.reason);
+        break;
+      case 'pull':
+        if (!this.streamSinks[data.streamId]) {
+          sendStreamResponse({
+            stream: 'pull_complete',
+            success: true
+          });
+          break;
+        }
+        if (this.streamSinks[data.streamId].desiredSize <= 0 && data.desiredSize > 0) {
+          this.streamSinks[data.streamId].sinkCapability.resolve();
+        }
+        this.streamSinks[data.streamId].desiredSize = data.desiredSize;
+        resolveCall(this.streamSinks[data.streamId].onPull).then(() => {
+          sendStreamResponse({
+            stream: 'pull_complete',
+            success: true
+          });
+        }, reason => {
+          sendStreamResponse({
+            stream: 'pull_complete',
+            success: false,
+            reason
+          });
+        });
+        break;
+      case 'enqueue':
+        this.streamControllers[data.streamId].controller.enqueue(data.chunk);
+        break;
+      case 'close':
+        this.streamControllers[data.streamId].controller.close();
+        deleteStreamController();
+        break;
+      case 'error':
+        this.streamControllers[data.streamId].controller.error(data.reason);
+        deleteStreamController();
+        break;
+      case 'cancel_complete':
+        resolveOrReject(this.streamControllers[data.streamId].cancelCall, data.success, data.reason);
+        deleteStreamController();
+        break;
+      case 'cancel':
+        resolveCall(this.streamSinks[data.streamId].onCancel, [data.reason]).then(() => {
+          sendStreamResponse({
+            stream: 'cancel_complete',
+            success: true
+          });
+        }, reason => {
+          sendStreamResponse({
+            stream: 'cancel_complete',
+            success: false,
+            reason
+          });
+        });
+        delete this.streamSinks[data.streamId];
+        break;
+      default:
+        throw new Error('Unexpected stream case');
+    }
+  },
   postMessage(message, transfers) {
     if (transfers && this.postMessageTransfers) {
       this.comObj.postMessage(message, transfers);
     } else {
       this.comObj.postMessage(message);
     }
   },
   destroy() {
@@ -3403,18 +3626,18 @@ var _UnsupportedManager = function Unsup
       for (var i = 0, ii = listeners.length; i < ii; i++) {
         listeners[i](featureId);
       }
     }
   };
 }();
 var version, build;
 {
-  exports.version = version = '1.8.423';
-  exports.build = build = '8654635b';
+  exports.version = version = '1.8.432';
+  exports.build = build = '93420545';
 }
 exports.getDocument = getDocument;
 exports.LoopbackPort = LoopbackPort;
 exports.PDFDataRangeTransport = PDFDataRangeTransport;
 exports.PDFWorker = PDFWorker;
 exports.PDFDocumentProxy = PDFDocumentProxy;
 exports.PDFPageProxy = PDFPageProxy;
 exports._UnsupportedManager = _UnsupportedManager;
@@ -4406,18 +4629,18 @@ var _text_layer = __w_pdfjs_require__(5)
 var _svg = __w_pdfjs_require__(4);
 
 var isWorker = typeof window === 'undefined';
 if (!_util.globalScope.PDFJS) {
   _util.globalScope.PDFJS = {};
 }
 var PDFJS = _util.globalScope.PDFJS;
 {
-  PDFJS.version = '1.8.423';
-  PDFJS.build = '8654635b';
+  PDFJS.version = '1.8.432';
+  PDFJS.build = '93420545';
 }
 PDFJS.pdfBug = false;
 if (PDFJS.verbosity !== undefined) {
   (0, _util.setVerbosityLevel)(PDFJS.verbosity);
 }
 delete PDFJS.verbosity;
 Object.defineProperty(PDFJS, 'verbosity', {
   get() {
@@ -6023,17 +6246,17 @@ exports.PDFJS = PDFJS;
     assert(stream._state === 'readable');
     stream._state = 'closed';
     var reader = stream._reader;
     if (reader === undefined) {
       return undefined;
     }
     if (IsReadableStreamDefaultReader(reader) === true) {
       for (var i = 0; i < reader._readRequests.length; i++) {
-        var _resolve = reader._readRequests[i];
+        var _resolve = reader._readRequests[i]._resolve;
         _resolve(CreateIterResultObject(undefined, true));
       }
       reader._readRequests = [];
     }
     defaultReaderClosedPromiseResolve(reader);
     return undefined;
   }
   function ReadableStreamError(stream, e) {
@@ -9754,18 +9977,18 @@ exports.TilingPattern = TilingPattern;
 
 /***/ }),
 /* 14 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.8.423';
-var pdfjsBuild = '8654635b';
+var pdfjsVersion = '1.8.432';
+var pdfjsBuild = '93420545';
 var pdfjsSharedUtil = __w_pdfjs_require__(0);
 var pdfjsDisplayGlobal = __w_pdfjs_require__(8);
 var pdfjsDisplayAPI = __w_pdfjs_require__(3);
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(5);
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(2);
 var pdfjsDisplayDOMUtils = __w_pdfjs_require__(1);
 var pdfjsDisplaySVG = __w_pdfjs_require__(4);
 exports.PDFJS = pdfjsDisplayGlobal.PDFJS;
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -992,69 +992,94 @@ var createObjectURL = function createObj
           d2 = (b1 & 3) << 4 | b2 >> 4;
       var d3 = i + 1 < ii ? (b2 & 0xF) << 2 | b3 >> 6 : 64;
       var d4 = i + 2 < ii ? b3 & 0x3F : 64;
       buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
     }
     return buffer;
   };
 }();
+function resolveCall(fn, args, thisArg = null) {
+  if (!fn) {
+    return Promise.resolve(undefined);
+  }
+  return new Promise((resolve, reject) => {
+    resolve(fn.apply(thisArg, args));
+  });
+}
+function resolveOrReject(capability, success, reason) {
+  if (success) {
+    capability.resolve();
+  } else {
+    capability.reject(reason);
+  }
+}
+function finalize(promise) {
+  return Promise.resolve(promise).catch(() => {});
+}
 function MessageHandler(sourceName, targetName, comObj) {
   this.sourceName = sourceName;
   this.targetName = targetName;
   this.comObj = comObj;
-  this.callbackIndex = 1;
+  this.callbackId = 1;
+  this.streamId = 1;
   this.postMessageTransfers = true;
-  var callbacksCapabilities = this.callbacksCapabilities = Object.create(null);
-  var ah = this.actionHandler = Object.create(null);
+  this.streamSinks = Object.create(null);
+  this.streamControllers = Object.create(null);
+  let callbacksCapabilities = this.callbacksCapabilities = Object.create(null);
+  let ah = this.actionHandler = Object.create(null);
   this._onComObjOnMessage = event => {
-    var data = event.data;
+    let data = event.data;
     if (data.targetName !== this.sourceName) {
       return;
     }
-    if (data.isReply) {
-      var callbackId = data.callbackId;
+    if (data.stream) {
+      this._processStreamMessage(data);
+    } else if (data.isReply) {
+      let callbackId = data.callbackId;
       if (data.callbackId in callbacksCapabilities) {
-        var callback = callbacksCapabilities[callbackId];
+        let callback = callbacksCapabilities[callbackId];
         delete callbacksCapabilities[callbackId];
         if ('error' in data) {
           callback.reject(data.error);
         } else {
           callback.resolve(data.data);
         }
       } else {
         error('Cannot resolve callback ' + callbackId);
       }
     } else if (data.action in ah) {
-      var action = ah[data.action];
+      let action = ah[data.action];
       if (data.callbackId) {
-        var sourceName = this.sourceName;
-        var targetName = data.sourceName;
+        let sourceName = this.sourceName;
+        let targetName = data.sourceName;
         Promise.resolve().then(function () {
           return action[0].call(action[1], data.data);
-        }).then(function (result) {
+        }).then(result => {
           comObj.postMessage({
             sourceName,
             targetName,
             isReply: true,
             callbackId: data.callbackId,
             data: result
           });
-        }, function (reason) {
+        }, reason => {
           if (reason instanceof Error) {
             reason = reason + '';
           }
           comObj.postMessage({
             sourceName,
             targetName,
             isReply: true,
             callbackId: data.callbackId,
             error: reason
           });
         });
+      } else if (data.streamId) {
+        this._createStreamSink(data);
       } else {
         action[0].call(action[1], data.data);
       }
     } else {
       error('Unknown action from worker: ' + data.action);
     }
   };
   comObj.addEventListener('message', this._onComObjOnMessage);
@@ -1072,33 +1097,231 @@ MessageHandler.prototype = {
       sourceName: this.sourceName,
       targetName: this.targetName,
       action: actionName,
       data
     };
     this.postMessage(message, transfers);
   },
   sendWithPromise(actionName, data, transfers) {
-    var callbackId = this.callbackIndex++;
+    var callbackId = this.callbackId++;
     var message = {
       sourceName: this.sourceName,
       targetName: this.targetName,
       action: actionName,
       data,
       callbackId
     };
     var capability = createPromiseCapability();
     this.callbacksCapabilities[callbackId] = capability;
     try {
       this.postMessage(message, transfers);
     } catch (e) {
       capability.reject(e);
     }
     return capability.promise;
   },
+  sendWithStream(actionName, data, queueingStrategy, transfers) {
+    let streamId = this.streamId++;
+    let sourceName = this.sourceName;
+    let targetName = this.targetName;
+    return new _streamsLib.ReadableStream({
+      start: controller => {
+        let startCapability = createPromiseCapability();
+        this.streamControllers[streamId] = {
+          controller,
+          startCall: startCapability
+        };
+        this.postMessage({
+          sourceName,
+          targetName,
+          action: actionName,
+          streamId,
+          data,
+          desiredSize: controller.desiredSize
+        });
+        return startCapability.promise;
+      },
+      pull: controller => {
+        let pullCapability = createPromiseCapability();
+        this.streamControllers[streamId].pullCall = pullCapability;
+        this.postMessage({
+          sourceName,
+          targetName,
+          stream: 'pull',
+          streamId,
+          desiredSize: controller.desiredSize
+        });
+        return pullCapability.promise;
+      },
+      cancel: reason => {
+        let cancelCapability = createPromiseCapability();
+        this.streamControllers[streamId].cancelCall = cancelCapability;
+        this.postMessage({
+          sourceName,
+          targetName,
+          stream: 'cancel',
+          reason,
+          streamId
+        });
+        return cancelCapability.promise;
+      }
+    }, queueingStrategy);
+  },
+  _createStreamSink(data) {
+    let self = this;
+    let action = this.actionHandler[data.action];
+    let streamId = data.streamId;
+    let desiredSize = data.desiredSize;
+    let sourceName = this.sourceName;
+    let targetName = data.sourceName;
+    let capability = createPromiseCapability();
+    let sendStreamRequest = ({ stream, chunk, success, reason }) => {
+      this.comObj.postMessage({
+        sourceName,
+        targetName,
+        stream,
+        streamId,
+        chunk,
+        success,
+        reason
+      });
+    };
+    let streamSink = {
+      enqueue(chunk, size = 1) {
+        let lastDesiredSize = this.desiredSize;
+        this.desiredSize -= size;
+        if (lastDesiredSize > 0 && this.desiredSize <= 0) {
+          this.sinkCapability = createPromiseCapability();
+          this.ready = this.sinkCapability.promise;
+        }
+        sendStreamRequest({
+          stream: 'enqueue',
+          chunk
+        });
+      },
+      close() {
+        sendStreamRequest({ stream: 'close' });
+        delete self.streamSinks[streamId];
+      },
+      error(reason) {
+        sendStreamRequest({
+          stream: 'error',
+          reason
+        });
+      },
+      sinkCapability: capability,
+      onPull: null,
+      onCancel: null,
+      desiredSize,
+      ready: null
+    };
+    streamSink.sinkCapability.resolve();
+    streamSink.ready = streamSink.sinkCapability.promise;
+    this.streamSinks[streamId] = streamSink;
+    resolveCall(action[0], [data.data, streamSink], action[1]).then(() => {
+      sendStreamRequest({
+        stream: 'start_complete',
+        success: true
+      });
+    }, reason => {
+      sendStreamRequest({
+        stream: 'start_complete',
+        success: false,
+        reason
+      });
+    });
+  },
+  _processStreamMessage(data) {
+    let sourceName = this.sourceName;
+    let targetName = data.sourceName;
+    let streamId = data.streamId;
+    let sendStreamResponse = ({ stream, success, reason }) => {
+      this.comObj.postMessage({
+        sourceName,
+        targetName,
+        stream,
+        success,
+        streamId,
+        reason
+      });
+    };
+    let deleteStreamController = () => {
+      Promise.all([this.streamControllers[data.streamId].startCall, this.streamControllers[data.streamId].pullCall, this.streamControllers[data.streamId].cancelCall].map(function (capability) {
+        return capability && finalize(capability.promise);
+      })).then(() => {
+        delete this.streamControllers[data.streamId];
+      });
+    };
+    switch (data.stream) {
+      case 'start_complete':
+        resolveOrReject(this.streamControllers[data.streamId].startCall, data.success, data.reason);
+        break;
+      case 'pull_complete':
+        resolveOrReject(this.streamControllers[data.streamId].pullCall, data.success, data.reason);
+        break;
+      case 'pull':
+        if (!this.streamSinks[data.streamId]) {
+          sendStreamResponse({
+            stream: 'pull_complete',
+            success: true
+          });
+          break;
+        }
+        if (this.streamSinks[data.streamId].desiredSize <= 0 && data.desiredSize > 0) {
+          this.streamSinks[data.streamId].sinkCapability.resolve();
+        }
+        this.streamSinks[data.streamId].desiredSize = data.desiredSize;
+        resolveCall(this.streamSinks[data.streamId].onPull).then(() => {
+          sendStreamResponse({
+            stream: 'pull_complete',
+            success: true
+          });
+        }, reason => {
+          sendStreamResponse({
+            stream: 'pull_complete',
+            success: false,
+            reason
+          });
+        });
+        break;
+      case 'enqueue':
+        this.streamControllers[data.streamId].controller.enqueue(data.chunk);
+        break;
+      case 'close':
+        this.streamControllers[data.streamId].controller.close();
+        deleteStreamController();
+        break;
+      case 'error':
+        this.streamControllers[data.streamId].controller.error(data.reason);
+        deleteStreamController();
+        break;
+      case 'cancel_complete':
+        resolveOrReject(this.streamControllers[data.streamId].cancelCall, data.success, data.reason);
+        deleteStreamController();
+        break;
+      case 'cancel':
+        resolveCall(this.streamSinks[data.streamId].onCancel, [data.reason]).then(() => {
+          sendStreamResponse({
+            stream: 'cancel_complete',
+            success: true
+          });
+        }, reason => {
+          sendStreamResponse({
+            stream: 'cancel_complete',
+            success: false,
+            reason
+          });
+        });
+        delete this.streamSinks[data.streamId];
+        break;
+      default:
+        throw new Error('Unexpected stream case');
+    }
+  },
   postMessage(message, transfers) {
     if (transfers && this.postMessageTransfers) {
       this.comObj.postMessage(message, transfers);
     } else {
       this.comObj.postMessage(message);
     }
   },
   destroy() {
@@ -25497,17 +25720,17 @@ exports.WorkerMessageHandler = WorkerMes
     assert(stream._state === 'readable');
     stream._state = 'closed';
     var reader = stream._reader;
     if (reader === undefined) {
       return undefined;
     }
     if (IsReadableStreamDefaultReader(reader) === true) {
       for (var i = 0; i < reader._readRequests.length; i++) {
-        var _resolve = reader._readRequests[i];
+        var _resolve = reader._readRequests[i]._resolve;
         _resolve(CreateIterResultObject(undefined, true));
       }
       reader._readRequests = [];
     }
     defaultReaderClosedPromiseResolve(reader);
     return undefined;
   }
   function ReadableStreamError(stream, e) {
@@ -39540,18 +39763,18 @@ exports.Type1Parser = Type1Parser;
 
 /***/ }),
 /* 36 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.8.423';
-var pdfjsBuild = '8654635b';
+var pdfjsVersion = '1.8.432';
+var pdfjsBuild = '93420545';
 var pdfjsCoreWorker = __w_pdfjs_require__(17);
 ;
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 37 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -2740,368 +2740,364 @@ exports.PDFCursorTools = PDFCursorTools;
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.PDFFindController = exports.FindStates = undefined;
+exports.PDFFindController = exports.FindState = undefined;
 
 var _pdfjsLib = __webpack_require__(1);
 
 var _ui_utils = __webpack_require__(0);
 
-var FindStates = {
-  FIND_FOUND: 0,
-  FIND_NOTFOUND: 1,
-  FIND_WRAPPED: 2,
-  FIND_PENDING: 3
+const FindState = {
+  FOUND: 0,
+  NOT_FOUND: 1,
+  WRAPPED: 2,
+  PENDING: 3
 };
-var FIND_SCROLL_OFFSET_TOP = -50;
-var FIND_SCROLL_OFFSET_LEFT = -400;
-var CHARACTERS_TO_NORMALIZE = {
+const FIND_SCROLL_OFFSET_TOP = -50;
+const FIND_SCROLL_OFFSET_LEFT = -400;
+const FIND_TIMEOUT = 250;
+const CHARACTERS_TO_NORMALIZE = {
   '\u2018': '\'',
   '\u2019': '\'',
   '\u201A': '\'',
   '\u201B': '\'',
   '\u201C': '"',
   '\u201D': '"',
   '\u201E': '"',
   '\u201F': '"',
   '\u00BC': '1/4',
   '\u00BD': '1/2',
   '\u00BE': '3/4'
 };
-var PDFFindController = function PDFFindControllerClosure() {
-  function PDFFindController(options) {
-    this.pdfViewer = options.pdfViewer || null;
+class PDFFindController {
+  constructor({ pdfViewer }) {
+    this.pdfViewer = pdfViewer;
     this.onUpdateResultsCount = null;
     this.onUpdateState = null;
     this.reset();
-    var replace = Object.keys(CHARACTERS_TO_NORMALIZE).join('');
+    let replace = Object.keys(CHARACTERS_TO_NORMALIZE).join('');
     this.normalizationRegex = new RegExp('[' + replace + ']', 'g');
   }
-  PDFFindController.prototype = {
-    reset: function PDFFindController_reset() {
-      this.startedTextExtraction = false;
-      this.extractTextPromises = [];
-      this.pendingFindMatches = Object.create(null);
-      this.active = false;
-      this.pageContents = [];
-      this.pageMatches = [];
-      this.pageMatchesLength = null;
-      this.matchCount = 0;
-      this.selected = {
-        pageIdx: -1,
-        matchIdx: -1
-      };
-      this.offset = {
-        pageIdx: null,
-        matchIdx: null
-      };
-      this.pagesToSearch = null;
-      this.resumePageIdx = null;
-      this.state = null;
-      this.dirtyMatch = false;
-      this.findTimeout = null;
-      this._firstPagePromise = new Promise(resolve => {
-        this.resolveFirstPage = resolve;
-      });
-    },
-    normalize: function PDFFindController_normalize(text) {
-      return text.replace(this.normalizationRegex, function (ch) {
-        return CHARACTERS_TO_NORMALIZE[ch];
-      });
-    },
-    _prepareMatches: function PDFFindController_prepareMatches(matchesWithLength, matches, matchesLength) {
-      function isSubTerm(matchesWithLength, currentIndex) {
-        var currentElem, prevElem, nextElem;
-        currentElem = matchesWithLength[currentIndex];
-        nextElem = matchesWithLength[currentIndex + 1];
-        if (currentIndex < matchesWithLength.length - 1 && currentElem.match === nextElem.match) {
-          currentElem.skipped = true;
-          return true;
-        }
-        for (var i = currentIndex - 1; i >= 0; i--) {
-          prevElem = matchesWithLength[i];
-          if (prevElem.skipped) {
-            continue;
-          }
-          if (prevElem.match + prevElem.matchLength < currentElem.match) {
-            break;
-          }
-          if (prevElem.match + prevElem.matchLength >= currentElem.match + currentElem.matchLength) {
-            currentElem.skipped = true;
-            return true;
-          }
-        }
-        return false;
-      }
-      var i, len;
-      matchesWithLength.sort(function (a, b) {
-        return a.match === b.match ? a.matchLength - b.matchLength : a.match - b.match;
-      });
-      for (i = 0, len = matchesWithLength.length; i < len; i++) {
-        if (isSubTerm(matchesWithLength, i)) {
+  reset() {
+    this.startedTextExtraction = false;
+    this.extractTextPromises = [];
+    this.pendingFindMatches = Object.create(null);
+    this.active = false;
+    this.pageContents = [];
+    this.pageMatches = [];
+    this.pageMatchesLength = null;
+    this.matchCount = 0;
+    this.selected = {
+      pageIdx: -1,
+      matchIdx: -1
+    };
+    this.offset = {
+      pageIdx: null,
+      matchIdx: null
+    };
+    this.pagesToSearch = null;
+    this.resumePageIdx = null;
+    this.state = null;
+    this.dirtyMatch = false;
+    this.findTimeout = null;
+    this._firstPagePromise = new Promise(resolve => {
+      this.resolveFirstPage = resolve;
+    });
+  }
+  normalize(text) {
+    return text.replace(this.normalizationRegex, function (ch) {
+      return CHARACTERS_TO_NORMALIZE[ch];
+    });
+  }
+  _prepareMatches(matchesWithLength, matches, matchesLength) {
+    function isSubTerm(matchesWithLength, currentIndex) {
+      let currentElem = matchesWithLength[currentIndex];
+      let nextElem = matchesWithLength[currentIndex + 1];
+      if (currentIndex < matchesWithLength.length - 1 && currentElem.match === nextElem.match) {
+        currentElem.skipped = true;
+        return true;
+      }
+      for (let i = currentIndex - 1; i >= 0; i--) {
+        let prevElem = matchesWithLength[i];
+        if (prevElem.skipped) {
           continue;
         }
-        matches.push(matchesWithLength[i].match);
-        matchesLength.push(matchesWithLength[i].matchLength);
-      }
-    },
-    calcFindPhraseMatch: function PDFFindController_calcFindPhraseMatch(query, pageIndex, pageContent) {
-      var matches = [];
-      var queryLen = query.length;
-      var matchIdx = -queryLen;
-      while (true) {
-        matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
-        if (matchIdx === -1) {
+        if (prevElem.match + prevElem.matchLength < currentElem.match) {
           break;
         }
-        matches.push(matchIdx);
-      }
-      this.pageMatches[pageIndex] = matches;
-    },
-    calcFindWordMatch: function PDFFindController_calcFindWordMatch(query, pageIndex, pageContent) {
-      var matchesWithLength = [];
-      var queryArray = query.match(/\S+/g);
-      var subquery, subqueryLen, matchIdx;
-      for (var i = 0, len = queryArray.length; i < len; i++) {
-        subquery = queryArray[i];
-        subqueryLen = subquery.length;
-        matchIdx = -subqueryLen;
-        while (true) {
-          matchIdx = pageContent.indexOf(subquery, matchIdx + subqueryLen);
-          if (matchIdx === -1) {
-            break;
-          }
-          matchesWithLength.push({
-            match: matchIdx,
-            matchLength: subqueryLen,
-            skipped: false
-          });
-        }
-      }
-      if (!this.pageMatchesLength) {
-        this.pageMatchesLength = [];
-      }
-      this.pageMatchesLength[pageIndex] = [];
-      this.pageMatches[pageIndex] = [];
-      this._prepareMatches(matchesWithLength, this.pageMatches[pageIndex], this.pageMatchesLength[pageIndex]);
-    },
-    calcFindMatch: function PDFFindController_calcFindMatch(pageIndex) {
-      var pageContent = this.normalize(this.pageContents[pageIndex]);
-      var query = this.normalize(this.state.query);
-      var caseSensitive = this.state.caseSensitive;
-      var phraseSearch = this.state.phraseSearch;
-      var queryLen = query.length;
-      if (queryLen === 0) {
-        return;
-      }
-      if (!caseSensitive) {
-        pageContent = pageContent.toLowerCase();
-        query = query.toLowerCase();
-      }
-      if (phraseSearch) {
-        this.calcFindPhraseMatch(query, pageIndex, pageContent);
-      } else {
-        this.calcFindWordMatch(query, pageIndex, pageContent);
-      }
-      this.updatePage(pageIndex);
-      if (this.resumePageIdx === pageIndex) {
-        this.resumePageIdx = null;
-        this.nextPageMatch();
-      }
-      if (this.pageMatches[pageIndex].length > 0) {
-        this.matchCount += this.pageMatches[pageIndex].length;
-        this.updateUIResultsCount();
-      }
-    },
-    extractText() {
-      if (this.startedTextExtraction) {
-        return;
-      }
-      this.startedTextExtraction = true;
-      this.pageContents.length = 0;
-      let promise = Promise.resolve();
-      for (let i = 0, ii = this.pdfViewer.pagesCount; i < ii; i++) {
-        let extractTextCapability = (0, _pdfjsLib.createPromiseCapability)();
-        this.extractTextPromises[i] = extractTextCapability.promise;
-        promise = promise.then(() => {
-          return this.pdfViewer.getPageTextContent(i).then(textContent => {
-            let textItems = textContent.items;
-            let strBuf = [];
-            for (let j = 0, jj = textItems.length; j < jj; j++) {
-              strBuf.push(textItems[j].str);
-            }
-            this.pageContents[i] = strBuf.join('');
-            extractTextCapability.resolve(i);
-          });
-        });
-      }
-    },
-    executeCommand: function PDFFindController_executeCommand(cmd, state) {
-      if (this.state === null || cmd !== 'findagain') {
-        this.dirtyMatch = true;
-      }
-      this.state = state;
-      this.updateUIState(FindStates.FIND_PENDING);
-      this._firstPagePromise.then(() => {
-        this.extractText();
-        clearTimeout(this.findTimeout);
-        if (cmd === 'find') {
-          this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
-        } else {
-          this.nextMatch();
-        }
-      });
-    },
-    updatePage: function PDFFindController_updatePage(index) {
-      if (this.selected.pageIdx === index) {
-        this.pdfViewer.currentPageNumber = index + 1;
-      }
-      var page = this.pdfViewer.getPageView(index);
-      if (page.textLayer) {
-        page.textLayer.updateMatches();
-      }
-    },
-    nextMatch: function PDFFindController_nextMatch() {
-      var previous = this.state.findPrevious;
-      var currentPageIndex = this.pdfViewer.currentPageNumber - 1;
-      var numPages = this.pdfViewer.pagesCount;
-      this.active = true;
-      if (this.dirtyMatch) {
-        this.dirtyMatch = false;
-        this.selected.pageIdx = this.selected.matchIdx = -1;
-        this.offset.pageIdx = currentPageIndex;
-        this.offset.matchIdx = null;
-        this.hadMatch = false;
-        this.resumePageIdx = null;
-        this.pageMatches = [];
-        this.matchCount = 0;
-        this.pageMatchesLength = null;
-        for (let i = 0; i < numPages; i++) {
-          this.updatePage(i);
-          if (!(i in this.pendingFindMatches)) {
-            this.pendingFindMatches[i] = true;
-            this.extractTextPromises[i].then(pageIdx => {
-              delete this.pendingFindMatches[pageIdx];
-              this.calcFindMatch(pageIdx);
-            });
-          }
-        }
-      }
-      if (this.state.query === '') {
-        this.updateUIState(FindStates.FIND_FOUND);
-        return;
-      }
-      if (this.resumePageIdx) {
-        return;
-      }
-      var offset = this.offset;
-      this.pagesToSearch = numPages;
-      if (offset.matchIdx !== null) {
-        var numPageMatches = this.pageMatches[offset.pageIdx].length;
-        if (!previous && offset.matchIdx + 1 < numPageMatches || previous && offset.matchIdx > 0) {
-          this.hadMatch = true;
-          offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
-          this.updateMatch(true);
-          return;
-        }
-        this.advanceOffsetPage(previous);
-      }
-      this.nextPageMatch();
-    },
-    matchesReady: function PDFFindController_matchesReady(matches) {
-      var offset = this.offset;
-      var numMatches = matches.length;
-      var previous = this.state.findPrevious;
-      if (numMatches) {
-        this.hadMatch = true;
-        offset.matchIdx = previous ? numMatches - 1 : 0;
-        this.updateMatch(true);
-        return true;
-      }
-      this.advanceOffsetPage(previous);
-      if (offset.wrapped) {
-        offset.matchIdx = null;
-        if (this.pagesToSearch < 0) {
-          this.updateMatch(false);
+        if (prevElem.match + prevElem.matchLength >= currentElem.match + currentElem.matchLength) {
+          currentElem.skipped = true;
           return true;
         }
       }
       return false;
-    },
-    updateMatchPosition: function PDFFindController_updateMatchPosition(pageIndex, index, elements, beginIdx) {
-      if (this.selected.matchIdx === index && this.selected.pageIdx === pageIndex) {
-        var spot = {
-          top: FIND_SCROLL_OFFSET_TOP,
-          left: FIND_SCROLL_OFFSET_LEFT
-        };
-        (0, _ui_utils.scrollIntoView)(elements[beginIdx], spot, true);
-      }
-    },
-    nextPageMatch: function PDFFindController_nextPageMatch() {
-      if (this.resumePageIdx !== null) {
-        console.error('There can only be one pending page.');
-      }
-      do {
-        var pageIdx = this.offset.pageIdx;
-        var matches = this.pageMatches[pageIdx];
-        if (!matches) {
-          this.resumePageIdx = pageIdx;
+    }
+    matchesWithLength.sort(function (a, b) {
+      return a.match === b.match ? a.matchLength - b.matchLength : a.match - b.match;
+    });
+    for (let i = 0, len = matchesWithLength.length; i < len; i++) {
+      if (isSubTerm(matchesWithLength, i)) {
+        continue;
+      }
+      matches.push(matchesWithLength[i].match);
+      matchesLength.push(matchesWithLength[i].matchLength);
+    }
+  }
+  calcFindPhraseMatch(query, pageIndex, pageContent) {
+    let matches = [];
+    let queryLen = query.length;
+    let matchIdx = -queryLen;
+    while (true) {
+      matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
+      if (matchIdx === -1) {
+        break;
+      }
+      matches.push(matchIdx);
+    }
+    this.pageMatches[pageIndex] = matches;
+  }
+  calcFindWordMatch(query, pageIndex, pageContent) {
+    let matchesWithLength = [];
+    let queryArray = query.match(/\S+/g);
+    for (let i = 0, len = queryArray.length; i < len; i++) {
+      let subquery = queryArray[i];
+      let subqueryLen = subquery.length;
+      let matchIdx = -subqueryLen;
+      while (true) {
+        matchIdx = pageContent.indexOf(subquery, matchIdx + subqueryLen);
+        if (matchIdx === -1) {
           break;
         }
-      } while (!this.matchesReady(matches));
-    },
-    advanceOffsetPage: function PDFFindController_advanceOffsetPage(previous) {
-      var offset = this.offset;
-      var numPages = this.extractTextPromises.length;
-      offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
+        matchesWithLength.push({
+          match: matchIdx,
+          matchLength: subqueryLen,
+          skipped: false
+        });
+      }
+    }
+    if (!this.pageMatchesLength) {
+      this.pageMatchesLength = [];
+    }
+    this.pageMatchesLength[pageIndex] = [];
+    this.pageMatches[pageIndex] = [];
+    this._prepareMatches(matchesWithLength, this.pageMatches[pageIndex], this.pageMatchesLength[pageIndex]);
+  }
+  calcFindMatch(pageIndex) {
+    let pageContent = this.normalize(this.pageContents[pageIndex]);
+    let query = this.normalize(this.state.query);
+    let caseSensitive = this.state.caseSensitive;
+    let phraseSearch = this.state.phraseSearch;
+    let queryLen = query.length;
+    if (queryLen === 0) {
+      return;
+    }
+    if (!caseSensitive) {
+      pageContent = pageContent.toLowerCase();
+      query = query.toLowerCase();
+    }
+    if (phraseSearch) {
+      this.calcFindPhraseMatch(query, pageIndex, pageContent);
+    } else {
+      this.calcFindWordMatch(query, pageIndex, pageContent);
+    }
+    this.updatePage(pageIndex);
+    if (this.resumePageIdx === pageIndex) {
+      this.resumePageIdx = null;
+      this.nextPageMatch();
+    }
+    if (this.pageMatches[pageIndex].length > 0) {
+      this.matchCount += this.pageMatches[pageIndex].length;
+      this.updateUIResultsCount();
+    }
+  }
+  extractText() {
+    if (this.startedTextExtraction) {
+      return;
+    }
+    this.startedTextExtraction = true;
+    this.pageContents.length = 0;
+    let promise = Promise.resolve();
+    for (let i = 0, ii = this.pdfViewer.pagesCount; i < ii; i++) {
+      let extractTextCapability = (0, _pdfjsLib.createPromiseCapability)();
+      this.extractTextPromises[i] = extractTextCapability.promise;
+      promise = promise.then(() => {
+        return this.pdfViewer.getPageTextContent(i).then(textContent => {
+          let textItems = textContent.items;
+          let strBuf = [];
+          for (let j = 0, jj = textItems.length; j < jj; j++) {
+            strBuf.push(textItems[j].str);
+          }
+          this.pageContents[i] = strBuf.join('');
+          extractTextCapability.resolve(i);
+        });
+      });
+    }
+  }
+  executeCommand(cmd, state) {
+    if (this.state === null || cmd !== 'findagain') {
+      this.dirtyMatch = true;
+    }
+    this.state = state;
+    this.updateUIState(FindState.PENDING);
+    this._firstPagePromise.then(() => {
+      this.extractText();
+      clearTimeout(this.findTimeout);
+      if (cmd === 'find') {
+        this.findTimeout = setTimeout(this.nextMatch.bind(this), FIND_TIMEOUT);
+      } else {
+        this.nextMatch();
+      }
+    });
+  }
+  updatePage(index) {
+    if (this.selected.pageIdx === index) {
+      this.pdfViewer.currentPageNumber = index + 1;
+    }
+    let page = this.pdfViewer.getPageView(index);
+    if (page.textLayer) {
+      page.textLayer.updateMatches();
+    }
+  }
+  nextMatch() {
+    let previous = this.state.findPrevious;
+    let currentPageIndex = this.pdfViewer.currentPageNumber - 1;
+    let numPages = this.pdfViewer.pagesCount;
+    this.active = true;
+    if (this.dirtyMatch) {
+      this.dirtyMatch = false;
+      this.selected.pageIdx = this.selected.matchIdx = -1;
+      this.offset.pageIdx = currentPageIndex;
+      this.offset.matchIdx = null;
+      this.hadMatch = false;
+      this.resumePageIdx = null;
+      this.pageMatches = [];
+      this.matchCount = 0;
+      this.pageMatchesLength = null;
+      for (let i = 0; i < numPages; i++) {
+        this.updatePage(i);
+        if (!(i in this.pendingFindMatches)) {
+          this.pendingFindMatches[i] = true;
+          this.extractTextPromises[i].then(pageIdx => {
+            delete this.pendingFindMatches[pageIdx];
+            this.calcFindMatch(pageIdx);
+          });
+        }
+      }
+    }
+    if (this.state.query === '') {
+      this.updateUIState(FindState.FOUND);
+      return;
+    }
+    if (this.resumePageIdx) {
+      return;
+    }
+    let offset = this.offset;
+    this.pagesToSearch = numPages;
+    if (offset.matchIdx !== null) {
+      let numPageMatches = this.pageMatches[offset.pageIdx].length;
+      if (!previous && offset.matchIdx + 1 < numPageMatches || previous && offset.matchIdx > 0) {
+        this.hadMatch = true;
+        offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
+        this.updateMatch(true);
+        return;
+      }
+      this.advanceOffsetPage(previous);
+    }
+    this.nextPageMatch();
+  }
+  matchesReady(matches) {
+    let offset = this.offset;
+    let numMatches = matches.length;
+    let previous = this.state.findPrevious;
+    if (numMatches) {
+      this.hadMatch = true;
+      offset.matchIdx = previous ? numMatches - 1 : 0;
+      this.updateMatch(true);
+      return true;
+    }
+    this.advanceOffsetPage(previous);
+    if (offset.wrapped) {
       offset.matchIdx = null;
-      this.pagesToSearch--;
-      if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
-        offset.pageIdx = previous ? numPages - 1 : 0;
-        offset.wrapped = true;
-      }
-    },
-    updateMatch: function PDFFindController_updateMatch(found) {
-      var state = FindStates.FIND_NOTFOUND;
-      var wrapped = this.offset.wrapped;
-      this.offset.wrapped = false;
-      if (found) {
-        var previousPage = this.selected.pageIdx;
-        this.selected.pageIdx = this.offset.pageIdx;
-        this.selected.matchIdx = this.offset.matchIdx;
-        state = wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND;
-        if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
-          this.updatePage(previousPage);
-        }
-      }
-      this.updateUIState(state, this.state.findPrevious);
-      if (this.selected.pageIdx !== -1) {
-        this.updatePage(this.selected.pageIdx);
-      }
-    },
-    updateUIResultsCount: function PDFFindController_updateUIResultsCount() {
-      if (this.onUpdateResultsCount) {
-        this.onUpdateResultsCount(this.matchCount);
-      }
-    },
-    updateUIState: function PDFFindController_updateUIState(state, previous) {
-      if (this.onUpdateState) {
-        this.onUpdateState(state, previous, this.matchCount);
-      }
-    }
-  };
-  return PDFFindController;
-}();
-exports.FindStates = FindStates;
+      if (this.pagesToSearch < 0) {
+        this.updateMatch(false);
+        return true;
+      }
+    }
+    return false;
+  }
+  updateMatchPosition(pageIndex, matchIndex, elements, beginIdx) {
+    if (this.selected.matchIdx === matchIndex && this.selected.pageIdx === pageIndex) {
+      let spot = {
+        top: FIND_SCROLL_OFFSET_TOP,
+        left: FIND_SCROLL_OFFSET_LEFT
+      };
+      (0, _ui_utils.scrollIntoView)(elements[beginIdx], spot, true);
+    }
+  }
+  nextPageMatch() {
+    if (this.resumePageIdx !== null) {
+      console.error('There can only be one pending page.');
+    }
+    let matches = null;
+    do {
+      let pageIdx = this.offset.pageIdx;
+      matches = this.pageMatches[pageIdx];
+      if (!matches) {
+        this.resumePageIdx = pageIdx;
+        break;
+      }
+    } while (!this.matchesReady(matches));
+  }
+  advanceOffsetPage(previous) {
+    let offset = this.offset;
+    let numPages = this.extractTextPromises.length;
+    offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
+    offset.matchIdx = null;
+    this.pagesToSearch--;
+    if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
+      offset.pageIdx = previous ? numPages - 1 : 0;
+      offset.wrapped = true;
+    }
+  }
+  updateMatch(found = false) {
+    let state = FindState.NOT_FOUND;
+    let wrapped = this.offset.wrapped;
+    this.offset.wrapped = false;
+    if (found) {
+      let previousPage = this.selected.pageIdx;
+      this.selected.pageIdx = this.offset.pageIdx;
+      this.selected.matchIdx = this.offset.matchIdx;
+      state = wrapped ? FindState.WRAPPED : FindState.FOUND;
+      if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
+        this.updatePage(previousPage);
+      }
+    }
+    this.updateUIState(state, this.state.findPrevious);
+    if (this.selected.pageIdx !== -1) {
+      this.updatePage(this.selected.pageIdx);
+    }
+  }
+  updateUIResultsCount() {
+    if (this.onUpdateResultsCount) {
+      this.onUpdateResultsCount(this.matchCount);
+    }
+  }
+  updateUIState(state, previous) {
+    if (this.onUpdateState) {
+      this.onUpdateState(state, previous, this.matchCount);
+    }
+  }
+}
+exports.FindState = FindState;
 exports.PDFFindController = PDFFindController;
 
 /***/ }),
 /* 8 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
@@ -4276,26 +4272,26 @@ class PDFFindBar {
       findPrevious: findPrev
     });
   }
   updateUIState(state, previous, matchCount) {
     var notFound = false;
     var findMsg = '';
     var status = '';
     switch (state) {
-      case _pdf_find_controller.FindStates.FIND_FOUND:
+      case _pdf_find_controller.FindState.FOUND:
         break;
-      case _pdf_find_controller.FindStates.FIND_PENDING:
+      case _pdf_find_controller.FindState.PENDING:
         status = 'pending';
         break;
-      case _pdf_find_controller.FindStates.FIND_NOTFOUND:
+      case _pdf_find_controller.FindState.NOT_FOUND:
         findMsg = this.l10n.get('find_not_found', null, 'Phrase not found');
         notFound = true;
         break;
-      case _pdf_find_controller.FindStates.FIND_WRAPPED:
+      case _pdf_find_controller.FindState.WRAPPED:
         if (previous) {
           findMsg = this.l10n.get('find_reached_top', null, 'Reached top of document, continued from bottom');
         } else {
           findMsg = this.l10n.get('find_reached_bottom', null, 'Reached end of document, continued from top');
         }
         break;
     }
     if (notFound) {
@@ -5437,17 +5433,21 @@ class PDFPresentationMode {
       this.contextMenuOpen = false;
       evt.preventDefault();
       return;
     }
     if (evt.button === 0) {
       var isInternalLink = evt.target.href && evt.target.classList.contains('internalLink');
       if (!isInternalLink) {
         evt.preventDefault();
-        this.pdfViewer.currentPageNumber += evt.shiftKey ? -1 : 1;
+        if (evt.shiftKey) {
+          this._goToPreviousPage();
+        } else {
+          this._goToNextPage();
+        }
       }
     }
   }
   _contextMenu() {
     this.contextMenuOpen = true;
   }
   _showControls() {
     if (this.controlsTimeout) {
--- a/dom/base/DirectionalityUtils.cpp
+++ b/dom/base/DirectionalityUtils.cpp
@@ -288,28 +288,19 @@ GetDirectionFromChar(uint32_t ch)
 inline static bool NodeAffectsDirAutoAncestor(nsINode* aTextNode)
 {
   Element* parent = aTextNode->GetParentElement();
   return (parent &&
           !DoesNotParticipateInAutoDirection(parent) &&
           parent->NodeOrAncestorHasDirAuto());
 }
 
-/**
- * Various methods for returning the directionality of a string using the
- * first-strong algorithm defined in http://unicode.org/reports/tr9/#P2
- *
- * @param[out] aFirstStrong the offset to the first character in the string with
- *             strong directionality, or UINT32_MAX if there is none (return
-               value is eDir_NotSet).
- * @return the directionality of the string
- */
-static Directionality
+Directionality
 GetDirectionFromText(const char16_t* aText, const uint32_t aLength,
-                     uint32_t* aFirstStrong = nullptr)
+                     uint32_t* aFirstStrong)
 {
   const char16_t* start = aText;
   const char16_t* end = aText + aLength;
 
   while (start < end) {
     uint32_t current = start - aText;
     uint32_t ch = *start++;
 
--- a/dom/base/DirectionalityUtils.h
+++ b/dom/base/DirectionalityUtils.h
@@ -25,16 +25,29 @@ namespace mozilla {
 enum Directionality : uint8_t {
   eDir_NotSet,
   eDir_RTL,
   eDir_LTR,
   eDir_Auto
 };
 
 /**
+ * Various methods for returning the directionality of a string using the
+ * first-strong algorithm defined in http://unicode.org/reports/tr9/#P2
+ *
+ * @param[out] aFirstStrong the offset to the first character in the string with
+ *             strong directionality, or UINT32_MAX if there is none (return
+               value is eDir_NotSet).
+ * @return the directionality of the string
+ */
+Directionality
+GetDirectionFromText(const char16_t* aText, const uint32_t aLength,
+                     uint32_t* aFirstStrong = nullptr);
+
+/**
  * Set the directionality of an element according to the algorithm defined at
  * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality,
  * not including elements with auto direction.
  *
  * @return the directionality that the element was set to
  */
 Directionality RecomputeDirectionality(mozilla::dom::Element* aElement,
                                        bool aNotify = true);
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -4299,16 +4299,37 @@ nsDOMWindowUtils::GetStorageUsage(nsIDOM
     return NS_ERROR_UNEXPECTED;
   }
 
   *aRetval = storage->GetOriginQuotaUsage();
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDOMWindowUtils::GetDirectionFromText(const nsAString& aString, int32_t* aRetval)
+{
+  Directionality dir = ::GetDirectionFromText(aString.BeginReading(), aString.Length(), nullptr);
+  switch (dir) {
+    case eDir_NotSet:
+      *aRetval = nsIDOMWindowUtils::DIRECTION_NOT_SET;
+      break;
+    case eDir_RTL:
+      *aRetval = nsIDOMWindowUtils::DIRECTION_RTL;
+      break;
+    case eDir_LTR:
+      *aRetval = nsIDOMWindowUtils::DIRECTION_LTR;
+      break;
+    case eDir_Auto:
+      MOZ_ASSERT_UNREACHABLE("GetDirectionFromText should never return this value");
+      return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
 NS_INTERFACE_MAP_BEGIN(nsTranslationNodeList)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsITranslationNodeList)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(nsTranslationNodeList)
 NS_IMPL_RELEASE(nsTranslationNodeList)
 
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -2006,16 +2006,26 @@ interface nsIDOMWindowUtils : nsISupport
   /**
    * Returns usage data for a given storage object.
    *
    * @param aStorage
    *    The storage object to get usage data for.
    */
   int64_t getStorageUsage(in nsIDOMStorage aStorage);
 
+  /**
+   * Returns the directionality of a string using the first-strong character
+   * algorithm defined in http://unicode.org/reports/tr9/#P2.
+   *
+   * @param aString the string to retrieve the direction for.
+   * @return one of DIRECTION_LTR, DIRECTION_RTL or DIRECTION_NOT_SET depending
+   *         on the first-strong character found in the string.
+   */
+  long getDirectionFromText(in AString aString);
+
   // These consts are only for testing purposes.
   const long DEFAULT_MOUSE_POINTER_ID = 0;
   const long DEFAULT_PEN_POINTER_ID   = 1;
   const long DEFAULT_TOUCH_POINTER_ID = 2;
 
   // Match WidgetMouseEventBase::buttonType.
   const long MOUSE_BUTTON_LEFT_BUTTON   = 0;
   const long MOUSE_BUTTON_MIDDLE_BUTTON = 1;
@@ -2029,16 +2039,21 @@ interface nsIDOMWindowUtils : nsISupport
   // Typically, "back" button being left side of 5-button
   // mice, see "buttons" attribute document of DOM3 Events.
   const long MOUSE_BUTTONS_4TH_BUTTON = 0x08;
   // Typically, "forward" button being right side of 5-button
   // mice, see "buttons" attribute document of DOM3 Events.
   const long MOUSE_BUTTONS_5TH_BUTTON = 0x10;
   // Buttons are not specified, will be calculated from |aButton|.
   const long MOUSE_BUTTONS_NOT_SPECIFIED = -1;
+
+  // Return values for getDirectionFromText().
+  const long DIRECTION_LTR = 0;
+  const long DIRECTION_RTL = 1;
+  const long DIRECTION_NOT_SET = 2;
 };
 
 [scriptable, uuid(c694e359-7227-4392-a138-33c0cc1f15a6)]
 interface nsITranslationNodeList : nsISupports {
   readonly attribute unsigned long length;
   nsIDOMNode item(in unsigned long index);
 
   // A translation root is a block element, or an inline element
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -403,16 +403,19 @@ public:
   }
 
   void AddUserData(UserDataKey *key, void *userData, void (*destroy)(void*)) {
     mUserData.Add(key, userData, destroy);
   }
   void *GetUserData(UserDataKey *key) {
     return mUserData.Get(key);
   }
+  void RemoveUserData(UserDataKey *key) {
+    mUserData.RemoveAndDestroy(key);
+  }
 
 protected:
   friend class DrawTargetCaptureImpl;
   friend class StoredPattern;
 
   // This is for internal use, it ensures the SourceSurface's data remains
   // valid during the lifetime of the SourceSurface.
   // @todo XXX - We need something better here :(. But we may be able to get rid
@@ -807,16 +810,20 @@ public:
 
   void AddUserData(UserDataKey *key, void *userData, void (*destroy)(void*)) {
     mUserData.Add(key, userData, destroy);
   }
   void *GetUserData(UserDataKey *key) {
     return mUserData.Get(key);
   }
 
+  void RemoveUserData(UserDataKey *key) {
+    mUserData.RemoveAndDestroy(key);
+  }
+
   const RefPtr<UnscaledFont>& GetUnscaledFont() const { return mUnscaledFont; }
 
 protected:
   explicit ScaledFont(const RefPtr<UnscaledFont>& aUnscaledFont)
     : mUnscaledFont(aUnscaledFont)
   {}
 
   UserData mUserData;
@@ -1408,16 +1415,17 @@ public:
                                          Color& aColor,
                                          std::vector<Glyph>& aGlyphs) = 0;
 };
 
 class DrawEventRecorder : public RefCounted<DrawEventRecorder>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorder)
+  virtual void Finish() = 0;
   virtual ~DrawEventRecorder() { }
 };
 
 struct Tile
 {
   RefPtr<DrawTarget> mDrawTarget;
   IntPoint mTileOrigin;
 };
--- a/gfx/2d/DrawEventRecorder.h
+++ b/gfx/2d/DrawEventRecorder.h
@@ -23,30 +23,59 @@ namespace gfx {
 class PathRecording;
 
 class DrawEventRecorderPrivate : public DrawEventRecorder
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderPrivate)
   explicit DrawEventRecorderPrivate(std::ostream *aStream);
   virtual ~DrawEventRecorderPrivate() { }
+  virtual void Finish() {
+    // The iteration is a bit awkward here because our iterator will
+    // be invalidated by the removal
+    for (auto font = mStoredFonts.begin(); font != mStoredFonts.end(); ) {
+      auto oldFont = font++;
+      (*oldFont)->RemoveUserData(reinterpret_cast<UserDataKey*>(this));
+    }
+    for (auto surface = mStoredSurfaces.begin(); surface != mStoredSurfaces.end(); ) {
+      auto oldSurface = surface++;
+      (*oldSurface)->RemoveUserData(reinterpret_cast<UserDataKey*>(this));
+    }
+
+  }
 
   void WriteHeader();
 
   void RecordEvent(const RecordedEvent &aEvent);
   void WritePath(const PathRecording *aPath);
 
   void AddStoredObject(const ReferencePtr aObject) {
     mStoredObjects.insert(aObject);
   }
 
   void RemoveStoredObject(const ReferencePtr aObject) {
     mStoredObjects.erase(aObject);
   }
 
+  void AddScaledFont(ScaledFont* aFont) {
+    mStoredFonts.insert(aFont);
+  }
+
+  void RemoveScaledFont(ScaledFont* aFont) {
+    mStoredFonts.erase(aFont);
+  }
+
+  void AddSourceSurface(SourceSurface* aSurface) {
+    mStoredSurfaces.insert(aSurface);
+  }
+
+  void RemoveSourceSurface(SourceSurface* aSurface) {
+    mStoredSurfaces.erase(aSurface);
+  }
+
   bool HasStoredObject(const ReferencePtr aObject) {
     return mStoredObjects.find(aObject) != mStoredObjects.end();
   }
 
   void AddStoredFontData(const uint64_t aFontDataKey) {
     mStoredFontData.insert(aFontDataKey);
   }
 
@@ -57,23 +86,29 @@ public:
 protected:
   std::ostream *mOutputStream;
 
   virtual void Flush() = 0;
 
 #if defined(_MSC_VER)
   typedef std::unordered_set<const void*> ObjectSet;
   typedef std::unordered_set<uint64_t> Uint64Set;
+  typedef std::unordered_set<ScaledFont*> FontSet;
+  typedef std::unordered_set<SourceSurface*> SurfaceSet;
 #else
   typedef std::set<const void*> ObjectSet;
   typedef std::set<uint64_t> Uint64Set;
+  typedef std::set<ScaledFont*> FontSet;
+  typedef std::set<SourceSurface*> SurfaceSet;
 #endif
 
   ObjectSet mStoredObjects;
   Uint64Set mStoredFontData;
+  FontSet mStoredFonts;
+  SurfaceSet mStoredSurfaces;
 };
 
 class DrawEventRecorderFile : public DrawEventRecorderPrivate
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderFile)
   explicit DrawEventRecorderFile(const char *aFilename);
   ~DrawEventRecorderFile();
--- a/gfx/2d/DrawTargetRecording.cpp
+++ b/gfx/2d/DrawTargetRecording.cpp
@@ -23,16 +23,17 @@ struct RecordingSourceSurfaceUserData
   RefPtr<DrawEventRecorderPrivate> recorder;
 };
 
 void RecordingSourceSurfaceUserDataFunc(void *aUserData)
 {
   RecordingSourceSurfaceUserData *userData =
     static_cast<RecordingSourceSurfaceUserData*>(aUserData);
 
+  userData->recorder->RemoveSourceSurface((SourceSurface*)userData->refPtr);
   userData->recorder->RemoveStoredObject(userData->refPtr);
   userData->recorder->RecordEvent(
     RecordedSourceSurfaceDestruction(userData->refPtr));
 
   delete userData;
 }
 
 static void
@@ -61,16 +62,17 @@ EnsureSurfaceStored(DrawEventRecorderPri
 {
   if (aRecorder->HasStoredObject(aSurface)) {
     return;
   }
 
   RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface();
   StoreSourceSurface(aRecorder, aSurface, dataSurf, reason);
   aRecorder->AddStoredObject(aSurface);
+  aRecorder->AddSourceSurface(aSurface);
 
   RecordingSourceSurfaceUserData *userData = new RecordingSourceSurfaceUserData;
   userData->refPtr = aSurface;
   userData->recorder = aRecorder;
   aSurface->AddUserData(reinterpret_cast<UserDataKey*>(aRecorder),
                         userData, &RecordingSourceSurfaceUserDataFunc);
   return;
 }
@@ -370,17 +372,17 @@ struct RecordingFontUserData
 };
 
 void RecordingFontUserDataDestroyFunc(void *aUserData)
 {
   RecordingFontUserData *userData =
     static_cast<RecordingFontUserData*>(aUserData);
 
   userData->recorder->RecordEvent(RecordedScaledFontDestruction(userData->refPtr));
-
+  userData->recorder->RemoveScaledFont((ScaledFont*)userData->refPtr);
   delete userData;
 }
 
 void
 DrawTargetRecording::FillGlyphs(ScaledFont *aFont,
                                 const GlyphBuffer &aBuffer,
                                 const Pattern &aPattern,
                                 const DrawOptions &aOptions,
@@ -416,16 +418,17 @@ DrawTargetRecording::FillGlyphs(ScaledFo
     }
 
     mRecorder->RecordEvent(RecordedScaledFontCreation(aFont, unscaledFont));
 
     RecordingFontUserData *userData = new RecordingFontUserData;
     userData->refPtr = aFont;
     userData->recorder = mRecorder;
     aFont->AddUserData(userDataKey, userData, &RecordingFontUserDataDestroyFunc);
+    userData->recorder->AddScaledFont(aFont);
   }
 
   mRecorder->RecordEvent(RecordedFillGlyphs(this, aFont, aPattern, aOptions, aBuffer.mGlyphs, aBuffer.mNumGlyphs));
   mFinalDT->FillGlyphs(aFont, aBuffer, *AdjustedPattern(aPattern), aOptions, aRenderingOptions);
 }
 
 void
 DrawTargetRecording::Mask(const Pattern &aSource,
--- a/gfx/2d/UserData.h
+++ b/gfx/2d/UserData.h
@@ -67,16 +67,31 @@ public:
           entries[i] = entries[i+1];
         }
         return userData;
       }
     }
     return nullptr;
   }
 
+  /* Remove and destroy a given key */
+  void RemoveAndDestroy(UserDataKey *key)
+  {
+    for (int i=0; i<count; i++) {
+      if (key == entries[i].key) {
+        entries[i].destroy(entries[i].userData);
+        // decrement before looping so entries[i+1] doesn't read past the end:
+        --count;
+        for (;i<count; i++) {
+          entries[i] = entries[i+1];
+        }
+      }
+    }
+  }
+
   /* Retrives the userData for the associated key */
   void *Get(UserDataKey *key) const
   {
     for (int i=0; i<count; i++) {
       if (key == entries[i].key) {
         return entries[i].userData;
       }
     }
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -871,17 +871,19 @@ nsEventStatus AsyncPanZoomController::Ha
     // scrolling, but here we're interested in the other direction.
     ParentLayerRect thumbRect =
         (node->GetTransform() * AsyncTransformMatrix()).TransformBounds(
               LayerRect(node->GetVisibleRegion().GetBounds()));
     ScrollDirection otherDirection = GetPerpendicularDirection(aDragMetrics.mDirection);
     ParentLayerCoord distance = GetAxisStart(otherDirection,
         thumbRect.DistanceTo(aEvent.mLocalOrigin));
     ParentLayerCoord thumbWidth = GetAxisLength(otherDirection, thumbRect);
-    if (thumbWidth * snapMultiplier < distance) {
+    // Avoid triggering this condition spuriously when the thumb is
+    // offscreen and its visible region is therefore empty.
+    if (thumbWidth > 0 && thumbWidth * snapMultiplier < distance) {
       isMouseAwayFromThumb = true;
     }
   }
 
   ReentrantMonitorAutoEnter lock(mMonitor);
   CSSCoord thumbPosition;
   if (isMouseAwayFromThumb) {
     thumbPosition = aInitialThumbPos;
--- a/gfx/layers/wr/WebRenderPaintedLayerBlob.cpp
+++ b/gfx/layers/wr/WebRenderPaintedLayerBlob.cpp
@@ -62,16 +62,18 @@ WebRenderPaintedLayerBlob::RenderLayer(w
                                            visibleRegion.ToUnknownRegion(), visibleRegion.ToUnknownRegion(),
                                            DrawRegionClip::DRAW, nsIntRegion(), WrManager()->GetPaintedLayerCallbackData());
 
     if (gfxPrefs::WebRenderHighlightPaintedLayers()) {
       dt->SetTransform(Matrix());
       dt->FillRect(Rect(0, 0, imageSize.width, imageSize.height), ColorPattern(Color(1.0, 0.0, 0.0, 0.5)));
     }
 
+    recorder->Finish();
+
     wr::ByteBuffer bytes;
     bytes.Allocate(recorder->RecordingSize());
     DebugOnly<bool> ok = recorder->CopyRecording((char*)bytes.AsSlice().begin().get(), bytes.AsSlice().length());
     MOZ_ASSERT(ok);
 
     //XXX: We should switch to updating the blob image instead of adding a new one
     //     That will get rid of this discard bit
     if (mImageKey.isSome()) {
--- a/testing/talos/talos/xtalos/xperf_whitelist.json
+++ b/testing/talos/talos/xtalos/xperf_whitelist.json
@@ -2,16 +2,17 @@
  "C:\\$Mft": {"ignore": true},
  "C:\\$Extend\\$UsnJrnl:$J": {"ignore": true},
  "C:\\Windows\\Prefetch\\{prefetch}.pf": {"ignore": true},
  "C:\\$Secure": {"ignore": true},
  "C:\\$logfile": {"ignore": true},
  "{firefox}\\omni.ja": {"mincount": 0, "maxcount": 46, "minbytes": 0, "maxbytes": 3014656},
  "{firefox}\\browser\\omni.ja": {"mincount": 0, "maxcount": 28, "minbytes": 0, "maxbytes": 1835008},
  "{firefox}\\browser\\features\\aushelper@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
+ "{firefox}\\browser\\features\\clicktoplay-rollout@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\e10srollout@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\flyweb@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\formautofill@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\loop@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\firefox@getpocket.com.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\presentation@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\webcompat@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\webcompat-reporter@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},