merge mozilla-central to autoland. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sat, 10 Jun 2017 11:19:26 +0200
changeset 413808 158930f1c0d31038b5015cdf60221e50bebcfd89
parent 413807 c11325203c125688c9d6d584572e9a509975d66a (current diff)
parent 413803 91dc9525c422f11041da33b008b14a8117ed9a40 (diff)
child 413809 cad0db00e0b473bab9f7abae5b57074cd7e99865
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone55.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 mozilla-central to autoland. r=merge a=merge
--- 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},