Merge mozilla-central to autoland. a=merge CLOSED TREE
authorTiberius Oros <toros@mozilla.com>
Sat, 06 Oct 2018 00:47:12 +0300
changeset 495589 9a47689aab049be860164e673ee32d4be58a033f
parent 495588 82e3da06b72f9ef230bd1c7f99df020993ae5c20 (current diff)
parent 495565 54cb6a2f028b033ef567f00af2f82f5fb97ab437 (diff)
child 495590 96f25e9de9033887a61358410bf8ecea8c71af15
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.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. a=merge CLOSED TREE
--- a/browser/base/content/browser-customization.js
+++ b/browser/base/content/browser-customization.js
@@ -38,30 +38,19 @@ var CustomizationHandler = {
 
     UpdateUrlbarSearchSplitterState();
 
     PlacesToolbarHelper.customizeStart();
   },
 
   _customizationEnding(aDetails) {
     // Update global UI elements that may have been added or removed
-    if (aDetails.changed) {
-      gURLBar = document.getElementById("urlbar");
-
-      if (AppConstants.platform != "macosx")
-        updateEditUIVisibility();
-
-      // Hacky: update the PopupNotifications' object's reference to the iconBox,
-      // if it already exists, since it may have changed if the URL bar was
-      // added/removed.
-      if (!window.__lookupGetter__("PopupNotifications")) {
-        PopupNotifications.iconBox =
-          document.getElementById("notification-popup-box");
-      }
-
+    if (aDetails.changed &&
+        AppConstants.platform != "macosx") {
+      updateEditUIVisibility();
     }
 
     PlacesToolbarHelper.customizeDone();
 
     UpdateUrlbarSearchSplitterState();
 
     // Update the urlbar
     URLBarSetURI();
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1596,16 +1596,18 @@ window._gBrowser = {
     }
 
     if (recordExecution) {
       aBrowser.setAttribute("recordExecution", recordExecution);
 
       // Web Replay middleman processes need the default URL to be loaded in
       // order to set up their rendering state.
       aBrowser.setAttribute("nodefaultsrc", "false");
+    } else if (aBrowser.hasAttribute("recordExecution")) {
+      aBrowser.removeAttribute("recordExecution");
     }
 
     // NB: This works with the hack in the browser constructor that
     // turns this normal property into a field.
     if (sameProcessAsFrameLoader) {
       // Always set sameProcessAsFrameLoader when passed in explicitly.
       aBrowser.sameProcessAsFrameLoader = sameProcessAsFrameLoader;
     } else if (!aShouldBeRemote || oldRemoteType == remoteType) {
--- a/browser/components/downloads/DownloadsSubview.jsm
+++ b/browser/components/downloads/DownloadsSubview.jsm
@@ -416,47 +416,73 @@ DownloadsSubview.Button = class extends 
     if (this._downloadState !== newState) {
       this._downloadState = newState;
       this.onStateChanged();
     } else {
       this._updateState();
     }
   }
 
-  /**
-   * Update the DOM representation of this download to match the current, recently
-   * updated, state.
-   */
+  // DownloadElementShell
+  connect() {}
+
+  // DownloadElementShell
+  showDisplayNameAndIcon(displayName, icon) {
+    this.element.setAttribute("label", displayName);
+    this.element.setAttribute("image", icon);
+  }
+
+  // DownloadElementShell
+  showProgress() {}
+
+  // DownloadElementShell
+  showStatus(status) {
+    this.element.setAttribute("status", status);
+    this.element.setAttribute("tooltiptext", status);
+  }
+
+  // DownloadElementShell
+  showButton() {}
+
+  // DownloadElementShell
+  hideButton() {}
+
+  // DownloadElementShell
   _updateState() {
+    // This view only show completed and failed downloads.
+    let state = DownloadsCommon.stateOfDownload(this.download);
+    let shouldDisplay = state == DownloadsCommon.DOWNLOAD_FINISHED ||
+                        state == DownloadsCommon.DOWNLOAD_FAILED;
+    this.element.hidden = !shouldDisplay;
+    if (!shouldDisplay) {
+      return;
+    }
+
     super._updateState();
-    this.element.setAttribute("label", this.element.getAttribute("displayName"));
-    this.element.setAttribute("tooltiptext", this.element.getAttribute("status"));
 
     if (this.isCommandEnabled("downloadsCmd_show")) {
       this.element.setAttribute("openLabel", kButtonLabels.open);
       this.element.setAttribute("showLabel", kButtonLabels.show);
       this.element.removeAttribute("retryLabel");
     } else if (this.isCommandEnabled("downloadsCmd_retry")) {
       this.element.setAttribute("retryLabel", kButtonLabels.retry);
       this.element.removeAttribute("openLabel");
       this.element.removeAttribute("showLabel");
     } else {
       this.element.removeAttribute("openLabel");
       this.element.removeAttribute("retryLabel");
       this.element.removeAttribute("showLabel");
     }
-
-    this._updateVisibility();
   }
 
-  _updateVisibility() {
-    let state = this.element.getAttribute("state");
-    // This view only show completed and failed downloads.
-    this.element.hidden = !(state == DownloadsCommon.DOWNLOAD_FINISHED ||
-      state == DownloadsCommon.DOWNLOAD_FAILED);
+  // DownloadElementShell
+  _updateStateInner() {
+    if (!this.element.hidden) {
+      super._updateStateInner();
+    }
   }
 
   /**
    * Command handler; copy the download URL to the OS general clipboard.
    */
   downloadsCmd_copyLocation() {
     DownloadsCommon.copyDownloadLink(this.download);
   }
--- a/browser/components/downloads/DownloadsViewUI.jsm
+++ b/browser/components/downloads/DownloadsViewUI.jsm
@@ -62,16 +62,24 @@ var gDownloadElementButtons = {
   },
   removeFile: {
     commandName: "downloadsCmd_confirmBlock",
     l10nId: "download-remove-file",
     iconClass: "downloadIconCancel",
   },
 };
 
+/**
+ * Associates each document with a pre-built DOM fragment representing the
+ * download list item. This is then cloned to create each individual list item.
+ * This is stored on the document to prevent leaks that would occur if a single
+ * instance created by one document's DOMParser was stored globally.
+ */
+var gDownloadListItemFragments = new WeakMap();
+
 var DownloadsViewUI = {
   /**
    * Returns true if the given string is the name of a command that can be
    * handled by the Downloads user interface, including standard commands.
    */
   isCommandName(name) {
     return name.startsWith("cmd_") || name.startsWith("downloadsCmd_");
   },
@@ -136,16 +144,79 @@ this.DownloadsViewUI.DownloadElementShel
 
 this.DownloadsViewUI.DownloadElementShell.prototype = {
   /**
    * The richlistitem for the download, initialized by the derived object.
    */
   element: null,
 
   /**
+   * Manages the "active" state of the shell. By default all the shells are
+   * inactive, thus their UI is not updated. They must be activated when
+   * entering the visible area.
+   */
+  ensureActive() {
+    if (!this._active) {
+      this._active = true;
+      this.connect();
+      this.onChanged();
+    }
+  },
+  get active() {
+    return !!this._active;
+  },
+
+  connect() {
+    let document = this.element.ownerDocument;
+    let downloadListItemFragment = gDownloadListItemFragments.get(document);
+    if (!downloadListItemFragment) {
+      let MozXULElement = document.defaultView.MozXULElement;
+      downloadListItemFragment = MozXULElement.parseXULToFragment(`
+        <hbox class="downloadMainArea" flex="1" align="center">
+          <stack>
+            <image class="downloadTypeIcon" validate="always"/>
+            <image class="downloadBlockedBadge" />
+          </stack>
+          <vbox class="downloadContainer" flex="1" pack="center">
+            <description class="downloadTarget" crop="center"/>
+            <progressmeter class="downloadProgress" min="0" max="100"/>
+            <description class="downloadDetails downloadDetailsNormal"
+                         crop="end"/>
+            <description class="downloadDetails downloadDetailsHover"
+                         crop="end"/>
+            <description class="downloadDetails downloadDetailsButtonHover"
+                         crop="end"/>
+          </vbox>
+        </hbox>
+        <toolbarseparator />
+        <button class="downloadButton"
+                oncommand="DownloadsView.onDownloadButton(event);"/>
+      `);
+      gDownloadListItemFragments.set(document, downloadListItemFragment);
+    }
+    this.element.setAttribute("active", true);
+    this.element.setAttribute("orient", "horizontal");
+    this.element.setAttribute("onclick",
+                              "DownloadsView.onDownloadClick(event);");
+    this.element.appendChild(document.importNode(downloadListItemFragment,
+                                                 true));
+    for (let [propertyName, selector] of [
+      ["_downloadTypeIcon", ".downloadTypeIcon"],
+      ["_downloadTarget", ".downloadTarget"],
+      ["_downloadProgress", ".downloadProgress"],
+      ["_downloadDetailsNormal", ".downloadDetailsNormal"],
+      ["_downloadDetailsHover", ".downloadDetailsHover"],
+      ["_downloadDetailsButtonHover", ".downloadDetailsButtonHover"],
+      ["_downloadButton", ".downloadButton"],
+    ]) {
+      this[propertyName] = this.element.querySelector(selector);
+    }
+  },
+
+  /**
    * Returns a string from the downloads stringbundleset, which contains legacy
    * strings that are loaded from DTD files instead of properties files. This
    * won't be necessary once localization is converted to Fluent (bug 1452637).
    */
   string(l10nId) {
     // These strings are not used often enough to require caching.
     return this.element.ownerDocument.getElementById("downloadsStrings")
                                      .getAttribute("string-" + l10nId);
@@ -170,43 +241,78 @@ this.DownloadsViewUI.DownloadElementShel
            (this.download.succeeded ? "&state=normal" : "");
   },
 
   get browserWindow() {
     return BrowserWindowTracker.getTopWindow();
   },
 
   /**
-   * The progress element for the download, or undefined in case the XBL binding
-   * has not been applied yet.
+   * Updates the display name and icon.
+   *
+   * @param displayName
+   *        This is usually the full file name of the download without the path.
+   * @param icon
+   *        URL of the icon to load, generally from the "image" property.
+   */
+  showDisplayNameAndIcon(displayName, icon) {
+    this._downloadTarget.setAttribute("value", displayName);
+    this._downloadTarget.setAttribute("tooltiptext", displayName);
+    this._downloadTypeIcon.setAttribute("src", icon);
+  },
+
+  /**
+   * Updates the displayed progress bar.
+   *
+   * @param mode
+   *        Either "normal" or "undetermined".
+   * @param value
+   *        Percentage of the progress bar to display, from 0 to 100.
+   * @param paused
+   *        True to display the progress bar style for paused downloads.
    */
-  get _progressElement() {
-    if (!this.__progressElement) {
-      // If the element is not available now, we will try again the next time.
-      this.__progressElement =
-           this.element.ownerDocument.getAnonymousElementByAttribute(
-                                         this.element, "anonid",
-                                         "progressmeter");
+  showProgress(mode, value, paused) {
+    // The "undetermined" mode of the progressmeter is implemented with a
+    // different element structure, and with support from platform code as well.
+    // On Linux only, this mode isn't compatible with the custom styling that we
+    // apply separately with the "progress-undetermined" attribute.
+    this._downloadProgress.setAttribute("mode",
+      AppConstants.platform == "linux" ? "normal" : mode);
+    if (mode == "undetermined") {
+      this._downloadProgress.setAttribute("progress-undetermined", "true");
+    } else {
+      this._downloadProgress.removeAttribute("progress-undetermined");
     }
-    return this.__progressElement;
+    this._downloadProgress.setAttribute("value", value);
+    if (paused) {
+      this._downloadProgress.setAttribute("paused", "true");
+    } else {
+      this._downloadProgress.removeAttribute("paused");
+    }
+
+    // Dispatch the ValueChange event for accessibility.
+    let event = this.element.ownerDocument.createEvent("Events");
+    event.initEvent("ValueChange", true, true);
+    this._downloadProgress.dispatchEvent(event);
   },
 
   /**
    * Updates the full status line.
    *
    * @param status
    *        Status line of the Downloads Panel or the Downloads View.
    * @param hoverStatus
    *        Label to show in the Downloads Panel when the mouse pointer is over
    *        the main area of the item. If not specified, this will be the same
    *        as the status line. This is ignored in the Downloads View.
    */
   showStatus(status, hoverStatus = status) {
-    this.element.setAttribute("status", status);
-    this.element.setAttribute("hoverStatus", hoverStatus);
+    this._downloadDetailsNormal.setAttribute("value", status);
+    this._downloadDetailsNormal.setAttribute("tooltiptext", status);
+    this._downloadDetailsHover.setAttribute("value", hoverStatus);
   },
 
   /**
    * Updates the status line combining the given state label with other labels.
    *
    * @param stateLabel
    *        Label representing the state of the download, for example "Failed".
    *        In the Downloads Panel, this is the only text displayed when the
@@ -233,45 +339,50 @@ this.DownloadsViewUI.DownloadElementShel
 
     if (!this.isPanel) {
       this.showStatus(fullStatus);
     } else {
       this.showStatus(stateLabel, hoverStatus || fullStatus);
     }
   },
 
+  /**
+   * Updates the main action button and makes it visible.
+   *
+   * @param type
+   *        One of the presets defined in gDownloadElementButtons.
+   */
   showButton(type) {
     let { commandName, l10nId, descriptionL10nId,
           iconClass } = gDownloadElementButtons[type];
 
     this.buttonCommandName = commandName;
-    let labelAttribute = this.isPanel ? "buttonarialabel" : "buttontooltiptext";
-    this.element.setAttribute(labelAttribute, this.string(l10nId));
+    let labelAttribute = this.isPanel ? "aria-label" : "tooltiptext";
+    this._downloadButton.setAttribute(labelAttribute, this.string(l10nId));
     if (this.isPanel && descriptionL10nId) {
-      this.element.setAttribute("buttonHoverStatus",
-                                this.string(descriptionL10nId));
+      this._downloadDetailsButtonHover.setAttribute("value",
+        this.string(descriptionL10nId));
     }
-    this.element.setAttribute("buttonclass", "downloadButton " + iconClass);
-    this.element.removeAttribute("buttonhidden");
+    this._downloadButton.setAttribute("class", "downloadButton " + iconClass);
+    this._downloadButton.removeAttribute("hidden");
   },
 
   hideButton() {
-    this.element.setAttribute("buttonhidden", "true");
+    this._downloadButton.setAttribute("hidden", "true");
   },
 
   lastEstimatedSecondsLeft: Infinity,
 
   /**
    * This is called when a major state change occurs in the download, but is not
    * called for every progress update in order to improve performance.
    */
   _updateState() {
-    this.element.setAttribute("displayName",
-                              DownloadsViewUI.getDisplayName(this.download));
-    this.element.setAttribute("image", this.image);
+    this.showDisplayNameAndIcon(DownloadsViewUI.getDisplayName(this.download),
+                                this.image);
     this.element.setAttribute("state",
                               DownloadsCommon.stateOfDownload(this.download));
 
     if (!this.download.stopped) {
       // When the download becomes in progress, we make all the major changes to
       // the user interface here. The _updateStateInner function takes care of
       // displaying the right button type for all other state changes.
       this.showButton("cancel");
@@ -419,43 +530,21 @@ this.DownloadsViewUI.DownloadElementShel
         this.element.removeAttribute("verdict");
       }
 
       this.element.classList.toggle("temporary-block",
                                     !!this.download.hasBlockedData);
     }
 
     // These attributes are set in all code paths, because they are relevant for
-    // downloads that are in progress and for other states where the progress
-    // bar is visible.
+    // downloads that are in progress and for other states.
     if (this.download.hasProgress) {
-      this.element.setAttribute("progressmode", "normal");
-      this.element.setAttribute("progress", this.download.progress);
-      this.element.removeAttribute("progress-undetermined");
+      this.showProgress("normal", this.download.progress, progressPaused);
     } else {
-      // Suppress the progress animation on Linux for the Downloads Panel
-      // progress bars when the file size is unknown.
-      this.element.setAttribute("progressmode",
-                                AppConstants.platform == "linux" ? "normal" :
-                                                                   "undetermined");
-      this.element.setAttribute("progress-undetermined", "true");
-      this.element.setAttribute("progress", "100");
-    }
-
-    if (progressPaused) {
-      this.element.setAttribute("progresspaused", "true");
-    } else {
-      this.element.removeAttribute("progresspaused");
-    }
-
-    // Dispatch the ValueChange event for accessibility, if possible.
-    if (this._progressElement) {
-      let event = this.element.ownerDocument.createEvent("Events");
-      event.initEvent("ValueChange", true, true);
-      this._progressElement.dispatchEvent(event);
+      this.showProgress("undetermined", 100, progressPaused);
     }
   },
 
   /**
    * Returns [title, [details1, details2]] for blocked downloads.
    */
   get rawBlockedTitleAndDetails() {
     let s = DownloadsCommon.strings;
--- a/browser/components/downloads/content/allDownloadsView.js
+++ b/browser/components/downloads/content/allDownloadsView.js
@@ -44,32 +44,16 @@ function HistoryDownloadElementShell(dow
   this.element.classList.add("download");
   this.element.classList.add("download-state");
 }
 
 HistoryDownloadElementShell.prototype = {
   __proto__: DownloadsViewUI.DownloadElementShell.prototype,
 
   /**
-   * Manages the "active" state of the shell.  By default all the shells are
-   * inactive, thus their UI is not updated.  They must be activated when
-   * entering the visible area.
-   */
-  ensureActive() {
-    if (!this._active) {
-      this._active = true;
-      this.element.setAttribute("active", true);
-      this.onChanged();
-    }
-  },
-  get active() {
-    return !!this._active;
-  },
-
-  /**
    * Overrides the base getter to return the Download or HistoryDownload object
    * for displaying information and executing commands in the user interface.
    */
   get download() {
     return this._download;
   },
 
   onStateChanged() {
--- a/browser/components/downloads/content/download.xml
+++ b/browser/components/downloads/content/download.xml
@@ -7,72 +7,22 @@
    - You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!DOCTYPE bindings SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
 
 <bindings id="downloadBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
-
-  <binding id="download"
-           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
-    <content orient="horizontal"
-             onclick="DownloadsView.onDownloadClick(event);">
-      <xul:hbox class="downloadMainArea"
-                flex="1"
-                align="center">
-        <xul:stack>
-          <xul:image class="downloadTypeIcon"
-                     validate="always"
-                     xbl:inherits="src=image"/>
-          <xul:image class="downloadBlockedBadge" />
-        </xul:stack>
-        <xul:vbox pack="center"
-                  flex="1"
-                  class="downloadContainer">
-          <!-- We're letting localizers put a min-width in here primarily
-               because of the downloads summary at the bottom of the list of
-               download items. An element in the summary has the same min-width
-               on a description, and we don't want the panel to change size if the
-               summary isn't being displayed, so we ensure that items share the
-               same minimum width.
-               -->
-          <xul:description class="downloadTarget"
-                           crop="center"
-                           xbl:inherits="value=displayName,tooltiptext=displayName"/>
-          <xul:progressmeter anonid="progressmeter"
-                             class="downloadProgress"
-                             min="0"
-                             max="100"
-                             xbl:inherits="progress-undetermined,mode=progressmode,value=progress,paused=progresspaused"/>
-          <xul:description class="downloadDetails downloadDetailsNormal"
-                           crop="end"
-                           xbl:inherits="value=status,tooltiptext=status"/>
-          <xul:description class="downloadDetails downloadDetailsHover"
-                           crop="end"
-                           xbl:inherits="value=hoverStatus"/>
-          <xul:description class="downloadDetails downloadDetailsButtonHover"
-                           crop="end"
-                           xbl:inherits="value=buttonHoverStatus"/>
-        </xul:vbox>
-      </xul:hbox>
-      <xul:toolbarseparator />
-      <xul:button class="downloadButton"
-                  xbl:inherits="class=buttonclass,aria-label=buttonarialabel,tooltiptext=buttontooltiptext"
-                  oncommand="DownloadsView.onDownloadButton(event);"/>
-    </content>
-  </binding>
-
   <binding id="download-subview-toolbarbutton"
            extends="chrome://global/content/bindings/button.xml#button-base">
     <content>
-      <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
+      <xul:image class="toolbarbutton-icon" validate="always" xbl:inherits="src=image"/>
       <xul:vbox class="toolbarbutton-text" flex="1">
-        <xul:label crop="end" xbl:inherits="value=label,accesskey,crop,wrap"/>
+        <xul:label crop="end" xbl:inherits="value=label"/>
         <xul:label class="status-text status-full" crop="end" xbl:inherits="value=status"/>
         <xul:label class="status-text status-open" crop="end" xbl:inherits="value=openLabel"/>
         <xul:label class="status-text status-retry" crop="end" xbl:inherits="value=retryLabel"/>
         <xul:label class="status-text status-show" crop="end" xbl:inherits="value=showLabel"/>
       </xul:vbox>
       <xul:toolbarbutton anonid="button" class="action-button"/>
     </content>
   </binding>
--- a/browser/components/downloads/content/downloads.css
+++ b/browser/components/downloads/content/downloads.css
@@ -1,18 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*** Downloads Panel ***/
 
-#downloadsListBox > richlistitem {
-  -moz-binding: url('chrome://browser/content/downloads/download.xml#download');
-}
-
 #downloadsListBox > richlistitem:not([selected]) button {
   /* Only focus buttons in the selected item. */
   -moz-user-focus: none;
 }
 
 #downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress,
 #downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails,
 #downloadsFooter:not([showingsummary]) #downloadsSummary {
@@ -39,24 +35,20 @@
  * This hack makes sure we don't apply any binding to inactive items (inactive
  * items are history downloads that haven't been in the visible area).
  * We can do this because the richlistbox implementation does not interact
  * much with the richlistitem binding.  However, this may turn out to have
  * some side effects (see bug 828111 for the details).
  *
  * We might be able to do away with this workaround once bug 653881 is fixed.
  */
-#downloadsRichListBox > richlistitem {
+#downloadsRichListBox > richlistitem:not([active]) {
   -moz-binding: none;
 }
 
-#downloadsRichListBox > richlistitem[active] {
-  -moz-binding: url("chrome://browser/content/downloads/download.xml#download");
-}
-
 #downloadsRichListBox > richlistitem button {
   /* These buttons should never get focus, as that would "disable"
      the downloads view controller (it's only used when the richlistbox
      is focused). */
   -moz-user-focus: none;
 }
 
 /*** Visibility of controls inside download items ***/
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -727,16 +727,17 @@ var DownloadsView = {
     let viewItem = new DownloadsViewItem(download, element);
     this._visibleViewItems.set(download, viewItem);
     this._itemsForElements.set(element, viewItem);
     if (aNewest) {
       this.richListBox.insertBefore(element, this.richListBox.firstElementChild);
     } else {
       this.richListBox.appendChild(element);
     }
+    viewItem.ensureActive();
   },
 
   /**
    * Removes the view item associated with the specified data item.
    */
   _removeViewItem(download) {
     DownloadsCommon.log("Removing a DownloadsViewItem from the downloads list.");
     let element = this._visibleViewItems.get(download).element;
@@ -906,39 +907,36 @@ XPCOMUtils.defineConstant(this, "Downloa
  *
  * @param download
  *        Download object to be associated with the view item.
  * @param aElement
  *        XUL element corresponding to the single download item in the view.
  */
 function DownloadsViewItem(download, aElement) {
   this.download = download;
-  this.downloadState = DownloadsCommon.stateOfDownload(download);
   this.element = aElement;
   this.element._shell = this;
 
   this.element.setAttribute("type", "download");
   this.element.classList.add("download-state");
 
   this.isPanel = true;
-
-  this._updateState();
 }
 
 DownloadsViewItem.prototype = {
   __proto__: DownloadsViewUI.DownloadElementShell.prototype,
 
   /**
    * The XUL element corresponding to the associated richlistbox item.
    */
   _element: null,
 
   onChanged() {
     let newState = DownloadsCommon.stateOfDownload(this.download);
-    if (this.downloadState != newState) {
+    if (this.downloadState !== newState) {
       this.downloadState = newState;
       this._updateState();
     } else {
       this._updateStateInner();
     }
   },
 
   isCommandEnabled(aCommand) {
--- a/browser/components/urlbar/UrlbarController.jsm
+++ b/browser/components/urlbar/UrlbarController.jsm
@@ -142,16 +142,24 @@ class UrlbarController {
    *
    * @param {object} listener The listener to add.
    */
   removeQueryListener(listener) {
     this._listeners.delete(listener);
   }
 
   /**
+   * When switching tabs, clear some internal caches to handle cases like
+   * backspace, autofill or repeated searches.
+   */
+  tabContextChanged() {
+    // TODO: implementation needed (bug 1496685)
+  }
+
+  /**
    * Internal function to notify listeners of results.
    *
    * @param {string} name Name of the notification.
    * @param {object} params Parameters to pass with the notification.
    */
   _notify(name, ...params) {
     for (let listener of this._listeners) {
       try {
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -409,16 +409,20 @@ class UrlbarInput {
     this._updateTextOverflow();
 
     this._updateUrlTooltip();
   }
 
   _on_scrollend(event) {
     this._updateTextOverflow();
   }
+
+  _on_TabSelect(event) {
+    this.controller.tabContextChanged();
+  }
 }
 
 /**
  * Handles copy and cut commands for the urlbar.
  */
 class CopyCutController {
   /**
    * @param {UrlbarInput} urlbar
--- 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: 2.0.892
+Current extension version is: 2.0.911
 
-Taken from upstream commit: ec10cae5
+Taken from upstream commit: ff2df9c5
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -118,18 +118,18 @@ return /******/ (function(modules) { // 
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '2.0.892';
-var pdfjsBuild = 'ec10cae5';
+var pdfjsVersion = '2.0.911';
+var pdfjsBuild = 'ff2df9c5';
 var pdfjsSharedUtil = __w_pdfjs_require__(1);
 var pdfjsDisplayAPI = __w_pdfjs_require__(7);
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(19);
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(20);
 var pdfjsDisplayDOMUtils = __w_pdfjs_require__(8);
 var pdfjsDisplaySVG = __w_pdfjs_require__(21);
 let pdfjsDisplayWorkerOptions = __w_pdfjs_require__(13);
 let pdfjsDisplayAPICompatibility = __w_pdfjs_require__(10);
@@ -4221,17 +4221,17 @@ function _fetchDocument(worker, source, 
     return Promise.reject(new Error('Worker was destroyed'));
   }
   if (pdfDataRangeTransport) {
     source.length = pdfDataRangeTransport.length;
     source.initialData = pdfDataRangeTransport.initialData;
   }
   return worker.messageHandler.sendWithPromise('GetDocRequest', {
     docId,
-    apiVersion: '2.0.892',
+    apiVersion: '2.0.911',
     source: {
       data: source.data,
       url: source.url,
       password: source.password,
       disableAutoFetch: source.disableAutoFetch,
       rangeChunkSize: source.rangeChunkSize,
       length: source.length
     },
@@ -5548,18 +5548,18 @@ var InternalRenderTask = function Intern
         }
       });
     }
   };
   return InternalRenderTask;
 }();
 var version, build;
 {
-  exports.version = version = '2.0.892';
-  exports.build = build = 'ec10cae5';
+  exports.version = version = '2.0.911';
+  exports.build = build = 'ff2df9c5';
 }
 exports.getDocument = getDocument;
 exports.LoopbackPort = LoopbackPort;
 exports.PDFDataRangeTransport = PDFDataRangeTransport;
 exports.PDFWorker = PDFWorker;
 exports.PDFDocumentProxy = PDFDocumentProxy;
 exports.PDFPageProxy = PDFPageProxy;
 exports.setPDFNetworkStreamFactory = setPDFNetworkStreamFactory;
@@ -5880,134 +5880,215 @@ exports.loadScript = loadScript;
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 exports.FontLoader = exports.FontFaceObject = undefined;
 
 var _util = __w_pdfjs_require__(1);
 
-function FontLoader(docId) {
-  this.docId = docId;
-  this.styleElement = null;
-}
-FontLoader.prototype = {
-  insertRule: function fontLoaderInsertRule(rule) {
-    var styleElement = this.styleElement;
+class BaseFontLoader {
+  constructor(docId) {
+    if (this.constructor === BaseFontLoader) {
+      (0, _util.unreachable)('Cannot initialize BaseFontLoader.');
+    }
+    this.docId = docId;
+    this.nativeFontFaces = [];
+    this.styleElement = null;
+    this.loadingContext = {
+      requests: [],
+      nextRequestId: 0
+    };
+  }
+  addNativeFontFace(nativeFontFace) {
+    this.nativeFontFaces.push(nativeFontFace);
+    document.fonts.add(nativeFontFace);
+  }
+  insertRule(rule) {
+    let styleElement = this.styleElement;
     if (!styleElement) {
       styleElement = this.styleElement = document.createElement('style');
-      styleElement.id = 'PDFJS_FONT_STYLE_TAG_' + this.docId;
+      styleElement.id = `PDFJS_FONT_STYLE_TAG_${this.docId}`;
       document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement);
     }
-    var styleSheet = styleElement.sheet;
+    const styleSheet = styleElement.sheet;
     styleSheet.insertRule(rule, styleSheet.cssRules.length);
-  },
-  clear: function fontLoaderClear() {
+  }
+  clear() {
+    this.nativeFontFaces.forEach(function (nativeFontFace) {
+      document.fonts.delete(nativeFontFace);
+    });
+    this.nativeFontFaces.length = 0;
     if (this.styleElement) {
       this.styleElement.remove();
       this.styleElement = null;
     }
   }
-};
-{
-  FontLoader.prototype.bind = function fontLoaderBind(fonts, callback) {
-    for (var i = 0, ii = fonts.length; i < ii; i++) {
-      var font = fonts[i];
-      if (font.attached) {
+  bind(fonts, callback) {
+    const rules = [];
+    const fontsToLoad = [];
+    const fontLoadPromises = [];
+    const getNativeFontPromise = function (nativeFontFace) {
+      return nativeFontFace.loaded.catch(function (reason) {
+        (0, _util.warn)(`Failed to load font "${nativeFontFace.family}": ${reason}`);
+      });
+    };
+    for (const font of fonts) {
+      if (font.attached || font.missingFile) {
         continue;
       }
       font.attached = true;
-      var rule = font.createFontFaceRule();
-      if (rule) {
-        this.insertRule(rule);
-      }
-    }
-    setTimeout(callback);
+      if (this.isFontLoadingAPISupported) {
+        const nativeFontFace = font.createNativeFontFace();
+        if (nativeFontFace) {
+          this.addNativeFontFace(nativeFontFace);
+          fontLoadPromises.push(getNativeFontPromise(nativeFontFace));
+        }
+      } else {
+        const rule = font.createFontFaceRule();
+        if (rule) {
+          this.insertRule(rule);
+          rules.push(rule);
+          fontsToLoad.push(font);
+        }
+      }
+    }
+    const request = this._queueLoadingCallback(callback);
+    if (this.isFontLoadingAPISupported) {
+      Promise.all(fontLoadPromises).then(request.complete);
+    } else if (rules.length > 0 && !this.isSyncFontLoadingSupported) {
+      this._prepareFontLoadEvent(rules, fontsToLoad, request);
+    } else {
+      request.complete();
+    }
+  }
+  _queueLoadingCallback(callback) {
+    function completeRequest() {
+      (0, _util.assert)(!request.done, 'completeRequest() cannot be called twice.');
+      request.done = true;
+      while (context.requests.length > 0 && context.requests[0].done) {
+        const otherRequest = context.requests.shift();
+        setTimeout(otherRequest.callback, 0);
+      }
+    }
+    const context = this.loadingContext;
+    const request = {
+      id: `pdfjs-font-loading-${context.nextRequestId++}`,
+      done: false,
+      complete: completeRequest,
+      callback
+    };
+    context.requests.push(request);
+    return request;
+  }
+  get isFontLoadingAPISupported() {
+    (0, _util.unreachable)('Abstract method `isFontLoadingAPISupported`.');
+  }
+  get isSyncFontLoadingSupported() {
+    (0, _util.unreachable)('Abstract method `isSyncFontLoadingSupported`.');
+  }
+  get _loadTestFont() {
+    (0, _util.unreachable)('Abstract method `_loadTestFont`.');
+  }
+  _prepareFontLoadEvent(rules, fontsToLoad, request) {
+    (0, _util.unreachable)('Abstract method `_prepareFontLoadEvent`.');
+  }
+}
+let FontLoader;
+{
+  exports.FontLoader = FontLoader = class MozcentralFontLoader extends BaseFontLoader {
+    get isFontLoadingAPISupported() {
+      return (0, _util.shadow)(this, 'isFontLoadingAPISupported', typeof document !== 'undefined' && !!document.fonts);
+    }
+    get isSyncFontLoadingSupported() {
+      return (0, _util.shadow)(this, 'isSyncFontLoadingSupported', true);
+    }
   };
 }
-;
-;
-var IsEvalSupportedCached = {
+const IsEvalSupportedCached = {
   get value() {
     return (0, _util.shadow)(this, 'value', (0, _util.isEvalSupported)());
   }
 };
-var FontFaceObject = function FontFaceObjectClosure() {
-  function FontFaceObject(translatedData, { isEvalSupported = true, disableFontFace = false, ignoreErrors = false, onUnsupportedFeature = null, fontRegistry = null }) {
+class FontFaceObject {
+  constructor(translatedData, { isEvalSupported = true, disableFontFace = false, ignoreErrors = false, onUnsupportedFeature = null, fontRegistry = null }) {
     this.compiledGlyphs = Object.create(null);
-    for (var i in translatedData) {
+    for (let i in translatedData) {
       this[i] = translatedData[i];
     }
     this.isEvalSupported = isEvalSupported !== false;
     this.disableFontFace = disableFontFace === true;
     this.ignoreErrors = ignoreErrors === true;
     this._onUnsupportedFeature = onUnsupportedFeature;
     this.fontRegistry = fontRegistry;
   }
-  FontFaceObject.prototype = {
-    createNativeFontFace: function FontFaceObject_createNativeFontFace() {
-      throw new Error('Not implemented: createNativeFontFace');
-    },
-    createFontFaceRule: function FontFaceObject_createFontFaceRule() {
-      if (!this.data || this.disableFontFace) {
-        return null;
-      }
-      var data = (0, _util.bytesToString)(new Uint8Array(this.data));
-      var fontName = this.loadedName;
-      var url = 'url(data:' + this.mimetype + ';base64,' + btoa(data) + ');';
-      var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}';
-      if (this.fontRegistry) {
-        this.fontRegistry.registerFont(this, url);
-      }
-      return rule;
-    },
-    getPathGenerator(objs, character) {
-      if (this.compiledGlyphs[character] !== undefined) {
-        return this.compiledGlyphs[character];
-      }
-      let cmds, current;
-      try {
-        cmds = objs.get(this.loadedName + '_path_' + character);
-      } catch (ex) {
-        if (!this.ignoreErrors) {
-          throw ex;
-        }
-        if (this._onUnsupportedFeature) {
-          this._onUnsupportedFeature({ featureId: _util.UNSUPPORTED_FEATURES.font });
-        }
-        (0, _util.warn)(`getPathGenerator - ignoring character: "${ex}".`);
-        return this.compiledGlyphs[character] = function (c, size) {};
-      }
-      if (this.isEvalSupported && IsEvalSupportedCached.value) {
-        let args,
-            js = '';
-        for (let i = 0, ii = cmds.length; i < ii; i++) {
-          current = cmds[i];
-          if (current.args !== undefined) {
-            args = current.args.join(',');
-          } else {
-            args = '';
-          }
-          js += 'c.' + current.cmd + '(' + args + ');\n';
-        }
-        return this.compiledGlyphs[character] = new Function('c', 'size', js);
-      }
-      return this.compiledGlyphs[character] = function (c, size) {
-        for (let i = 0, ii = cmds.length; i < ii; i++) {
-          current = cmds[i];
-          if (current.cmd === 'scale') {
-            current.args = [size, -size];
-          }
-          c[current.cmd].apply(c, current.args);
-        }
-      };
-    }
-  };
-  return FontFaceObject;
-}();
+  createNativeFontFace() {
+    if (!this.data || this.disableFontFace) {
+      return null;
+    }
+    const nativeFontFace = new FontFace(this.loadedName, this.data, {});
+    if (this.fontRegistry) {
+      this.fontRegistry.registerFont(this);
+    }
+    return nativeFontFace;
+  }
+  createFontFaceRule() {
+    if (!this.data || this.disableFontFace) {
+      return null;
+    }
+    const data = (0, _util.bytesToString)(new Uint8Array(this.data));
+    const url = `url(data:${this.mimetype};base64,${btoa(data)});`;
+    const rule = `@font-face {font-family:"${this.loadedName}";src:${url}}`;
+    if (this.fontRegistry) {
+      this.fontRegistry.registerFont(this, url);
+    }
+    return rule;
+  }
+  getPathGenerator(objs, character) {
+    if (this.compiledGlyphs[character] !== undefined) {
+      return this.compiledGlyphs[character];
+    }
+    let cmds, current;
+    try {
+      cmds = objs.get(this.loadedName + '_path_' + character);
+    } catch (ex) {
+      if (!this.ignoreErrors) {
+        throw ex;
+      }
+      if (this._onUnsupportedFeature) {
+        this._onUnsupportedFeature({ featureId: _util.UNSUPPORTED_FEATURES.font });
+      }
+      (0, _util.warn)(`getPathGenerator - ignoring character: "${ex}".`);
+      return this.compiledGlyphs[character] = function (c, size) {};
+    }
+    if (this.isEvalSupported && IsEvalSupportedCached.value) {
+      let args,
+          js = '';
+      for (let i = 0, ii = cmds.length; i < ii; i++) {
+        current = cmds[i];
+        if (current.args !== undefined) {
+          args = current.args.join(',');
+        } else {
+          args = '';
+        }
+        js += 'c.' + current.cmd + '(' + args + ');\n';
+      }
+      return this.compiledGlyphs[character] = new Function('c', 'size', js);
+    }
+    return this.compiledGlyphs[character] = function (c, size) {
+      for (let i = 0, ii = cmds.length; i < ii; i++) {
+        current = cmds[i];
+        if (current.cmd === 'scale') {
+          current.args = [size, -size];
+        }
+        c[current.cmd].apply(c, current.args);
+      }
+    };
+  }
+}
 exports.FontFaceObject = FontFaceObject;
 exports.FontLoader = FontLoader;
 
 /***/ }),
 /* 10 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
@@ -10184,16 +10265,18 @@ class AnnotationElementFactory {
       case _util.AnnotationType.LINE:
         return new LineAnnotationElement(parameters);
       case _util.AnnotationType.SQUARE:
         return new SquareAnnotationElement(parameters);
       case _util.AnnotationType.CIRCLE:
         return new CircleAnnotationElement(parameters);
       case _util.AnnotationType.POLYLINE:
         return new PolylineAnnotationElement(parameters);
+      case _util.AnnotationType.INK:
+        return new InkAnnotationElement(parameters);
       case _util.AnnotationType.POLYGON:
         return new PolygonAnnotationElement(parameters);
       case _util.AnnotationType.HIGHLIGHT:
         return new HighlightAnnotationElement(parameters);
       case _util.AnnotationType.UNDERLINE:
         return new UnderlineAnnotationElement(parameters);
       case _util.AnnotationType.SQUIGGLY:
         return new SquigglyAnnotationElement(parameters);
@@ -10500,17 +10583,17 @@ class ChoiceWidgetAnnotationElement exte
   }
 }
 class PopupAnnotationElement extends AnnotationElement {
   constructor(parameters) {
     let isRenderable = !!(parameters.data.title || parameters.data.contents);
     super(parameters, isRenderable);
   }
   render() {
-    const IGNORE_TYPES = ['Line', 'Square', 'Circle', 'PolyLine', 'Polygon'];
+    const IGNORE_TYPES = ['Line', 'Square', 'Circle', 'PolyLine', 'Polygon', 'Ink'];
     this.container.className = 'popupAnnotation';
     if (IGNORE_TYPES.includes(this.data.parentType)) {
       return this.container;
     }
     let selector = '[data-annotation-id="' + this.data.parentId + '"]';
     let parentElement = this.layer.querySelector(selector);
     if (!parentElement) {
       return this.container;
@@ -10716,16 +10799,52 @@ class PolylineAnnotationElement extends 
 }
 class PolygonAnnotationElement extends PolylineAnnotationElement {
   constructor(parameters) {
     super(parameters);
     this.containerClassName = 'polygonAnnotation';
     this.svgElementName = 'svg:polygon';
   }
 }
+class InkAnnotationElement extends AnnotationElement {
+  constructor(parameters) {
+    let isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    super(parameters, isRenderable, true);
+    this.containerClassName = 'inkAnnotation';
+    this.svgElementName = 'svg:polyline';
+  }
+  render() {
+    this.container.className = this.containerClassName;
+    let data = this.data;
+    let width = data.rect[2] - data.rect[0];
+    let height = data.rect[3] - data.rect[1];
+    let svg = this.svgFactory.create(width, height);
+    let inkLists = data.inkLists;
+    for (let i = 0, ii = inkLists.length; i < ii; i++) {
+      let inkList = inkLists[i];
+      let points = [];
+      for (let j = 0, jj = inkList.length; j < jj; j++) {
+        let x = inkList[j].x - data.rect[0];
+        let y = data.rect[3] - inkList[j].y;
+        points.push(x + ',' + y);
+      }
+      points = points.join(' ');
+      let borderWidth = data.borderStyle.width;
+      let polyline = this.svgFactory.createElement(this.svgElementName);
+      polyline.setAttribute('points', points);
+      polyline.setAttribute('stroke-width', borderWidth);
+      polyline.setAttribute('stroke', 'transparent');
+      polyline.setAttribute('fill', 'none');
+      this._createPopup(this.container, polyline, data);
+      svg.appendChild(polyline);
+    }
+    this.container.append(svg);
+    return this.container;
+  }
+}
 class HighlightAnnotationElement extends AnnotationElement {
   constructor(parameters) {
     let isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
     super(parameters, isRenderable, true);
   }
   render() {
     this.container.className = 'highlightAnnotation';
     if (!this.data.hasPopup) {
@@ -10784,24 +10903,27 @@ class StampAnnotationElement extends Ann
       this._createPopup(this.container, null, this.data);
     }
     return this.container;
   }
 }
 class FileAttachmentAnnotationElement extends AnnotationElement {
   constructor(parameters) {
     super(parameters, true);
-    let file = this.data.file;
-    this.filename = (0, _dom_utils.getFilenameFromUrl)(file.filename);
-    this.content = file.content;
-    this.linkService.onFileAttachmentAnnotation({
-      id: (0, _util.stringToPDFString)(file.filename),
-      filename: file.filename,
-      content: file.content
-    });
+    const { filename, content } = this.data.file;
+    this.filename = (0, _dom_utils.getFilenameFromUrl)(filename);
+    this.content = content;
+    if (this.linkService.eventBus) {
+      this.linkService.eventBus.dispatch('fileattachmentannotation', {
+        source: this,
+        id: (0, _util.stringToPDFString)(filename),
+        filename,
+        content
+      });
+    }
   }
   render() {
     this.container.className = 'fileAttachmentAnnotation';
     let trigger = document.createElement('div');
     trigger.style.height = this.container.style.height;
     trigger.style.width = this.container.style.width;
     trigger.addEventListener('dblclick', this._download.bind(this));
     if (!this.data.hasPopup && (this.data.title || this.data.contents)) {
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -118,18 +118,18 @@ return /******/ (function(modules) { // 
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '2.0.892';
-var pdfjsBuild = 'ec10cae5';
+var pdfjsVersion = '2.0.911';
+var pdfjsBuild = 'ff2df9c5';
 var pdfjsCoreWorker = __w_pdfjs_require__(1);
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 1 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
@@ -322,17 +322,17 @@ var WorkerMessageHandler = {
     });
   },
   createDocumentHandler(docParams, port) {
     var pdfManager;
     var terminated = false;
     var cancelXHRs = null;
     var WorkerTasks = [];
     let apiVersion = docParams.apiVersion;
-    let workerVersion = '2.0.892';
+    let workerVersion = '2.0.911';
     if (apiVersion !== workerVersion) {
       throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
     }
     var docId = docParams.docId;
     var docBaseUrl = docParams.docBaseUrl;
     var workerHandlerName = docParams.docId + '_worker';
     var handler = new _message_handler.MessageHandler(workerHandlerName, docId, port);
     handler.postMessageTransfers = docParams.postMessageTransfers;
@@ -17700,16 +17700,18 @@ class AnnotationFactory {
       case 'Square':
         return new SquareAnnotation(parameters);
       case 'Circle':
         return new CircleAnnotation(parameters);
       case 'PolyLine':
         return new PolylineAnnotation(parameters);
       case 'Polygon':
         return new PolygonAnnotation(parameters);
+      case 'Ink':
+        return new InkAnnotation(parameters);
       case 'Highlight':
         return new HighlightAnnotation(parameters);
       case 'Underline':
         return new UnderlineAnnotation(parameters);
       case 'Squiggly':
         return new SquigglyAnnotation(parameters);
       case 'StrikeOut':
         return new StrikeOutAnnotation(parameters);
@@ -18290,16 +18292,36 @@ class PolylineAnnotation extends Annotat
   }
 }
 class PolygonAnnotation extends PolylineAnnotation {
   constructor(parameters) {
     super(parameters);
     this.data.annotationType = _util.AnnotationType.POLYGON;
   }
 }
+class InkAnnotation extends Annotation {
+  constructor(parameters) {
+    super(parameters);
+    this.data.annotationType = _util.AnnotationType.INK;
+    let dict = parameters.dict;
+    const xref = parameters.xref;
+    let originalInkLists = dict.getArray('InkList');
+    this.data.inkLists = [];
+    for (let i = 0, ii = originalInkLists.length; i < ii; ++i) {
+      this.data.inkLists.push([]);
+      for (let j = 0, jj = originalInkLists[i].length; j < jj; j += 2) {
+        this.data.inkLists[i].push({
+          x: xref.fetchIfRef(originalInkLists[i][j]),
+          y: xref.fetchIfRef(originalInkLists[i][j + 1])
+        });
+      }
+    }
+    this._preparePopup(dict);
+  }
+}
 class HighlightAnnotation extends Annotation {
   constructor(parameters) {
     super(parameters);
     this.data.annotationType = _util.AnnotationType.HIGHLIGHT;
     this._preparePopup(parameters.dict);
   }
 }
 class UnderlineAnnotation extends Annotation {
@@ -22676,17 +22698,16 @@ var Font = function FontClosure() {
     this.data = data;
     this.fontType = getFontType(type, subtype);
     this.fontMatrix = properties.fontMatrix;
     this.widths = properties.widths;
     this.defaultWidth = properties.defaultWidth;
     this.toUnicode = properties.toUnicode;
     this.encoding = properties.baseEncoding;
     this.seacMap = properties.seacMap;
-    this.loading = true;
   }
   Font.getFontID = function () {
     var ID = 1;
     return function Font_getFontID() {
       return String(ID++);
     };
   }();
   function int16(b0, b1) {
@@ -23137,17 +23158,16 @@ var Font = function FontClosure() {
             if (unicode !== -1) {
               unicodeCharCode = unicode;
             }
           }
           this.toFontChar[charCode] = unicodeCharCode;
         });
       }
       this.loadedName = fontName.split('-')[0];
-      this.loading = false;
       this.fontType = getFontType(type, subtype);
     },
     checkAndRepair: function Font_checkAndRepair(name, font, properties) {
       const VALID_TABLES = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'post', 'loca', 'glyf', 'fpgm', 'prep', 'cvt ', 'CFF '];
       function readTables(file, numTables) {
         let tables = Object.create(null);
         tables['OS/2'] = null;
         tables['cmap'] = null;
@@ -24429,17 +24449,17 @@ var Font = function FontClosure() {
     }
   };
   return Font;
 }();
 var ErrorFont = function ErrorFontClosure() {
   function ErrorFont(error) {
     this.error = error;
     this.loadedName = 'g_font_error';
-    this.loading = false;
+    this.missingFile = true;
   }
   ErrorFont.prototype = {
     charsToGlyphs: function ErrorFont_charsToGlyphs() {
       return [];
     },
     exportData: function ErrorFont_exportData() {
       return { error: this.error };
     }
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -249,16 +249,17 @@
 .annotationLayer .underlineAnnotation,
 .annotationLayer .squigglyAnnotation,
 .annotationLayer .strikeoutAnnotation,
 .annotationLayer .lineAnnotation svg line,
 .annotationLayer .squareAnnotation svg rect,
 .annotationLayer .circleAnnotation svg ellipse,
 .annotationLayer .polylineAnnotation svg polyline,
 .annotationLayer .polygonAnnotation svg polygon,
+.annotationLayer .inkAnnotation svg polyline,
 .annotationLayer .stampAnnotation,
 .annotationLayer .fileAttachmentAnnotation {
   cursor: pointer;
 }
 
 .pdfViewer .canvasWrapper {
   overflow: hidden;
 }
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -473,17 +473,17 @@ let PDFViewerApplication = {
     this.l10n = this.externalServices.createL10n({ locale: _app_options.AppOptions.get('locale') });
     const dir = await this.l10n.getDirection();
     document.getElementsByTagName('html')[0].dir = dir;
   },
   async _initializeViewerComponents() {
     const appConfig = this.appConfig;
     this.overlayManager = new _overlay_manager.OverlayManager();
     const dispatchToDOM = _app_options.AppOptions.get('eventBusDispatchToDOM');
-    let eventBus = appConfig.eventBus || (0, _dom_events.getGlobalEventBus)(dispatchToDOM);
+    const eventBus = appConfig.eventBus || (0, _dom_events.getGlobalEventBus)(dispatchToDOM);
     this.eventBus = eventBus;
     let pdfRenderingQueue = new _pdf_rendering_queue.PDFRenderingQueue();
     pdfRenderingQueue.onIdle = this.cleanup.bind(this);
     this.pdfRenderingQueue = pdfRenderingQueue;
     let pdfLinkService = new _pdf_link_service.PDFLinkService({
       eventBus,
       externalLinkTarget: _app_options.AppOptions.get('externalLinkTarget'),
       externalLinkRel: _app_options.AppOptions.get('externalLinkRel')
@@ -526,19 +526,17 @@ let PDFViewerApplication = {
       l10n: this.l10n
     });
     pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
     this.pdfHistory = new _pdf_history.PDFHistory({
       linkService: pdfLinkService,
       eventBus
     });
     pdfLinkService.setHistory(this.pdfHistory);
-    let findBarConfig = Object.create(appConfig.findBar);
-    findBarConfig.eventBus = eventBus;
-    this.findBar = new _pdf_find_bar.PDFFindBar(findBarConfig, this.l10n);
+    this.findBar = new _pdf_find_bar.PDFFindBar(appConfig.findBar, eventBus, this.l10n);
     this.pdfDocumentProperties = new _pdf_document_properties.PDFDocumentProperties(appConfig.documentProperties, this.overlayManager, eventBus, this.l10n);
     this.pdfCursorTools = new _pdf_cursor_tools.PDFCursorTools({
       container,
       eventBus,
       cursorToolOnLoad: _app_options.AppOptions.get('cursorToolOnLoad')
     });
     this.toolbar = new _toolbar.Toolbar(appConfig.toolbar, container, eventBus, this.l10n);
     this.secondaryToolbar = new _secondary_toolbar.SecondaryToolbar(appConfig.secondaryToolbar, container, eventBus);
@@ -560,19 +558,17 @@ let PDFViewerApplication = {
     this.pdfAttachmentViewer = new _pdf_attachment_viewer.PDFAttachmentViewer({
       container: appConfig.sidebar.attachmentsView,
       eventBus,
       downloadManager
     });
     let sidebarConfig = Object.create(appConfig.sidebar);
     sidebarConfig.pdfViewer = this.pdfViewer;
     sidebarConfig.pdfThumbnailViewer = this.pdfThumbnailViewer;
-    sidebarConfig.pdfOutlineViewer = this.pdfOutlineViewer;
-    sidebarConfig.eventBus = eventBus;
-    this.pdfSidebar = new _pdf_sidebar.PDFSidebar(sidebarConfig, this.l10n);
+    this.pdfSidebar = new _pdf_sidebar.PDFSidebar(sidebarConfig, eventBus, this.l10n);
     this.pdfSidebar.onToggled = this.forceRendering.bind(this);
     this.pdfSidebarResizer = new _pdf_sidebar_resizer.PDFSidebarResizer(appConfig.sidebarResizer, eventBus, this.l10n);
   },
   run(config) {
     this.initialize(config).then(webViewerInitialized);
   },
   zoomIn(ticks) {
     let newScale = this.pdfViewer.currentScale;
@@ -687,17 +683,16 @@ let PDFViewerApplication = {
     errorWrapper.setAttribute('hidden', 'true');
     if (!this.pdfLoadingTask) {
       return;
     }
     let promise = this.pdfLoadingTask.destroy();
     this.pdfLoadingTask = null;
     if (this.pdfDocument) {
       this.pdfDocument = null;
-      this.findController.setDocument(null);
       this.pdfThumbnailViewer.setDocument(null);
       this.pdfViewer.setDocument(null);
       this.pdfLinkService.setDocument(null);
       this.pdfDocumentProperties.setDocument(null);
     }
     this.store = null;
     this.isInitialViewSet = false;
     this.downloadComplete = false;
@@ -868,17 +863,16 @@ let PDFViewerApplication = {
       });
     });
     let pageModePromise = pdfDocument.getPageMode().catch(function () {});
     this.toolbar.setPagesCount(pdfDocument.numPages, false);
     this.secondaryToolbar.setPagesCount(pdfDocument.numPages);
     const store = this.store = new _view_history.ViewHistory(pdfDocument.fingerprint);
     let baseDocumentUrl;
     baseDocumentUrl = this.baseUrl;
-    this.findController.setDocument(pdfDocument);
     this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl);
     this.pdfDocumentProperties.setDocument(pdfDocument, this.url);
     let pdfViewer = this.pdfViewer;
     pdfViewer.setDocument(pdfDocument);
     let firstPagePromise = pdfViewer.firstPagePromise;
     let pagesPromise = pdfViewer.pagesPromise;
     let onePageRendered = pdfViewer.onePageRendered;
     let pdfThumbnailViewer = this.pdfThumbnailViewer;
@@ -2833,35 +2827,34 @@ var _pdf_rendering_queue = __webpack_req
 const UI_NOTIFICATION_CLASS = 'pdfSidebarNotification';
 const SidebarView = {
   NONE: 0,
   THUMBS: 1,
   OUTLINE: 2,
   ATTACHMENTS: 3
 };
 class PDFSidebar {
-  constructor(options, l10n = _ui_utils.NullL10n) {
+  constructor(options, eventBus, l10n = _ui_utils.NullL10n) {
     this.isOpen = false;
     this.active = SidebarView.THUMBS;
     this.isInitialViewSet = false;
     this.onToggled = null;
     this.pdfViewer = options.pdfViewer;
     this.pdfThumbnailViewer = options.pdfThumbnailViewer;
-    this.pdfOutlineViewer = options.pdfOutlineViewer;
     this.outerContainer = options.outerContainer;
     this.viewerContainer = options.viewerContainer;
-    this.eventBus = options.eventBus;
     this.toggleButton = options.toggleButton;
     this.thumbnailButton = options.thumbnailButton;
     this.outlineButton = options.outlineButton;
     this.attachmentsButton = options.attachmentsButton;
     this.thumbnailView = options.thumbnailView;
     this.outlineView = options.outlineView;
     this.attachmentsView = options.attachmentsView;
     this.disableNotification = options.disableNotification || false;
+    this.eventBus = eventBus;
     this.l10n = l10n;
     this._addEventListeners();
   }
   reset() {
     this.isInitialViewSet = false;
     this._hideUINotification(null);
     this.switchView(SidebarView.THUMBS);
     this.outlineButton.disabled = false;
@@ -3070,17 +3063,17 @@ class PDFSidebar {
     });
     this.thumbnailButton.addEventListener('click', () => {
       this.switchView(SidebarView.THUMBS);
     });
     this.outlineButton.addEventListener('click', () => {
       this.switchView(SidebarView.OUTLINE);
     });
     this.outlineButton.addEventListener('dblclick', () => {
-      this.pdfOutlineViewer.toggleOutlineTree();
+      this.eventBus.dispatch('toggleoutlinetree', { source: this });
     });
     this.attachmentsButton.addEventListener('click', () => {
       this.switchView(SidebarView.ATTACHMENTS);
     });
     this.eventBus.on('outlineloaded', evt => {
       let outlineCount = evt.outlineCount;
       this.outlineButton.disabled = !outlineCount;
       if (outlineCount) {
@@ -3992,35 +3985,35 @@ exports.PDFDocumentProperties = PDFDocum
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 exports.PDFFindBar = undefined;
 
+var _ui_utils = __webpack_require__(2);
+
 var _pdf_find_controller = __webpack_require__(16);
 
-var _ui_utils = __webpack_require__(2);
-
 const MATCHES_COUNT_LIMIT = 1000;
 class PDFFindBar {
-  constructor(options, l10n = _ui_utils.NullL10n) {
+  constructor(options, eventBus = (0, _ui_utils.getGlobalEventBus)(), l10n = _ui_utils.NullL10n) {
     this.opened = false;
     this.bar = options.bar || null;
     this.toggleButton = options.toggleButton || null;
     this.findField = options.findField || null;
     this.highlightAll = options.highlightAllCheckbox || null;
     this.caseSensitive = options.caseSensitiveCheckbox || null;
     this.entireWord = options.entireWordCheckbox || null;
     this.findMsg = options.findMsg || null;
     this.findResultsCount = options.findResultsCount || null;
     this.findPreviousButton = options.findPreviousButton || null;
     this.findNextButton = options.findNextButton || null;
-    this.eventBus = options.eventBus;
+    this.eventBus = eventBus;
     this.l10n = l10n;
     this.toggleButton.addEventListener('click', () => {
       this.toggle();
     });
     this.findField.addEventListener('input', () => {
       this.dispatchEvent('');
     });
     this.bar.addEventListener('keydown', e => {
@@ -4201,23 +4194,17 @@ const CHARACTERS_TO_NORMALIZE = {
   '\u00BD': '1/2',
   '\u00BE': '3/4'
 };
 class PDFFindController {
   constructor({ linkService, eventBus = (0, _dom_events.getGlobalEventBus)() }) {
     this._linkService = linkService;
     this._eventBus = eventBus;
     this._reset();
-    eventBus.on('findbarclose', () => {
-      this._highlightMatches = false;
-      eventBus.dispatch('updatetextlayermatches', {
-        source: this,
-        pageIndex: -1
-      });
-    });
+    eventBus.on('findbarclose', this._onFindBarClose.bind(this));
     const replace = Object.keys(CHARACTERS_TO_NORMALIZE).join('');
     this._normalizationRegex = new RegExp(`[${replace}]`, 'g');
   }
   get highlightMatches() {
     return this._highlightMatches;
   }
   get pageMatches() {
     return this._pageMatches;
@@ -4234,31 +4221,39 @@ class PDFFindController {
   setDocument(pdfDocument) {
     if (this._pdfDocument) {
       this._reset();
     }
     if (!pdfDocument) {
       return;
     }
     this._pdfDocument = pdfDocument;
+    this._firstPageCapability.resolve();
   }
   executeCommand(cmd, state) {
-    if (!this._pdfDocument) {
-      return;
-    }
+    const pdfDocument = this._pdfDocument;
     if (this._state === null || cmd !== 'findagain') {
       this._dirtyMatch = true;
     }
     this._state = state;
     this._updateUIState(FindState.PENDING);
-    this._firstPagePromise.then(() => {
+    this._firstPageCapability.promise.then(() => {
+      if (!this._pdfDocument || pdfDocument && this._pdfDocument !== pdfDocument) {
+        return;
+      }
       this._extractText();
-      clearTimeout(this._findTimeout);
+      if (this._findTimeout) {
+        clearTimeout(this._findTimeout);
+        this._findTimeout = null;
+      }
       if (cmd === 'find') {
-        this._findTimeout = setTimeout(this._nextMatch.bind(this), FIND_TIMEOUT);
+        this._findTimeout = setTimeout(() => {
+          this._nextMatch();
+          this._findTimeout = null;
+        }, FIND_TIMEOUT);
       } else {
         this._nextMatch();
       }
     });
   }
   _reset() {
     this._highlightMatches = false;
     this._pdfDocument = null;
@@ -4275,24 +4270,19 @@ class PDFFindController {
     };
     this._extractTextPromises = [];
     this._pageContents = [];
     this._matchesCountTotal = 0;
     this._pagesToSearch = null;
     this._pendingFindMatches = Object.create(null);
     this._resumePageIdx = null;
     this._dirtyMatch = false;
+    clearTimeout(this._findTimeout);
     this._findTimeout = null;
-    this._firstPagePromise = new Promise(resolve => {
-      const eventBus = this._eventBus;
-      eventBus.on('pagesinit', function onPagesInit() {
-        eventBus.off('pagesinit', onPagesInit);
-        resolve();
-      });
-    });
+    this._firstPageCapability = (0, _pdfjsLib.createPromiseCapability)();
   }
   _normalize(text) {
     return text.replace(this._normalizationRegex, function (ch) {
       return CHARACTERS_TO_NORMALIZE[ch];
     });
   }
   _prepareMatches(matchesWithLength, matches, matchesLength) {
     function isSubTerm(matchesWithLength, currentIndex) {
@@ -4556,16 +4546,34 @@ class PDFFindController {
         this._updatePage(previousPage);
       }
     }
     this._updateUIState(state, this._state.findPrevious);
     if (this._selected.pageIdx !== -1) {
       this._updatePage(this._selected.pageIdx);
     }
   }
+  _onFindBarClose(evt) {
+    const pdfDocument = this._pdfDocument;
+    this._firstPageCapability.promise.then(() => {
+      if (!this._pdfDocument || pdfDocument && this._pdfDocument !== pdfDocument) {
+        return;
+      }
+      if (this._findTimeout) {
+        clearTimeout(this._findTimeout);
+        this._findTimeout = null;
+        this._updateUIState(FindState.FOUND);
+      }
+      this._highlightMatches = false;
+      this._eventBus.dispatch('updatetextlayermatches', {
+        source: this,
+        pageIndex: -1
+      });
+    });
+  }
   _requestMatchesCount() {
     const { pageIdx, matchIdx } = this._selected;
     let current = 0,
         total = this._matchesCountTotal;
     if (matchIdx !== -1) {
       for (let i = 0; i < pageIdx; i++) {
         current += this._pageMatches[i] && this._pageMatches[i].length || 0;
       }
@@ -5284,24 +5292,16 @@ class PDFLinkService {
       default:
         break;
     }
     this.eventBus.dispatch('namedaction', {
       source: this,
       action
     });
   }
-  onFileAttachmentAnnotation({ id, filename, content }) {
-    this.eventBus.dispatch('fileattachmentannotation', {
-      source: this,
-      id,
-      filename,
-      content
-    });
-  }
   cachePageRef(pageNum, pageRef) {
     if (!pageRef) {
       return;
     }
     let refStr = pageRef.num + ' ' + pageRef.gen + ' R';
     this._pagesRefCache[refStr] = pageNum;
   }
   _cachedPageNumber(pageRef) {
@@ -5377,17 +5377,16 @@ class SimpleLinkService {
   getDestinationHash(dest) {
     return '#';
   }
   getAnchorUrl(hash) {
     return '#';
   }
   setHash(hash) {}
   executeNamedAction(action) {}
-  onFileAttachmentAnnotation({ id, filename, content }) {}
   cachePageRef(pageNum, pageRef) {}
 }
 exports.PDFLinkService = PDFLinkService;
 exports.SimpleLinkService = SimpleLinkService;
 
 /***/ }),
 /* 20 */
 /***/ (function(module, exports, __webpack_require__) {
@@ -5404,16 +5403,17 @@ var _pdfjsLib = __webpack_require__(3);
 
 const DEFAULT_TITLE = '\u2013';
 class PDFOutlineViewer {
   constructor({ container, linkService, eventBus }) {
     this.container = container;
     this.linkService = linkService;
     this.eventBus = eventBus;
     this.reset();
+    eventBus.on('toggleoutlinetree', this.toggleOutlineTree.bind(this));
   }
   reset() {
     this.outline = null;
     this.lastToggleIsShow = true;
     this.container.textContent = '';
     this.container.classList.remove('outlineWithDeepNesting');
   }
   _dispatchEvent(outlineCount) {
@@ -6779,16 +6779,19 @@ class BaseViewer {
   }
   get _setDocumentViewerElement() {
     throw new Error('Not implemented: _setDocumentViewerElement');
   }
   setDocument(pdfDocument) {
     if (this.pdfDocument) {
       this._cancelRendering();
       this._resetView();
+      if (this.findController) {
+        this.findController.setDocument(null);
+      }
     }
     this.pdfDocument = pdfDocument;
     if (!pdfDocument) {
       return;
     }
     let pagesCount = pdfDocument.numPages;
     let pagesCapability = (0, _pdfjsLib.createPromiseCapability)();
     this.pagesPromise = pagesCapability.promise;
@@ -6867,16 +6870,19 @@ class BaseViewer {
             console.error(`Unable to get page ${pageNum} to initialize viewer`, reason);
             if (--getPagesLeft === 0) {
               pagesCapability.resolve();
             }
           });
         }
       });
       this.eventBus.dispatch('pagesinit', { source: this });
+      if (this.findController) {
+        this.findController.setDocument(pdfDocument);
+      }
       if (this.defaultRenderingQueue) {
         this.update();
       }
     }).catch(reason => {
       console.error('Unable to initialize viewer', reason);
     });
   }
   setPageLabels(labels) {
--- a/browser/extensions/pdfjs/moz.yaml
+++ b/browser/extensions/pdfjs/moz.yaml
@@ -15,15 +15,15 @@ origin:
   description: Portable Document Format (PDF) viewer that is built with HTML5
 
   # Full URL for the package's homepage/etc
   # Usually different from repository url
   url: https://github.com/mozilla/pdf.js
 
   # Human-readable identifier for this version/release
   # Generally "version NNN", "tag SSS", "bookmark SSS"
-  release: version 2.0.892
+  release: version 2.0.911
 
   # The package's license, where possible using the mnemonic from
   # https://spdx.org/licenses/
   # Multiple licenses can be specified (as a YAML list)
   # A "LICENSE" file must exist containing the full license text
   license: Apache-2.0
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -21,16 +21,18 @@ loader.lazyGetter(this, "MemoryPanel", (
 loader.lazyGetter(this, "PerformancePanel", () => require("devtools/client/performance/panel").PerformancePanel);
 loader.lazyGetter(this, "NewPerformancePanel", () => require("devtools/client/performance-new/panel").PerformancePanel);
 loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/client/netmonitor/panel").NetMonitorPanel);
 loader.lazyGetter(this, "StoragePanel", () => require("devtools/client/storage/panel").StoragePanel);
 loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/client/scratchpad/panel").ScratchpadPanel);
 loader.lazyGetter(this, "DomPanel", () => require("devtools/client/dom/panel").DomPanel);
 loader.lazyGetter(this, "AccessibilityPanel", () => require("devtools/client/accessibility/panel").AccessibilityPanel);
 loader.lazyGetter(this, "ApplicationPanel", () => require("devtools/client/application/panel").ApplicationPanel);
+loader.lazyGetter(this, "reloadAndRecordTab", () => require("devtools/client/webreplay/menu.js").reloadAndRecordTab);
+loader.lazyGetter(this, "reloadAndStopRecordingTab", () => require("devtools/client/webreplay/menu.js").reloadAndStopRecordingTab);
 
 // Other dependencies
 loader.lazyRequireGetter(this, "AccessibilityStartup", "devtools/client/accessibility/accessibility-startup", true);
 loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsive.html/manager", true);
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 loader.lazyRequireGetter(this, "getScreenshotFront", "resource://devtools/shared/fronts/screenshot", true);
 
 const {MultiLocalizationHelper} = require("devtools/shared/l10n");
@@ -529,16 +531,36 @@ exports.ToolboxButtons = [
   },
   { id: "command-button-scratchpad",
     description: l10n("toolbox.buttons.scratchpad"),
     isTargetSupported: target => target.isLocalTab,
     onClick(event, toolbox) {
       ScratchpadManager.openScratchpad();
     }
   },
+  {
+    id: "command-button-replay",
+    description: l10n("toolbox.buttons.replay"),
+    isTargetSupported: target =>
+      Services.prefs.getBoolPref("devtools.recordreplay.mvp.enabled")
+      && !target.canRewind
+      && target.isLocalTab,
+    onClick: () => reloadAndRecordTab(),
+    isChecked: () => false
+  },
+  {
+    id: "command-button-stop-replay",
+    description: l10n("toolbox.buttons.stopReplay"),
+    isTargetSupported: target =>
+      Services.prefs.getBoolPref("devtools.recordreplay.mvp.enabled")
+      && target.canRewind
+      && target.isLocalTab,
+    onClick: () => reloadAndStopRecordingTab(),
+    isChecked: () => true
+  },
   { id: "command-button-responsive",
     description: l10n("toolbox.buttons.responsive",
                       osString == "Darwin" ? "Cmd+Opt+M" : "Ctrl+Shift+M"),
     isTargetSupported: target => target.isLocalTab,
     onClick(event, toolbox) {
       const tab = toolbox.target.tab;
       const browserWindow = tab.ownerDocument.defaultView;
       ResponsiveUIManager.toggle(browserWindow, tab, { trigger: "toolbox" });
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -457,16 +457,20 @@ TabTarget.prototype = {
   get isLocalTab() {
     return !!this._tab;
   },
 
   get isMultiProcess() {
     return !this.window;
   },
 
+  get canRewind() {
+    return this.activeTab.traits.canRewind;
+  },
+
   getExtensionPathName(url) {
     // Return the url if the target is not a webextension.
     if (!this.isWebExtension) {
       throw new Error("Target is not a WebExtension");
     }
 
     try {
       const parsedURL = new URL(url);
@@ -858,16 +862,20 @@ WorkerTarget.prototype = {
   get activeConsole() {
     return this.client._clients.get(this.form.consoleActor);
   },
 
   get client() {
     return this._workerClient.client;
   },
 
+  get canRewind() {
+    return false;
+  },
+
   destroy: function() {
     this._workerClient.detach();
   },
 
   hasActor: function(name) {
     // console is the only one actor implemented by WorkerTargetActor
     if (name == "console") {
       return true;
--- a/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js
+++ b/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js
@@ -67,52 +67,54 @@ function testPreferenceAndUIStateIsConsi
   for (const tool of toolbox.toolbarButtons) {
     const isVisible = getBoolPref(tool.visibilityswitch);
 
     const button = toolboxButtonNodes.find(toolboxButton => toolboxButton.id === tool.id);
     is(!!button, isVisible,
       "Button visibility matches pref for " + tool.id);
 
     const check = checkNodes.filter(node => node.id === tool.id)[0];
-    is(check.checked, isVisible,
-      "Checkbox should be selected based on current pref for " + tool.id);
+    if (check) {
+      is(check.checked, isVisible,
+        "Checkbox should be selected based on current pref for " + tool.id);
+    }
   }
 }
 
 function testToggleToolboxButtons() {
   const checkNodes = [...panelWin.document.querySelectorAll(
     "#enabled-toolbox-buttons-box input[type=checkbox]")];
 
   const visibleToolbarButtons = toolbox.toolbarButtons.filter(tool => tool.isVisible);
 
   const toolbarButtonNodes = [...doc.querySelectorAll(".command-button")];
 
-  is(checkNodes.length, toolbox.toolbarButtons.length,
+  // NOTE: the web-replay buttons are not checkboxes
+  is(checkNodes.length + 2, toolbox.toolbarButtons.length,
     "All of the buttons are toggleable.");
   is(visibleToolbarButtons.length, toolbarButtonNodes.length,
     "All of the DOM buttons are toggleable.");
 
   for (const tool of toolbox.toolbarButtons) {
     const id = tool.id;
     const matchedCheckboxes = checkNodes.filter(node => node.id === id);
     const matchedButtons = toolbarButtonNodes.filter(button => button.id === id);
-    is(matchedCheckboxes.length, 1,
-      "There should be a single toggle checkbox for: " + id);
     if (tool.isVisible) {
+      is(matchedCheckboxes.length, 1,
+        "There should be a single toggle checkbox for: " + id);
+      is(matchedCheckboxes[0].nextSibling.textContent, tool.description,
+        "The label for checkbox matches the tool definition.");
       is(matchedButtons.length, 1,
         "There should be a DOM button for the visible: " + id);
       is(matchedButtons[0].getAttribute("title"), tool.description,
         "The tooltip for button matches the tool definition.");
     } else {
       is(matchedButtons.length, 0,
         "There should not be a DOM button for the invisible: " + id);
     }
-
-    is(matchedCheckboxes[0].nextSibling.textContent, tool.description,
-      "The label for checkbox matches the tool definition.");
   }
 
   // Store modified pref names so that they can be cleared on error.
   for (const tool of toolbox.toolbarButtons) {
     const pref = tool.visibilityswitch;
     modifiedPrefs.push(pref);
   }
 
@@ -132,17 +134,21 @@ function testToggleToolboxButtons() {
       "Clicking on the node should have toggled visibility preference for " +
       tool.visibilityswitch);
   }
 
   return promise.resolve();
 }
 
 function getBoolPref(key) {
-  return Services.prefs.getBoolPref(key);
+  try {
+    return Services.prefs.getBoolPref(key);
+  } catch (e) {
+    return false;
+  }
 }
 
 function cleanup() {
   toolbox.destroy().then(function() {
     gBrowser.removeCurrentTab();
     for (const pref of modifiedPrefs) {
       Services.prefs.clearUserPref(pref);
     }
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -160,25 +160,23 @@ function Inspector(toolbox) {
 Inspector.prototype = {
   /**
    * open is effectively an asynchronous constructor
    */
   async init() {
     // Localize all the nodes containing a data-localization attribute.
     localizeMarkup(this.panelDoc);
 
-    this._cssProperties = await initCssProperties(this.toolbox);
-    await this._getPageStyle();
+    await Promise.all([
+      this._getCssProperties(),
+      this._getPageStyle(),
+      this._getDefaultSelection()
+    ]);
 
-    // This may throw if the document is still loading and we are
-    // refering to a dead about:blank document
-    const defaultSelection = await this._getDefaultNodeForSelection()
-      .catch(this._handleRejectionIfNotDestroyed);
-
-    return this._deferredOpen(defaultSelection);
+    return this._deferredOpen();
   },
 
   get toolbox() {
     return this._toolbox;
   },
 
   get inspector() {
     return this.toolbox.inspector;
@@ -259,78 +257,89 @@ Inspector.prototype = {
    * while still initializing (and making protocol requests).
    */
   _handleRejectionIfNotDestroyed: function(e) {
     if (!this._panelDestroyer) {
       console.error(e);
     }
   },
 
-  _deferredOpen: async function(defaultSelection) {
+  _deferredOpen: async function() {
+    this._initMarkup();
+    this.isReady = false;
+
+    // Set the node front so that the markup and sidebar panels will have the selected
+    // nodeFront ready when they're initialized.
+    if (this._defaultNode) {
+      this.selection.setNodeFront(this._defaultNode, { reason: "inspector-open" });
+    }
+
+    // Setup the splitter before the sidebar is displayed so, we don't miss any events.
+    this.setupSplitter();
+
+    // We can display right panel with: tab bar, markup view and breadbrumb. Right after
+    // the splitter set the right and left panel sizes, in order to avoid resizing it
+    // during load of the inspector.
+    this.panelDoc.getElementById("inspector-main-content").style.visibility = "visible";
+
+    // Setup the sidebar panels.
+    this.setupSidebar();
+
+    await this.once("markuploaded");
+    this.isReady = true;
+
+    // All the components are initialized. Take care of the remaining initialization
+    // and setup.
     this.breadcrumbs = new HTMLBreadcrumbs(this);
+    this.setupExtensionSidebars();
+    this.setupSearchBox();
+    await this.setupToolbar();
+
+    this.onNewSelection();
 
     this.walker.on("new-root", this.onNewRoot);
     this.toolbox.on("host-changed", this.onHostChanged);
     this.selection.on("new-node-front", this.onNewSelection);
     this.selection.on("detached-front", this.onDetached);
 
     if (this.target.isLocalTab) {
       this.target.on("thread-paused", this._updateDebuggerPausedWarning);
       this.target.on("thread-resumed", this._updateDebuggerPausedWarning);
       this.toolbox.on("select", this._updateDebuggerPausedWarning);
       this._updateDebuggerPausedWarning();
     }
 
-    this._initMarkup();
-    this.isReady = false;
-
-    this.setupSearchBox();
-
-    // Setup the splitter before the sidebar is displayed so,
-    // we don't miss any events.
-    this.setupSplitter();
-
-    // We can display right panel with: tab bar, markup view and breadbrumb. Right after
-    // the splitter set the right and left panel sizes, in order to avoid resizing it
-    // during load of the inspector.
-    this.panelDoc.getElementById("inspector-main-content").style.visibility = "visible";
-
-    this.setupSidebar();
-    this.setupExtensionSidebars();
-
-    await this.once("markuploaded");
-    this.isReady = true;
-
-    // All the components are initialized. Let's select a node.
-    if (defaultSelection) {
-      const onAllPanelsUpdated = this.once("inspector-updated");
-      this.selection.setNodeFront(defaultSelection, { reason: "inspector-open" });
-      await onAllPanelsUpdated;
-      await this.markup.expandNode(this.selection.nodeFront);
-    }
-
-    // Setup the toolbar only now because it may depend on the document.
-    await this.setupToolbar();
-
     // Log the 3 pane inspector setting on inspector open. The question we want to answer
     // is:
     // "What proportion of users use the 3 pane vs 2 pane inspector on inspector open?"
     this.telemetry.keyedScalarAdd(THREE_PANE_ENABLED_SCALAR, this.is3PaneModeEnabled, 1);
 
     this.emit("ready");
     return this;
   },
 
   _onBeforeNavigate: function() {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
     this._destroyMarkup();
     this._pendingSelection = null;
   },
 
+  _getCssProperties: function() {
+    return initCssProperties(this.toolbox).then(cssProperties => {
+      this._cssProperties = cssProperties;
+    }, this._handleRejectionIfNotDestroyed);
+  },
+
+  _getDefaultSelection: function() {
+    // This may throw if the document is still loading and we are
+    // refering to a dead about:blank document
+    return this._getDefaultNodeForSelection().catch(this._handleRejectionIfNotDestroyed);
+  },
+
   _getPageStyle: function() {
     return this.inspector.getPageStyle().then(pageStyle => {
       this.pageStyle = pageStyle;
     }, this._handleRejectionIfNotDestroyed);
   },
 
   /**
    * Return a promise that will resolve to the default node for selection.
@@ -1236,16 +1245,28 @@ Inspector.prototype = {
     if (this._selectionCssSelector &&
         this._selectionCssSelector.url === this._target.url) {
       return this._selectionCssSelector.selector;
     }
     return null;
   },
 
   /**
+   * On any new selection made by the user, store the unique css selector
+   * of the selected node so it can be restored after reload of the same page
+   */
+  updateSelectionCssSelector() {
+    if (this.selection.isElementNode()) {
+      this.selection.nodeFront.getUniqueSelector().then(selector => {
+        this.selectionCssSelector = selector;
+      }, this._handleRejectionIfNotDestroyed);
+    }
+  },
+
+  /**
    * Can a new HTML element be inserted into the currently selected element?
    * @return {Boolean}
    */
   canAddHTMLChild: function() {
     const selection = this.selection;
 
     // Don't allow to insert an element into these elements. This should only
     // contain elements where walker.insertAdjacentHTML has no effect.
@@ -1255,16 +1276,28 @@ Inspector.prototype = {
            selection.isElementNode() &&
            !selection.isPseudoElementNode() &&
            !selection.isAnonymousNode() &&
            !invalidTagNames.includes(
             selection.nodeFront.nodeName.toLowerCase());
   },
 
   /**
+   * Update the state of the add button in the toolbar depending on the current selection.
+   */
+  updateAddElementButton() {
+    const btn = this.panelDoc.getElementById("inspector-element-add-button");
+    if (this.canAddHTMLChild()) {
+      btn.removeAttribute("disabled");
+    } else {
+      btn.setAttribute("disabled", "true");
+    }
+  },
+
+  /**
    * Handler for the "host-changed" event from the toolbox. Resets the inspector
    * sidebar sizes when the toolbox host type changes.
    */
   async onHostChanged() {
     // Eagerly call our resize handling code to process the fact that we
     // switched hosts. If we don't do this, we'll wait for resize events + 200ms
     // to have passed, which causes the old layout to noticeably show up in the
     // new host, followed by the updated one.
@@ -1281,41 +1314,23 @@ Inspector.prototype = {
   /**
    * When a new node is selected.
    */
   onNewSelection: function(value, reason) {
     if (reason === "selection-destroy") {
       return;
     }
 
-    // Wait for all the known tools to finish updating and then let the
-    // client know.
-    const selection = this.selection.nodeFront;
-
-    // Update the state of the add button in the toolbar depending on the
-    // current selection.
-    const btn = this.panelDoc.querySelector("#inspector-element-add-button");
-    if (this.canAddHTMLChild()) {
-      btn.removeAttribute("disabled");
-    } else {
-      btn.setAttribute("disabled", "true");
-    }
-
-    // On any new selection made by the user, store the unique css selector
-    // of the selected node so it can be restored after reload of the same page
-    if (this.selection.isElementNode()) {
-      selection.getUniqueSelector().then(selector => {
-        this.selectionCssSelector = selector;
-      }, this._handleRejectionIfNotDestroyed);
-    }
+    this.updateAddElementButton();
+    this.updateSelectionCssSelector();
 
     const selfUpdate = this.updating("inspector-panel");
     executeSoon(() => {
       try {
-        selfUpdate(selection);
+        selfUpdate(this.selection.nodeFront);
       } catch (ex) {
         console.error(ex);
       }
     });
   },
 
   /**
    * Delay the "inspector-updated" notification while a tool
@@ -1943,21 +1958,18 @@ Inspector.prototype = {
     this._markupFrame.addEventListener("load", this._onMarkupFrameLoad, true);
     this._markupFrame.setAttribute("src", "markup/markup.xhtml");
     this._markupFrame.setAttribute("aria-label",
       INSPECTOR_L10N.getStr("inspector.panelLabel.markupView"));
   },
 
   _onMarkupFrameLoad: function() {
     this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
-
     this._markupFrame.contentWindow.focus();
-
     this.markup = new MarkupView(this, this._markupFrame, this._toolbox.win);
-
     this._markupBox.style.visibility = "visible";
     this.emit("markuploaded");
   },
 
   _destroyMarkup: function() {
     let destroyPromise;
 
     if (this._markupFrame) {
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -129,16 +129,17 @@ function MarkupView(inspector, frame, co
     this._initTooltips();
   } else {
     this._elt.addEventListener("mousemove", () => {
       this._initTooltips();
     }, { once: true });
   }
 
   this._onNewSelection();
+  this.expandNode(this.inspector.selection.nodeFront);
 
   this._prefObserver = new PrefObserver("devtools.markup");
   this._prefObserver.on(ATTR_COLLAPSE_ENABLED_PREF, this._onCollapseAttributesPrefChange);
   this._prefObserver.on(ATTR_COLLAPSE_LENGTH_PREF, this._onCollapseAttributesPrefChange);
 
   this._initShortcuts();
 }
 
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -139,16 +139,17 @@ devtools.jar:
     skin/images/filetypes/dir-open.svg (themes/images/filetypes/dir-open.svg)
     skin/images/filetypes/globe.svg (themes/images/filetypes/globe.svg)
     skin/images/alerticon-warning.png (themes/images/alerticon-warning.png)
     skin/images/alerticon-warning@2x.png (themes/images/alerticon-warning@2x.png)
     skin/rules.css (themes/rules.css)
     skin/images/command-paintflashing.svg (themes/images/command-paintflashing.svg)
     skin/images/command-screenshot.svg (themes/images/command-screenshot.svg)
     skin/images/command-responsivemode.svg (themes/images/command-responsivemode.svg)
+    skin/images/command-replay.svg (themes/images/command-replay.svg)
     skin/images/command-pick.svg (themes/images/command-pick.svg)
     skin/images/command-pick-accessibility.svg (themes/images/command-pick-accessibility.svg)
     skin/images/command-frames.svg (themes/images/command-frames.svg)
     skin/images/command-console.svg (themes/images/command-console.svg)
     skin/images/command-eyedropper.svg (themes/images/command-eyedropper.svg)
     skin/images/command-rulers.svg (themes/images/command-rulers.svg)
     skin/images/command-measure.svg (themes/images/command-measure.svg)
     skin/images/command-noautohide.svg (themes/images/command-noautohide.svg)
--- a/devtools/client/locales/en-US/startup.properties
+++ b/devtools/client/locales/en-US/startup.properties
@@ -283,16 +283,26 @@ application.panelLabel=Application Panel
 application.tooltip=Application Panel
 
 # LOCALIZATION NOTE (toolbox.buttons.responsive):
 # This is the tooltip of the button in the toolbox toolbar that toggles
 # the Responsive mode.
 # Keyboard shortcut will be shown inside brackets.
 toolbox.buttons.responsive = Responsive Design Mode (%S)
 
+# LOCALIZATION NOTE (toolbox.buttons.replay):
+# This is the tooltip of the button in the toolbox toolbar that enables
+# the web replay record feature.
+toolbox.buttons.replay = Enable WebReplay
+
+# LOCALIZATION NOTE (toolbox.buttons.stopReplay):
+# This is the tooltip of the button in the toolbox toolbar that dissables
+# the web replay feature.
+toolbox.buttons.stopReplay = Disable WebReplay
+
 # LOCALIZATION NOTE (toolbox.buttons.paintflashing):
 # This is the tooltip of the paintflashing button in the toolbox toolbar
 # that toggles paintflashing.
 toolbox.buttons.paintflashing = Toggle paint flashing
 
 # LOCALIZATION NOTE (toolbox.buttons.scratchpad):
 # This is the tooltip of the button in the toolbox toolbar that opens
 # the scratchpad window
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/command-replay.svg
@@ -0,0 +1,7 @@
+<!-- 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/. -->
+<svg width="20px" height="20px" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" fill="context-fill #0b0b0b">
+	<circle id="Oval" cx="10" cy="10" r="6"></circle>
+	<path d="M10,20 C4.4771525,20 0,15.5228475 0,10 C0,4.4771525 4.4771525,0 10,0 C15.5228475,0 20,4.4771525 20,10 C20,15.5228475 15.5228475,20 10,20 Z M10,18 C14.418278,18 18,14.418278 18,10 C18,5.581722 14.418278,2 10,2 C5.581722,2 2,5.581722 2,10 C2,14.418278 5.581722,18 10,18 Z" id="Combined-Shape"></path>
+</svg>
\ No newline at end of file
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -12,16 +12,17 @@
   --more-button-image: url(chrome://devtools/skin/images/more.svg);
   --settings-image: url(chrome://devtools/skin/images/tool-options-photon.svg);
 
   --command-noautohide-image: url(images/command-noautohide.svg);
   --command-console-image: url(images/command-console.svg);
   --command-paintflashing-image: url(images/command-paintflashing.svg);
   --command-screenshot-image: url(images/command-screenshot.svg);
   --command-responsive-image: url(images/command-responsivemode.svg);
+  --command-replay-image: url(images/command-replay.svg);
   --command-scratchpad-image: url(images/tool-scratchpad.svg);
   --command-pick-image: url(images/command-pick.svg);
   --command-pick-accessibility-image: url(images/command-pick-accessibility.svg);
   --command-frames-image: url(images/command-frames.svg);
   --command-rulers-image: url(images/command-rulers.svg);
   --command-measure-image: url(images/command-measure.svg);
   --command-chevron-image: url(images/command-chevron.svg);
 }
@@ -286,16 +287,37 @@
   fill: var(--theme-toolbar-photon-icon-color);
   -moz-context-properties: fill;
 }
 
 #command-button-responsive.checked::before {
   fill: currentColor;
 }
 
+#command-button-stop-replay::before, #command-button-replay::before {
+  background-image: var(--command-replay-image);
+  fill: var(--theme-toolbar-photon-icon-color);
+  -moz-context-properties: fill;
+  background-repeat: no-repeat;
+  height: 16px;
+  background-size: contain;
+}
+
+#command-button-replay, #command-button-stop-replay {
+  background-color: transparent;
+}
+
+#command-button-replay:hover, #command-button-stop-replay:hover {
+  background: var(--toolbarbutton-background);
+}
+
+#command-button-stop-replay::before {
+  fill: currentColor;
+}
+
 #command-button-scratchpad::before {
   background-image: var(--command-scratchpad-image);
 }
 
 #command-button-pick::before {
   background-image: var(--command-pick-image);
 }
 
--- a/devtools/client/webreplay/menu.js
+++ b/devtools/client/webreplay/menu.js
@@ -28,16 +28,26 @@ function ReloadAndRecordTab() {
   const url = gBrowser.currentURI.spec;
   gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, true,
                                    { recordExecution: "*", newFrameloader: true });
   gBrowser.loadURI(url, {
     triggeringPrincipal: gBrowser.selectedBrowser.contentPrincipal,
   });
 }
 
+function ReloadAndStopRecordingTab() {
+  const { gBrowser } = Services.wm.getMostRecentWindow("navigator:browser");
+  const url = gBrowser.currentURI.spec;
+  gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, true,
+                                   { newFrameloader: true });
+  gBrowser.loadURI(url, {
+    triggeringPrincipal: gBrowser.selectedBrowser.contentPrincipal,
+  });
+}
+
 function SaveRecording() {
   const { gBrowser } = Services.wm.getMostRecentWindow("navigator:browser");
   const fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
   const window = gBrowser.ownerGlobal;
   fp.init(window, null, Ci.nsIFilePicker.modeSave);
   fp.open(rv => {
     if (rv == Ci.nsIFilePicker.returnOK || rv == Ci.nsIFilePicker.returnReplace) {
       const tabParent = gBrowser.selectedTab.linkedBrowser.frameLoader.tabParent;
@@ -85,8 +95,11 @@ exports.addWebReplayMenu = function(doc)
     popup.appendChild(menuitem);
   }
 
   const mds = doc.getElementById("menu_devtools_separator");
   if (mds) {
     mds.parentNode.insertBefore(menu, mds);
   }
 };
+
+exports.reloadAndRecordTab = ReloadAndRecordTab;
+exports.reloadAndStopRecordingTab = ReloadAndStopRecordingTab;
--- a/devtools/server/actors/stylesheets.js
+++ b/devtools/server/actors/stylesheets.js
@@ -7,18 +7,16 @@
 const {Ci} = require("chrome");
 const Services = require("Services");
 const defer = require("devtools/shared/defer");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {fetch} = require("devtools/shared/DevToolsUtils");
 const {mediaRuleSpec, styleSheetSpec,
        styleSheetsSpec} = require("devtools/shared/specs/stylesheets");
-const {
-  addPseudoClassLock, removePseudoClassLock } = require("devtools/server/actors/highlighters/utils/markup");
 const InspectorUtils = require("InspectorUtils");
 
 loader.lazyRequireGetter(this, "CssLogic", "devtools/shared/inspector/css-logic");
 loader.lazyRequireGetter(this, "addPseudoClassLock",
   "devtools/server/actors/highlighters/utils/markup", true);
 loader.lazyRequireGetter(this, "removePseudoClassLock",
   "devtools/server/actors/highlighters/utils/markup", true);
 loader.lazyRequireGetter(this, "loadSheet", "devtools/shared/layout/utils", true);
--- a/devtools/shared/css/color.js
+++ b/devtools/shared/css/color.js
@@ -1,24 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
-
-loader.lazyRequireGetter(this, "CSS_ANGLEUNIT",
-  "devtools/shared/css/properties-db", true);
-
-const {getAngleValueInDegrees} = require("devtools/shared/css/parsing-utils");
-
 const {getCSSLexer} = require("devtools/shared/css/lexer");
 const {cssColors} = require("devtools/shared/css/color-db");
 
+loader.lazyRequireGetter(this, "getAngleValueInDegrees", "devtools/shared/css/parsing-utils", true);
+loader.lazyRequireGetter(this, "CSS_ANGLEUNIT", "devtools/shared/css/properties-db", true);
+
 const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
 
 const SPECIALVALUES = new Set([
   "currentcolor",
   "initial",
   "inherit",
   "transparent",
   "unset"
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -1105,17 +1105,19 @@ ScriptLoader::StartLoad(ScriptLoadReques
       nsIChannel::LOAD_CLASSIFY_URI);
 
   NS_ENSURE_SUCCESS(rv, rv);
 
   // To avoid decoding issues, the build-id is part of the JSBytecodeMimeType
   // constant.
   aRequest->mCacheInfo = nullptr;
   nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(channel));
-  if (cic && nsContentUtils::IsBytecodeCacheEnabled()) {
+  if (cic && nsContentUtils::IsBytecodeCacheEnabled() &&
+      // Bug 1436400: no bytecode cache support for modules yet.
+      !aRequest->IsModuleRequest()) {
     if (!aRequest->IsLoadingSource()) {
       // Inform the HTTP cache that we prefer to have information coming from the
       // bytecode cache instead of the sources, if such entry is already registered.
       LOG(("ScriptLoadRequest (%p): Maybe request bytecode", aRequest));
       cic->PreferAlternativeDataType(nsContentUtils::JSBytecodeMimeType());
     } else {
       // If we are explicitly loading from the sources, such as after a
       // restarted request, we might still want to save the bytecode after.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
@@ -582,17 +582,18 @@ public final class GeckoRuntimeSettings 
         final GeckoBundle data = new GeckoBundle(1);
         data.putString("languageTag", mLocale);
         EventDispatcher.getInstance().dispatch("GeckoView:SetLocale", data);
     }
 
     // Sync values with nsICookieService.idl.
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({ COOKIE_ACCEPT_ALL, COOKIE_ACCEPT_FIRST_PARTY,
-              COOKIE_ACCEPT_NONE, COOKIE_ACCEPT_VISITED })
+              COOKIE_ACCEPT_NONE, COOKIE_ACCEPT_VISITED,
+              COOKIE_ACCEPT_NON_TRACKERS })
     /* package */ @interface CookieBehavior {}
 
     /**
      * Accept first-party and third-party cookies and site data.
      */
     public static final int COOKIE_ACCEPT_ALL = 0;
     /**
      * Accept only first-party cookies and site data to block cookies which are
@@ -603,16 +604,22 @@ public final class GeckoRuntimeSettings 
      * Do not store any cookies and site data.
      */
     public static final int COOKIE_ACCEPT_NONE = 2;
     /**
      * Accept first-party and third-party cookies and site data only from
      * sites previously visited in a first-party context.
      */
     public static final int COOKIE_ACCEPT_VISITED = 3;
+    /**
+     * Accept only first-party and non-tracking third-party cookies and site data
+     * to block cookies which are not associated with the domain of the visited
+     * site set by known trackers.
+     */
+    public static final int COOKIE_ACCEPT_NON_TRACKERS = 4;
 
     /**
      * Get the assigned cookie storage behavior.
      *
      * @return The assigned behavior, as one of {@link #COOKIE_ACCEPT_ALL COOKIE_ACCEPT_*} flags.
      */
     public @CookieBehavior int getCookieBehavior() {
         return mCookieBehavior.get();
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1086,16 +1086,18 @@ pref("browser.dom.window.dump.enabled", 
 pref("toolkit.dump.emit", false);
 
 // Enable recording/replaying executions.
 #if defined(XP_MACOSX) && defined(NIGHTLY_BUILD)
 pref("devtools.recordreplay.enabled", true);
 pref("devtools.recordreplay.enableRewinding", true);
 #endif
 
+pref("devtools.recordreplay.mvp.enabled", false);
+
 // view source
 pref("view_source.syntax_highlight", true);
 pref("view_source.wrap_long_lines", false);
 pref("view_source.editor.path", "");
 // allows to add further arguments to the editor; use the %LINE% placeholder
 // for jumping to a specific line (e.g. "/line:%LINE%" or "--goto %LINE%")
 pref("view_source.editor.args", "");
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/mixed-content/link-prefetch-tag/no-opt-in/same-host-https/top-level/keep-scheme-redirect/allowed/allowed.https.html.ini
@@ -0,0 +1,2 @@
+[allowed.https.html]
+  disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1451142