Merge inbound to mozilla-central. a=merge
authorTiberius Oros <toros@mozilla.com>
Sat, 06 Oct 2018 00:44:03 +0300
changeset 495565 54cb6a2f028b
parent 495548 d58b6e90927f (current diff)
parent 495564 2614ca0a8884 (diff)
child 495589 9a47689aab04
child 495628 6c48732725ec
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
54cb6a2f028b / 64.0a1 / 20181005220146 / files
nightly linux64
54cb6a2f028b / 64.0a1 / 20181005220146 / files
nightly mac
54cb6a2f028b / 64.0a1 / 20181005220146 / files
nightly win32
54cb6a2f028b / 64.0a1 / 20181005220146 / files
nightly win64
54cb6a2f028b / 64.0a1 / 20181005220146 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
modules/libpref/init/all.js
--- 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