Merge m-c to oak DONTBUILD
authorRobert Strong <robert.bugzilla@gmail.com>
Mon, 08 May 2017 20:00:19 -0700
changeset 1476238 2e784e4d2e0423665882447a7bab20f136aae1af
parent 1476237 bd5e30c8066b3a077822c894024bd752e8e9792c (current diff)
parent 1117494 b21b974d60d3075ae24f6fb1bae75d0f122f28fc (diff)
child 1476239 37a28562f59fb8b406d95bd6a5833bcf898db513
push id263306
push usercatlee@mozilla.com
push dateFri, 06 Apr 2018 15:43:50 +0000
treeherdertry@a0bfb6549eeb [default view] [failures only]
milestone55.0a1
Merge m-c to oak DONTBUILD
.cron.yml
CLOBBER
browser/app/profile/firefox.js
browser/base/content/aboutDialog-appUpdater.js
browser/base/content/browser-addons.js
browser/base/content/browser.css
browser/base/content/browser.js
browser/base/content/gcli_sec_bad.svg
browser/base/content/gcli_sec_good.svg
browser/base/content/gcli_sec_moderate.svg
browser/base/content/test/appUpdate/head.js
browser/base/moz.build
browser/components/customizableui/content/panelUI.inc.xul
browser/components/customizableui/content/panelUI.js
browser/components/customizableui/test/browser_panelUINotifications.js
browser/components/downloads/test/browser/browser_downloads_panel_footer.js
browser/components/preferences/in-content/advanced.js
browser/config/mozconfigs/linux32/devedition
browser/config/mozconfigs/linux64/devedition
browser/config/mozconfigs/macosx64/devedition
browser/config/mozconfigs/win32/devedition
browser/config/mozconfigs/win64/devedition
browser/confvars.sh
browser/extensions/formautofill/skin/shared/editProfile.css
browser/locales/en-US/chrome/browser/aboutDialog.dtd
browser/themes/shared/customizableui/panelUI.inc.css
browser/themes/shared/privatebrowsing/check.svg
devtools/client/netmonitor/src/netmonitor-controller.js
devtools/client/netmonitor/src/request-list-context-menu.js
devtools/client/netmonitor/src/utils/client.js
devtools/client/netmonitor/test/browser_net_page-nav.js
devtools/client/netmonitor/test/browser_net_simple-init.js
devtools/server/content-globals.js
devtools/shared/tests/mochitest/test_css-logic.html
devtools/shared/tests/mochitest/test_devtools_extensions.html
dom/base/ScriptSettings.cpp
dom/base/ScriptSettings.h
dom/base/nsIScriptElement.h
dom/base/nsIScriptLoaderObserver.idl
dom/base/nsScriptElement.cpp
dom/base/nsScriptElement.h
dom/base/nsScriptLoader.cpp
dom/base/nsScriptLoader.h
dom/base/test/test_selection_with_anon_trees.html
dom/media/test/crashtests/1228484.html
image/ImageBlocker.cpp
image/ImageBlocker.h
layout/tables/nsTablePainter.cpp
layout/tables/nsTablePainter.h
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/errors.rs
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/internal.rs
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/device_fmt.rs
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/device_pref.rs
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/device_type.rs
media/libcubeb/cubeb-pulse-rs/cubeb-ffi/src/types/mod.rs
old-configure.in
parser/html/nsHtml5AtomList.h
parser/html/nsHtml5Atoms.cpp
parser/html/nsHtml5Atoms.h
servo/components/style/gecko/generated/gecko_pseudo_element_helper.rs
servo/components/style/gecko_bindings/bindings.rs
servo/components/style/gecko_bindings/structs_debug.rs
servo/components/style/gecko_bindings/structs_release.rs
servo/components/style/gecko_string_cache/atom_macro.rs
taskcluster/taskgraph/util/scriptworker.py
testing/mozharness/configs/builds/branch_specifics.py
testing/mozharness/configs/builds/releng_base_mac_64_builds.py
testing/mozharness/configs/merge_day/aurora_to_beta.py
testing/mozharness/configs/merge_day/central_to_aurora.py
testing/mozharness/configs/merge_day/central_to_beta.py
testing/mozharness/mozharness/mozilla/building/buildbase.py
toolkit/components/telemetry/Histograms.json
toolkit/crashreporter/nsExceptionHandler.cpp
toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
toolkit/mozapps/update/updater/updater.cpp
toolkit/themes/linux/mozapps/extensions/dictionaryGeneric-16.png
toolkit/themes/linux/mozapps/extensions/themeGeneric-16.png
toolkit/themes/osx/mozapps/extensions/dictionaryGeneric-16.png
toolkit/themes/osx/mozapps/extensions/search.png
toolkit/themes/osx/mozapps/extensions/themeGeneric-16.png
toolkit/themes/windows/mozapps/extensions/dictionaryGeneric-16.png
toolkit/themes/windows/mozapps/extensions/themeGeneric-16.png
tools/profiler/gecko/Profiler.jsm
xpcom/build/XPCOMInit.cpp
--- a/.cron.yml
+++ b/.cron.yml
@@ -27,16 +27,26 @@ jobs:
           type: decision-task
           treeherder-symbol: Nd-OSX
           triggered-by: nightly
           target-tasks-method: nightly_macosx
       run-on-projects:
           - date
       when: [] # never (hook only)
 
+    - name: nightly-desktop-win64
+      job:
+          type: decision-task
+          treeherder-symbol: Nd-Win64
+          triggered-by: nightly
+          target-tasks-method: nightly_win64
+      run-on-projects:
+          - date
+      when: [] # never (hook only)
+
     - name: nightly-android
       job:
           type: decision-task
           treeherder-symbol: Na
           triggered-by: nightly
           target-tasks-method: nightly_fennec
       run-on-projects:
           - mozilla-central
--- a/.eslintignore
+++ b/.eslintignore
@@ -4,17 +4,16 @@
 # Exclude expected objdirs.
 obj*/**
 
 # We ignore all these directories by default, until we get them enabled.
 # If you are enabling a directory, please add directory specific exclusions
 # below.
 addon-sdk/**
 build/**
-caps/**
 chrome/**
 config/**
 db/**
 docshell/**
 editor/**
 embedding/**
 extensions/**
 gfx/**
@@ -73,16 +72,19 @@ browser/extensions/pocket/content/panels
 browser/extensions/pocket/content/panels/js/vendor/**
 browser/locales/**
 # generated or library files in activity-stream
 browser/extensions/activity-stream/data/content/activity-stream.bundle.js
 browser/extensions/activity-stream/vendor/**
 # imported from chromium
 browser/extensions/mortar/**
 
+# caps/ exclusions
+caps/tests/mochitest/browser_checkloaduri.js
+
 # devtools/ exclusions
 devtools/client/canvasdebugger/**
 devtools/client/commandline/**
 devtools/client/debugger/**
 devtools/client/framework/**
 !devtools/client/framework/devtools.js
 !devtools/client/framework/devtools-browser.js
 !devtools/client/framework/selection.js
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1357107 - Removing a directory from extensions/ requires re-running configure to update MOZ_EXTENSIONS
+Bug 1356927 - Mac builds in automation require a clobber for a change in which ranlib they use
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -790,20 +790,16 @@ NotificationController::WillRefresh(mozi
     if (!mDocument)
       return;
   }
 
   // Process invalidation list of the document after all accessible tree
   // modification are done.
   mDocument->ProcessInvalidationList();
 
-  // We cannot rely on DOM tree to keep aria-owns relations updated. Make
-  // a validation to remove dead links.
-  mDocument->ValidateARIAOwned();
-
   // Process relocation list.
   for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) {
     if (mRelocations[idx]->IsInDocument()) {
       mDocument->DoARIAOwnsRelocation(mRelocations[idx]);
     }
   }
   mRelocations.Clear();
 
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1967,45 +1967,56 @@ DocAccessible::FireEventsOnInsertion(Acc
         break;
       }
     }
     while ((ancestor = ancestor->Parent()));
   }
 }
 
 void
-DocAccessible::ContentRemoved(Accessible* aContent)
+DocAccessible::ContentRemoved(Accessible* aChild)
 {
-  MOZ_DIAGNOSTIC_ASSERT(aContent->Parent(), "Unattached accessible from tree");
+  Accessible* parent = aChild->Parent();
+  MOZ_DIAGNOSTIC_ASSERT(parent, "Unattached accessible from tree");
 
 #ifdef A11Y_LOG
   logging::TreeInfo("process content removal", 0,
-                    "container", aContent->Parent(), "child", aContent, nullptr);
+                    "container", parent, "child", aChild, nullptr);
 #endif
 
-  TreeMutation mt(aContent->Parent());
-  mt.BeforeRemoval(aContent);
-  aContent->Parent()->RemoveChild(aContent);
-  UncacheChildrenInSubtree(aContent);
+  TreeMutation mt(parent);
+  mt.BeforeRemoval(aChild);
+
+  if (aChild->IsRelocated()) {
+    nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(parent);
+    MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
+    owned->RemoveElement(aChild);
+    if (owned->Length() == 0) {
+      mARIAOwnsHash.Remove(parent);
+    }
+  }
+  parent->RemoveChild(aChild);
+  UncacheChildrenInSubtree(aChild);
+
   mt.Done();
 }
 
 void
 DocAccessible::ContentRemoved(nsIContent* aContentNode)
 {
   // If child node is not accessible then look for its accessible children.
   Accessible* acc = GetAccessible(aContentNode);
   if (acc) {
     ContentRemoved(acc);
   }
-  else {
-    TreeWalker walker(this, aContentNode);
-    while (Accessible* acc = walker.Next()) {
-      ContentRemoved(acc);
-    }
+
+  dom::AllChildrenIterator iter =
+    dom::AllChildrenIterator(aContentNode, nsIContent::eAllChildren, true);
+  while (nsIContent* childNode = iter.GetNextChild()) {
+    ContentRemoved(childNode);
   }
 }
 
 bool
 DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement)
 {
   if (!aElement->HasID())
     return false;
@@ -2023,60 +2034,16 @@ DocAccessible::RelocateARIAOwnedIfNeeded
       }
     }
   }
 
   return false;
 }
 
 void
-DocAccessible::ValidateARIAOwned()
-{
-  for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
-    Accessible* owner = it.Key();
-    nsTArray<RefPtr<Accessible> >* children = it.UserData();
-
-    // Owner is about to die, put children back if applicable.
-    if (owner != this &&
-        (!mAccessibleCache.GetWeak(reinterpret_cast<void*>(owner)) ||
-         !owner->IsInDocument())) {
-      PutChildrenBack(children, 0);
-      it.Remove();
-      continue;
-    }
-
-    for (uint32_t idx = 0; idx < children->Length(); idx++) {
-      Accessible* child = children->ElementAt(idx);
-      if (!child->IsInDocument()) {
-        children->RemoveElementAt(idx);
-        idx--;
-        continue;
-      }
-
-      NS_ASSERTION(child->Parent(), "No parent for ARIA owned?");
-
-      // If DOM node doesn't have a frame anymore then shutdown its accessible.
-      if (child->Parent() && !child->GetFrame()) {
-        ContentRemoved(child);
-        children->RemoveElementAt(idx);
-        idx--;
-        continue;
-      }
-
-      NS_ASSERTION(child->Parent() == owner,
-                   "Illigally stolen ARIA owned child!");
-    }
-
-    if (children->Length() == 0) {
-      it.Remove();
-    }
-  }
-}
-
-void
 DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner)
 {
   nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.LookupOrAdd(aOwner);
 
   MOZ_ASSERT(aOwner, "aOwner must be a valid pointer");
   MOZ_ASSERT(aOwner->Elm(), "aOwner->Elm() must be a valid pointer");
 
 #ifdef A11Y_LOG
@@ -2189,17 +2156,17 @@ DocAccessible::PutChildrenBack(nsTArray<
     int32_t idxInParent = -1;
     Accessible* origContainer = GetContainerAccessible(child->GetContent());
     if (origContainer) {
       TreeWalker walker(origContainer);
       if (walker.Seek(child->GetContent())) {
         Accessible* prevChild = walker.Prev();
         if (prevChild) {
           idxInParent = prevChild->IndexInParent() + 1;
-          MOZ_ASSERT(origContainer == prevChild->Parent(), "Broken tree");
+          MOZ_DIAGNOSTIC_ASSERT(origContainer == prevChild->Parent(), "Broken tree");
           origContainer = prevChild->Parent();
         }
         else {
           idxInParent = 0;
         }
       }
     }
     MoveChild(child, origContainer, idxInParent);
@@ -2220,18 +2187,22 @@ DocAccessible::MoveChild(Accessible* aCh
 #ifdef A11Y_LOG
   logging::TreeInfo("move child", 0,
                     "old parent", curParent, "new parent", aNewParent,
                     "child", aChild, nullptr);
 #endif
 
   // If the child was taken from from an ARIA owns element.
   if (aChild->IsRelocated()) {
-    nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(curParent);
-    children->RemoveElement(aChild);
+    nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(curParent);
+    MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
+    owned->RemoveElement(aChild);
+    if (owned->Length() == 0) {
+      mARIAOwnsHash.Remove(curParent);
+    }
   }
 
   NotificationController::MoveGuard mguard(mNotificationController);
 
   if (curParent == aNewParent) {
     MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
     curParent->MoveChild(aIdxInParent, aChild);
 
@@ -2322,20 +2293,29 @@ DocAccessible::CacheChildrenInSubtree(Ac
 }
 
 void
 DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot)
 {
   aRoot->mStateFlags |= eIsNotInDocument;
   RemoveDependentIDsFor(aRoot);
 
+  nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(aRoot);
   uint32_t count = aRoot->ContentChildCount();
   for (uint32_t idx = 0; idx < count; idx++) {
     Accessible* child = aRoot->ContentChildAt(idx);
 
+    if (child->IsRelocated()) {
+      MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
+      owned->RemoveElement(child);
+      if (owned->Length() == 0) {
+        mARIAOwnsHash.Remove(aRoot);
+      }
+    }
+
     // Removing this accessible from the document doesn't mean anything about
     // accessibles for subdocuments, so skip removing those from the tree.
     if (!child->IsDoc()) {
       UncacheChildrenInSubtree(child);
     }
   }
 
   if (aRoot->IsNodeMapEntry() &&
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -339,17 +339,17 @@ public:
    */
   void ContentInserted(nsIContent* aContainerNode,
                        nsIContent* aStartChildNode,
                        nsIContent* aEndChildNode);
 
   /**
    * Update the tree on content removal.
    */
-  void ContentRemoved(Accessible* aContent);
+  void ContentRemoved(Accessible* aAccessible);
   void ContentRemoved(nsIContent* aContentNode);
 
   /**
    * Updates accessible tree when rendered text is changed.
    */
   void UpdateText(nsIContent* aTextNode);
 
   /**
@@ -500,21 +500,16 @@ protected:
    *
    * While children are cached we may encounter the case there's no accessible
    * for referred content by related accessible. Store these related nodes to
    * invalidate their containers later.
    */
   void ProcessInvalidationList();
 
   /**
-   * Validates all aria-owns connections and updates the tree accordingly.
-   */
-  void ValidateARIAOwned();
-
-  /**
    * Steals or puts back accessible subtrees.
    */
   void DoARIAOwnsRelocation(Accessible* aOwner);
 
   /**
    * Moves children back under their original parents.
    */
   void PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
--- a/accessible/tests/mochitest/treeupdate/test_ariaowns.html
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -671,16 +671,68 @@
         testAccessibleTree("t9_container", tree);
       }
 
       this.getID = () => {
         return `Set ARIA owns on a document (part3)`;
       }
     }
 
+    /**
+     * Put ARIA owned child back when ARIA owner removed.
+     */
+    function test10_removeARIAOwner()
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getAccessible('t10_owner'))
+      ];
+
+      this.invoke = () => {
+        let tree =
+          { SECTION: [ // t10_container
+            { SECTION: [ // t10_owner
+              { ENTRY: [] } // t10_child
+            ] }
+          ] };
+        testAccessibleTree('t10_container', tree);
+
+        getNode('t10_owner').remove();
+      }
+
+      this.getID = () => {
+        return 'Put aria owned child back when aria owner removed';
+      }
+    }
+
+    function test10_finishTest()
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_REORDER, 't10_container')
+      ];
+
+      this.invoke = () => {
+        // trigger a tree update.
+        getNode('t10_container').append(document.createElement('p'));
+      }
+
+      this.finalCheck = () => {
+        let tree =
+          { SECTION: [ // t10_container
+            // { ENTRY: [] }, // t10_child
+            { PARAGRAPH: [] }
+          ] };
+        testAccessibleTree('t10_container', tree);
+        todo(false, 'Input accessible has be moved back in the tree');
+      }
+
+      this.getID = () => {
+        return `Put aria owned child back when aria owner removed (finish test)`;
+      }
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Test
     ////////////////////////////////////////////////////////////////////////////
 
     //gA11yEventDumpToConsole = true;
     //enableLogging("tree,eventTree,verbose"); // debug stuff
 
     var gQueue = null;
@@ -723,16 +775,19 @@
 
       gQueue.push(new setARIAOwnsOnElToRemove("t7_parent", "t7_child"));
 
       gQueue.push(new test8());
       gQueue.push(new test9_prepare());
       gQueue.push(new test9_setARIAOwns());
       gQueue.push(new test9_finish());
 
+      gQueue.push(new test10_removeARIAOwner());
+      gQueue.push(new test10_finishTest());
+
       gQueue.invoke(); // SimpleTest.finish() will be called in the end
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
 
   </script>
 </head>
@@ -787,11 +842,16 @@
     </div>
   </div>
 
   <div id="t8_container">
     <input id="t8_button" type="button"><span id="t8_span"><input><input><input></span>
   </div>
 
   <iframe id="t9_container"></iframe>
+
+  <div id="t10_container">
+    <div id="t10_owner" aria-owns="t10_child"></div>
+    <input id="t10_child">
+  </div>
 </body>
 
 </html>
--- a/addon-sdk/source/lib/sdk/io/buffer.js
+++ b/addon-sdk/source/lib/sdk/io/buffer.js
@@ -42,17 +42,17 @@ function Buffer(subject, encoding /*, bu
 
   switch (type) {
     case 'number':
       // Create typed array of the given size if number.
       try {
         let buffer = new Uint8Array(subject > 0 ? Math.floor(subject) : 0);
         return buffer;
       } catch (e) {
-        if (/size and count too large/.test(e.message) ||
+        if (/invalid array length/.test(e.message) ||
             /invalid arguments/.test(e.message))
           throw new RangeError('Could not instantiate buffer: size of buffer may be too large');
         else
           throw new Error('Could not instantiate buffer');
       }
       break;
     case 'string':
       // If string encode it and use buffer for the returned Uint8Array
--- a/addon-sdk/source/lib/sdk/loader/sandbox.js
+++ b/addon-sdk/source/lib/sdk/loader/sandbox.js
@@ -10,39 +10,28 @@ module.metadata = {
 const { Cc, Ci, CC, Cu } = require('chrome');
 const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
 const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
                      getService(Ci.mozIJSSubScriptLoader);
 const self = require('sdk/self');
 const { getTabId } = require('../tabs/utils');
 const { getInnerId } = require('../window/utils');
 
-const { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const { require: devtoolsRequire } = devtools;
-const { addContentGlobal, removeContentGlobal } = devtoolsRequire("devtools/server/content-globals");
-
 /**
  * Make a new sandbox that inherits given `source`'s principals. Source can be
  * URI string, DOMWindow or `null` for system principals.
  */
 function sandbox(target, options) {
   options = options || {};
   options.metadata = options.metadata ? options.metadata : {};
   options.metadata.addonID = options.metadata.addonID ?
     options.metadata.addonID : self.id;
 
   let sandbox = Cu.Sandbox(target || systemPrincipal, options);
   Cu.setSandboxMetadata(sandbox, options.metadata);
-  let innerWindowID = options.metadata['inner-window-id']
-  if (innerWindowID) {
-    addContentGlobal({
-      global: sandbox,
-      'inner-window-id': innerWindowID
-    });
-  }
   return sandbox;
 }
 exports.sandbox = sandbox;
 
 /**
  * Evaluates given `source` in a given `sandbox` and returns result.
  */
 function evaluate(sandbox, code, uri, line, version) {
--- a/addon-sdk/source/python-lib/cuddlefish/prefs.py
+++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py
@@ -211,18 +211,16 @@ DEFAULT_TEST_PREFS = {
     'security.default_personal_cert': 'Select Automatically',
     'network.http.prompt-temp-redirect': False,
     'security.warn_viewing_mixed': False,
     'extensions.defaultProviders.enabled': True,
     'datareporting.policy.dataSubmissionPolicyBypassNotification': True,
     'layout.css.report_errors': True,
     'layout.css.grid.enabled': True,
     'layout.spammy_warnings.enabled': False,
-    # Make sure the disk cache doesn't get auto disabled
-    'network.http.bypass-cachelock-threshold': 200000,
     # Always use network provider for geolocation tests
     # so we bypass the OSX dialog raised by the corelocation provider
     'geo.provider.testing': True,
     # Background thumbnails in particular cause grief, and disabling thumbnails
     # in general can't hurt - we re-enable them when tests need them.
     'browser.pagethumbnails.capturing_disabled': True,
     # Indicate that the download panel has been shown once so that whichever
     # download test runs first doesn't show the popup inconsistently.
--- a/addon-sdk/source/test/preferences/test.json
+++ b/addon-sdk/source/test/preferences/test.json
@@ -26,17 +26,16 @@
   "security.default_personal_cert": "Select Automatically",
   "network.http.prompt-temp-redirect": false,
   "security.warn_viewing_mixed": false,
   "extensions.defaultProviders.enabled": true,
   "datareporting.policy.dataSubmissionPolicyBypassNotification": true,
   "layout.css.report_errors": true,
   "layout.css.grid.enabled": true,
   "layout.spammy_warnings.enabled": false,
-  "network.http.bypass-cachelock-threshold": 200000,
   "geo.provider.testing": true,
   "browser.pagethumbnails.capturing_disabled": true,
   "browser.download.panel.shown": true,
   "general.useragent.updates.enabled": false,
   "media.eme.enabled": true,
   "dom.ipc.tabs.shutdownTimeoutSecs": 0,
   "general.useragent.locale": "en-US",
   "intl.locale.matchOS": "en-US",
--- a/addon-sdk/source/test/test-page-mod-debug.js
+++ b/addon-sdk/source/test/test-page-mod-debug.js
@@ -5,20 +5,16 @@
 
 const { Cc, Ci, Cu } = require("chrome");
 const { PageMod } = require("sdk/page-mod");
 const { testPageMod, handleReadyState, openNewTab,
         contentScriptWhenServer, createLoader } = require("./page-mod/helpers");
 const { cleanUI, after } = require("sdk/test/utils");
 const { open, getFrames, getMostRecentBrowserWindow, getInnerId } = require("sdk/window/utils");
 
-const { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const { require: devtoolsRequire } = devtools;
-const contentGlobals = devtoolsRequire("devtools/server/content-globals");
-
 // The following adds Debugger constructor to the global namespace.
 const { addDebuggerToGlobal } = require('resource://gre/modules/jsdebugger.jsm');
 addDebuggerToGlobal(this);
 
 exports.testDebugMetadata = function(assert, done) {
   let dbg = new Debugger;
   let globalDebuggees = [];
   dbg.onNewGlobalObject = function(global) {
@@ -41,26 +37,14 @@ exports.testDebugMetadata = function(ass
           return false;
         }
       }), "one of the globals is a content script");
       done();
     }
   );
 };
 
-exports.testDevToolsExtensionsGetContentGlobals = function(assert, done) {
-  let mods = testPageMod(assert, done, "about:", [{
-      include: "about:",
-      contentScriptWhen: "start",
-      contentScript: "null;",
-    }], function(win, done) {
-      assert.equal(contentGlobals.getContentGlobals({ 'inner-window-id': getInnerId(win) }).length, 1);
-      done();
-    }
-  );
-};
-
 after(exports, function*(name, assert) {
   assert.pass("cleaning ui.");
   yield cleanUI();
 });
 
 require('sdk/test').run(exports);
--- a/addon-sdk/source/test/test-xpcom.js
+++ b/addon-sdk/source/test/test-xpcom.js
@@ -140,16 +140,17 @@ function testRegister(assert, text) {
       newChannel : function(aURI, aLoadInfo) {
         var ios = Cc["@mozilla.org/network/io-service;1"].
                   getService(Ci.nsIIOService);
 
         var uri = ios.newURI("data:text/plain;charset=utf-8," + text);
         var channel = ios.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
 
         channel.originalURI = aURI;
+        aLoadInfo.resultPrincipalURI = aURI;
         return channel;
       },
       getURIFlags: function(aURI) {
         return Ci.nsIAboutModule.ALLOW_SCRIPT;
       }
     })
   });
 
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -112,16 +112,20 @@
     <emItem blockID="i523" id="/^({7e8a1050-cf67-4575-92df-dcc60e7d952d}|{b3420a9c-a397-4409-b90d-bcf22da1a08a}|{eca6641f-2176-42ba-bdbe-f3e327f8e0af}|{707dca12-3f99-4d94-afea-06dcc0ae0108}|{aea20431-87fc-40be-bc5b-18066fe2819c}|{30ee6676-1ba6-455a-a7e8-298fa863a546})$/">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i732" id="{e935dd68-f90d-46a6-b89e-c4657534b353}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="edad04eb-ea16-42f3-a4a7-20dded33cc37" id="@safesearchscoutee">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="i436" id="/(\{7aeae561-714b-45f6-ace3-4a8aed6e227b\})|(\{01e86e69-a2f8-48a0-b068-83869bdba3d0\})|(\{77f5fe49-12e3-4cf5-abb4-d993a0164d9e\})/">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i700" id="2bbadf1f-a5af-499f-9642-9942fcdb7c76@f05a14cc-8842-4eee-be17-744677a917ed.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
@@ -176,16 +180,20 @@
     <emItem blockID="i626" id="{20AD702C-661E-4534-8CE9-BA4EC9AD6ECC}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="3fd71895-7fc6-4f3f-aa22-1cbb0c5fd922" id="/^({95E84BD3-3604-4AAC-B2CA-D9AC3E55B64B}|{E3605470-291B-44EB-8648-745EE356599A}|{95E5E0AD-65F9-4FFC-A2A2-0008DCF6ED25}|{FF20459C-DA6E-41A7-80BC-8F4FEFD9C575}|{6E727987-C8EA-44DA-8749-310C0FBE3C3E}|{12E8A6C2-B125-479F-AB3C-13B8757C7F04}|{EB6628CF-0675-4DAE-95CE-EFFA23169743})$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="i716" id="{cc6cc772-f121-49e0-b1f0-c26583cb0c5e}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i501" id="xivars@aol.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -355,22 +355,16 @@ pref("browser.download.loglevel", "Error
 // feedback from their action.
 pref("browser.download.saveLinkAsFilenameTimeout", 4000);
 
 pref("browser.download.useDownloadDir", true);
 pref("browser.download.folderList", 1);
 pref("browser.download.manager.addToRecentDocs", true);
 pref("browser.download.manager.resumeOnWakeDelay", 10000);
 
-#ifdef RELEASE_OR_BETA
-pref("browser.download.showPanelDropmarker", false);
-#else
-pref("browser.download.showPanelDropmarker", true);
-#endif
-
 // This allows disabling the animated notifications shown by
 // the Downloads Indicator when a download starts or completes.
 pref("browser.download.animateNotifications", true);
 
 // This records whether or not the panel has been shown at least once.
 pref("browser.download.panel.shown", false);
 
 #ifndef XP_MACOSX
@@ -865,16 +859,18 @@ pref("browser.sessionstore.upgradeBackup
 // End-users should not run sessionstore in debug mode
 pref("browser.sessionstore.debug", false);
 // Causes SessionStore to ignore non-final update messages from
 // browser tabs that were not caused by a flush from the parent.
 // This is a testing flag and should not be used by end-users.
 pref("browser.sessionstore.debug.no_auto_updates", false);
 // Forget closed windows/tabs after two weeks
 pref("browser.sessionstore.cleanup.forget_closed_after", 1209600000);
+// Maximum number of bytes of DOMSessionStorage data we collect per origin.
+pref("browser.sessionstore.dom_storage_limit", 2048);
 
 // allow META refresh by default
 pref("accessibility.blockautorefresh", false);
 
 // Whether history is enabled or not.
 pref("places.history.enabled", true);
 
 // the (maximum) number of the recent visits to sample
@@ -1020,17 +1016,17 @@ pref("security.sandbox.content.level", 1
 pref("security.sandbox.windows.log.stackTraceDepth", 0);
 #endif
 
 // This controls the strength of the Windows GPU process sandbox.  Changes
 // will require restart.
 // For information on what the level number means, see
 // SetSecurityLevelForGPUProcess() in
 // security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
-pref("security.sandbox.gpu.level", 1);
+pref("security.sandbox.gpu.level", 0);
 #endif
 
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // This pref is discussed in bug 1083344, the naming is inspired from its
 // Windows counterpart, but on Mac it's an integer which means:
 // 0 -> "no sandbox"
 // 1 -> "preliminary content sandboxing enabled: write access to
 //       home directory is prevented"
--- a/browser/base/content/aboutDialog-appUpdater.js
+++ b/browser/base/content/aboutDialog-appUpdater.js
@@ -1,13 +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/. */
 
-// Note: this file is included in aboutDialog.xul if MOZ_UPDATER is defined.
+// Note: this file is included in aboutDialog.xul and preferences/advanced.xul
+// if MOZ_UPDATER is defined.
 
 /* import-globals-from aboutDialog.js */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                   "resource://gre/modules/UpdateUtils.jsm");
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -495,75 +495,68 @@ const gExtensionsNotifications = {
     // uninit() can race ahead of init() in some cases, if that happens,
     // we have no handler to remove.
     if (!this.initialized) {
       return;
     }
     ExtensionsUI.off("change", this.boundUpdate);
   },
 
+  _createAddonButton(text, icon, callback) {
+    let button = document.createElement("toolbarbutton");
+    button.setAttribute("label", text);
+    const DEFAULT_EXTENSION_ICON =
+      "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+    button.setAttribute("image", icon || DEFAULT_EXTENSION_ICON);
+    button.className = "addon-banner-item";
+
+    button.addEventListener("click", callback);
+    PanelUI.addonNotificationContainer.appendChild(button);
+  },
+
   updateAlerts() {
     let sideloaded = ExtensionsUI.sideloaded;
     let updates = ExtensionsUI.updates;
     if (sideloaded.size + updates.size == 0) {
       PanelUI.removeNotification("addon-alert");
     } else {
       PanelUI.showBadgeOnlyNotification("addon-alert");
     }
 
-    let container = document.getElementById("PanelUI-footer-addons");
+    let container = PanelUI.addonNotificationContainer;
 
     while (container.firstChild) {
       container.firstChild.remove();
     }
 
-    const DEFAULT_EXTENSION_ICON =
-      "chrome://mozapps/skin/extensions/extensionGeneric.svg";
     let items = 0;
     for (let update of updates) {
       if (++items > 4) {
         break;
       }
-
-      let button = document.createElement("toolbarbutton");
       let text = gNavigatorBundle.getFormattedString("webextPerms.updateMenuItem", [update.addon.name]);
-      button.setAttribute("label", text);
-
-      let icon = update.addon.iconURL || DEFAULT_EXTENSION_ICON;
-      button.setAttribute("image", icon);
-
-      button.addEventListener("click", evt => {
+      this._createAddonButton(text, update.addon.iconURL, evt => {
         ExtensionsUI.showUpdate(gBrowser, update);
       });
-
-      container.appendChild(button);
     }
 
     let appName;
     for (let addon of sideloaded) {
       if (++items > 4) {
         break;
       }
       if (!appName) {
         let brandBundle = document.getElementById("bundle_brand");
         appName = brandBundle.getString("brandShortName");
       }
 
-      let button = document.createElement("toolbarbutton");
       let text = gNavigatorBundle.getFormattedString("webextPerms.sideloadMenuItem", [addon.name, appName]);
-      button.setAttribute("label", text);
-
-      let icon = addon.iconURL || DEFAULT_EXTENSION_ICON;
-      button.setAttribute("image", icon);
-
-      button.addEventListener("click", evt => {
+      this._createAddonButton(text, addon.iconURL, evt => {
         ExtensionsUI.showSideloaded(gBrowser, addon);
       });
-
-      container.appendChild(button);
     }
   },
 };
 
 var LightWeightThemeWebInstaller = {
   init() {
     let mm = window.messageManager;
     mm.addMessageListener("LightWeightThemeWebInstaller:Install", this);
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -7,16 +7,17 @@
 @namespace svg url("http://www.w3.org/2000/svg");
 
 :root {
   --identity-popup-expander-width: 38px;
   --panelui-subview-transition-duration: 150ms;
   --lwt-additional-images: none;
   --lwt-background-alignment: right top;
   --lwt-background-tiling: no-repeat;
+  --animation-easing-function: cubic-bezier(.07, .95, 0, 1);
 }
 
 :root:-moz-lwtheme {
   color: var(--lwt-text-color) !important;
 }
 
 :root:-moz-lwtheme:not([customization-lwtheme]) {
   background-color: var(--lwt-accent-color) !important;
@@ -188,18 +189,19 @@ tabbrowser {
 }
 
 .tabbrowser-tabs[movingtab] > .tabbrowser-tab[selected] {
   position: relative;
   z-index: 2;
   pointer-events: none; /* avoid blocking dragover events on scroll buttons */
 }
 
+.tabbrowser-tab[tabdrop-samewindow],
 .tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
-  transition: transform 200ms ease-out;
+  transition: transform 200ms var(--animation-easing-function);
 }
 
 .new-tab-popup,
 #alltabs-popup {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
 }
 
 toolbar[printpreview="true"] {
@@ -322,49 +324,49 @@ toolbarpaletteitem:-moz-any([place="pale
 }
 
 toolbarpaletteitem > toolbaritem[sdkstylewidget="true"][cui-areatype="toolbar"] > .toolbarbutton-text {
   display: -moz-box;
 }
 
 @media not all and (min-resolution: 1.1dppx) {
   .webextension-browser-action {
-    list-style-image: var(--webextension-toolbar-image);
+    list-style-image: var(--webextension-toolbar-image, inherit);
   }
 
   .webextension-browser-action[cui-areatype="menu-panel"],
   toolbarpaletteitem[place="palette"] > .webextension-browser-action {
-    list-style-image: var(--webextension-menupanel-image);
+    list-style-image: var(--webextension-menupanel-image, inherit);
   }
 
   .webextension-page-action {
-    list-style-image: var(--webextension-urlbar-image);
+    list-style-image: var(--webextension-urlbar-image, inherit);
   }
 
   .webextension-menuitem {
-    list-style-image: var(--webextension-menuitem-image);
+    list-style-image: var(--webextension-menuitem-image, inherit);
   }
 }
 
 @media (min-resolution: 1.1dppx) {
   .webextension-browser-action {
-    list-style-image: var(--webextension-toolbar-image-2x);
+    list-style-image: var(--webextension-toolbar-image-2x, inherit);
   }
 
   .webextension-browser-action[cui-areatype="menu-panel"],
   toolbarpaletteitem[place="palette"] > .webextension-browser-action {
-    list-style-image: var(--webextension-menupanel-image-2x);
+    list-style-image: var(--webextension-menupanel-image-2x, inherit);
   }
 
   .webextension-page-action {
-    list-style-image: var(--webextension-urlbar-image-2x);
+    list-style-image: var(--webextension-urlbar-image-2x, inherit);
   }
 
   .webextension-menuitem {
-    list-style-image: var(--webextension-menuitem-image-2x);
+    list-style-image: var(--webextension-menuitem-image-2x, inherit);
   }
 }
 
 toolbarbutton.webextension-menuitem > .toolbarbutton-icon {
   width: 16px;
   height: 16px;
 }
 
@@ -1182,17 +1184,18 @@ toolbarpaletteitem[place="palette"][hidd
 
 #customization-palette .toolbarpaletteitem-box {
   -moz-box-pack: center;
   -moz-box-flex: 1;
   width: 10em;
   max-width: 10em;
 }
 
-#main-window[customizing=true] .PanelUI-notification-menu-item {
+#main-window[customizing=true] .addon-banner-item,
+#main-window[customizing=true] .panel-banner-item {
   display: none;
 }
 
 /* UI Tour */
 
 @keyframes uitour-wobble {
   from {
     transform: rotate(0deg) translateX(3px) rotate(0deg);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3484,20 +3484,17 @@ function getPEMString(cert) {
 var PrintPreviewListener = {
   _printPreviewTab: null,
   _simplifiedPrintPreviewTab: null,
   _tabBeforePrintPreview: null,
   _simplifyPageTab: null,
   _lastRequestedPrintPreviewTab: null,
 
   _createPPBrowser() {
-    if (!this._tabBeforePrintPreview) {
-      this._tabBeforePrintPreview = gBrowser.selectedTab;
-    }
-    let browser = this._tabBeforePrintPreview.linkedBrowser;
+    let browser = this.getSourceBrowser();
     let preferredRemoteType = browser.remoteType;
     return gBrowser.loadOneTab("about:printpreview", {
       inBackground: true,
       preferredRemoteType,
       sameProcessAsFrameLoader: browser.frameLoader
     });
   },
   getPrintPreviewBrowser() {
@@ -3514,26 +3511,28 @@ var PrintPreviewListener = {
       this._simplifiedPrintPreviewTab = this._createPPBrowser();
     }
     gBrowser._allowTabChange = true;
     this._lastRequestedPrintPreviewTab = gBrowser.selectedTab = this._simplifiedPrintPreviewTab;
     gBrowser._allowTabChange = false;
     return gBrowser.getBrowserForTab(this._simplifiedPrintPreviewTab);
   },
   createSimplifiedBrowser() {
-    let browser = this._tabBeforePrintPreview.linkedBrowser;
+    let browser = this.getSourceBrowser();
     this._simplifyPageTab = gBrowser.loadOneTab("about:printpreview", {
       inBackground: true,
       sameProcessAsFrameLoader: browser.frameLoader
      });
     return this.getSimplifiedSourceBrowser();
   },
   getSourceBrowser() {
-    return this._tabBeforePrintPreview ?
-      this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser;
+    if (!this._tabBeforePrintPreview) {
+      this._tabBeforePrintPreview = gBrowser.selectedTab;
+    }
+    return this._tabBeforePrintPreview.linkedBrowser;
   },
   getSimplifiedSourceBrowser() {
     return this._simplifyPageTab ?
       gBrowser.getBrowserForTab(this._simplifyPageTab) : null;
   },
   getNavToolbox() {
     return gNavToolbox;
   },
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -46,16 +46,18 @@ XPCOMUtils.defineLazyGetter(this, "PageM
   let tmp = {};
   Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
   return new tmp.PageMenuChild();
 });
 XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames",
   "resource://gre/modules/WebNavigationFrames.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
   "resource:///modules/Feeds.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "findCssSelector",
+  "resource://gre/modules/css-selector.js");
 
 Cu.importGlobalProperties(["URL"]);
 
 // TabChildGlobal
 var global = this;
 
 // Load the form validation popup handler
 var formSubmitObserver = new FormSubmitObserver(content, this);
@@ -159,16 +161,17 @@ var handleContentContextMenu = function(
       } catch (e) {}
     } catch (e) {}
   }
 
   let selectionInfo = BrowserUtils.getSelectionDetails(content);
 
   let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
   let userContextId = loadContext.originAttributes.userContextId;
+  let popupNodeSelectors = getNodeSelectors(event.target);
 
   if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
     let editFlags = SpellCheckHelper.isEditable(event.target, content);
     let spellInfo;
     if (editFlags &
         (SpellCheckHelper.EDITABLE | SpellCheckHelper.CONTENTEDITABLE)) {
       spellInfo =
         InlineSpellCheckerContent.initContextMenu(event, editFlags, this);
@@ -178,31 +181,37 @@ var handleContentContextMenu = function(
     // determine what was context-clicked on. Then, update the state of the
     // commands on the context menu.
     docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
             .setCommandNode(event.target);
     event.target.ownerGlobal.updateCommands("contentcontextmenu");
 
     let customMenuItems = PageMenuChild.build(event.target);
     let principal = doc.nodePrincipal;
+
     sendRpcMessage("contextmenu",
                    { editFlags, spellInfo, customMenuItems, addonInfo,
                      principal, docLocation, charSet, baseURI, referrer,
                      referrerPolicy, contentType, contentDisposition,
                      frameOuterWindowID, selectionInfo, disableSetDesktopBg,
-                     loginFillInfo, parentAllowsMixedContent, userContextId },
-                   { event, popupNode: event.target });
+                     loginFillInfo, parentAllowsMixedContent, userContextId,
+                     popupNodeSelectors,
+                   }, {
+                     event,
+                     popupNode: event.target,
+                   });
   } else {
     // Break out to the parent window and pass the add-on info along
     let browser = docShell.chromeEventHandler;
     let mainWin = browser.ownerGlobal;
     mainWin.gContextMenuContentData = {
       isRemote: false,
       event,
       popupNode: event.target,
+      popupNodeSelectors,
       browser,
       addonInfo,
       documentURIObject: doc.documentURIObject,
       docLocation,
       charSet,
       referrer,
       referrerPolicy,
       contentType,
@@ -240,16 +249,38 @@ const MOZILLA_PKIX_ERROR_NOT_YET_VALID_I
 const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
 
 const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
 
 const PREF_SSL_IMPACT = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
   return prefs.concat(Services.prefs.getChildList(root));
 }, []);
 
+/**
+ * Retrieve the array of CSS selectors corresponding to the provided node. The first item
+ * of the array is the selector of the node in its owner document. Additional items are
+ * used if the node is inside a frame, each representing the CSS selector for finding the
+ * frame element in its parent document.
+ *
+ * This format is expected by DevTools in order to handle the Inspect Node context menu
+ * item.
+ *
+ * @param  {Node}
+ *         The node for which the CSS selectors should be computed
+ * @return {Array} array of css selectors (strings).
+ */
+function getNodeSelectors(node) {
+  let selectors = [];
+  while (node) {
+    selectors.push(findCssSelector(node));
+    node = node.ownerGlobal.frameElement;
+  }
+
+  return selectors;
+}
 
 function getSerializedSecurityInfo(docShell) {
   let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
                     .getService(Ci.nsISerializationHelper);
 
   let securityInfo = docShell.failedChannel && docShell.failedChannel.securityInfo;
   if (!securityInfo) {
     return "";
--- a/browser/base/content/moz.build
+++ b/browser/base/content/moz.build
@@ -24,17 +24,17 @@ with Files("newtab/**"):
 
 with Files("pageinfo/**"):
     BUG_COMPONENT = ("Firefox", "Page Info Window")
 
 with Files("sync/**"):
     BUG_COMPONENT = ("Firefox", "Sync")
 
 with Files("test/alerts/**"):
-    BUG_COMPONENT = ("Toolkit", "Notification and Alerts")
+    BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
 
 with Files("test/appUpdate/**"):
     BUG_COMPONENT = ("Toolkit", "Application Update")
 
 with Files("test/captivePortal/**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 with Files("test/chrome/**"):
@@ -149,19 +149,16 @@ with Files("browser-tabPreviews.xml"):
     BUG_COMPONENT = ("Firefox", "Tabbed Browser")
 
 with Files("contentSearch*"):
     BUG_COMPONENT = ("Firefox", "Search")
 
 with Files("*.svg"):
     BUG_COMPONENT = ("Firefox", "Theme")
 
-with Files("gcli*"):
-    BUG_COMPONENT = ("Core", "DOM: Security")
-
 with Files("hiddenWindow.xul"):
     BUG_COMPONENT = ("Firefox", "Device Permissions")
 
 with Files("macBrowserOverlay.xul"):
     BUG_COMPONENT = ("Firefox", "Shell Integration")
 
 with Files("report-phishing-overlay.xul"):
     BUG_COMPONENT = ("Toolkit", "Safe Browsing")
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -28,16 +28,17 @@ function openContextMenu(aMessage) {
   if (spellInfo)
     spellInfo.target = aMessage.target.messageManager;
   let documentURIObject = makeURI(data.docLocation,
                                   data.charSet,
                                   makeURI(data.baseURI));
   gContextMenuContentData = { isRemote: true,
                               event: aMessage.objects.event,
                               popupNode: aMessage.objects.popupNode,
+                              popupNodeSelectors: data.popupNodeSelectors,
                               browser,
                               editFlags: data.editFlags,
                               spellInfo,
                               principal: data.principal,
                               customMenuItems: data.customMenuItems,
                               addonInfo: data.addonInfo,
                               documentURIObject,
                               docLocation: data.docLocation,
@@ -210,25 +211,25 @@ nsContextMenu.prototype = {
     this.showItem("context-openlinkinusercontext-menu", shouldShow && !isWindowPrivate && showContainers);
     this.showItem("context-openlinkincurrent", this.onPlainTextLink);
     this.showItem("context-sep-open", shouldShow);
   },
 
   initNavigationItems: function CM_initNavigationItems() {
     var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
                        this.onCanvas || this.onVideo || this.onAudio ||
-                       this.onTextInput || this.onSocial);
+                       this.onTextInput) && this.inTabBrowser;
     this.showItem("context-navigation", shouldShow);
     this.showItem("context-sep-navigation", shouldShow);
 
     let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
 
     let stopReloadItem = "";
-    if (shouldShow || this.onSocial) {
-      stopReloadItem = (stopped || this.onSocial) ? "reload" : "stop";
+    if (shouldShow || !this.inTabBrowser) {
+      stopReloadItem = (stopped || !this.inTabBrowser) ? "reload" : "stop";
     }
 
     this.showItem("context-reload", stopReloadItem == "reload");
     this.showItem("context-stop", stopReloadItem == "stop");
 
     // XXX: Stop is determined in browser.js; the canStop broadcaster is broken
     // this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
   },
@@ -285,19 +286,22 @@ nsContextMenu.prototype = {
                   this.isContentSelected);
     this.showItem("context-viewpartialsource-mathml",
                   this.onMathML && !this.isContentSelected);
 
     var shouldShow = !(this.isContentSelected ||
                        this.onImage || this.onCanvas ||
                        this.onVideo || this.onAudio ||
                        this.onLink || this.onTextInput);
-    var showInspect = !this.onSocial && gPrefService.getBoolPref("devtools.inspector.enabled");
+    var showInspect = this.inTabBrowser && gPrefService.getBoolPref("devtools.inspector.enabled");
     this.showItem("context-viewsource", shouldShow);
     this.showItem("context-viewinfo", shouldShow);
+    // The page info is broken for WebExtension popups, as the browser is
+    // destroyed when the popup is closed.
+    this.setItemAttr("context-viewinfo", "disabled", this.webExtBrowserType === "popup");
     this.showItem("inspect-separator", showInspect);
     this.showItem("context-inspect", showInspect);
 
     this.showItem("context-sep-viewsource", shouldShow);
 
     // Set as Desktop background depends on whether an image was clicked on,
     // and only works if we have a shell service.
     var haveSetDesktopBackground = false;
@@ -336,30 +340,34 @@ nsContextMenu.prototype = {
                                          !this.inSyntheticDoc);
     this.showItem("context-sep-viewbgimage", shouldShow &&
                                              !this._hasMultipleBGImages &&
                                              !this.inSyntheticDoc);
     document.getElementById("context-viewbgimage")
             .disabled = !this.hasBGImage;
 
     this.showItem("context-viewimageinfo", this.onImage);
+    // The image info popup is broken for WebExtension popups, since the browser
+    // is destroyed when the popup is closed.
+    this.setItemAttr("context-viewimageinfo", "disabled", this.webExtBrowserType === "popup");
     this.showItem("context-viewimagedesc", this.onImage && this.imageDescURL !== "");
   },
 
   initMiscItems: function CM_initMiscItems() {
     // Use "Bookmark This Link" if on a link.
     let bookmarkPage = document.getElementById("context-bookmarkpage");
     this.showItem(bookmarkPage,
                   !(this.isContentSelected || this.onTextInput || this.onLink ||
                     this.onImage || this.onVideo || this.onAudio || this.onSocial ||
-                    this.onCanvas));
+                    this.onCanvas || this.inWebExtBrowser));
     bookmarkPage.setAttribute("tooltiptext", bookmarkPage.getAttribute("buttontooltiptext"));
 
     this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
-                                           !this.onSocial) || this.onPlainTextLink);
+                                           !this.onSocial && !this.onMozExtLink) ||
+                                          this.onPlainTextLink);
     this.showItem("context-keywordfield",
                   this.onTextInput && this.onKeywordField);
     this.showItem("frame", this.inFrame);
 
     let showSearchSelect = (this.isTextSelected || this.onLink) && !this.onImage;
     this.showItem("context-searchselect", showSearchSelect);
     if (showSearchSelect) {
       this.formatSearchContextItem();
@@ -393,23 +401,24 @@ nsContextMenu.prototype = {
     this.showItem("context-bidi-page-direction-toggle",
                   !this.onTextInput && top.gBidiUI);
 
     // SocialShare
     let shareButton = SocialShare.shareButton;
     let shareEnabled = shareButton && !shareButton.disabled && !this.onSocial;
     let pageShare = shareEnabled && !(this.isContentSelected ||
                             this.onTextInput || this.onLink || this.onImage ||
-                            this.onVideo || this.onAudio || this.onCanvas);
+                            this.onVideo || this.onAudio || this.onCanvas ||
+                            this.inWebExtBrowser);
     this.showItem("context-sharepage", pageShare);
     this.showItem("context-shareselect", shareEnabled && this.isContentSelected);
-    this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink);
+    this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink && !this.onMozExtLink);
     this.showItem("context-shareimage", shareEnabled && this.onImage);
     this.showItem("context-sharevideo", shareEnabled && this.onVideo);
-    this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL || this.mediaURL.startsWith("blob:"));
+    this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL || this.mediaURL.startsWith("blob:") || this.mediaURL.startsWith("moz-extension:"));
   },
 
   initSpellingItems() {
     var canSpell = InlineSpellCheckerUI.canSpellCheck &&
                    !InlineSpellCheckerUI.initialSpellCheckPending &&
                    this.canSpellCheck;
     let showDictionaries = canSpell && InlineSpellCheckerUI.enabled;
     var onMisspelling = InlineSpellCheckerUI.overMisspelling;
@@ -607,17 +616,17 @@ nsContextMenu.prototype = {
   openPasswordManager() {
     LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
   },
 
   inspectNode() {
     let gBrowser = this.browser.ownerGlobal.gBrowser;
     let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
     let { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
-    return gDevToolsBrowser.inspectNode(gBrowser.selectedTab, this.target);
+    return gDevToolsBrowser.inspectNode(gBrowser.selectedTab, this.targetSelectors);
   },
 
   /**
    * Set various context menu attributes based on the state of the world.
    * Note: If the context menu is on a remote process the supplied parameters
    * will be overwritten with data from gContextMenuContentData.
    *
    * @param {Object} aNode The node that this menu is being opened on.
@@ -672,29 +681,39 @@ nsContextMenu.prototype = {
     this.inSyntheticDoc    = false;
     this.hasBGImage        = false;
     this.bgImageURL        = "";
     this.onEditableArea    = false;
     this.isDesignMode      = false;
     this.onCTPPlugin       = false;
     this.canSpellCheck     = false;
     this.onPassword        = false;
+    this.webExtBrowserType = "";
+    this.inWebExtBrowser   = false;
+    this.inTabBrowser      = true;
+    this.onMozExtLink      = false;
 
     if (this.isRemote) {
       this.selectionInfo = gContextMenuContentData.selectionInfo;
     } else {
       this.selectionInfo = BrowserUtils.getSelectionDetails(window);
     }
 
     this.textSelected      = this.selectionInfo.text;
     this.isTextSelected    = this.textSelected.length != 0;
 
     // Remember the node that was clicked.
     this.target = aNode;
 
+    // Remember the CSS selectors corresponding to clicked node. gContextMenuContentData
+    // can be null if the menu was triggered by tests in which case use an empty array.
+    this.targetSelectors = gContextMenuContentData
+                              ? gContextMenuContentData.popupNodeSelectors
+                              : [];
+
     let ownerDoc = this.target.ownerDocument;
     this.ownerDoc = ownerDoc;
 
     let editFlags;
 
     // If this is a remote context menu event, use the information from
     // gContextMenuContentData instead.
     if (this.isRemote) {
@@ -708,16 +727,20 @@ nsContextMenu.prototype = {
                              .QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebNavigation)
                              .QueryInterface(Ci.nsIDocShell)
                              .chromeEventHandler;
       this.principal = ownerDoc.nodePrincipal;
       this.frameOuterWindowID = WebNavigationFrames.getFrameId(ownerDoc.defaultView);
     }
     this.onSocial = !!this.browser.getAttribute("origin");
+    this.webExtBrowserType = this.browser.getAttribute("webextension-view-type");
+    this.inWebExtBrowser = !!this.webExtBrowserType;
+    this.inTabBrowser = this.browser.ownerGlobal.gBrowser ?
+      !!this.browser.ownerGlobal.gBrowser.getTabForBrowser(this.browser) : false;
 
     // Check if we are in a synthetic document (stand alone image, video, etc.).
     this.inSyntheticDoc = ownerDoc.mozSyntheticDocument;
 
     this._setTargetForNodesNoChildren(editFlags, aRangeParent, aRangeOffset);
 
     this._setTargetForNodesWithChildren(editFlags, aRangeParent, aRangeOffset);
   },
@@ -864,16 +887,17 @@ nsContextMenu.prototype = {
 
           // Remember corresponding element.
           this.link = elem;
           this.linkURL = this.getLinkURL();
           this.linkURI = this.getLinkURI();
           this.linkTextStr = this.getLinkText();
           this.linkProtocol = this.getLinkProtocol();
           this.onMailtoLink = (this.linkProtocol == "mailto");
+          this.onMozExtLink = (this.linkProtocol == "moz-extension");
           this.onSaveableLink = this.isLinkSaveable( this.link );
           this.linkHasNoReferrer = BrowserUtils.linkHasNoReferrer(elem);
           try {
             if (elem.download) {
               // Ignore download attribute on cross-origin links
               this.principal.checkMayLoad(this.linkURI, false, true);
               this.linkDownload = elem.download;
             }
@@ -1028,18 +1052,21 @@ nsContextMenu.prototype = {
                    referrerPolicy: gContextMenuContentData.referrerPolicy,
                    frameOuterWindowID: gContextMenuContentData.frameOuterWindowID,
                    noReferrer: this.linkHasNoReferrer };
     for (let p in extra) {
       params[p] = extra[p];
     }
 
     if (!this.isRemote) {
-      params.frameOuterWindowID = WebNavigationFrames.getFrameId(this.target.ownerGlobal);
+      // Propagate the frameOuterWindowID value saved when
+      // the context menu has been opened.
+      params.frameOuterWindowID = this.frameOuterWindowID;
     }
+
     // If we want to change userContextId, we must be sure that we don't
     // propagate the referrer.
     if ("userContextId" in params &&
         params.userContextId != gContextMenuContentData.userContextId) {
       params.noReferrer = true;
     }
 
     return params;
@@ -1906,17 +1933,17 @@ nsContextMenu.prototype = {
     };
     aXulMenu.ownerDocument.addEventListener("command", activationHandler, true);
     aXulMenu.addEventListener("popuphiding", this._onPopupHiding, true);
   },
 
   _getTelemetryPageContextInfo() {
     let rv = [];
     for (let k of ["isContentSelected", "onLink", "onImage", "onCanvas", "onVideo", "onAudio",
-                   "onTextInput", "onSocial"]) {
+                   "onTextInput", "onSocial", "inWebExtBrowser", "inTabBrowser"]) {
       if (this[k]) {
         rv.push(k.replace(/^(?:is|on)(.)/, (match, firstLetter) => firstLetter.toLowerCase()));
       }
     }
     if (!rv.length) {
       rv.push("other");
     }
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -6191,31 +6191,33 @@
           let pinned = draggedTab.pinned;
           let numPinned = this.tabbrowser._numPinnedTabs;
           let tabs = this.tabbrowser.visibleTabs
                                     .slice(pinned ? 0 : numPinned,
                                            pinned ? numPinned : undefined);
           if (rtl)
             tabs.reverse();
           let tabWidth = draggedTab.getBoundingClientRect().width;
+          draggedTab._dragData.tabWidth = tabWidth;
 
           // Move the dragged tab based on the mouse position.
 
           let leftTab = tabs[0];
           let rightTab = tabs[tabs.length - 1];
           let tabScreenX = draggedTab.boxObject.screenX;
           let translateX = screenX - draggedTab._dragData.screenX;
           if (!pinned)
             translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
           let leftBound = leftTab.boxObject.screenX - tabScreenX;
           let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
                            (tabScreenX + tabWidth);
           translateX = Math.max(translateX, leftBound);
           translateX = Math.min(translateX, rightBound);
           draggedTab.style.transform = "translateX(" + translateX + "px)";
+          draggedTab._dragData.translateX = translateX;
 
           // Determine what tab we're dragging over.
           // * Point of reference is the center of the dragged tab. If that
           //   point touches a background tab, the dragged tab would take that
           //   tab's position when dropped.
           // * We're doing a binary search in order to reduce the amount of
           //   tabs we need to check.
 
@@ -6877,24 +6879,52 @@
         if (draggedTab && dropEffect == "copy") {
           // copy the dropped tab (wherever it's from)
           let newIndex = this._getDropIndex(event, false);
           let newTab = this.tabbrowser.duplicateTab(draggedTab);
           this.tabbrowser.moveTabTo(newTab, newIndex);
           if (draggedTab.parentNode != this || event.shiftKey)
             this.selectedItem = newTab;
         } else if (draggedTab && draggedTab.parentNode == this) {
-          this._finishAnimateTabMove();
-
-          // actually move the dragged tab
-          if ("animDropIndex" in draggedTab._dragData) {
-            let newIndex = draggedTab._dragData.animDropIndex;
-            if (newIndex > draggedTab._tPos)
-              newIndex--;
-            this.tabbrowser.moveTabTo(draggedTab, newIndex);
+          let oldTranslateX = draggedTab._dragData.translateX;
+          let tabWidth = draggedTab._dragData.tabWidth;
+          let translateOffset = oldTranslateX % tabWidth;
+          let newTranslateX = oldTranslateX - translateOffset;
+          if (oldTranslateX > 0 && translateOffset > tabWidth / 2) {
+            newTranslateX += tabWidth;
+          } else if (oldTranslateX < 0 && -translateOffset > tabWidth / 2) {
+            newTranslateX -= tabWidth;
+          }
+
+          let dropIndex = "animDropIndex" in draggedTab._dragData &&
+                          draggedTab._dragData.animDropIndex;
+          if (dropIndex && dropIndex > draggedTab._tPos)
+            dropIndex--;
+
+          if (oldTranslateX && oldTranslateX != newTranslateX) {
+            draggedTab.setAttribute("tabdrop-samewindow", "true");
+            draggedTab.style.transform = "translateX(" + newTranslateX + "px)";
+            let onTransitionEnd = transitionendEvent => {
+              if (transitionendEvent.propertyName != "transform" ||
+                  transitionendEvent.originalTarget != draggedTab) {
+                return;
+              }
+              draggedTab.removeEventListener("transitionend", onTransitionEnd);
+
+              draggedTab.removeAttribute("tabdrop-samewindow");
+
+              this._finishAnimateTabMove();
+              if (dropIndex !== false)
+                this.tabbrowser.moveTabTo(draggedTab, dropIndex);
+            }
+            draggedTab.addEventListener("transitionend", onTransitionEnd);
+          } else {
+            this._finishAnimateTabMove();
+            if (dropIndex !== false)
+              this.tabbrowser.moveTabTo(draggedTab, dropIndex);
           }
         } else if (draggedTab) {
           let newIndex = this._getDropIndex(event, false);
           this.tabbrowser.adoptTab(draggedTab, newIndex, true);
         } else {
           // Pass true to disallow dropping javascript: or data: urls
           let links;
           try {
@@ -6925,24 +6955,27 @@
         }
 
         if (draggedTab) {
           delete draggedTab._dragData;
         }
       ]]></handler>
 
       <handler event="dragend"><![CDATA[
-        // Note: while this case is correctly handled here, this event
-        // isn't dispatched when the tab is moved within the tabstrip,
-        // see bug 460801.
+        var dt = event.dataTransfer;
+        var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+
+        // Prevent this code from running if a tabdrop animation is
+        // running since calling _finishAnimateTabMove would clear
+        // any CSS transition that is running.
+        if (draggedTab.hasAttribute("tabdrop-samewindow"))
+          return;
 
         this._finishAnimateTabMove();
 
-        var dt = event.dataTransfer;
-        var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
         if (dt.mozUserCancelled || dt.dropEffect != "none" || this._isCustomizing) {
           delete draggedTab._dragData;
           return;
         }
 
         // Disable detach within the browser toolbox
         var eX = event.screenX;
         var eY = event.screenY;
--- a/browser/base/content/test/appUpdate/head.js
+++ b/browser/base/content/test/appUpdate/head.js
@@ -203,17 +203,17 @@ function processStep({notificationId, bu
     is(shownNotification, notificationId, "The right notification showed up.");
     if (shownNotification != notificationId) {
       if (cleanup) {
         yield cleanup();
       }
       return;
     }
 
-    let notification = document.getElementById(`PanelUI-${notificationId}-notification`);
+    let notification = document.getElementById(`appMenu-${notificationId}-notification`);
     is(notification.hidden, false, `${notificationId} notification is showing`);
     if (beforeClick) {
       yield Task.spawn(beforeClick);
     }
 
     let buttonEl = document.getAnonymousElementByAttribute(notification, "anonid", button);
 
     buttonEl.click();
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/contextMenu/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/browser-test"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  !/browser/base/content/test/general/contextmenu_common.js
+  subtst_contextmenu_webext.html
+
+[browser_contextmenu_mozextension.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_mozextension.js
@@ -0,0 +1,82 @@
+"use strict";
+
+var { SocialService } = Cu.import("resource:///modules/SocialService.jsm", {});
+
+let contextMenu;
+let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
+let hasContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
+
+// A social share provider
+let manifest = {
+  name: "provider 1",
+  origin: "https://example.com",
+  iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
+  shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
+};
+
+add_task(function* test_setup() {
+  const example_base = "http://example.com/browser/browser/base/content/test/contextMenu/";
+  const url = example_base + "subtst_contextmenu_webext.html";
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+  const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
+  const contextmenu_common = chrome_base + "contextmenu_common.js";
+  /* import-globals-from ../general/contextmenu_common.js */
+  Services.scriptloader.loadSubScript(contextmenu_common, this);
+
+  // Enable social sharing functions in the browser, so the context menu item is shown.
+  CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+
+  yield new Promise((resolve) => SocialService.addProvider(manifest, resolve));
+  ok(SocialShare.shareButton && !SocialShare.shareButton.disabled, "Sharing is enabled");
+});
+
+add_task(function* test_link() {
+  // gets hidden for this case.
+  yield test_contextmenu("#link",
+    ["context-openlinkintab", true,
+       ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+       // We need a blank entry here because the containers submenu is
+       // dynamically generated with no ids.
+       ...(hasContainers ? ["", null] : []),
+       "context-openlink",      true,
+       "context-openlinkprivate", true,
+       "---",                   null,
+       "context-savelink",      true,
+       "context-copylink",      true,
+       "context-searchselect",  true]);
+});
+
+add_task(function* test_video() {
+  yield test_contextmenu("#video",
+  ["context-media-play",         null,
+   "context-media-mute",         null,
+   "context-media-playbackrate", null,
+       ["context-media-playbackrate-050x", null,
+        "context-media-playbackrate-100x", null,
+        "context-media-playbackrate-125x", null,
+        "context-media-playbackrate-150x", null,
+        "context-media-playbackrate-200x", null], null,
+   "context-media-loop",         null,
+   "context-media-showcontrols", null,
+   "context-video-fullscreen",   null,
+   "---",                        null,
+   "context-viewvideo",          null,
+   "context-copyvideourl",       null,
+   "---",                        null,
+   "context-savevideo",          null,
+   "context-sharevideo",         false,
+   "context-video-saveimage",    null,
+   "context-sendvideo",          null,
+   "context-castvideo",          null,
+     [], null
+  ]);
+});
+
+add_task(function* test_cleanup() {
+  lastElementSelector = null;
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  yield new Promise((resolve) => {
+    return SocialService.disableProvider(manifest.origin, resolve);
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/contextMenu/subtst_contextmenu_webext.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Subtest for browser context menu</title>
+</head>
+<body>
+  Browser context menu subtest.
+  <a href="moz-extension://foo-bar/tab.html" id="link">Link to an extension resource</a>
+  <video src="moz-extension://foo-bar/video.ogg" id="video"></video>
+</body>
+</html>
--- a/browser/base/content/test/forms/browser.ini
+++ b/browser/base/content/test/forms/browser.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_selectpopup.js]
 skip-if = os == "linux" # Bug 1329991 - test fails intermittently on Linux builds
 [browser_selectpopup_colors.js]
 skip-if = os == "linux" # Bug 1329991 - test fails intermittently on Linux builds
+[browser_selectpopup_searchfocus.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/forms/browser_selectpopup_searchfocus.js
@@ -0,0 +1,42 @@
+let SELECT =
+  "<html><body><select id='one'>";
+for (let i = 0; i < 75; i++) {
+  SELECT +=
+    `  <option>${i}${i}${i}${i}${i}</option>`;
+}
+SELECT +=
+    '  <option selected="true">{"end": "true"}</option>' +
+  "</select></body></html>";
+
+add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({
+    "set": [
+      ["dom.select_popup_in_parent.enabled", true],
+      ["dom.forms.selectSearch", true]
+    ]
+  });
+});
+
+add_task(function* test_focus_on_search_shouldnt_close_popup() {
+  const pageUrl = "data:text/html," + escape(SELECT);
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+  let menulist = document.getElementById("ContentSelectDropdown");
+  let selectPopup = menulist.menupopup;
+
+  let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
+  yield popupShownPromise;
+
+  let searchInput = selectPopup.querySelector("textbox[type='search']");
+  searchInput.scrollIntoView();
+  let searchFocused = BrowserTestUtils.waitForEvent(searchInput, "focus");
+  yield EventUtils.synthesizeMouseAtCenter(searchInput, {}, window);
+  yield searchFocused;
+
+  is(selectPopup.state, "open", "select popup should still be open after clicking on the search field");
+
+  yield hideSelectPopup(selectPopup, "escape");
+  yield BrowserTestUtils.removeTab(tab);
+});
+
--- a/browser/base/content/test/general/browser_tabReorder.js
+++ b/browser/base/content/test/general/browser_tabReorder.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function test() {
+add_task(function*() {
   let initialTabsLength = gBrowser.tabs.length;
 
   let newTab1 = gBrowser.selectedTab = gBrowser.addTab("about:robots", {skipAnimation: true});
   let newTab2 = gBrowser.selectedTab = gBrowser.addTab("about:about", {skipAnimation: true});
   let newTab3 = gBrowser.selectedTab = gBrowser.addTab("about:config", {skipAnimation: true});
   registerCleanupFunction(function() {
     while (gBrowser.tabs.length > initialTabsLength) {
       gBrowser.removeTab(gBrowser.tabs[initialTabsLength]);
@@ -18,32 +18,37 @@ function test() {
   is(gBrowser.tabs[initialTabsLength + 1], newTab2, "newTab2 position is correct");
   is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 position is correct");
 
   let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                      getService(Ci.mozIJSSubScriptLoader);
   let EventUtils = {};
   scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
 
-  function dragAndDrop(tab1, tab2, copy) {
+  function* dragAndDrop(tab1, tab2, copy) {
     let rect = tab2.getBoundingClientRect();
     let event = {
       ctrlKey: copy,
       altKey: copy,
       clientX: rect.left + rect.width / 2 + 10,
       clientY: rect.top + rect.height / 2,
     };
 
+    let originalTPos = tab1._tPos;
     EventUtils.synthesizeDrop(tab1, tab2, null, copy ? "copy" : "move", window, window, event);
+    if (!copy) {
+      yield BrowserTestUtils.waitForCondition(() => tab1._tPos != originalTPos,
+        "Waiting for tab position to be updated");
+    }
   }
 
-  dragAndDrop(newTab1, newTab2, false);
+  yield dragAndDrop(newTab1, newTab2, false);
   is(gBrowser.tabs.length, initialTabsLength + 3, "tabs are still there");
   is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 and newTab1 are swapped");
   is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 and newTab2 are swapped");
   is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 stays same place");
 
-  dragAndDrop(newTab2, newTab1, true);
+  yield dragAndDrop(newTab2, newTab1, true);
   is(gBrowser.tabs.length, initialTabsLength + 4, "a tab is duplicated");
   is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 stays same place");
   is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 stays same place");
   is(gBrowser.tabs[initialTabsLength + 3], newTab3, "a new tab is inserted before newTab3");
-}
+});
--- a/browser/base/content/test/general/browser_tab_detach_restore.js
+++ b/browser/base/content/test/general/browser_tab_detach_restore.js
@@ -20,15 +20,15 @@ add_task(function*() {
 
   is(win.gBrowser.selectedBrowser.permanentKey, key, "Should have properly copied the permanentKey");
   yield BrowserTestUtils.closeWindow(win);
 
   is(SessionStore.getClosedWindowCount(), 1, "Should have restore data for the closed window");
 
   win = SessionStore.undoCloseWindow(0);
   yield BrowserTestUtils.waitForEvent(win, "load");
-  yield BrowserTestUtils.waitForEvent(win.gBrowser.tabs[0], "SSTabRestored");
+  yield BrowserTestUtils.waitForEvent(win.gBrowser.tabContainer, "SSTabRestored");
 
   is(win.gBrowser.tabs.length, 1, "Should have restored one tab");
   is(win.gBrowser.selectedBrowser.currentURI.spec, uri, "Should have restored the right page");
 
   yield promiseWindowClosed(win);
 });
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -209,20 +209,16 @@ var whitelist = new Set([
   {file: "chrome://global/skin/tree/sort-dsc.png", platforms: ["linux"]},
   // Bug 1344267
   {file: "chrome://marionette/content/test_anonymous_content.xul"},
   {file: "chrome://marionette/content/test_dialog.properties"},
   {file: "chrome://marionette/content/test_dialog.xul"},
   // Bug 1348533
   {file: "chrome://mozapps/skin/downloads/buttons.png", platforms: ["macosx"]},
   {file: "chrome://mozapps/skin/downloads/downloadButtons.png", platforms: ["linux", "win"]},
-  // Bug 1348555
-  {file: "chrome://mozapps/skin/extensions/dictionaryGeneric-16.png"},
-  {file: "chrome://mozapps/skin/extensions/search.png", platforms: ["macosx"]},
-  {file: "chrome://mozapps/skin/extensions/themeGeneric-16.png"},
   // Bug 1348556
   {file: "chrome://mozapps/skin/plugins/pluginBlocked.png"},
   // Bug 1348558
   {file: "chrome://mozapps/skin/update/downloadButtons.png",
    platforms: ["linux"]},
   // Bug 1348559
   {file: "chrome://pippki/content/resetpassword.xul"},
   // Bug 1351078
@@ -230,20 +226,16 @@ var whitelist = new Set([
   // Bug 1351070
   {file: "resource://gre/modules/ContentPrefInstance.jsm"},
   // Bug 1351079
   {file: "resource://gre/modules/ISO8601DateUtils.jsm"},
   // Bug 1337345
   {file: "resource://gre/modules/Manifest.jsm"},
   // Bug 1351089
   {file: "resource://gre/modules/PresentationDeviceInfoManager.jsm"},
-  // Bug 1351091
-  {file: "resource://gre/modules/Profiler.jsm"},
-  // Bug 1351658
-  {file: "resource://gre/modules/PropertyListUtils.jsm", platforms: ["linux", "win"]},
   // Bug 1351097
   {file: "resource://gre/modules/accessibility/AccessFu.jsm"},
   // Bug 1351637
   {file: "resource://gre/modules/sdk/bootstrap.js"},
 
 ].filter(item =>
   ("isFromDevTools" in item) == isDevtools &&
   (!item.skipNightly || !AppConstants.NIGHTLY_BUILD) &&
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -1,14 +1,16 @@
 [DEFAULT]
 support-files =
   dummy_page.html
+  test_bug1358314.html
 
 [browser_abandonment_telemetry.js]
 [browser_allow_process_switches_despite_related_browser.js]
+[browser_contextmenu_openlink_after_tabnavigated.js]
 [browser_tabCloseProbes.js]
 [browser_tabSpinnerProbe.js]
 skip-if = !e10s # Tab spinner is e10s only.
 [browser_tabSpinnerTypeProbe.js]
 skip-if = !e10s # Tab spinner is e10s only.
 [browser_tabSwitchPrintPreview.js]
 skip-if = os == 'mac'
 [browser_navigatePinnedTab.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_contextmenu_openlink_after_tabnavigated.js
@@ -0,0 +1,43 @@
+"use strict";
+
+const example_base = "http://example.com/browser/browser/base/content/test/tabs/";
+
+add_task(function* test_contextmenu_openlink_after_tabnavigated() {
+  let url = example_base + "test_bug1358314.html";
+
+  const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+  const contextMenu = document.getElementById("contentAreaContextMenu");
+  let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+  yield BrowserTestUtils.synthesizeMouse("a", 0, 0, {
+    type: "contextmenu",
+    button: 2,
+  }, gBrowser.selectedBrowser);
+  yield awaitPopupShown;
+  info("Popup Shown");
+
+  info("Navigate the tab with the opened context menu");
+  gBrowser.selectedBrowser.loadURI("about:blank");
+  yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+  let awaitNewTabOpen = BrowserTestUtils.waitForNewTab(gBrowser, "http://example.com/");
+
+  info("Click the 'open link in new tab' menu item");
+  let openLinkMenuItem = contextMenu.querySelector("#context-openlinkintab");
+  openLinkMenuItem.click();
+
+  info("Wait for the new tab to be opened");
+  const newTab = yield awaitNewTabOpen;
+
+  // Close the contextMenu popup if it has not been closed yet.
+  contextMenu.hidePopup();
+
+  yield BrowserTestUtils.browserLoaded(newTab.linkedBrowser);
+  const newTabURL = yield ContentTask.spawn(newTab.linkedBrowser, null, function* () {
+    return content.location.href;
+  });
+  is(newTabURL, "http://example.com/", "Got the expected URL loaded in the new tab");
+
+  yield BrowserTestUtils.removeTab(newTab);
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js
+++ b/browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js
@@ -1,17 +1,11 @@
 const kURL1 = "data:text/html,Should I stay or should I go?";
 const kURL2 = "data:text/html,I shouldn't be here!";
 
-add_task(function* setup() {
-  yield SpecialPowers.pushPrefEnv({
-    set: [["dom.ipc.processCount", 1]]
-  });
-});
-
 /**
  * Verify that if we open a new tab and try to make it the selected tab while
  * print preview is up, that doesn't happen.
  */
 add_task(function* () {
   yield BrowserTestUtils.withNewTab(kURL1, function* (browser) {
     let tab = gBrowser.addTab(kURL2);
     document.getElementById("cmd_printPreview").doCommand();
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/test_bug1358314.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <p>Test page</p>
+    <a href="/">Link</a>
+  </body>
+</html>
--- a/browser/base/content/test/webextensions/browser_extension_sideloading.js
+++ b/browser/base/content/test/webextensions/browser_extension_sideloading.js
@@ -180,17 +180,17 @@ add_task(function* () {
 
   // Check for the addons badge on the hamburger menu
   let menuButton = document.getElementById("PanelUI-menu-button");
   is(menuButton.getAttribute("badge-status"), "addon-alert", "Should have addon alert badge");
 
   // Find the menu entries for sideloaded extensions
   yield PanelUI.show();
 
-  let addons = document.getElementById("PanelUI-footer-addons");
+  let addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 4, "Have 4 menu entries for sideloaded extensions");
 
   // Click the first sideloaded extension
   let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   addons.children[0].click();
 
   // When we get the permissions prompt, we should be at the extensions
   // list in about:addons
@@ -221,17 +221,17 @@ add_task(function* () {
   is(addon3.userDisabled, true, "Addon 3 should still be disabled");
   is(addon4.userDisabled, true, "Addon 4 should still be disabled");
 
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   // Should still have 3 entries in the hamburger menu
   yield PanelUI.show();
 
-  addons = document.getElementById("PanelUI-footer-addons");
+  addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 3, "Have 3 menu entries for sideloaded extensions");
 
   // Click the second sideloaded extension and wait for the notification
   popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   addons.children[0].click();
   panel = yield popupPromise;
 
   // Again we should be at the extentions list in about:addons
@@ -255,17 +255,17 @@ add_task(function* () {
   is(addon1.userDisabled, true, "Addon 1 should still be disabled");
   is(addon2.userDisabled, false, "Addon 2 should now be enabled");
   is(addon3.userDisabled, true, "Addon 3 should still be disabled");
   is(addon4.userDisabled, true, "Addon 4 should still be disabled");
 
   // Should still have 2 entries in the hamburger menu
   yield PanelUI.show();
 
-  addons = document.getElementById("PanelUI-footer-addons");
+  addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 2, "Have 2 menu entries for sideloaded extensions");
 
   // Close the hamburger menu and go directly to the addons manager
   yield PanelUI.hide();
 
   win = yield BrowserOpenAddonsMgr(VIEW);
 
   let list = win.document.getElementById("addon-list");
@@ -294,17 +294,17 @@ add_task(function* () {
   is(value, false, "userDisabled should be set on addon 3");
 
   addon3 = yield AddonManager.getAddonByID(ID3);
   is(addon3.userDisabled, false, "Addon 3 should be enabled");
 
   // Should still have 1 entry in the hamburger menu
   yield PanelUI.show();
 
-  addons = document.getElementById("PanelUI-footer-addons");
+  addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 1, "Have 1 menu entry for sideloaded extensions");
 
   // Close the hamburger menu and go to the detail page for this addon
   yield PanelUI.hide();
 
   win = yield BrowserOpenAddonsMgr(`addons://detail/${encodeURIComponent(ID4)}`);
   let button = win.document.getElementById("detail-enable-btn");
 
--- a/browser/base/content/test/webextensions/browser_extension_update_background.js
+++ b/browser/base/content/test/webextensions/browser_extension_update_background.js
@@ -76,17 +76,17 @@ function* backgroundUpdateTest(url, id, 
   AddonManagerPrivate.backgroundUpdateCheck();
   yield updatePromise;
 
   is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
 
   // Find the menu entry for the update
   yield PanelUI.show();
 
-  let addons = document.getElementById("PanelUI-footer-addons");
+  let addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 1, "Have a menu entry for the update");
 
   // Click the menu item
   let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
   let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   addons.children[0].click();
 
   // about:addons should load and go to the list of extensions
@@ -116,31 +116,31 @@ function* backgroundUpdateTest(url, id, 
   is(addon.version, "1.0", "Should still be running the old version");
 
   yield BrowserTestUtils.removeTab(tab);
 
   // Alert badge and hamburger menu items should be gone
   is(getBadgeStatus(), "", "Addon alert badge should be gone");
 
   yield PanelUI.show();
-  addons = document.getElementById("PanelUI-footer-addons");
+  addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 0, "Update menu entries should be gone");
   yield PanelUI.hide();
 
   // Re-check for an update
   updatePromise = promiseInstallEvent(addon, "onDownloadEnded");
   yield AddonManagerPrivate.backgroundUpdateCheck();
   yield updatePromise;
 
   is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
 
   // Find the menu entry for the update
   yield PanelUI.show();
 
-  addons = document.getElementById("PanelUI-footer-addons");
+  addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 1, "Have a menu entry for the update");
 
   // Click the menu item
   tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
   popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   addons.children[0].click();
 
   // Wait for about:addons to load
--- a/browser/base/content/test/webextensions/browser_extension_update_background_noprompt.js
+++ b/browser/base/content/test/webextensions/browser_extension_update_background_noprompt.js
@@ -54,17 +54,17 @@ async function testNoPrompt(origUrl, id)
   let updatePromise = waitForUpdate(addon);
   AddonManagerPrivate.backgroundUpdateCheck();
   await updatePromise;
 
   // There should be no notifications about the update
   is(getBadgeStatus(), "", "Should not have addon alert badge");
 
   await PanelUI.show();
-  let addons = document.getElementById("PanelUI-footer-addons");
+  let addons = PanelUI.addonNotificationContainer;
   is(addons.children.length, 0, "Have 0 updates in the PanelUI menu");
   await PanelUI.hide();
 
   ok(!sawPopup, "Should not have seen permissions notification");
 
   addon = await AddonManager.getAddonByID(id);
   is(addon.version, "2.0", "Update should have applied");
 
--- a/browser/base/content/test/webrtc/browser.ini
+++ b/browser/base/content/test/webrtc/browser.ini
@@ -7,15 +7,14 @@ support-files =
 
 [browser_devices_get_user_media.js]
 skip-if = (os == "linux" && debug) # linux: bug 976544
 [browser_devices_get_user_media_anim.js]
 [browser_devices_get_user_media_in_frame.js]
 [browser_devices_get_user_media_multi_process.js]
 skip-if = e10s && (asan || debug) # bug 1347625
 [browser_devices_get_user_media_screen.js]
-skip-if = (os == "linux") || (os == "win" && !debug) # bug 1320994 for linux opt, bug 1338038 for windows and linux debug
 [browser_devices_get_user_media_tear_off_tab.js]
 [browser_devices_get_user_media_unprompted_access.js]
 [browser_devices_get_user_media_unprompted_access_in_frame.js]
 [browser_devices_get_user_media_unprompted_access_tear_off_tab.js]
-skip-if = (os == "linux") || (os == "win" && bits == 64) # linux: bug 1331616, win8: bug 1334752
+skip-if = (os == "win" && bits == 64) # win8: bug 1334752
 [browser_webrtc_hooks.js]
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
@@ -50,17 +50,17 @@ var gTests = [
       is(item.getAttribute("devicetype"), "Screen", "the devicetype attribute is set correctly");
       ok(item.scary, "the screen item is marked as scary");
     }
 
     // Select a screen, a preview with a scary warning should appear.
     menulist.getItemAtIndex(2).doCommand();
     ok(!document.getElementById("webRTC-all-windows-shared").hidden,
        "the 'all windows will be shared' warning should now be visible");
-    yield promiseWaitForCondition(() => !document.getElementById("webRTC-preview").hidden);
+    yield promiseWaitForCondition(() => !document.getElementById("webRTC-preview").hidden, 100);
     ok(!document.getElementById("webRTC-preview").hidden,
        "the preview area is visible");
     ok(!document.getElementById("webRTC-previewWarning").hidden,
        "the scary warning is visible");
 
     // Select the 'No Screen' item again, the preview should be hidden.
     menulist.getItemAtIndex(0).doCommand();
     ok(document.getElementById("webRTC-all-windows-shared").hidden,
@@ -405,30 +405,30 @@ var gTests = [
     yield share(false, true);
     yield check({video: true, audio: true, screen: "Screen"});
 
     info("Stop the screen share, mic+cam should continue");
     yield stopSharing("screen", true);
     yield check({video: true, audio: true});
 
     info("Stop the camera, everything should stop.");
-    yield stopSharing("camera", false, true);
+    yield stopSharing("camera");
 
     info("Now, share only the screen...");
     indicator = promiseIndicatorWindow();
     yield share(false, false, true);
     yield indicator;
     yield check({screen: "Screen"});
 
     info("... and add camera and microphone in a second request.");
     yield share(true, true);
     yield check({video: true, audio: true, screen: "Screen"});
 
     info("Stop the camera, this should stop everything.");
-    yield stopSharing("camera", false, true);
+    yield stopSharing("camera");
   }
 },
 
 {
   desc: "getUserMedia screen: reloading the page removes all gUM UI",
   run: function* checkReloading() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(false, true, null, "screen");
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access.js
@@ -57,17 +57,17 @@ var gTests = [
     });
 
     yield expectObserverCalled("getUserMedia:response:deny");
     SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
 
     // After closing all streams, gUM(audio+camera) causes a prompt.
-    yield closeStream(false, 0, 2);
+    yield closeStream();
     promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, true);
     yield promise;
     yield expectObserverCalled("getUserMedia:request");
     checkDeviceSelectors(true, true);
 
     yield promiseMessage(permissionError, () => {
       activateSecondaryAction(kActionDeny);
@@ -164,17 +164,17 @@ var gTests = [
     yield expectObserverCalled("recording-device-events");
 
     Assert.deepEqual((yield getMediaCaptureState()), {video: true},
                      "expected camera to be shared");
 
     yield checkSharingUI({audio: false, video: true});
 
     // close all streams
-    yield closeStream(false, 0, 2);
+    yield closeStream();
   }
 },
 
 {
   desc: "getUserMedia audio",
   run: function* checkAudioVideoWhileLiveTracksExist_audio() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, false);
@@ -236,17 +236,17 @@ var gTests = [
     yield expectObserverCalled("recording-device-events");
 
     Assert.deepEqual((yield getMediaCaptureState()), {audio: true},
                      "expected microphone to be shared");
 
     yield checkSharingUI({audio: true, video: false});
 
     // close all streams
-    yield closeStream(false, 0, 2);
+    yield closeStream();
   }
 }
 
 ];
 
 add_task(async function test() {
   await runTests(gTests);
 });
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_in_frame.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_in_frame.js
@@ -52,17 +52,17 @@ var gTests = [
     yield promiseRequestDevice(true, true, "frame1");
     yield promise;
     yield expectObserverCalled("getUserMedia:request");
     yield promiseNoPopupNotification("webRTC-shareDevices");
     yield expectObserverCalled("getUserMedia:response:allow");
     yield expectObserverCalled("recording-device-events");
 
     // close the stream
-    yield closeStream(false, "frame1", 2);
+    yield closeStream(false, "frame1");
   }
 },
 
 {
   desc: "getUserMedia audio+camera in frame 1 - part II",
   run: function* checkAudioVideoWhileLiveTracksExist_frame_partII() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, true, "frame1");
@@ -192,17 +192,17 @@ var gTests = [
     yield promiseMessage(permissionError, () => {
       activateSecondaryAction(kActionDeny);
     });
 
     yield expectObserverCalled("getUserMedia:response:deny");
     yield expectObserverCalled("recording-window-ended");
 
     // close the stream
-    yield closeStream(false);
+    yield closeStream();
     SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
   }
 }
 
 ];
 
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_tear_off_tab.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_tear_off_tab.js
@@ -36,17 +36,16 @@ var gTests = [
     yield promiseRequestDevice(true, true, null, null, win.gBrowser.selectedBrowser);
     yield promiseObserverCalled("getUserMedia:request");
     let promises = [promiseNoPopupNotification("webRTC-shareDevices"),
                     promiseObserverCalled("getUserMedia:response:allow"),
                     promiseObserverCalled("recording-device-events")];
     yield Promise.all(promises);
 
     promises = [promiseObserverCalled("recording-device-events"),
-                promiseObserverCalled("recording-device-events"),
                 promiseObserverCalled("recording-window-ended")];
     yield BrowserTestUtils.closeWindow(win);
     yield Promise.all(promises);
 
     yield checkNotSharing();
   }
 }
 
--- a/browser/base/content/test/webrtc/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -25,19 +25,19 @@ function waitForCondition(condition, nex
     if (conditionPassed) {
       moveOn();
     }
     tries++;
   }, 100);
   var moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
-function promiseWaitForCondition(aConditionFn) {
+function promiseWaitForCondition(aConditionFn, retryTimes) {
   let deferred = Promise.defer();
-  waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass.");
+  waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass.", retryTimes);
   return deferred.promise;
 }
 
 /**
  * Waits for a window with the given URL to exist.
  *
  * @param url
  *        The url of the window.
@@ -210,35 +210,27 @@ function expectObserverCalled(aTopic) {
       is(data.count, 1, "expected notification " + aTopic);
       mm.removeMessageListener("Test:ExpectObserverCalled:Reply", listener);
       resolve();
     });
     mm.sendAsyncMessage("Test:ExpectObserverCalled", aTopic);
   });
 }
 
-function expectNoObserverCalled(aIgnoreDeviceEvents = false) {
+function expectNoObserverCalled() {
   return new Promise(resolve => {
     let mm = _mm();
     mm.addMessageListener("Test:ExpectNoObserverCalled:Reply",
                           function listener({data}) {
       mm.removeMessageListener("Test:ExpectNoObserverCalled:Reply", listener);
       for (let topic in data) {
         if (!data[topic])
           continue;
 
-        // If we are stopping tracks that were created from 2 different
-        // getUserMedia calls, the "recording-device-events" notification is
-        // fired twice on Windows and Mac, and intermittently twice on Linux.
-        if (topic == "recording-device-events" && aIgnoreDeviceEvents) {
-          todo(false, "Got " + data[topic] + " unexpected " + topic +
-               " notifications, see bug 1320994");
-        } else {
-          is(data[topic], 0, topic + " notification unexpected");
-        }
+        is(data[topic], 0, topic + " notification unexpected");
       }
       resolve();
     });
     mm.sendAsyncMessage("Test:ExpectNoObserverCalled");
   });
 }
 
 function ignoreObserversCalled() {
@@ -349,35 +341,34 @@ function getMediaCaptureState() {
     let mm = _mm();
     mm.addMessageListener("Test:MediaCaptureState", ({data}) => {
       resolve(data);
     });
     mm.sendAsyncMessage("Test:GetMediaCaptureState");
   });
 }
 
-function* stopSharing(aType = "camera", aShouldKeepSharing = false,
-                      aExpectDoubleRecordingEvent = false) {
+function* stopSharing(aType = "camera", aShouldKeepSharing = false) {
   let promiseRecordingEvent = promiseObserverCalled("recording-device-events");
   gIdentityHandler._identityBox.click();
   let permissions = document.getElementById("identity-popup-permission-list");
   let cancelButton =
     permissions.querySelector(".identity-popup-permission-icon." + aType + "-icon ~ " +
                               ".identity-popup-permission-remove-button");
   cancelButton.click();
   gIdentityHandler._identityPopup.hidden = true;
   yield promiseRecordingEvent;
   yield expectObserverCalled("getUserMedia:revoke");
 
   // If we are stopping screen sharing and expect to still have another stream,
   // "recording-window-ended" won't be fired.
   if (!aShouldKeepSharing)
     yield expectObserverCalled("recording-window-ended");
 
-  yield expectNoObserverCalled(aExpectDoubleRecordingEvent);
+  yield expectNoObserverCalled();
 
   if (!aShouldKeepSharing)
     yield* checkNotSharing();
 }
 
 function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId, aType,
                               aBrowser = gBrowser.selectedBrowser) {
   info("requesting devices");
@@ -386,26 +377,23 @@ function promiseRequestDevice(aRequestAu
                            function*(args) {
     let global = content.wrappedJSObject;
     if (args.aFrameId)
       global = global.document.getElementById(args.aFrameId).contentWindow;
     global.requestDevice(args.aRequestAudio, args.aRequestVideo, args.aType);
   });
 }
 
-function* closeStream(aAlreadyClosed, aFrameId, aStreamCount = 1) {
+function* closeStream(aAlreadyClosed, aFrameId) {
   yield expectNoObserverCalled();
 
   let promises;
   if (!aAlreadyClosed) {
-    promises = [];
-    for (let i = 0; i < aStreamCount; i++) {
-      promises.push(promiseObserverCalled("recording-device-events"));
-    }
-    promises.push(promiseObserverCalled("recording-window-ended"));
+    promises = [promiseObserverCalled("recording-device-events"),
+                promiseObserverCalled("recording-window-ended")];
   }
 
   info("closing the stream");
   yield ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(contentFrameId) {
     let global = content.wrappedJSObject;
     if (contentFrameId)
       global = global.document.getElementById(contentFrameId).contentWindow;
     global.closeStream();
--- a/browser/base/content/test/windows/browser_toolbariconcolor_restyles.js
+++ b/browser/base/content/test/windows/browser_toolbariconcolor_restyles.js
@@ -12,16 +12,26 @@ add_task(function* test_toolbar_element_
   // create a window and snapshot the elementsStyled
   let win1 = yield BrowserTestUtils.openNewBrowserWindow();
   yield new Promise(resolve => waitForFocus(resolve, win1));
 
   // create a 2nd window and snapshot the elementsStyled
   let win2 = yield BrowserTestUtils.openNewBrowserWindow();
   yield new Promise(resolve => waitForFocus(resolve, win2));
 
+  // Flush any pending styles before we take a measurement.
+  win1.getComputedStyle(win1.document.firstElementChild);
+  win2.getComputedStyle(win2.document.firstElementChild);
+
+  // Clear the focused element from each window so that when
+  // we raise them, the focus of the element doesn't cause an
+  // unrelated style flush.
+  Services.focus.clearFocus(win1);
+  Services.focus.clearFocus(win2);
+
   let utils1 = SpecialPowers.getDOMWindowUtils(win1);
   restyles.win1.initial = utils1.elementsRestyled;
 
   let utils2 = SpecialPowers.getDOMWindowUtils(win2);
   restyles.win2.initial = utils2.elementsRestyled;
 
   // switch back to 1st window, and snapshot elementsStyled
   Services.focus.activeWindow = win1;
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -112,19 +112,16 @@ browser.jar:
         content/browser/defaultthemes/4.preview.png   (content/defaultthemes/4.preview.png)
         content/browser/defaultthemes/5.footer.png    (content/defaultthemes/5.footer.png)
         content/browser/defaultthemes/5.header.png    (content/defaultthemes/5.header.png)
         content/browser/defaultthemes/5.icon.jpg      (content/defaultthemes/5.icon.jpg)
         content/browser/defaultthemes/5.preview.jpg   (content/defaultthemes/5.preview.jpg)
         content/browser/defaultthemes/compact.header.png    (content/defaultthemes/compact.header.png)
         content/browser/defaultthemes/compactdark.icon.svg  (content/defaultthemes/compactdark.icon.svg)
         content/browser/defaultthemes/compactlight.icon.svg (content/defaultthemes/compactlight.icon.svg)
-        content/browser/gcli_sec_bad.svg              (content/gcli_sec_bad.svg)
-        content/browser/gcli_sec_good.svg             (content/gcli_sec_good.svg)
-        content/browser/gcli_sec_moderate.svg         (content/gcli_sec_moderate.svg)
         content/browser/newtab/newTab.xhtml           (content/newtab/newTab.xhtml)
 *       content/browser/newtab/newTab.js              (content/newtab/newTab.js)
         content/browser/newtab/newTab.css             (content/newtab/newTab.css)
         content/browser/newtab/newTab.inadjacent.json         (content/newtab/newTab.inadjacent.json)
         content/browser/newtab/alternativeDefaultSites.json   (content/newtab/alternativeDefaultSites.json)
 *       content/browser/pageinfo/pageInfo.xul         (content/pageinfo/pageInfo.xul)
         content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
         content/browser/pageinfo/pageInfo.css         (content/pageinfo/pageInfo.css)
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -12,16 +12,17 @@ MOCHITEST_MANIFESTS += [
 
 MOCHITEST_CHROME_MANIFESTS += [
     'content/test/chrome/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
     'content/test/alerts/browser.ini',
     'content/test/captivePortal/browser.ini',
+    'content/test/contextMenu/browser.ini',
     'content/test/forms/browser.ini',
     'content/test/general/browser.ini',
     'content/test/newtab/browser.ini',
     'content/test/pageinfo/browser.ini',
     'content/test/permissions/browser.ini',
     'content/test/plugins/browser.ini',
     'content/test/popupNotifications/browser.ini',
     'content/test/popups/browser.ini',
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -167,37 +167,33 @@ AboutRedirector::NewChannel(nsIURI* aURI
         url.AssignASCII(redir.url);
       }
 
       nsCOMPtr<nsIChannel> tempChannel;
       nsCOMPtr<nsIURI> tempURI;
       rv = NS_NewURI(getter_AddRefs(tempURI), url);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      // If tempURI links to an external URI (i.e. something other than
-      // chrome:// or resource://) then set the LOAD_REPLACE flag on the
-      // channel which forces the channel owner to reflect the displayed
-      // URL rather then being the systemPrincipal.
+      // If tempURI links to an internal URI (chrome://, resource://)
+      // then set the result principal URL on the channel's load info.
+      // Otherwise, we leave it null which forces the channel principal
+      // to reflect the displayed URL rather than being the systemPrincipal.
       bool isUIResource = false;
       rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                &isUIResource);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      nsLoadFlags loadFlags = isUIResource
-                    ? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL)
-                    : static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
-
       rv = NS_NewChannelInternal(getter_AddRefs(tempChannel),
                                  tempURI,
-                                 aLoadInfo,
-                                 nullptr, // aLoadGroup
-                                 nullptr, // aCallbacks
-                                 loadFlags);
+                                 aLoadInfo);
       NS_ENSURE_SUCCESS(rv, rv);
 
+      if (isUIResource) {
+        aLoadInfo->SetResultPrincipalURI(aURI);
+      }
       tempChannel->SetOriginalURI(aURI);
 
       NS_ADDREF(*result = tempChannel);
       return rv;
     }
   }
 
   return NS_ERROR_ILLEGAL_VALUE;
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -12,27 +12,22 @@
   <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
     <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
       <vbox id="PanelUI-contents-scroller">
         <vbox id="PanelUI-contents" class="panelUI-grid"/>
       </vbox>
 
       <footer id="PanelUI-footer">
         <vbox id="PanelUI-footer-addons"></vbox>
-        <toolbarbutton id="PanelUI-update-available-menu-item"
-                       wrap="true"
-                       label="&updateAvailable.panelUI.label;"
-                       hidden="true"/>
-        <toolbarbutton id="PanelUI-update-manual-menu-item"
+        <toolbarbutton class="panel-banner-item"
+                       label-update-available="&updateAvailable.panelUI.label;"
+                       label-update-manual="&updateManual.panelUI.label;"
+                       label-update-restart="&updateRestart.panelUI.label;"
+                       oncommand="PanelUI._onBannerItemSelected(event)"
                        wrap="true"
-                       label="&updateManual.panelUI.label;"
-                       hidden="true"/>
-        <toolbarbutton id="PanelUI-update-restart-menu-item"
-                       wrap="true"
-                       label="&updateRestart.panelUI.label;"
                        hidden="true"/>
         <hbox id="PanelUI-footer-fxa">
           <hbox id="PanelUI-fxa-status"
                 label="&fxaSignedIn.tooltip;"
                 defaultlabel="&fxaSignIn.label;"
                 signedinTooltiptext="&fxaSignedIn.tooltip;"
                 tooltiptext="&fxaSignedIn.tooltip;"
                 errorlabel="&fxaSignInError.label;"
@@ -426,27 +421,27 @@
       <description>&panicButton.thankyou.msg2;</description>
     </vbox>
   </hbox>
   <button label="&panicButton.thankyou.buttonlabel;"
           id="panic-button-success-closebutton"
           oncommand="PanicButtonNotifier.close()"/>
 </panel>
 
-<panel id="PanelUI-notification-popup"
+<panel id="appMenu-notification-popup"
        class="popup-notification-panel"
        type="arrow"
        position="after_start"
        hidden="true"
        orient="vertical"
        noautofocus="true"
        noautohide="true"
        nopreventnavboxhide="true"
        role="alert">
-  <popupnotification id="PanelUI-update-available-notification"
+  <popupnotification id="appMenu-update-available-notification"
                      popupid="update-available"
                      label="&updateAvailable.header.message;"
                      buttonlabel="&updateAvailable.acceptButton.label;"
                      buttonaccesskey="&updateAvailable.acceptButton.accesskey;"
                      closebuttonhidden="true"
                      secondarybuttonlabel="&updateAvailable.cancelButton.label;"
                      secondarybuttonaccesskey="&updateAvailable.cancelButton.accesskey;"
                      dropmarkerhidden="true"
@@ -454,17 +449,17 @@
                      hidden="true">
     <popupnotificationcontent id="update-available-notification-content" orient="vertical">
       <description id="update-available-description">&updateAvailable.message;
         <label id="update-available-whats-new" class="text-link" value="&updateAvailable.whatsnew.label;" />
       </description>
     </popupnotificationcontent>
   </popupnotification>
 
-  <popupnotification id="PanelUI-update-manual-notification"
+  <popupnotification id="appMenu-update-manual-notification"
                      popupid="update-manual"
                      label="&updateManual.header.message;"
                      buttonlabel="&updateManual.acceptButton.label;"
                      buttonaccesskey="&updateManual.acceptButton.accesskey;"
                      closebuttonhidden="true"
                      secondarybuttonlabel="&updateManual.cancelButton.label;"
                      secondarybuttonaccesskey="&updateManual.cancelButton.accesskey;"
                      dropmarkerhidden="true"
@@ -472,17 +467,17 @@
                      hidden="true">
     <popupnotificationcontent id="update-manual-notification-content" orient="vertical">
       <description id="update-manual-description">&updateManual.message;
         <label id="update-manual-whats-new" class="text-link" value="&updateManual.whatsnew.label;" />
       </description>
     </popupnotificationcontent>
   </popupnotification>
 
-  <popupnotification id="PanelUI-update-restart-notification"
+  <popupnotification id="appMenu-update-restart-notification"
                      popupid="update-restart"
                      label="&updateRestart.header.message2;"
                      buttonlabel="&updateRestart.acceptButton.label;"
                      buttonaccesskey="&updateRestart.acceptButton.accesskey;"
                      closebuttonhidden="true"
                      secondarybuttonlabel="&updateRestart.cancelButton.label;"
                      secondarybuttonaccesskey="&updateRestart.cancelButton.accesskey;"
                      dropmarkerhidden="true"
@@ -500,16 +495,24 @@
        type="arrow"
        hidden="true"
        flip="slide"
        position="bottomcenter topright"
        noautofocus="true">
   <panelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView">
     <panelview id="appMenu-mainView" class="cui-widget-panelview PanelUI-subView">
       <vbox class="panel-subview-body">
+        <vbox id="appMenu-addon-banners"/>
+        <toolbarbutton class="panel-banner-item"
+                       label-update-available="&updateAvailable.panelUI.label;"
+                       label-update-manual="&updateManual.panelUI.label;"
+                       label-update-restart="&updateRestart.panelUI.label;"
+                       oncommand="PanelUI._onBannerItemSelected(event)"
+                       wrap="true"
+                       hidden="true"/>
         <toolbarbutton id="appMenu-new-window-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&newNavigatorCmd.label;"
                        key="key_newNavigator"
                        command="cmd_newNavigator"/>
         <toolbarbutton id="appMenu-private-window-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&newPrivateWindow.label;"
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -30,19 +30,19 @@ const PanelUI = {
   get kElements() {
     return {
       contents: "PanelUI-contents",
       mainView: gPhotonStructure ? "appMenu-mainView" : "PanelUI-mainView",
       multiView: gPhotonStructure ? "appMenu-multiView" : "PanelUI-multiView",
       helpView: "PanelUI-helpView",
       menuButton: "PanelUI-menu-button",
       panel: gPhotonStructure ? "appMenu-popup" : "PanelUI-popup",
-      notificationPanel: "PanelUI-notification-popup",
+      notificationPanel: "appMenu-notification-popup",
       scroller: "PanelUI-contents-scroller",
-      footer: "PanelUI-footer",
+      addonNotificationContainer: gPhotonStructure ? "appMenu-addon-banners" : "PanelUI-footer-addons",
 
       overflowFixedList: gPhotonStructure ? "widget-overflow-fixed-list" : "",
     };
   },
 
   _initialized: false,
   init() {
     for (let [k, v] of Object.entries(this.kElements)) {
@@ -698,31 +698,31 @@ const PanelUI = {
 
     if (this.panel.state == "showing" || this.panel.state == "open") {
       // If the menu is already showing, then we need to dismiss all notifications
       // since we don't want their doorhangers competing for attention
       doorhangers.forEach(n => { n.dismissed = true; })
       this._hidePopup();
       this._clearBadge();
       if (!this.notifications[0].options.badgeOnly) {
-        this._showMenuItem(this.notifications[0]);
+        this._showBannerItem(this.notifications[0]);
       }
     } else if (doorhangers.length > 0) {
       if (window.fullScreen) {
         this._hidePopup();
         this._showBadge(doorhangers[0]);
-        this._showMenuItem(doorhangers[0]);
+        this._showBannerItem(doorhangers[0]);
       } else {
         this._clearBadge();
         this._showNotificationPanel(doorhangers[0]);
       }
     } else {
       this._hidePopup();
       this._showBadge(this.notifications[0]);
-      this._showMenuItem(this.notifications[0]);
+      this._showBannerItem(this.notifications[0]);
     }
   },
 
   _showNotificationPanel(notification) {
     this._refreshNotificationPanel(notification);
 
     if (this.isNotificationPanelOpen) {
       return;
@@ -739,17 +739,17 @@ const PanelUI = {
       popupnotification.hidden = true;
       popupnotification.notification = null;
     }
   },
 
   _clearAllNotifications() {
     this._clearNotificationPanel();
     this._clearBadge();
-    this._clearMenuItems();
+    this._clearBannerItem();
   },
 
   _refreshNotificationPanel(notification) {
     this._clearNotificationPanel();
 
     let popupnotificationID = this._getPopupId(notification);
     let popupnotification = document.getElementById(popupnotificationID);
 
@@ -761,42 +761,41 @@ const PanelUI = {
     popupnotification.hidden = false;
   },
 
   _showBadge(notification) {
     let badgeStatus = this._getBadgeStatus(notification);
     this.menuButton.setAttribute("badge-status", badgeStatus);
   },
 
-  // "Menu item" here refers to an item in the hamburger panel menu. They will
-  // typically show up as a colored row near the bottom of the panel.
-  _showMenuItem(notification) {
-    this._clearMenuItems();
-
-    let menuItemId = this._getMenuItemId(notification);
-    let menuItem = document.getElementById(menuItemId);
-    if (menuItem) {
-      menuItem.notification = notification;
-      menuItem.setAttribute("oncommand", "PanelUI._onNotificationMenuItemSelected(event)");
-      menuItem.classList.add("PanelUI-notification-menu-item");
-      menuItem.hidden = false;
-      menuItem.fromPanelUINotifications = true;
+  // "Banner item" here refers to an item in the hamburger panel menu. They will
+  // typically show up as a colored row in the panel.
+  _showBannerItem(notification) {
+    if (!this._panelBannerItem) {
+      this._panelBannerItem = this.mainView.querySelector(".panel-banner-item");
     }
+    let label = this._panelBannerItem.getAttribute("label-" + notification.id);
+    // Ignore items we don't know about.
+    if (!label) {
+      return;
+    }
+    this._panelBannerItem.setAttribute("notificationid", notification.id);
+    this._panelBannerItem.setAttribute("label", label);
+    this._panelBannerItem.hidden = false;
+    this._panelBannerItem.notification = notification;
   },
 
   _clearBadge() {
     this.menuButton.removeAttribute("badge-status");
   },
 
-  _clearMenuItems() {
-    for (let child of this.footer.children) {
-      if (child.fromPanelUINotifications) {
-        child.notification = null;
-        child.hidden = true;
-      }
+  _clearBannerItem() {
+    if (this._panelBannerItem) {
+      this._panelBannerItem.notification = null;
+      this._panelBannerItem.hidden = true;
     }
   },
 
   _removeNotification(notification) {
     // This notification may already be removed, in which case let's just fail
     // silently.
     let notifications = this.notifications;
     if (!notifications)
@@ -847,17 +846,17 @@ const PanelUI = {
       notification.dismissed = true;
       this._notify(notification.id, "dismissed");
     } else {
       this._removeNotification(notification);
     }
     this._updateNotifications();
   },
 
-  _onNotificationMenuItemSelected(event) {
+  _onBannerItemSelected(event) {
     let target = event.originalTarget;
     if (!target.notification)
       throw "menucommand target has no associated action/notification";
 
     event.stopPropagation();
 
     try {
       target.notification.mainAction.callback(false);
@@ -865,22 +864,20 @@ const PanelUI = {
     } catch (error) {
       Cu.reportError(error);
     }
 
     this._removeNotification(target.notification);
     this._updateNotifications();
   },
 
-  _getPopupId(notification) { return "PanelUI-" + notification.id + "-notification"; },
+  _getPopupId(notification) { return "appMenu-" + notification.id + "-notification"; },
 
   _getBadgeStatus(notification) { return notification.id; },
 
-  _getMenuItemId(notification) { return "PanelUI-" + notification.id + "-menu-item"; },
-
   _getPanelAnchor(candidate) {
     let iconAnchor =
       document.getAnonymousElementByAttribute(candidate, "class",
                                               "toolbarbutton-icon");
     return iconAnchor || candidate;
   },
 
   _addedShortcuts: false,
--- a/browser/components/customizableui/test/browser_panelUINotifications.js
+++ b/browser/components/customizableui/test/browser_panelUINotifications.js
@@ -27,17 +27,17 @@ add_task(function* testMainActionCalled(
       callback: () => { extraMainActionCalled = true; }
     };
     extraWindow.PanelUI.showNotification("update-manual", extraMainAction)
 
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     let mainActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
     mainActionButton.click();
 
     ok(mainActionCalled, "Main action callback was called");
     isnot(extraMainActionCalled, true, "Extra window's main action callback was not called");
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(extraWindow.PanelUI.notificationPanel.state, "closed", "Extra window's update-manual doorhanger is closed.");
@@ -77,29 +77,30 @@ add_task(function* testSecondaryActionWo
       callback: () => { extraMainActionCalled = true; }
     };
     extraWindow.PanelUI.showNotification("update-manual", extraMainAction)
 
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     let secondaryActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
     secondaryActionButton.click();
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(extraWindow.PanelUI.notificationPanel.state, "closed", "Extra window's update-manual doorhanger is closed.");
 
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is displaying on PanelUI button.");
 
     yield PanelUI.show();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is hidden on PanelUI button.");
-    let menuItem = doc.getElementById("PanelUI-update-manual-menu-item");
+    let menuItem = PanelUI.mainView.querySelector(".panel-banner-item");
+    is(menuItem.label, menuItem.getAttribute("label-update-manual"), "Showing correct label");
     is(menuItem.hidden, false, "update-manual menu item is showing.");
 
     yield PanelUI.hide();
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is shown on PanelUI button.");
 
     yield PanelUI.show();
     menuItem.click();
     ok(mainActionCalled, "Main action callback was called");
@@ -131,28 +132,29 @@ add_task(function* testInteractionWithBa
     };
     PanelUI.showNotification("update-manual", mainAction);
 
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is hidden on PanelUI button.");
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     let secondaryActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
     secondaryActionButton.click();
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
 
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is displaying on PanelUI button.");
 
     yield PanelUI.show();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is hidden on PanelUI button.");
-    let menuItem = doc.getElementById("PanelUI-update-manual-menu-item");
+    let menuItem = PanelUI.mainView.querySelector(".panel-banner-item");
+    is(menuItem.label, menuItem.getAttribute("label-update-manual"), "Showing correct label");
     is(menuItem.hidden, false, "update-manual menu item is showing.");
 
     menuItem.click();
     ok(mainActionCalled, "Main action callback was called");
 
     is(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is shown on PanelUI button.");
     PanelUI.removeNotification(/.*/);
     is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
@@ -174,17 +176,17 @@ add_task(function* testAddingBadgeWhileD
     PanelUI.showNotification("update-manual", mainAction);
     PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
 
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is hidden on PanelUI button.");
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     let mainActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
     mainActionButton.click();
 
     ok(mainActionCalled, "Main action callback was called");
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is shown on PanelUI button.");
     PanelUI.removeNotification(/.*/);
@@ -260,48 +262,47 @@ add_task(function* testMultipleNonBadges
 
     let notifications;
     let doorhanger;
 
     isnot(PanelUI.notificationPanel.state, "closed", "Doorhanger is showing.");
     notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     PanelUI.showNotification("update-restart", updateRestartAction);
 
     isnot(PanelUI.notificationPanel.state, "closed", "Doorhanger is showing.");
     notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     doorhanger = notifications[0];
-    is(doorhanger.id, "PanelUI-update-restart-notification", "PanelUI is displaying the update-restart notification.");
+    is(doorhanger.id, "appMenu-update-restart-notification", "PanelUI is displaying the update-restart notification.");
 
     let secondaryActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
     secondaryActionButton.click();
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-restart", "update-restart badge is displaying on PanelUI button.");
 
-    let menuItem;
-
     yield PanelUI.show();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-restart", "update-restart badge is hidden on PanelUI button.");
-    menuItem = doc.getElementById("PanelUI-update-restart-menu-item");
+    let menuItem = PanelUI.mainView.querySelector(".panel-banner-item");
+    is(menuItem.label, menuItem.getAttribute("label-update-restart"), "Showing correct label");
     is(menuItem.hidden, false, "update-restart menu item is showing.");
 
     menuItem.click();
     ok(updateRestartAction.called, "update-restart main action callback was called");
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "update-manual badge is displaying on PanelUI button.");
 
     yield PanelUI.show();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "update-manual badge is hidden on PanelUI button.");
-    menuItem = doc.getElementById("PanelUI-update-manual-menu-item");
+    is(menuItem.label, menuItem.getAttribute("label-update-manual"), "Showing correct label");
     is(menuItem.hidden, false, "update-manual menu item is showing.");
 
     menuItem.click();
     ok(updateManualAction.called, "update-manual main action callback was called");
   });
 });
 
 add_task(function* testFullscreen() {
@@ -313,17 +314,17 @@ add_task(function* testFullscreen() {
     callback: () => { mainActionCalled = true; }
   };
   PanelUI.showNotification("update-manual", mainAction);
 
   isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
   let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
   is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
   let doorhanger = notifications[0];
-  is(doorhanger.id, "PanelUI-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+  is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
   let popuphiddenPromise = BrowserTestUtils.waitForEvent(PanelUI.notificationPanel, "popuphidden");
   EventUtils.synthesizeKey("VK_F11", {});
   yield popuphiddenPromise;
   yield new Promise(executeSoon);
   is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
 
   window.FullScreen.showNavToolbox();
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -66,18 +66,16 @@ XPCOMUtils.defineLazyGetter(this, "Downl
   let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
   let consoleOptions = {
     maxLogLevelPref: "browser.download.loglevel",
     prefix: "Downloads"
   };
   return new ConsoleAPI(consoleOptions);
 });
 
-const nsIDM = Ci.nsIDownloadManager;
-
 const kDownloadsStringBundleUrl =
   "chrome://browser/locale/downloads/downloads.properties";
 
 const kDownloadsStringsRequiringFormatting = {
   sizeWithUnits: true,
   shortTimeLeftSeconds: true,
   shortTimeLeftMinutes: true,
   shortTimeLeftHours: true,
@@ -123,27 +121,39 @@ var PrefObserver = {
       });
     }
   },
 };
 
 PrefObserver.register({
   // prefName: defaultValue
   animateNotifications: true,
-  showPanelDropmarker: true,
 });
 
 
 // DownloadsCommon
 
 /**
  * This object is exposed directly to the consumers of this JavaScript module,
  * and provides shared methods for all the instances of the user interface.
  */
 this.DownloadsCommon = {
+  // The following legacy constants are still returned by stateOfDownload, but
+  // individual properties of the Download object should normally be used.
+  DOWNLOAD_NOTSTARTED: -1,
+  DOWNLOAD_DOWNLOADING: 0,
+  DOWNLOAD_FINISHED: 1,
+  DOWNLOAD_FAILED: 2,
+  DOWNLOAD_CANCELED: 3,
+  DOWNLOAD_PAUSED: 4,
+  DOWNLOAD_BLOCKED_PARENTAL: 6,
+  DOWNLOAD_DIRTY: 8,
+  DOWNLOAD_BLOCKED_POLICY: 9,
+
+  // The following are the possible values of the "attention" property.
   ATTENTION_NONE: "",
   ATTENTION_SUCCESS: "success",
   ATTENTION_WARNING: "warning",
   ATTENTION_SEVERE: "severe",
 
   /**
    * This can be used by add-on experiments as a killswitch for the new style
    * progress indication. This will be removed in bug 1329109 after the new
@@ -220,23 +230,16 @@ this.DownloadsCommon = {
    * Indicates whether we should show visual notification on the indicator
    * when a download event is triggered.
    */
   get animateNotifications() {
     return PrefObserver.animateNotifications;
   },
 
   /**
-   * Indicates whether we should show the dropmarker in the Downloads Panel.
-   */
-  get showPanelDropmarker() {
-    return PrefObserver.showPanelDropmarker;
-  },
-
-  /**
    * Get access to one of the DownloadsData or PrivateDownloadsData objects,
    * depending on the privacy status of the window in question.
    *
    * @param aWindow
    *        The browser window which owns the download button.
    */
   getData(aWindow) {
     if (PrivateBrowsingUtils.isContentWindowPrivate(aWindow)) {
@@ -292,37 +295,37 @@ this.DownloadsCommon = {
   _privateSummary: null,
 
   /**
    * Returns the legacy state integer value for the provided Download object.
    */
   stateOfDownload(download) {
     // Collapse state using the correct priority.
     if (!download.stopped) {
-      return nsIDM.DOWNLOAD_DOWNLOADING;
+      return DownloadsCommon.DOWNLOAD_DOWNLOADING;
     }
     if (download.succeeded) {
-      return nsIDM.DOWNLOAD_FINISHED;
+      return DownloadsCommon.DOWNLOAD_FINISHED;
     }
     if (download.error) {
       if (download.error.becauseBlockedByParentalControls) {
-        return nsIDM.DOWNLOAD_BLOCKED_PARENTAL;
+        return DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL;
       }
       if (download.error.becauseBlockedByReputationCheck) {
-        return nsIDM.DOWNLOAD_DIRTY;
+        return DownloadsCommon.DOWNLOAD_DIRTY;
       }
-      return nsIDM.DOWNLOAD_FAILED;
+      return DownloadsCommon.DOWNLOAD_FAILED;
     }
     if (download.canceled) {
       if (download.hasPartialData) {
-        return nsIDM.DOWNLOAD_PAUSED;
+        return DownloadsCommon.DOWNLOAD_PAUSED;
       }
-      return nsIDM.DOWNLOAD_CANCELED;
+      return DownloadsCommon.DOWNLOAD_CANCELED;
     }
-    return nsIDM.DOWNLOAD_NOTSTARTED;
+    return DownloadsCommon.DOWNLOAD_NOTSTARTED;
   },
 
   /**
    * Helper function required because the Downloads Panel and the Downloads View
    * don't share the controller yet.
    */
   removeAndFinalizeDownload(download) {
     Downloads.getList(Downloads.ALL)
--- a/browser/components/downloads/DownloadsViewUI.jsm
+++ b/browser/components/downloads/DownloadsViewUI.jsm
@@ -320,28 +320,28 @@ this.DownloadsViewUI.DownloadElementShel
   /**
    * Returns the name of the default command to use for the current state of the
    * download, when there is a double click or another default interaction. If
    * there is no default command for the current state, returns an empty string.
    * The commands are implemented as functions on this object or derived ones.
    */
   get currentDefaultCommandName() {
     switch (DownloadsCommon.stateOfDownload(this.download)) {
-      case Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED:
+      case DownloadsCommon.DOWNLOAD_NOTSTARTED:
         return "downloadsCmd_cancel";
-      case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
-      case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
+      case DownloadsCommon.DOWNLOAD_FAILED:
+      case DownloadsCommon.DOWNLOAD_CANCELED:
         return "downloadsCmd_retry";
-      case Ci.nsIDownloadManager.DOWNLOAD_PAUSED:
+      case DownloadsCommon.DOWNLOAD_PAUSED:
         return "downloadsCmd_pauseResume";
-      case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
+      case DownloadsCommon.DOWNLOAD_FINISHED:
         return "downloadsCmd_open";
-      case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
+      case DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL:
         return "downloadsCmd_openReferrer";
-      case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
+      case DownloadsCommon.DOWNLOAD_DIRTY:
         return "downloadsCmd_showBlockedInfo";
     }
     return "";
   },
 
   /**
    * Returns true if the specified command can be invoked on the current item.
    * The commands are implemented as functions on this object or derived ones.
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -25,18 +25,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
-const nsIDM = Ci.nsIDownloadManager;
-
 const DESTINATION_FILE_URI_ANNO  = "downloads/destinationFileURI";
 const DOWNLOAD_META_DATA_ANNO    = "downloads/metaData";
 
 /**
  * Represents a download from the browser history. It implements part of the
  * interface of the Download object.
  *
  * @param aPlacesNode
@@ -67,27 +65,27 @@ HistoryDownload.prototype = {
       this.target.path = Cc["@mozilla.org/network/protocol;1?name=file"]
                            .getService(Ci.nsIFileProtocolHandler)
                            .getFileFromURLSpec(metaData.targetFileSpec).path;
     } catch (ex) {
       this.target.path = undefined;
     }
 
     if ("state" in metaData) {
-      this.succeeded = metaData.state == nsIDM.DOWNLOAD_FINISHED;
-      this.canceled = metaData.state == nsIDM.DOWNLOAD_CANCELED ||
-                      metaData.state == nsIDM.DOWNLOAD_PAUSED;
+      this.succeeded = metaData.state == DownloadsCommon.DOWNLOAD_FINISHED;
+      this.canceled = metaData.state == DownloadsCommon.DOWNLOAD_CANCELED ||
+                      metaData.state == DownloadsCommon.DOWNLOAD_PAUSED;
       this.endTime = metaData.endTime;
 
       // Recreate partial error information from the state saved in history.
-      if (metaData.state == nsIDM.DOWNLOAD_FAILED) {
+      if (metaData.state == DownloadsCommon.DOWNLOAD_FAILED) {
         this.error = { message: "History download failed." };
-      } else if (metaData.state == nsIDM.DOWNLOAD_BLOCKED_PARENTAL) {
+      } else if (metaData.state == DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL) {
         this.error = { becauseBlockedByParentalControls: true };
-      } else if (metaData.state == nsIDM.DOWNLOAD_DIRTY) {
+      } else if (metaData.state == DownloadsCommon.DOWNLOAD_DIRTY) {
         this.error = {
           becauseBlockedByReputationCheck: true,
           reputationCheckVerdict: metaData.reputationCheckVerdict || "",
         };
       } else {
         this.error = null;
       }
 
--- a/browser/components/downloads/content/downloads.css
+++ b/browser/components/downloads/content/downloads.css
@@ -8,35 +8,29 @@ richlistitem[type="download"] {
   -moz-binding: url('chrome://browser/content/downloads/download.xml#download');
 }
 
 richlistitem[type="download"]:not([selected]) button {
   /* Only focus buttons in the selected item. */
   -moz-user-focus: none;
 }
 
-.downloadsHideDropmarker > #downloadsFooterButtonsSplitter,
-.downloadsHideDropmarker > #downloadsFooterDropmarker {
-  display: none;
-}
-
 richlistitem[type="download"].download-state[state="1"]:not([exists]) > .downloadButtonArea,
 richlistitem[type="download"].download-state[state="1"]:not([exists]) > toolbarseparator {
   display: none;
 }
 
 #downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress,
 #downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails,
 #downloadsFooter:not([showingsummary]) #downloadsSummary {
   display: none;
 }
 
-#downloadsFooter[showingdropdown] > stack > #downloadsSummary,
 #downloadsFooter[showingsummary] > stack:hover > #downloadsSummary,
-#downloadsFooter[showingsummary]:not([showingdropdown]) > stack:not(:hover) > #downloadsFooterButtons {
+#downloadsFooter[showingsummary] > stack:not(:hover) > #downloadsFooterButtons {
   /* If we used "visibility: hidden;" then the mouseenter event of
      #downloadsHistory wouldn't be triggered immediately, and the hover styling
      of the button would not apply until the mouse is moved again.
 
      "-moz-user-focus: ignore;" prevents the elements with "opacity: 0;" from
      being focused with the keyboard. */
   opacity: 0;
   -moz-user-focus: ignore;
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -219,24 +219,16 @@ const DownloadsPanel = {
 
     if (this.isPanelShowing) {
       DownloadsCommon.log("Panel is already showing - focusing instead.");
       this._focusPanel();
       return;
     }
 
     this.initialize(() => {
-      let downloadsFooterButtons =
-        document.getElementById("downloadsFooterButtons");
-      if (DownloadsCommon.showPanelDropmarker) {
-        downloadsFooterButtons.classList.remove("downloadsHideDropmarker");
-      } else {
-        downloadsFooterButtons.classList.add("downloadsHideDropmarker");
-      }
-
       // Delay displaying the panel because this function will sometimes be
       // called while another window is closing (like the window for selecting
       // whether to save or open the file), and that would cause the panel to
       // close immediately.
       setTimeout(() => this._openPopupIfDataReady(), 0);
     });
 
     DownloadsCommon.log("Waiting for the downloads panel to appear.");
@@ -382,55 +374,30 @@ const DownloadsPanel = {
 
     // Allow the anchor to be hidden.
     DownloadsButton.releaseAnchor();
 
     // Allow the panel to be reopened.
     this._state = this.kStateHidden;
   },
 
-  onFooterPopupShowing(aEvent) {
-    let itemClearList = document.getElementById("downloadsDropdownItemClearList");
-    if (DownloadsCommon.getData(window).canRemoveFinished) {
-      itemClearList.removeAttribute("hidden");
-    } else {
-      itemClearList.setAttribute("hidden", "true");
-    }
-    DownloadsViewController.updateCommands();
-
-    document.getElementById("downloadsFooter")
-      .setAttribute("showingdropdown", true);
-  },
-
-  onFooterPopupHidden(aEvent) {
-    document.getElementById("downloadsFooter")
-      .removeAttribute("showingdropdown");
-  },
-
   // Related operations
 
   /**
    * Shows or focuses the user interface dedicated to downloads history.
    */
   showDownloadsHistory() {
     DownloadsCommon.log("Showing download history.");
     // Hide the panel before showing another window, otherwise focus will return
     // to the browser window when the panel closes automatically.
     this.hidePanel();
 
     BrowserDownloadsUI();
   },
 
-  openDownloadsFolder() {
-    Downloads.getPreferredDownloadsDirectory().then(downloadsPath => {
-      DownloadsCommon.showDirectory(new FileUtils.File(downloadsPath));
-    }).catch(Cu.reportError);
-    this.hidePanel();
-  },
-
   // Internal functions
 
   /**
    * Attach event listeners to a panel element. These listeners should be
    * removed in _unattachEventListeners. This is called automatically after the
    * panel has successfully loaded.
    */
   _attachEventListeners() {
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -149,33 +149,16 @@
               </hbox>
               <hbox id="downloadsFooterButtons">
                 <button id="downloadsHistory"
                         class="downloadsPanelFooterButton"
                         label="&downloadsHistory.label;"
                         accesskey="&downloadsHistory.accesskey;"
                         flex="1"
                         oncommand="DownloadsPanel.showDownloadsHistory();"/>
-                <toolbarseparator id="downloadsFooterButtonsSplitter"
-                        class="downloadsDropmarkerSplitter"/>
-                <button id="downloadsFooterDropmarker"
-                        class="downloadsPanelFooterButton downloadsDropmarker"
-                        type="menu">
-                  <menupopup id="downloadSubPanel"
-                             onpopupshowing="DownloadsPanel.onFooterPopupShowing(event);"
-                             onpopuphidden="DownloadsPanel.onFooterPopupHidden(event);"
-                             position="after_end">
-                    <menuitem id="downloadsDropdownItemClearList"
-                              command="downloadsCmd_clearList"
-                              label="&cmd.clearList2.label;"/>
-                    <menuitem id="downloadsDropdownItemOpenDownloadsFolder"
-                              oncommand="DownloadsPanel.openDownloadsFolder();"
-                              label="&openDownloadsFolder.label;"/>
-                  </menupopup>
-                </button>
               </hbox>
             </stack>
           </vbox>
         </panelview>
 
         <panelview id="downloadsPanel-blockedSubview"
                    orient="vertical"
                    flex="1">
--- a/browser/components/downloads/test/browser/browser.ini
+++ b/browser/components/downloads/test/browser/browser.ini
@@ -7,10 +7,9 @@ skip-if = os == "linux" # Bug 949434
 [browser_overflow_anchor.js]
 skip-if = os == "linux" # Bug 952422
 [browser_confirm_unblock_download.js]
 [browser_iframe_gone_mid_download.js]
 [browser_indicatorDrop.js]
 [browser_libraryDrop.js]
 [browser_downloads_panel_block.js]
 skip-if = true # Bug 1352792
-[browser_downloads_panel_footer.js]
 [browser_downloads_panel_height.js]
--- a/browser/components/downloads/test/browser/browser_basic_functionality.js
+++ b/browser/components/downloads/test/browser/browser_basic_functionality.js
@@ -9,21 +9,21 @@ registerCleanupFunction(function*() {
 
 /**
  * Make sure the downloads panel can display items in the right order and
  * contains the expected data.
  */
 add_task(function* test_basic_functionality() {
   // Display one of each download state.
   const DownloadData = [
-    { state: nsIDM.DOWNLOAD_NOTSTARTED },
-    { state: nsIDM.DOWNLOAD_PAUSED },
-    { state: nsIDM.DOWNLOAD_FINISHED },
-    { state: nsIDM.DOWNLOAD_FAILED },
-    { state: nsIDM.DOWNLOAD_CANCELED },
+    { state: DownloadsCommon.DOWNLOAD_NOTSTARTED },
+    { state: DownloadsCommon.DOWNLOAD_PAUSED },
+    { state: DownloadsCommon.DOWNLOAD_FINISHED },
+    { state: DownloadsCommon.DOWNLOAD_FAILED },
+    { state: DownloadsCommon.DOWNLOAD_CANCELED },
   ];
 
   // Wait for focus first
   yield promiseFocus();
 
   // Ensure that state is reset in case previous tests didn't finish.
   yield task_resetState();
 
--- a/browser/components/downloads/test/browser/browser_downloads_panel_block.js
+++ b/browser/components/downloads/test/browser/browser_downloads_panel_block.js
@@ -134,17 +134,17 @@ function promisePanelHidden() {
     DownloadsPanel.panel.addEventListener("popuphidden", function() {
       setTimeout(resolve, 0);
     }, {once: true});
   });
 }
 
 function makeDownload(verdict) {
   return {
-    state: nsIDM.DOWNLOAD_DIRTY,
+    state: DownloadsCommon.DOWNLOAD_DIRTY,
     hasBlockedData: true,
     errorObj: {
       result: Components.results.NS_ERROR_FAILURE,
       message: "Download blocked.",
       becauseBlocked: true,
       becauseBlockedByReputationCheck: true,
       reputationCheckVerdict:  verdict,
     },
deleted file mode 100644
--- a/browser/components/downloads/test/browser/browser_downloads_panel_footer.js
+++ /dev/null
@@ -1,95 +0,0 @@
-"use strict";
-
-function *task_openDownloadsSubPanel() {
-  let downloadSubPanel = document.getElementById("downloadSubPanel");
-  let popupShownPromise = BrowserTestUtils.waitForEvent(downloadSubPanel, "popupshown");
-
-  let downloadsDropmarker = document.getElementById("downloadsFooterDropmarker");
-  EventUtils.synthesizeMouseAtCenter(downloadsDropmarker, {}, window);
-
-  yield popupShownPromise;
-}
-
-add_task(function* test_openDownloadsFolder() {
-  yield SpecialPowers.pushPrefEnv({"set": [["browser.download.showPanelDropmarker", true]]});
-  yield task_openPanel();
-
-  yield task_openDownloadsSubPanel();
-
-  yield new Promise(resolve => {
-    sinon.stub(DownloadsCommon, "showDirectory", file => {
-      resolve(Downloads.getPreferredDownloadsDirectory().then(downloadsPath => {
-        is(file.path, downloadsPath, "Check the download folder path.");
-      }));
-    });
-
-    let itemOpenDownloadsFolder =
-      document.getElementById("downloadsDropdownItemOpenDownloadsFolder");
-    EventUtils.synthesizeMouseAtCenter(itemOpenDownloadsFolder, {}, window);
-  });
-
-  yield task_resetState();
-});
-
-add_task(function* test_clearList() {
-  const kTestCases = [{
-    downloads: [
-      { state: nsIDM.DOWNLOAD_NOTSTARTED },
-      { state: nsIDM.DOWNLOAD_FINISHED },
-      { state: nsIDM.DOWNLOAD_FAILED },
-      { state: nsIDM.DOWNLOAD_CANCELED },
-    ],
-    expectClearListShown: true,
-    expectedItemNumber: 0,
-  }, {
-    downloads: [
-      { state: nsIDM.DOWNLOAD_NOTSTARTED },
-      { state: nsIDM.DOWNLOAD_FINISHED },
-      { state: nsIDM.DOWNLOAD_FAILED },
-      { state: nsIDM.DOWNLOAD_PAUSED },
-      { state: nsIDM.DOWNLOAD_CANCELED },
-    ],
-    expectClearListShown: true,
-    expectedItemNumber: 1,
-  }, {
-    downloads: [
-      { state: nsIDM.DOWNLOAD_PAUSED },
-    ],
-    expectClearListShown: false,
-    expectedItemNumber: 1,
-  }];
-
-  for (let testCase of kTestCases) {
-    yield verify_clearList(testCase);
-  }
-});
-
-function *verify_clearList(testCase) {
-  let downloads = testCase.downloads;
-  yield task_addDownloads(downloads);
-
-  yield task_openPanel();
-  is(DownloadsView._downloads.length, downloads.length,
-    "Expect the number of download items");
-
-  yield task_openDownloadsSubPanel();
-
-  let itemClearList = document.getElementById("downloadsDropdownItemClearList");
-  let itemNumberPromise = BrowserTestUtils.waitForCondition(() => {
-    return DownloadsView._downloads.length === testCase.expectedItemNumber;
-  });
-  if (testCase.expectClearListShown) {
-    isnot("true", itemClearList.getAttribute("hidden"),
-      "Should show Clear Preview Panel button");
-    EventUtils.synthesizeMouseAtCenter(itemClearList, {}, window);
-  } else {
-    is("true", itemClearList.getAttribute("hidden"),
-      "Should not show Clear Preview Panel button");
-  }
-
-  yield itemNumberPromise;
-  is(DownloadsView._downloads.length, testCase.expectedItemNumber,
-    "Download items remained.");
-
-  yield task_resetState();
-}
--- a/browser/components/downloads/test/browser/browser_downloads_panel_height.js
+++ b/browser/components/downloads/test/browser/browser_downloads_panel_height.js
@@ -5,17 +5,17 @@
 
 /**
  * This test exists because we use a <panelmultiview> element and it handles
  * some of the height changes for us. We need to verify that the height is
  * updated correctly if downloads are removed while the panel is hidden.
  */
 add_task(function* test_height_reduced_after_removal() {
   yield task_addDownloads([
-    { state: nsIDM.DOWNLOAD_FINISHED },
+    { state: DownloadsCommon.DOWNLOAD_FINISHED },
   ]);
 
   yield task_openPanel();
   let panel = document.getElementById("downloadsPanel");
   let heightBeforeRemoval = panel.getBoundingClientRect().height;
 
   // We want to close the panel before we remove the download from the list.
   DownloadsPanel.hidePanel();
--- a/browser/components/downloads/test/browser/head.js
+++ b/browser/components/downloads/test/browser/head.js
@@ -19,18 +19,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
     "resource://testing-common/httpd.js");
 
-const nsIDM = Ci.nsIDownloadManager;
-
 var gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]);
 gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
 
 // Load mocking/stubbing library, sinon
 // docs: http://sinonjs.org/docs/
 /* global sinon:false */
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
 
@@ -158,21 +156,22 @@ function* task_addDownloads(aItems) {
   for (let item of aItems) {
     let download = {
       source: {
         url: "http://www.example.com/test-download.txt",
       },
       target: {
         path: gTestTargetFile.path,
       },
-      succeeded: item.state == nsIDM.DOWNLOAD_FINISHED,
-      canceled: item.state == nsIDM.DOWNLOAD_CANCELED ||
-                item.state == nsIDM.DOWNLOAD_PAUSED,
-      error: item.state == nsIDM.DOWNLOAD_FAILED ? new Error("Failed.") : null,
-      hasPartialData: item.state == nsIDM.DOWNLOAD_PAUSED,
+      succeeded: item.state == DownloadsCommon.DOWNLOAD_FINISHED,
+      canceled: item.state == DownloadsCommon.DOWNLOAD_CANCELED ||
+                item.state == DownloadsCommon.DOWNLOAD_PAUSED,
+      error: item.state == DownloadsCommon.DOWNLOAD_FAILED ?
+             new Error("Failed.") : null,
+      hasPartialData: item.state == DownloadsCommon.DOWNLOAD_PAUSED,
       hasBlockedData: item.hasBlockedData || false,
       startTime: new Date(startTimeMs++),
     };
     // `"errorObj" in download` must be false when there's no error.
     if (item.errorObj) {
       download.errorObj = item.errorObj;
     }
     yield publicList.add(yield Downloads.createDownload(download));
--- a/browser/components/extensions/ExtensionPopups.jsm
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -228,16 +228,17 @@ class BasePopup {
     let document = viewNode.ownerDocument;
     let browser = document.createElementNS(XUL_NS, "browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
     browser.setAttribute("transparent", "true");
     browser.setAttribute("class", "webextension-popup-browser");
     browser.setAttribute("webextension-view-type", "popup");
     browser.setAttribute("tooltip", "aHTMLTooltip");
+    browser.setAttribute("contextmenu", "contentAreaContextMenu");
 
     if (this.extension.remote) {
       browser.setAttribute("remote", "true");
       browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
     }
 
     // We only need flex sizing for the sake of the slide-in sub-views of the
     // main menu panel, so that the browser occupies the full width of the view,
@@ -281,16 +282,19 @@ class BasePopup {
       }
       return setupBrowser(browser);
     }
 
     return readyPromise.then(() => {
       setupBrowser(browser);
       let mm = browser.messageManager;
 
+      // Sets the context information for context menus.
+      mm.loadFrameScript("chrome://browser/content/content.js", true);
+
       mm.loadFrameScript(
         "chrome://extensions/content/ext-browser-content.js", false);
 
       mm.sendAsyncMessage("Extension:InitBrowser", {
         allowScriptsToClose: true,
         blockParser: this.blockParser,
         fixedWidth: this.fixedWidth,
         maxWidth: 800,
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -14,16 +14,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
                                    "@mozilla.org/inspector/dom-utils;1",
                                    "inIDOMUtils");
 
 Cu.import("resource://gre/modules/EventEmitter.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 var {
+  DefaultWeakMap,
   IconDetails,
 } = ExtensionUtils;
 
 const POPUP_PRELOAD_TIMEOUT_MS = 200;
 
 var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 function isAncestorOrSelf(target, node) {
@@ -52,16 +53,18 @@ this.browserAction = class extends Exten
     return browserActionMap.get(extension);
   }
 
   onManifestEntry(entryName) {
     let {extension} = this;
 
     let options = extension.manifest.browser_action;
 
+    this.iconData = new DefaultWeakMap(icons => this.getIconData(icons));
+
     let widgetId = makeWidgetId(extension.id);
     this.id = `${widgetId}-browser-action`;
     this.viewId = `PanelUI-webext-${widgetId}-browser-action-view`;
     this.widget = null;
 
     this.pendingPopup = null;
     this.pendingPopupTimeout = null;
 
@@ -316,17 +319,17 @@ this.browserAction = class extends Exten
    */
   getPopup(window, popupURL, blockParser = false) {
     this.clearPopupTimeout();
     let {pendingPopup} = this;
     this.pendingPopup = null;
 
     if (pendingPopup) {
       if (pendingPopup.window === window && pendingPopup.popupURL === popupURL) {
-        if (!this.blockParser) {
+        if (!blockParser) {
           pendingPopup.unblockParser();
         }
 
         return pendingPopup;
       }
       pendingPopup.destroy();
     }
 
@@ -362,68 +365,84 @@ this.browserAction = class extends Exten
       this.pendingPopupTimeout = null;
     }
   }
 
   // Update the toolbar button |node| with the tab context data
   // in |tabData|.
   updateButton(node, tabData) {
     let title = tabData.title || this.extension.name;
-    node.setAttribute("tooltiptext", title);
-    node.setAttribute("label", title);
+
+    node.ownerGlobal.requestAnimationFrame(() => {
+      node.setAttribute("tooltiptext", title);
+      node.setAttribute("label", title);
 
-    if (tabData.badgeText) {
-      node.setAttribute("badge", tabData.badgeText);
-    } else {
-      node.removeAttribute("badge");
-    }
+      if (tabData.badgeText) {
+        node.setAttribute("badge", tabData.badgeText);
+      } else {
+        node.removeAttribute("badge");
+      }
 
-    if (tabData.enabled) {
-      node.removeAttribute("disabled");
-    } else {
-      node.setAttribute("disabled", "true");
-    }
+      if (tabData.enabled) {
+        node.removeAttribute("disabled");
+      } else {
+        node.setAttribute("disabled", "true");
+      }
 
-    let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node,
-                                        "class", "toolbarbutton-badge");
-    if (badgeNode) {
-      let color = tabData.badgeBackgroundColor;
-      if (color) {
-        color = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3] / 255})`;
+      let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node,
+                                          "class", "toolbarbutton-badge");
+      if (badgeNode) {
+        let color = tabData.badgeBackgroundColor;
+        if (color) {
+          color = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3] / 255})`;
+        }
+        badgeNode.style.backgroundColor = color || "";
       }
-      badgeNode.style.backgroundColor = color || "";
-    }
+
+      let {style, legacy} = this.iconData.get(tabData.icon);
+      const LEGACY_CLASS = "toolbarbutton-legacy-addon";
+      if (legacy) {
+        node.classList.add(LEGACY_CLASS);
+      } else {
+        node.classList.remove(LEGACY_CLASS);
+      }
 
-    const LEGACY_CLASS = "toolbarbutton-legacy-addon";
-    node.classList.remove(LEGACY_CLASS);
+      node.setAttribute("style", style);
+    });
+  }
 
+  getIconData(icons) {
     let baseSize = 16;
-    let {icon, size} = IconDetails.getPreferredIcon(tabData.icon, this.extension, baseSize);
+    let {icon, size} = IconDetails.getPreferredIcon(icons, this.extension, baseSize);
+
+    let legacy = false;
 
     // If the best available icon size is not divisible by 16, check if we have
     // an 18px icon to fall back to, and trim off the padding instead.
     if (size % 16 && !icon.endsWith(".svg")) {
-      let result = IconDetails.getPreferredIcon(tabData.icon, this.extension, 18);
+      let result = IconDetails.getPreferredIcon(icons, this.extension, 18);
 
       if (result.size % 18 == 0) {
         baseSize = 18;
         icon = result.icon;
-        node.classList.add(LEGACY_CLASS);
+        legacy = true;
       }
     }
 
     let getIcon = size => IconDetails.escapeUrl(
-      IconDetails.getPreferredIcon(tabData.icon, this.extension, size).icon);
+      IconDetails.getPreferredIcon(icons, this.extension, size).icon);
 
-    node.setAttribute("style", `
+    let style = `
       --webextension-menupanel-image: url("${getIcon(32)}");
       --webextension-menupanel-image-2x: url("${getIcon(64)}");
       --webextension-toolbar-image: url("${IconDetails.escapeUrl(icon)}");
       --webextension-toolbar-image-2x: url("${getIcon(baseSize * 2)}");
-    `);
+    `;
+
+    return {style, legacy};
   }
 
   // Update the toolbar button for a given window.
   updateWindow(window) {
     let widget = this.widget.forWindow(window);
     if (widget) {
       let tab = window.gBrowser.selectedTab;
       this.updateButton(widget.node, this.tabContext.get(tab));
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -3,31 +3,34 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "PanelPopup",
                                   "resource:///modules/ExtensionPopups.jsm");
 
 Cu.import("resource://gre/modules/Task.jsm");
 
 var {
+  DefaultWeakMap,
   IconDetails,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> PageAction]
 let pageActionMap = new WeakMap();
 
 this.pageAction = class extends ExtensionAPI {
   static for(extension) {
     return pageActionMap.get(extension);
   }
 
   onManifestEntry(entryName) {
     let {extension} = this;
     let options = extension.manifest.page_action;
 
+    this.iconData = new DefaultWeakMap(icons => this.getIconData(icons));
+
     this.id = makeWidgetId(extension.id) + "-page-action";
 
     this.tabManager = extension.tabManager;
 
     this.defaults = {
       show: false,
       title: options.default_title || extension.name,
       icon: IconDetails.normalize({path: options.default_icon}, extension),
@@ -99,41 +102,50 @@ this.pageAction = class extends Extensio
     let tabData = this.tabContext.get(window.gBrowser.selectedTab);
 
     if (!(tabData.show || this.buttons.has(window))) {
       // Don't bother creating a button for a window until it actually
       // needs to be shown.
       return;
     }
 
-    let button = this.getButton(window);
+    window.requestAnimationFrame(() => {
+      let button = this.getButton(window);
+
+      if (tabData.show) {
+        // Update the title and icon only if the button is visible.
 
-    if (tabData.show) {
-      // Update the title and icon only if the button is visible.
+        let title = tabData.title || this.extension.name;
+        button.setAttribute("tooltiptext", title);
+        button.setAttribute("aria-label", title);
+        button.classList.add("webextension-page-action");
 
-      let title = tabData.title || this.extension.name;
-      button.setAttribute("tooltiptext", title);
-      button.setAttribute("aria-label", title);
+        let {style} = this.iconData.get(tabData.icon);
+
+        button.setAttribute("style", style);
+      }
 
-      // These URLs should already be properly escaped, but make doubly sure CSS
-      // string escape characters are escaped here, since they could lead to a
-      // sandbox break.
-      let escape = str => str.replace(/[\\\s"]/g, encodeURIComponent);
-
-      let getIcon = size => escape(IconDetails.getPreferredIcon(tabData.icon, this.extension, size).icon);
+      button.hidden = !tabData.show;
+    });
+  }
 
-      button.setAttribute("style", `
-        --webextension-urlbar-image: url("${getIcon(16)}");
-        --webextension-urlbar-image-2x: url("${getIcon(32)}");
-      `);
+  getIconData(icons) {
+    // These URLs should already be properly escaped, but make doubly sure CSS
+    // string escape characters are escaped here, since they could lead to a
+    // sandbox break.
+    let escape = str => str.replace(/[\\\s"]/g, encodeURIComponent);
 
-      button.classList.add("webextension-page-action");
-    }
+    let getIcon = size => escape(IconDetails.getPreferredIcon(icons, this.extension, size).icon);
 
-    button.hidden = !tabData.show;
+    let style = `
+      --webextension-urlbar-image: url("${getIcon(16)}");
+      --webextension-urlbar-image-2x: url("${getIcon(32)}");
+    `;
+
+    return {style};
   }
 
   // Create an |image| node and add it to the |urlbar-icons|
   // container in the given window.
   addButton(window) {
     let document = window.document;
 
     let button = document.createElement("image");
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -26,16 +26,17 @@ support-files =
   webNav_createdTargetSource_subframe.html
   serviceWorker.js
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
   ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js
 
 [browser_ext_browserAction_area.js]
 [browser_ext_browserAction_context.js]
+[browser_ext_browserAction_contextMenu.js]
 [browser_ext_browserAction_disabled.js]
 [browser_ext_browserAction_pageAction_icon.js]
 [browser_ext_browserAction_pageAction_icon_permissions.js]
 [browser_ext_browserAction_popup.js]
 [browser_ext_browserAction_popup_preload.js]
 [browser_ext_browserAction_popup_resize.js]
 [browser_ext_browserAction_simple.js]
 [browser_ext_browsingData_formData.js]
@@ -68,16 +69,17 @@ support-files =
 [browser_ext_incognito_views.js]
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_omnibox.js]
 skip-if = debug || asan # Bug 1354681
 [browser_ext_optionsPage_browser_style.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
+[browser_ext_pageAction_contextMenu.js]
 [browser_ext_pageAction_popup.js]
 [browser_ext_pageAction_popup_resize.js]
 [browser_ext_pageAction_simple.js]
 [browser_ext_pageAction_title.js]
 [browser_ext_popup_api_injection.js]
 [browser_ext_popup_background.js]
 [browser_ext_popup_corners.js]
 [browser_ext_popup_sendMessage.js]
@@ -88,16 +90,17 @@ skip-if = debug || asan # Bug 1354681
 [browser_ext_sessions_forgetClosedTab.js]
 [browser_ext_sessions_forgetClosedWindow.js]
 [browser_ext_sessions_getRecentlyClosed.js]
 [browser_ext_sessions_getRecentlyClosed_private.js]
 [browser_ext_sessions_getRecentlyClosed_tabs.js]
 [browser_ext_sessions_restore.js]
 [browser_ext_sidebarAction.js]
 [browser_ext_sidebarAction_context.js]
+[browser_ext_sidebarAction_contextMenu.js]
 [browser_ext_sidebarAction_tabs.js]
 [browser_ext_sidebarAction_windows.js]
 [browser_ext_simple.js]
 [browser_ext_tab_runtimeConnect.js]
 [browser_ext_tabs_audio.js]
 [browser_ext_tabs_captureVisibleTab.js]
 [browser_ext_tabs_create.js]
 [browser_ext_tabs_create_invalid_url.js]
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -119,17 +119,19 @@ function* runTests(options) {
       is(badgeColor, expectedColor, "badge color is correct");
     }
 
 
     // TODO: Popup URL.
   }
 
   let awaitFinish = new Promise(resolve => {
-    extension.onMessage("nextTest", (expecting, testsRemaining) => {
+    extension.onMessage("nextTest", async (expecting, testsRemaining) => {
+      await promiseAnimationFrame();
+
       checkDetails(expecting);
 
       if (testsRemaining) {
         extension.sendMessage("runNextTest");
       } else {
         resolve();
       }
     });
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
@@ -0,0 +1,106 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+let extData = {
+  manifest: {
+    "permissions": ["contextMenus"],
+    "browser_action": {
+      "default_popup": "popup.html",
+    },
+  },
+  useAddonManager: "temporary",
+
+  files: {
+    "popup.html": `
+      <!DOCTYPE html>
+      <html>
+      <head><meta charset="utf-8"/>
+      </head>
+      <body>
+      <span id="text">A Test Popup</span>
+      <img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
+      </body></html>
+    `,
+  },
+
+  background: function() {
+    browser.contextMenus.create({
+      id: "clickme-page",
+      title: "Click me!",
+      contexts: ["all"],
+    });
+  },
+};
+
+let contextMenuItems = {
+  "context-navigation": "hidden",
+  "context-sep-navigation": "hidden",
+  "context-viewsource": "",
+  "context-viewinfo": "disabled",
+  "inspect-separator": "hidden",
+  "context-inspect": "hidden",
+  "context-bookmarkpage": "hidden",
+  "context-sharepage": "hidden",
+};
+
+add_task(function* browseraction_popup_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+
+  yield clickBrowserAction(extension, window);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension);
+  let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
+  is(item.length, 1, "contextMenu item for page was found");
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* browseraction_popup_contextmenu_hidden_items() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+
+  yield clickBrowserAction(extension);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#text");
+
+  let item, state;
+  for (const itemID in contextMenuItems) {
+    item = contentAreaContextMenu.querySelector(`#${itemID}`);
+    state = contextMenuItems[itemID];
+
+    if (state !== "") {
+      ok(item[state], `${itemID} is ${state}`);
+
+      if (state !== "hidden") {
+        ok(!item.hidden, `Disabled ${itemID} is not hidden`);
+      }
+    } else {
+      ok(!item.hidden, `${itemID} is not hidden`);
+      ok(!item.disabled, `${itemID} is not disabled`);
+    }
+  }
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* browseraction_popup_image_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+
+  yield clickBrowserAction(extension);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#testimg");
+
+  let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
+  ok(!item.hidden);
+  ok(item.disabled);
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
@@ -263,16 +263,18 @@ add_task(function* testDetailsObjects() 
   let pageActionId = `${makeWidgetId(extension.id)}-page-action`;
   let browserActionWidget = getBrowserActionWidget(extension);
 
   let tests = yield extension.awaitMessage("ready");
   for (let test of tests) {
     extension.sendMessage("setIcon", test);
     yield extension.awaitMessage("iconSet");
 
+    yield promiseAnimationFrame();
+
     let browserActionButton = browserActionWidget.forWindow(window).node;
     let pageActionImage = document.getElementById(pageActionId);
 
 
     // Test icon sizes in the toolbar/urlbar.
     for (let resolution of Object.keys(test.resolutions)) {
       yield SpecialPowers.pushPrefEnv({set: [[RESOLUTION_PREF, resolution]]});
 
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js
@@ -100,16 +100,18 @@ add_task(function* testDefaultDetails() 
       },
     });
 
     yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
 
     let browserActionId = makeWidgetId(extension.id) + "-browser-action";
     let pageActionId = makeWidgetId(extension.id) + "-page-action";
 
+    yield promiseAnimationFrame();
+
     let browserActionButton = document.getElementById(browserActionId);
     let image = getListStyleImage(browserActionButton);
 
     ok(expectedURL.test(image), `browser action image ${image} matches ${expectedURL}`);
 
     let pageActionImage = document.getElementById(pageActionId);
     image = getListStyleImage(pageActionImage);
 
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup_preload.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup_preload.js
@@ -107,16 +107,17 @@ add_task(function* testBrowserActionDisa
         browser.test.fail("Should not get here");
       },
     },
   });
 
   yield extension.startup();
 
   yield extension.awaitMessage("browserAction-disabled");
+  yield promiseAnimationFrame();
 
   const {GlobalManager, Management: {global: {browserActionFor}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
   let ext = GlobalManager.extensionMap.get(extension.id);
   let browserAction = browserActionFor(ext);
 
   let widget = getBrowserActionWidget(extension).forWindow(window);
 
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_contextMenu.js
@@ -0,0 +1,116 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+let extData = {
+  manifest: {
+    "permissions": ["contextMenus"],
+    "page_action": {
+      "default_popup": "popup.html",
+    },
+  },
+  useAddonManager: "temporary",
+
+  files: {
+    "popup.html": `
+      <!DOCTYPE html>
+      <html>
+      <head><meta charset="utf-8"/>
+      </head>
+      <body>
+      <span id="text">A Test Popup</span>
+      <img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
+      </body></html>
+    `,
+  },
+
+  background: function() {
+    browser.contextMenus.create({
+      id: "clickme-page",
+      title: "Click me!",
+      contexts: ["all"],
+    });
+    browser.tabs.query({active: true, currentWindow: true}, tabs => {
+      const tabId = tabs[0].id;
+
+      browser.pageAction.show(tabId).then(() => {
+        browser.test.sendMessage("action-shown");
+      });
+    });
+  },
+};
+
+let contextMenuItems = {
+  "context-navigation": "hidden",
+  "context-sep-navigation": "hidden",
+  "context-viewsource": "",
+  "context-viewinfo": "disabled",
+  "inspect-separator": "hidden",
+  "context-inspect": "hidden",
+  "context-bookmarkpage": "hidden",
+  "context-sharepage": "hidden",
+};
+
+add_task(function* pageaction_popup_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  yield extension.awaitMessage("action-shown");
+
+  yield clickPageAction(extension, window);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension);
+  let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
+  is(item.length, 1, "contextMenu item for page was found");
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* pageaction_popup_contextmenu_hidden_items() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  yield extension.awaitMessage("action-shown");
+
+  yield clickPageAction(extension, window);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#text");
+
+  let item, state;
+  for (const itemID in contextMenuItems) {
+    item = contentAreaContextMenu.querySelector(`#${itemID}`);
+    state = contextMenuItems[itemID];
+
+    if (state !== "") {
+      ok(item[state], `${itemID} is ${state}`);
+
+      if (state !== "hidden") {
+        ok(!item.hidden, `Disabled ${itemID} is not hidden`);
+      }
+    } else {
+      ok(!item.hidden, `${itemID} is not hidden`);
+      ok(!item.disabled, `${itemID} is not disabled`);
+    }
+  }
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* pageaction_popup_image_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  yield extension.awaitMessage("action-shown");
+
+  yield clickPageAction(extension, window);
+
+  let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#testimg");
+
+  let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
+  ok(!item.hidden);
+  ok(item.disabled);
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
--- a/browser/components/extensions/test/browser/browser_ext_sidebarAction.js
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction.js
@@ -1,15 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 let extData = {
   manifest: {
-    "permissions": ["contextMenus"],
     "sidebar_action": {
       "default_panel": "sidebar.html",
     },
   },
   useAddonManager: "temporary",
 
   files: {
     "sidebar.html": `
@@ -26,22 +25,16 @@ let extData = {
     "sidebar.js": function() {
       window.onload = () => {
         browser.test.sendMessage("sidebar");
       };
     },
   },
 
   background: function() {
-    browser.contextMenus.create({
-      id: "clickme-page",
-      title: "Click me!",
-      contexts: ["all"],
-    });
-
     browser.test.onMessage.addListener(msg => {
       if (msg === "set-panel") {
         browser.sidebarAction.setPanel({panel: ""}).then(() => {
           browser.test.notifyFail("empty panel settable");
         }).catch(() => {
           browser.test.notifyPass("unable to set empty panel");
         });
       }
@@ -98,26 +91,12 @@ add_task(function* sidebar_empty_panel()
   // Test sidebar is opened on install
   yield extension.awaitMessage("sidebar");
   ok(!document.getElementById("sidebar-box").hidden, "sidebar box is visible in first window");
   extension.sendMessage("set-panel");
   yield extension.awaitFinish();
   yield extension.unload();
 });
 
-add_task(function* sidebar_contextmenu() {
-  let extension = ExtensionTestUtils.loadExtension(extData);
-  yield extension.startup();
-  // Test sidebar is opened on install
-  yield extension.awaitMessage("sidebar");
-
-  let contentAreaContextMenu = yield openContextMenuInSidebar();
-  let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
-  is(item.length, 1, "contextMenu item for page was found");
-  yield closeContextMenu(contentAreaContextMenu);
-
-  yield extension.unload();
-});
-
 add_task(function* cleanup() {
   // This is set on initial sidebar install.
   Services.prefs.clearUserPref("extensions.sidebar-button.shown");
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_contextMenu.js
@@ -0,0 +1,119 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+let extData = {
+  manifest: {
+    "permissions": ["contextMenus"],
+    "sidebar_action": {
+      "default_panel": "sidebar.html",
+    },
+  },
+  useAddonManager: "temporary",
+
+  files: {
+    "sidebar.html": `
+      <!DOCTYPE html>
+      <html>
+      <head><meta charset="utf-8"/>
+      <script src="sidebar.js"></script>
+      </head>
+      <body>
+      <span id="text">A Test Sidebar</span>
+      <img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
+      </body></html>
+    `,
+
+    "sidebar.js": function() {
+      window.onload = () => {
+        browser.test.sendMessage("sidebar");
+      };
+    },
+  },
+
+  background: function() {
+    browser.contextMenus.create({
+      id: "clickme-page",
+      title: "Click me!",
+      contexts: ["all"],
+    });
+  },
+};
+
+let contextMenuItems = {
+  "context-navigation": "hidden",
+  "context-sep-navigation": "hidden",
+  "context-viewsource": "",
+  "context-viewinfo": "",
+  "inspect-separator": "hidden",
+  "context-inspect": "hidden",
+  "context-bookmarkpage": "hidden",
+  "context-sharepage": "hidden",
+};
+
+add_task(function* sidebar_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  // Test sidebar is opened on install
+  yield extension.awaitMessage("sidebar");
+
+  let contentAreaContextMenu = yield openContextMenuInSidebar();
+  let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
+  is(item.length, 1, "contextMenu item for page was found");
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+
+add_task(function* sidebar_contextmenu_hidden_items() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  // Test sidebar is opened on install
+  yield extension.awaitMessage("sidebar");
+
+  let contentAreaContextMenu = yield openContextMenuInSidebar("#text");
+
+  let item, state;
+  for (const itemID in contextMenuItems) {
+    item = contentAreaContextMenu.querySelector(`#${itemID}`);
+    state = contextMenuItems[itemID];
+
+    if (state !== "") {
+      ok(item[state], `${itemID} is ${state}`);
+
+      if (state !== "hidden") {
+        ok(!item.hidden, `Disabled ${itemID} is not hidden`);
+      }
+    } else {
+      ok(!item.hidden, `${itemID} is not hidden`);
+      ok(!item.disabled, `${itemID} is not disabled`);
+    }
+  }
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* sidebar_image_contextmenu() {
+  let extension = ExtensionTestUtils.loadExtension(extData);
+  yield extension.startup();
+  // Test sidebar is opened on install
+  yield extension.awaitMessage("sidebar");
+
+  let contentAreaContextMenu = yield openContextMenuInSidebar("#testimg");
+
+  let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
+  ok(!item.hidden);
+  ok(!item.disabled);
+
+  yield closeContextMenu(contentAreaContextMenu);
+
+  yield extension.unload();
+});
+
+add_task(function* cleanup() {
+  // This is set on initial sidebar install.
+  Services.prefs.clearUserPref("extensions.sidebar-button.shown");
+});
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -3,25 +3,27 @@
 "use strict";
 
 /* exported CustomizableUI makeWidgetId focusWindow forceGC
  *          getBrowserActionWidget
  *          clickBrowserAction clickPageAction
  *          getBrowserActionPopup getPageActionPopup
  *          closeBrowserAction closePageAction
  *          promisePopupShown promisePopupHidden
- *          openContextMenu closeContextMenu openContextMenuInSidebar
+ *          openContextMenu closeContextMenu
+ *          openContextMenuInSidebar openContextMenuInPopup
  *          openExtensionContextMenu closeExtensionContextMenu
  *          openActionContextMenu openSubmenu closeActionContextMenu
  *          openTabContextMenu closeTabContextMenu
  *          imageBuffer imageBufferFromDataURI
  *          getListStyleImage getPanelForNode
  *          awaitExtensionPanel awaitPopupResize
  *          promiseContentDimensions alterContent
  *          promisePrefChangeObserved openContextMenuInFrame
+ *          promiseAnimationFrame
  */
 
 const {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
 const {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm", {});
 
 // We run tests under two different configurations, from browser.ini and
 // browser-remote.ini. When running from browser-remote.ini, the tests are
 // copied to the sub-directory "test-oop-extensions", which we detect here, and
@@ -77,16 +79,23 @@ var imageBuffer = imageBufferFromDataURI
 function getListStyleImage(button) {
   let style = button.ownerGlobal.getComputedStyle(button);
 
   let match = /^url\("(.*)"\)$/.exec(style.listStyleImage);
 
   return match && match[1];
 }
 
+async function promiseAnimationFrame(win = window) {
+  await new Promise(resolve => win.requestAnimationFrame(resolve));
+
+  let {mainThread} = Services.tm;
+  return new Promise(resolve => mainThread.dispatch(resolve, mainThread.DISPATCH_NORMAL));
+}
+
 function promisePopupShown(popup) {
   return new Promise(resolve => {
     if (popup.state == "open") {
       resolve();
     } else {
       let onPopupShown = event => {
         popup.removeEventListener("popupshown", onPopupShown);
         resolve();
@@ -211,32 +220,43 @@ var showBrowserAction = Task.async(funct
   if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {
     ok(!widget.overflowed, "Expect widget not to be overflowed");
   } else if (group.areaType == CustomizableUI.TYPE_MENU_PANEL) {
     yield win.PanelUI.show();
   }
 });
 
 var clickBrowserAction = Task.async(function* (extension, win = window) {
+  yield promiseAnimationFrame(win);
   yield showBrowserAction(extension, win);
 
   let widget = getBrowserActionWidget(extension).forWindow(win);
 
   EventUtils.synthesizeMouseAtCenter(widget.node, {}, win);
 });
 
 function closeBrowserAction(extension, win = window) {
   let group = getBrowserActionWidget(extension);
 
   let node = win.document.getElementById(group.viewId);
   CustomizableUI.hidePanelForNode(node);
 
   return Promise.resolve();
 }
 
+async function openContextMenuInPopup(extension, selector = "body") {
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+  let browser = await awaitExtensionPanel(extension);
+  let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+  await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "mousedown", button: 2}, browser);
+  await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "contextmenu"}, browser);
+  await popupShownPromise;
+  return contentAreaContextMenu;
+}
+
 async function openContextMenuInSidebar(selector = "body") {
   let contentAreaContextMenu = SidebarUI.browser.contentDocument.getElementById("contentAreaContextMenu");
   let browser = SidebarUI.browser.contentDocument.getElementById("webext-panels-browser");
   let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
   await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "mousedown", button: 2}, browser);
   await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "contextmenu"}, browser);
   await popupShownPromise;
   return contentAreaContextMenu;
@@ -263,45 +283,45 @@ async function openContextMenu(selector 
 
 async function closeContextMenu(contextMenu) {
   let contentAreaContextMenu = contextMenu || document.getElementById("contentAreaContextMenu");
   let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
   contentAreaContextMenu.hidePopup();
   await popupHiddenPromise;
 }
 
-function* openExtensionContextMenu(selector = "#img1") {
-  let contextMenu = yield openContextMenu(selector);
+async function openExtensionContextMenu(selector = "#img1") {
+  let contextMenu = await openContextMenu(selector);
   let topLevelMenu = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
 
   // Return null if the extension only has one item and therefore no extension menu.
   if (topLevelMenu.length == 0) {
     return null;
   }
 
   let extensionMenu = topLevelMenu[0].childNodes[0];
   let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
   EventUtils.synthesizeMouseAtCenter(extensionMenu, {});
-  yield popupShownPromise;
+  await popupShownPromise;
   return extensionMenu;
 }
 
-function* closeExtensionContextMenu(itemToSelect, modifiers = {}) {
+async function closeExtensionContextMenu(itemToSelect, modifiers = {}) {
   let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
   let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
   EventUtils.synthesizeMouseAtCenter(itemToSelect, modifiers);
-  yield popupHiddenPromise;
+  await popupHiddenPromise;
 }
 
-function* openChromeContextMenu(menuId, target, win = window) {
+async function openChromeContextMenu(menuId, target, win = window) {
   const node = win.document.querySelector(target);
   const menu = win.document.getElementById(menuId);
   const shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
   EventUtils.synthesizeMouseAtCenter(node, {type: "contextmenu"}, win);
-  yield shown;
+  await shown;
   return menu;
 }
 
 function* openSubmenu(submenuItem, win = window) {
   const submenu = submenuItem.firstChild;
   const shown = BrowserTestUtils.waitForEvent(submenu, "popupshown");
   EventUtils.synthesizeMouseAtCenter(submenuItem, {}, win);
   yield shown;
@@ -314,19 +334,20 @@ function closeChromeContextMenu(menuId, 
   if (itemToSelect) {
     EventUtils.synthesizeMouseAtCenter(itemToSelect, {}, win);
   } else {
     menu.hidePopup();
   }
   return hidden;
 }
 
-function openActionContextMenu(extension, kind, win = window) {
+async function openActionContextMenu(extension, kind, win = window) {
   // See comment from clickPageAction below.
   SetPageProxyState("valid");
+  await promiseAnimationFrame(win);
   const id = `#${makeWidgetId(extension.id)}-${kind}-action`;
   return openChromeContextMenu("toolbar-context-menu", id, win);
 }
 
 function closeActionContextMenu(itemToSelect, win = window) {
   return closeChromeContextMenu("toolbar-context-menu", itemToSelect, win);
 }
 
@@ -338,26 +359,28 @@ function closeTabContextMenu(itemToSelec
   return closeChromeContextMenu("tabContextMenu", itemToSelect, win);
 }
 
 function getPageActionPopup(extension, win = window) {
   let panelId = makeWidgetId(extension.id) + "-panel";
   return win.document.getElementById(panelId);
 }
 
-function clickPageAction(extension, win = window) {
+async function clickPageAction(extension, win = window) {
   // This would normally be set automatically on navigation, and cleared
   // when the user types a value into the URL bar, to show and hide page
   // identity info and icons such as page action buttons.
   //
   // Unfortunately, that doesn't happen automatically in browser chrome
   // tests.
   /* globals SetPageProxyState */
   SetPageProxyState("valid");
 
+  await promiseAnimationFrame(win);
+
   let pageActionId = makeWidgetId(extension.id) + "-page-action";
   let elem = win.document.getElementById(pageActionId);
 
   EventUtils.synthesizeMouseAtCenter(elem, {}, win);
   return new Promise(SimpleTest.executeSoon);
 }
 
 function closePageAction(extension, win = window) {
--- a/browser/components/extensions/test/browser/head_pageAction.js
+++ b/browser/components/extensions/test/browser/head_pageAction.js
@@ -1,14 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 /* exported runTests */
-/* globals getListStyleImage */
+/* globals getListStyleImage, promiseAnimationFrame */
 
 function* runTests(options) {
   function background(getTests) {
     let tabs;
     let tests;
 
     // Gets the current details of the page action, and returns a
     // promise that resolves to an object containing them.
@@ -103,21 +103,23 @@ function* runTests(options) {
       is(image.getAttribute("aria-label"), title, "image aria-label is correct");
       // TODO: Popup URL.
     }
   }
 
   let testNewWindows = 1;
 
   let awaitFinish = new Promise(resolve => {
-    extension.onMessage("nextTest", (expecting, testsRemaining) => {
+    extension.onMessage("nextTest", async (expecting, testsRemaining) => {
       if (!pageActionId) {
         pageActionId = `${makeWidgetId(extension.id)}-page-action`;
       }
 
+      await promiseAnimationFrame();
+
       checkDetails(expecting);
 
       if (testsRemaining) {
         extension.sendMessage("runNextTest");
       } else if (testNewWindows) {
         testNewWindows--;
 
         BrowserTestUtils.openNewBrowserWindow().then(window => {
--- a/browser/components/extensions/test/mochitest/mochitest.ini
+++ b/browser/components/extensions/test/mochitest/mochitest.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 support-files =
   ../../../../../toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
+  ../../../../../toolkit/components/extensions/test/mochitest/file_sample.html
 tags = webextensions
 
 [test_ext_all_apis.html]
--- a/browser/components/feeds/FeedConverter.js
+++ b/browser/components/feeds/FeedConverter.js
@@ -248,16 +248,17 @@ FeedConverter.prototype = {
         // Store the result in the result service so that the display
         // page can access it.
         feedService.addFeedResult(result);
 
         // Now load the actual XUL document.
         let aboutFeedsURI = ios.newURI("about:feeds");
         chromeChannel = ios.newChannelFromURIWithLoadInfo(aboutFeedsURI, loadInfo);
         chromeChannel.originalURI = result.uri;
+        loadInfo.resultPrincipalURI = result.uri;
 
         // carry the origin attributes from the channel that loaded the feed.
         chromeChannel.owner =
           Services.scriptSecurityManager.createCodebasePrincipal(aboutFeedsURI,
                                                                  loadInfo.originAttributes);
       } else {
         chromeChannel = ios.newChannelFromURIWithLoadInfo(result.uri, loadInfo);
       }
@@ -555,20 +556,22 @@ GenericProtocolHandler.prototype = {
     let inner = aUri.QueryInterface(Ci.nsINestedURI).innerURI;
     let channel = Cc["@mozilla.org/network/io-service;1"].
                   getService(Ci.nsIIOService).
                   newChannelFromURIWithLoadInfo(inner, aLoadInfo);
 
     const schemeId = this._getTelemetrySchemeId();
     Services.telemetry.getHistogramById("FEED_PROTOCOL_USAGE").add(schemeId);
 
-    if (channel instanceof Components.interfaces.nsIHttpChannel)
+    if (channel instanceof Components.interfaces.nsIHttpChannel) {
       // Set this so we know this is supposed to be a feed
       channel.setRequestHeader("X-Moz-Is-Feed", "1", false);
+    }
     channel.originalURI = aUri;
+    aLoadInfo.resultPrincipalURI = aUri;
     return channel;
   },
 
   QueryInterface(iid) {
     if (iid.equals(Ci.nsIProtocolHandler) ||
         iid.equals(Ci.nsISupports))
       return this;
     throw Cr.NS_ERROR_NO_INTERFACE;
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -133,35 +133,42 @@ var gEditItemOverlay = {
                         PlacesUIUtils.getItemDescription(this._paneInfo.itemId));
   },
 
   _initKeywordField: Task.async(function* (newKeyword = "") {
     if (!this._paneInfo.isBookmark) {
       throw new Error("_initKeywordField called unexpectedly");
     }
 
+    // Reset the field status synchronously now, eventually we'll reinit it
+    // later if we find an existing keyword. This way we can ensure to be in a
+    // consistent status when reusing the panel across different bookmarks.
+    this._keyword = newKeyword;
+    this._initTextField(this._keywordField, newKeyword);
+
     if (!newKeyword) {
       let entries = [];
       yield PlacesUtils.keywords.fetch({ url: this._paneInfo.uri.spec },
                                        e => entries.push(e));
       if (entries.length > 0) {
         // We show an existing keyword if either POST data was not provided, or
         // if the POST data is the same.
         let existingKeyword = entries[0].keyword;
         let postData = this._paneInfo.postData;
         if (postData) {
           let sameEntry = entries.find(e => e.postData === postData);
           existingKeyword = sameEntry ? sameEntry.keyword : "";
         }
         if (existingKeyword) {
-          this._keyword = newKeyword = existingKeyword;
+          this._keyword = existingKeyword;
+          // Update the text field to the existing keyword.
+          this._initTextField(this._keywordField, this._keyword);
         }
       }
     }
-    this._initTextField(this._keywordField, newKeyword);
   }),
 
   _initLoadInSidebar: Task.async(function* () {
     if (!this._paneInfo.isBookmark)
       throw new Error("_initLoadInSidebar called unexpectedly");
 
     this._loadInSidebarCheckbox.checked =
       PlacesUtils.annotations.itemHasAnnotation(
--- a/browser/components/places/tests/chrome/chrome.ini
+++ b/browser/components/places/tests/chrome/chrome.ini
@@ -4,11 +4,12 @@ support-files = head.js
 [test_0_bug510634.xul]
 [test_0_multiple_left_pane.xul]
 [test_bug1163447_selectItems_through_shortcut.xul]
 [test_bug427633_no_newfolder_if_noip.xul]
 [test_bug485100-change-case-loses-tag.xul]
 [test_bug549192.xul]
 [test_bug549491.xul]
 [test_bug631374_tags_selector_scroll.xul]
+[test_editBookmarkOverlay_keywords.xul]
 [test_editBookmarkOverlay_tags_liveUpdate.xul]
 [test_selectItems_on_nested_tree.xul]
 [test_treeview_date.xul]
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_editBookmarkOverlay_keywords.xul
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
+
+<!DOCTYPE window [
+  <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+  %editBookmarkOverlayDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        title="Bug 1343256 - Bookmark keywords disappear from one bookmark when adding a keyword to another bookmark"
+        onload="runTest();">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+  <script type="application/javascript"
+          src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+
+  <body xmlns="http://www.w3.org/1999/xhtml" />
+
+  <vbox id="editBookmarkPanelContent"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+    function runTest() {
+      SimpleTest.waitForExplicitFinish();
+      Task.spawn(test.bind(this))
+          .catch(ex => ok(false, ex))
+          .then(() => PlacesUtils.bookmarks.eraseEverything())
+          .then(SimpleTest.finish);
+    }
+
+    function promiseOnItemChanged() {
+      return new Promise(resolve => {
+        PlacesUtils.bookmarks.addObserver({
+          onBeginUpdateBatch() {},
+          onEndUpdateBatch() {},
+          onItemAdded() {},
+          onItemRemoved() {},
+          onItemVisited() {},
+          onItemMoved() {},
+          onItemChanged(id, property, isAnno, value) {
+            PlacesUtils.bookmarks.removeObserver(this);
+            resolve({ property, value });
+          },
+          QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
+        });
+      });
+    }
+
+    function* test() {
+      ok(gEditItemOverlay, "Sanity check: gEditItemOverlay is in context");
+      let keywordField = document.getElementById("editBMPanel_keywordField");
+
+      for (let i = 0; i < 2; ++i) {
+        let bm = yield PlacesUtils.bookmarks.insert({
+          url: `http://www.test${i}.me/`,
+          parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+        });
+        info(`Init panel on bookmark #${i+1}`);
+        let node = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
+        gEditItemOverlay.initPanel({ node });
+        is(document.getElementById("editBMPanel_keywordField").value, "",
+          "The keyword field should be empty");
+        info("Add a keyword to the bookmark");
+        let promise = promiseOnItemChanged();
+        keywordField.focus();
+        keywordField.value = "kw";
+        synthesizeKey(i.toString(), {});
+        synthesizeKey("VK_RETURN", {});
+        keywordField.blur();
+        let {property, value} = yield promise;
+        is(property, "keyword", "The keyword should have been changed");
+        is(value, `kw${i}`, "The new keyword value is correct");
+      }
+
+      for (let i = 0; i < 2; ++i) {
+        let entry = yield PlacesUtils.keywords.fetch({ url: `http://www.test${i}.me/` });
+        is(entry.keyword, `kw${i}`, `The keyword for http://www.test${i}.me/ is correct`);
+      }
+    };
+  ]]>
+  </script>
+
+</window>
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -1,13 +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/. */
 
 /* import-globals-from preferences.js */
+/* import-globals-from ../../../base/content/aboutDialog-appUpdater.js */
 
 // Load DownloadUtils module for convertByteUnits
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
 Components.utils.import("resource://gre/modules/LoadContextInfo.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var gAdvancedPane = {
   _inited: false,
@@ -15,17 +16,66 @@ var gAdvancedPane = {
   init() {
     function setEventListener(aId, aEventType, aCallback) {
       document.getElementById(aId)
               .addEventListener(aEventType, aCallback.bind(gAdvancedPane));
     }
 
     this._inited = true;
 
+    let version = AppConstants.MOZ_APP_VERSION_DISPLAY;
+
+    // Include the build ID if this is an "a#" (nightly) build
+    if (/a\d+$/.test(version)) {
+      let buildID = Services.appinfo.appBuildID;
+      let year = buildID.slice(0, 4);
+      let month = buildID.slice(4, 6);
+      let day = buildID.slice(6, 8);
+      version += ` (${year}-${month}-${day})`;
+    }
+
+    // Append "(32-bit)" or "(64-bit)" build architecture to the version number:
+    let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+    let archResource = Services.appinfo.is64Bit
+                       ? "aboutDialog.architecture.sixtyFourBit"
+                       : "aboutDialog.architecture.thirtyTwoBit";
+    let arch = bundle.GetStringFromName(archResource);
+    version += ` (${arch})`;
+
+    document.getElementById("version").textContent = version;
+
+    // Show a release notes link if we have a URL.
+    let relNotesLink = document.getElementById("releasenotes");
+    let relNotesPrefType = Services.prefs.getPrefType("app.releaseNotesURL");
+    if (relNotesPrefType != Services.prefs.PREF_INVALID) {
+      let relNotesURL = Services.urlFormatter.formatURLPref("app.releaseNotesURL");
+      if (relNotesURL != "about:blank") {
+        relNotesLink.href = relNotesURL;
+        relNotesLink.hidden = false;
+      }
+    }
+
+    let distroId = Services.prefs.getCharPref("distribution.id", "");
+    if (distroId) {
+      let distroVersion = Services.prefs.getCharPref("distribution.version");
+
+      let distroIdField = document.getElementById("distributionId");
+      distroIdField.value = distroId + " - " + distroVersion;
+      distroIdField.hidden = false;
+
+      let distroAbout = Services.prefs.getStringPref("distribution.about", "");
+      if (distroAbout) {
+        let distroField = document.getElementById("distribution");
+        distroField.value = distroAbout;
+        distroField.hidden = false;
+      }
+    }
+
     if (AppConstants.MOZ_UPDATER) {
+      gAppUpdater = new appUpdater();
       let onUnload = () => {
         window.removeEventListener("unload", onUnload);
         Services.prefs.removeObserver("app.update.", this);
       };
       window.addEventListener("unload", onUnload);
       Services.prefs.addObserver("app.update.", this);
       this.updateReadPrefs();
       setEventListener("updateRadioGroup", "command",
--- a/browser/components/preferences/in-content/advanced.xul
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -1,14 +1,18 @@
 # 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/.
 
 <!-- Advanced panel -->
 
+#ifdef MOZ_UPDATER
+  <script type="application/javascript" src="chrome://browser/content/aboutDialog-appUpdater.js"/>
+#endif
+
 <script type="application/javascript"
         src="chrome://browser/content/preferences/in-content/advanced.js"/>
 
 <preferences id="advancedPreferences" hidden="true" data-category="paneAdvanced">
   <preference id="browser.preferences.advanced.selectedTabIndex"
               name="browser.preferences.advanced.selectedTabIndex"
               type="int"/>
 
@@ -47,42 +51,140 @@
       hidden="true"
       data-category="paneAdvanced">
   <label class="header-name" flex="1">&paneUpdates.title;</label>
 </hbox>
 
 <!-- Update -->
 <groupbox id="updateApp" data-category="paneAdvanced" hidden="true">
   <caption><label>&updateApplication.label;</label></caption>
-#ifdef MOZ_UPDATER
-  <description>&updateApplication.description;</description>
   <hbox align="start">
     <vbox flex="1">
-      <radiogroup id="updateRadioGroup">
-        <radio id="autoDesktop"
-               value="auto"
-               label="&updateAuto2.label;"
-               accesskey="&updateAuto2.accesskey;"/>
-        <radio value="checkOnly"
-              label="&updateCheckChoose2.label;"
-              accesskey="&updateCheckChoose2.accesskey;"/>
-        <radio value="manual"
-              label="&updateManual2.label;"
-              accesskey="&updateManual2.accesskey;"/>
-      </radiogroup>
+      <description>
+        &updateApplication.version.pre;<label id="version"/>&updateApplication.version.post;
+        <label id="releasenotes" class="learnMore text-link" hidden="true">&releaseNotes.link;</label>
+      </description>
+      <description id="distribution" class="text-blurb" hidden="true"/>
+      <description id="distributionId" class="text-blurb" hidden="true"/>
     </vbox>
+#ifdef MOZ_UPDATER
     <spacer flex="1"/>
     <vbox>
       <button id="showUpdateHistory"
               class="accessory-button"
               label="&updateHistory2.label;"
               accesskey="&updateHistory2.accesskey;"
               preference="app.update.disable_button.showUpdateHistory"/>
     </vbox>
+#endif
   </hbox>
+#ifdef MOZ_UPDATER
+  <vbox id="updateBox">
+    <deck id="updateDeck" orient="vertical">
+      <hbox id="checkForUpdates" align="center">
+        <spacer flex="1"/>
+        <button id="checkForUpdatesButton"
+                label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                oncommand="gAppUpdater.checkForUpdates();"/>
+      </hbox>
+      <hbox id="downloadAndInstall" align="center">
+        <spacer flex="1"/>
+        <button id="downloadAndInstallButton"
+                oncommand="gAppUpdater.startDownload();"/>
+                <!-- label and accesskey will be filled by JS -->
+      </hbox>
+      <hbox id="apply" align="center">
+        <spacer flex="1"/>
+        <button id="updateButton"
+                label="&update.updateButton.label3;"
+                accesskey="&update.updateButton.accesskey;"
+                oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
+      </hbox>
+      <hbox id="checkingForUpdates" align="center">
+        <image class="update-throbber"/><label>&update.checkingForUpdates;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+      <hbox id="downloading" align="center">
+        <image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
+      </hbox>
+      <hbox id="applying" align="center">
+        <image class="update-throbber"/><label>&update.applying;</label>
+      </hbox>
+      <hbox id="downloadFailed" align="center">
+        <label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                oncommand="gAppUpdater.checkForUpdates();"/>
+      </hbox>
+      <hbox id="adminDisabled" align="center">
+        <label>&update.adminDisabled;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+      <hbox id="noUpdatesFound" align="center">
+        <label>&update.noUpdatesFound;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                oncommand="gAppUpdater.checkForUpdates();"/>
+      </hbox>
+      <hbox id="otherInstanceHandlingUpdates" align="center">
+        <label>&update.otherInstanceHandlingUpdates;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+      <hbox id="manualUpdate" align="center">
+        <label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+      <hbox id="unsupportedSystem" align="center">
+        <label>&update.unsupported.start;</label><label id="unsupportedLink" class="text-link">&update.unsupported.linkText;</label><label>&update.unsupported.end;</label>
+        <spacer flex="1"/>
+        <button label="&update.checkForUpdatesButton.label;"
+                accesskey="&update.checkForUpdatesButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+      <hbox id="restarting" align="center">
+        <label>&update.restarting;</label>
+        <spacer flex="1"/>
+        <button label="&update.updateButton.label3;"
+                accesskey="&update.updateButton.accesskey;"
+                disabled="true"/>
+      </hbox>
+    </deck>
+  </vbox>
+#endif
+
+  <separator/>
+#ifdef MOZ_UPDATER
+  <description>&updateApplication.description;</description>
+  <radiogroup id="updateRadioGroup">
+    <radio id="autoDesktop"
+           value="auto"
+           label="&updateAuto2.label;"
+           accesskey="&updateAuto2.accesskey;"/>
+    <radio value="checkOnly"
+          label="&updateCheckChoose2.label;"
+          accesskey="&updateCheckChoose2.accesskey;"/>
+    <radio value="manual"
+          label="&updateManual2.label;"
+          accesskey="&updateManual2.accesskey;"/>
+  </radiogroup>
 #ifdef MOZ_MAINTENANCE_SERVICE
   <checkbox id="useService"
             label="&useService.label;"
             accesskey="&useService.accesskey;"
             preference="app.update.service.enabled"/>
 #endif
 #endif
   <checkbox id="enableSearchUpdate"
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -198,25 +198,26 @@ var gSearchResultsPane = {
       gotoPref("paneSearchResults");
 
       this.searchResultsCategory.hidden = false;
 
       let resultsFound = false;
 
       // Building the range for highlighted areas
       let rootPreferences = document.getElementById("mainPrefPane")
-      let rootPreferencesChildren = rootPreferences.children;
+      let rootPreferencesChildren = rootPreferences
+        .querySelectorAll(":not([data-hidden-from-search])");
 
       // Showing all the children to bind JS, Access Keys, etc
-      for (let i = 0; i < rootPreferences.childElementCount; i++) {
+      for (let i = 0; i < rootPreferencesChildren.length; i++) {
         rootPreferencesChildren[i].hidden = false;
       }
 
       // Showing or Hiding specific section depending on if words in query are found
-      for (let i = 0; i < rootPreferences.childElementCount; i++) {
+      for (let i = 0; i < rootPreferencesChildren.length; i++) {
         if (rootPreferencesChildren[i].className != "header" &&
             rootPreferencesChildren[i].className != "no-results-message" &&
             this.searchWithinNode(rootPreferencesChildren[i], query)) {
           rootPreferencesChildren[i].hidden = false;
           resultsFound = true;
         } else {
           rootPreferencesChildren[i].hidden = true;
         }
@@ -290,16 +291,16 @@ var gSearchResultsPane = {
         valueResult = this.stringMatchesFilters(nodeObject.getAttribute("value"), searchPhrase);
       }
 
       matchesFound = matchesFound || complexTextNodesResult || labelResult || valueResult;
     }
 
     for (let i = 0; i < nodeObject.childNodes.length; i++) {
       // Search only if child node is not hidden
-      if (!nodeObject.childNodes[i].hidden) {
+      if (!nodeObject.childNodes[i].hidden && nodeObject.getAttribute("data-hidden-from-search") !== "true") {
         let result = this.searchWithinNode(nodeObject.childNodes[i], searchPhrase);
         matchesFound = matchesFound || result;
       }
     }
     return matchesFound;
   }
 }
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -32,16 +32,18 @@
 <!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
 <!ENTITY % mainDTD SYSTEM "chrome://browser/locale/preferences/main.dtd">
 <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
 <!ENTITY % contentDTD SYSTEM "chrome://browser/locale/preferences/content.dtd">
 <!ENTITY % applicationsDTD SYSTEM
   "chrome://browser/locale/preferences/applications.dtd">
 <!ENTITY % advancedDTD SYSTEM
   "chrome://browser/locale/preferences/advanced.dtd">
+<!ENTITY % aboutDialogDTD SYSTEM "chrome://browser/locale/aboutDialog.dtd" >
+%aboutDialogDTD;
 %brandDTD;
 %globalPreferencesDTD;
 %preferencesDTD;
 %privacyDTD;
 %tabsDTD;
 %searchDTD;
 %syncBrandDTD;
 %syncDTD;
--- a/browser/components/preferences/in-content/tests/browser_advanced_update.js
+++ b/browser/components/preferences/in-content/tests/browser_advanced_update.js
@@ -105,24 +105,29 @@ add_task(function*() {
   ok(!enableSearchUpdate.checked, "Ensure search updates are disabled");
   Services.prefs.setBoolPref("browser.search.update", true);
   ok(enableSearchUpdate.checked, "Ensure search updates are enabled");
 
   gBrowser.removeCurrentTab();
 });
 
 add_task(function*() {
-  mockUpdateManager.register();
-
   yield openPreferencesViaOpenPreferencesAPI("advanced", { leaveOpen: true });
   let doc = gBrowser.selectedBrowser.contentDocument;
 
   let showBtn = doc.getElementById("showUpdateHistory");
   let dialogOverlay = doc.getElementById("dialogOverlay");
 
+  // XXX: For unknown reasons, this mock cannot be loaded by
+  // XPCOMUtils.defineLazyServiceGetter() called in aboutDialog-appUpdater.js.
+  // It is registered here so that we could assert update history subdialog
+  // without stopping the preferences advanced pane from loading.
+  // See bug 1361929.
+  mockUpdateManager.register();
+
   // Test the dialog window opens
   is(dialogOverlay.style.visibility, "", "The dialog should be invisible");
   showBtn.doCommand();
   yield promiseLoadSubDialog("chrome://mozapps/content/update/history.xul");
   is(dialogOverlay.style.visibility, "visible", "The dialog should be visible");
 
   let dialogFrame = doc.getElementById("dialogFrame");
   let frameDoc = dialogFrame.contentDocument;
--- a/browser/components/preferences/in-content/tests/browser_search_within_preferences.js
+++ b/browser/components/preferences/in-content/tests/browser_search_within_preferences.js
@@ -166,8 +166,40 @@ add_task(function*() {
   searchInput.value = "";
   searchInput.doCommand()
 
   // Checks if back to normal
   is_element_visible(generalPane, "Should be in generalPane");
 
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
+
+/**
+ * Test for "Site Data" case, verifying elements with data-hidden-from-search = true
+ * are hidden in search result.
+ */
+add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({"set": [["browser.storageManager.enabled", false]]});
+  yield openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
+  let generalPane = gBrowser.contentDocument.getElementById("header-general");
+
+  is_element_hidden(generalPane, "Should not be in general");
+
+  // Performs search
+  let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+  searchInput.doCommand()
+  searchInput.value = "site data";
+  searchInput.doCommand()
+
+  let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
+
+  let child = mainPrefTag.querySelector("#siteDataGroup");
+  is_element_hidden(child, "Should be hidden in search results");
+
+  // Takes search off
+  searchInput.value = "";
+  searchInput.doCommand()
+
+  // Checks if back to normal
+  is_element_visible(generalPane, "Should be in generalPane");
+
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
--- a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
@@ -27,17 +27,17 @@
 
   <body dir="&locale.dir;">
     <p class="showNormal">&aboutPrivateBrowsing.notPrivate;</p>
     <button xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
             id="startPrivateBrowsing"
             class="showNormal"
             label="&privatebrowsingpage.openPrivateWindow.label;"
             accesskey="&privatebrowsingpage.openPrivateWindow.accesskey;"/>
-    <div class="showPrivate about-content-container">
+    <div class="showPrivate container">
       <h1 class="title">
         <span id="title">&privateBrowsing.title;</span>
         <span id="titleTracking">&privateBrowsing.title.tracking;</span>
       </h1>
       <section class="section-main">
         <p>&aboutPrivateBrowsing.info.notsaved.before;<strong>&aboutPrivateBrowsing.info.notsaved.emphasize;</strong>&aboutPrivateBrowsing.info.notsaved.after;</p>
         <div class="list-row">
           <ul>
--- a/browser/components/resistfingerprinting/test/browser/browser.ini
+++ b/browser/components/resistfingerprinting/test/browser/browser.ini
@@ -7,8 +7,9 @@ support-files =
 [browser_roundedWindow_dialogWindow.js]
 [browser_roundedWindow_newWindow.js]
 [browser_roundedWindow_open_max.js]
 [browser_roundedWindow_open_mid.js]
 [browser_roundedWindow_open_min.js]
 [browser_roundedWindow_windowSetting_max.js]
 [browser_roundedWindow_windowSetting_mid.js]
 [browser_roundedWindow_windowSetting_min.js]
+[browser_timezone.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_timezone.js
@@ -0,0 +1,38 @@
+/**
+ * Bug 1330890 - A test case for verifying Date() object of javascript will use
+ *               UTC timezone after fingerprinting resistance is enabled.
+ */
+
+const TEST_DOMAIN = "http://example.net/";
+const TEST_PATH = TEST_DOMAIN + "browser/browser/components/resistFingerprinting/test/browser/";
+
+add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({"set":
+    [["privacy.resistFingerprinting", true]]
+  });
+});
+
+add_task(function* test_timezone() {
+  // Load a page and verify the timezone.
+  let tab = yield BrowserTestUtils.openNewForegroundTab(
+    gBrowser, TEST_PATH + "file_dummy.html");
+
+  yield ContentTask.spawn(tab.linkedBrowser, null,
+    function* () {
+      let dateObj = new Date();
+      let dateString = dateObj.toString();
+
+      ok(dateString.endsWith("(UTC)"), "The date string is in UTC timezone.");
+      is(dateObj.getFullYear(), dateObj.getUTCFullYear(),
+           "The full year reports in UTC timezone.");
+      is(dateObj.getMonth(), dateObj.getUTCMonth(), "The month reports in UTC timezone.");
+      is(dateObj.getDate(), dateObj.getUTCDate(), "The month reports in UTC timezone.");
+      is(dateObj.getDay(), dateObj.getUTCDay(), "The day reports in UTC timezone.");
+      is(dateObj.getHours(), dateObj.getUTCHours(), "The hours reports in UTC timezone.");
+      is(dateObj.getTimezoneOffset(), 0, "The difference with UTC timezone is 0.");
+
+    }
+  );
+
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/browser/components/sessionstore/SessionStorage.jsm
+++ b/browser/components/sessionstore/SessionStorage.jsm
@@ -10,16 +10,19 @@ const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/Console.jsm");
 
+// A bound to the size of data to store for DOM Storage.
+const DOM_STORAGE_LIMIT_PREF = "browser.sessionstore.dom_storage_limit";
+
 // Returns the principal for a given |frame| contained in a given |docShell|.
 function getPrincipalForFrame(docShell, frame) {
   let ssm = Services.scriptSecurityManager;
   let uri = frame.document.documentURIObject;
   return ssm.getDocShellCodebasePrincipal(uri, docShell);
 }
 
 this.SessionStorage = Object.freeze({
@@ -174,22 +177,33 @@ var SessionStorageInternal = {
       let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
       storage = storageManager.getStorage(window, aPrincipal);
       storage.length; // XXX: Bug 1232955 - storage.length can throw, catch that failure
     } catch (e) {
       // sessionStorage might throw if it's turned off, see bug 458954
       storage = null;
     }
 
-    if (storage && storage.length) {
-       for (let i = 0; i < storage.length; i++) {
-        try {
-          let key = storage.key(i);
-          hostData[key] = storage.getItem(key);
-        } catch (e) {
-          // This currently throws for secured items (cf. bug 442048).
-        }
+    if (!storage || !storage.length) {
+      return hostData;
+    }
+
+    // If the DOMSessionStorage contains too much data, ignore it.
+    let usage = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIDOMWindowUtils)
+                      .getStorageUsage(storage);
+    Services.telemetry.getHistogramById("FX_SESSION_RESTORE_DOM_STORAGE_SIZE_ESTIMATE_CHARS").add(usage);
+    if (usage > Services.prefs.getIntPref(DOM_STORAGE_LIMIT_PREF)) {
+      return hostData;
+    }
+
+    for (let i = 0; i < storage.length; i++) {
+      try {
+        let key = storage.key(i);
+        hostData[key] = storage.getItem(key);
+      } catch (e) {
+        // This currently throws for secured items (cf. bug 442048).
       }
     }
 
     return hostData;
   }
 };
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3223,19 +3223,16 @@ var SessionStoreInternal = {
    *        {firstWindow: true} if this is the first non-private window we're
    *                            restoring in this session, that might open an
    *                            external link as well
    */
   restoreWindow: function ssi_restoreWindow(aWindow, winData, aOptions = {}) {
     let overwriteTabs = aOptions && aOptions.overwriteTabs;
     let isFollowUp = aOptions && aOptions.isFollowUp;
     let firstWindow = aOptions && aOptions.firstWindow;
-    // See SessionStoreInternal.restoreTabs for a description of what
-    // selectTab represents.
-    let selectTab = (overwriteTabs ? parseInt(winData.selected || 1, 10) : 0);
 
     if (isFollowUp) {
       this.windowToFocus = aWindow;
     }
 
     // initialize window if necessary
     if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
       this.onLoad(aWindow);
@@ -3250,120 +3247,77 @@ var SessionStoreInternal = {
       winData.tabs = [];
     // don't restore a single blank tab when we've had an external
     // URL passed in for loading at startup (cf. bug 357419)
     } else if (firstWindow && !overwriteTabs && winData.tabs.length == 1 &&
              (!winData.tabs[0].entries || winData.tabs[0].entries.length == 0)) {
       winData.tabs = [];
     }
 
-    var tabbrowser = aWindow.gBrowser;
-    var openTabCount = overwriteTabs ? tabbrowser.browsers.length : -1;
-    var newTabCount = winData.tabs.length;
+    // See SessionStoreInternal.restoreTabs for a description of what
+    // selectTab represents.
+    let selectTab = 0;
+    if (overwriteTabs) {
+      selectTab = parseInt(winData.selected || 1, 10);
+      selectTab = Math.max(selectTab, 1);
+      selectTab = Math.min(selectTab, winData.tabs.length);
+    }
+
+    let tabbrowser = aWindow.gBrowser;
+    let tabsToRemove = overwriteTabs ? tabbrowser.browsers.length : 0;
+    let newTabCount = winData.tabs.length;
     var tabs = [];
 
     // disable smooth scrolling while adding, moving, removing and selecting tabs
-    var tabstrip = tabbrowser.tabContainer.mTabstrip;
-    var smoothScroll = tabstrip.smoothScroll;
+    let tabstrip = tabbrowser.tabContainer.mTabstrip;
+    let smoothScroll = tabstrip.smoothScroll;
     tabstrip.smoothScroll = false;
 
-    // unpin all tabs to ensure they are not reordered in the next loop
-    if (overwriteTabs) {
-      for (let t = tabbrowser._numPinnedTabs - 1; t > -1; t--)
-        tabbrowser.unpinTab(tabbrowser.tabs[t]);
-    }
-
     // We need to keep track of the initially open tabs so that they
     // can be moved to the end of the restored tabs.
     let initialTabs = [];
     if (!overwriteTabs && firstWindow) {
       initialTabs = Array.slice(tabbrowser.tabs);
     }
 
-    // make sure that the selected tab won't be closed in order to
-    // prevent unnecessary flickering
-    if (overwriteTabs && tabbrowser.selectedTab._tPos >= newTabCount)
-      tabbrowser.moveTabTo(tabbrowser.selectedTab, newTabCount - 1);
-
     let numVisibleTabs = 0;
 
     let restoreTabsLazily = this._prefBranch.getBoolPref("sessionstore.restore_tabs_lazily") &&
       this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
 
     for (var t = 0; t < newTabCount; t++) {
-      // When trying to restore into existing tab, we also take the userContextId
-      // into account if present.
       let userContextId = winData.tabs[t].userContextId;
-      let createLazyBrowser = restoreTabsLazily && !winData.tabs[t].pinned;
-      let reuseExisting = t < openTabCount &&
-                          (tabbrowser.tabs[t].getAttribute("usercontextid") == (userContextId || ""));
-      let tab = reuseExisting ? this._maybeUpdateBrowserRemoteness(tabbrowser.tabs[t])
-                              : tabbrowser.addTab("about:blank",
-                                                  { createLazyBrowser,
-                                                    skipAnimation: true,
-                                                    userContextId,
-                                                    skipBackgroundNotify: true });
-
-      // If we inserted a new tab because the userContextId didn't match with the
-      // open tab, even though `t < openTabCount`, we need to remove that open tab
-      // and put the newly added tab in its place.
-      if (!reuseExisting && t < openTabCount) {
-        tabbrowser.removeTab(tabbrowser.tabs[t]);
-        tabbrowser.moveTabTo(tab, t);
+      let select = t == selectTab - 1;
+      let createLazyBrowser = restoreTabsLazily && !select && !winData.tabs[t].pinned;
+      let tab = tabbrowser.addTab("about:blank",
+                                  { createLazyBrowser,
+                                    skipAnimation: true,
+                                    userContextId,
+                                    skipBackgroundNotify: true });
+
+      if (select) {
+        // Select a new tab first to prevent the removeTab loop from changing
+        // the selected tab over and over again.
+        tabbrowser.selectedTab = tab;
+
+        // Remove superfluous tabs.
+        for (let i = 0; i < tabsToRemove; i++) {
+          tabbrowser.removeTab(tabbrowser.tabs[0]);
+        }
+        tabsToRemove = 0;
       }
 
       tabs.push(tab);
 
       if (winData.tabs[t].hidden) {
         tabbrowser.hideTab(tabs[t]);
       } else {
         tabbrowser.showTab(tabs[t]);
         numVisibleTabs++;
       }
-
-      if (!!winData.tabs[t].muted != tabs[t].linkedBrowser.audioMuted) {
-        tabs[t].toggleMuteAudio(winData.tabs[t].muteReason);
-      }
-    }
-
-    if (selectTab > 0 && selectTab <= tabs.length) {
-      // The state we're restoring wants to select a particular tab. This
-      // implies that we're overwriting tabs.
-      let currentIndex = tabbrowser.tabContainer.selectedIndex;
-      let targetIndex = selectTab - 1;
-
-      if (currentIndex != targetIndex) {
-        // We need to change the selected tab. There are two ways of doing this:
-        //
-        // 1) The fast path: swap the currently selected tab with the one in the
-        //    position of the selected tab in the restored state. Note that this
-        //    can only work if the user contexts between the two tabs being swapped
-        //    match. This should be the common case.
-        //
-        // 2) The slow path: switch to the selected tab.
-        //
-        // We'll try to do (1), and then fallback to (2).
-
-        let selectedTab = tabbrowser.selectedTab;
-        let tabAtTargetIndex = tabs[targetIndex];
-        let userContextsMatch = selectedTab.userContextId == tabAtTargetIndex.userContextId;
-
-        if (userContextsMatch) {
-          tabbrowser.moveTabTo(selectedTab, targetIndex);
-          tabbrowser.moveTabTo(tabAtTargetIndex, currentIndex);
-          // We also have to do a similar "move" in the aTabs Array to
-          // make sure that the restored content shows up in the right
-          // order.
-          tabs[targetIndex] = tabs[currentIndex];
-          tabs[currentIndex] = tabAtTargetIndex;
-        } else {
-          // Otherwise, go the slow path, and switch to the target tab.
-          tabbrowser.selectedTab = tabs[targetIndex];
-        }
-      }
     }
 
     for (let i = 0; i < newTabCount; ++i) {
       if (winData.tabs[i].pinned) {
         tabbrowser.pinTab(tabs[i]);
       } else {
         // Pinned tabs are clustered at the start of the tab strip. As
         // soon as we reach a tab that isn't pinned, we know there aren't
@@ -3371,51 +3325,34 @@ var SessionStoreInternal = {
         break;
       }
     }
 
     if (!overwriteTabs && firstWindow) {
       // Move the originally open tabs to the end
       let endPosition = tabbrowser.tabs.length - 1;
       for (let i = 0; i < initialTabs.length; i++) {
+        tabbrowser.unpinTab(initialTabs[i]);
         tabbrowser.moveTabTo(initialTabs[i], endPosition);
       }
     }
 
     // if all tabs to be restored are hidden, make the first one visible
     if (!numVisibleTabs && winData.tabs.length) {
       winData.tabs[0].hidden = false;
       tabbrowser.showTab(tabs[0]);
     }
 
-    // If overwriting tabs, we want to reset each tab's "restoring" state. Since
-    // we're overwriting those tabs, they should no longer be restoring. The
-    // tabs will be rebuilt and marked if they need to be restored after loading
-    // state (in restoreTabs).
-    if (overwriteTabs) {
-      for (let i = 0; i < tabbrowser.tabs.length; i++) {
-        let tab = tabbrowser.tabs[i];
-        if (tabbrowser.browsers[i].__SS_restoreState)
-          this._resetTabRestoringState(tab);
-      }
-    }
-
     // We want to correlate the window with data from the last session, so
     // assign another id if we have one. Otherwise clear so we don't do
     // anything with it.
     delete aWindow.__SS_lastSessionWindowID;
     if (winData.__lastSessionWindowID)
       aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID;
 
-    // when overwriting tabs, remove all superflous ones
-    if (overwriteTabs && newTabCount < openTabCount) {
-      Array.slice(tabbrowser.tabs, newTabCount, openTabCount)
-           .forEach(tabbrowser.removeTab, tabbrowser);
-    }
-
     if (overwriteTabs) {
       this.restoreWindowFeatures(aWindow, winData);
       delete this._windows[aWindow.__SSi].extData;
     }
 
     // Restore cookies from legacy sessions, i.e. before bug 912717.
     SessionCookies.restore(winData.cookies || []);
 
@@ -3824,17 +3761,16 @@ var SessionStoreInternal = {
       let epoch = this.startNextEpoch(browser);
 
       this._sendRestoreHistory(browser, {
         tabData,
         epoch,
         loadArguments: aLoadArguments,
         isRemotenessUpdate,
       });
-
     }
 
     // If the restored browser wants to show view source content, start up a
     // view source browser that will load the required frame script.
     if (uri && ViewSourceBrowser.isViewSource(uri)) {
       new ViewSourceBrowser(browser);
     }
 
@@ -4073,37 +4009,16 @@ var SessionStoreInternal = {
     }
     this._closedObjectsChanged = false;
     setTimeout(() => {
       Services.obs.notifyObservers(null, NOTIFY_CLOSED_OBJECTS_CHANGED);
     }, 0);
   },
 
   /**
-   * Determines whether or not a tab that is being restored needs
-   * to have its remoteness flipped first.
-   *
-   * @param tab (<xul:tab>):
-   *        The tab being restored.
-   *
-   * @returns tab (<xul:tab>)
-   *        The tab that was passed.
-   */
-  _maybeUpdateBrowserRemoteness(tab) {
-    let win = tab.ownerGlobal;
-    let tabbrowser = win.gBrowser;
-    let browser = tab.linkedBrowser;
-    if (win.gMultiProcessBrowser && !browser.isRemoteBrowser) {
-      tabbrowser.updateBrowserRemoteness(browser, true);
-    }
-
-    return tab;
-  },
-
-  /**
    * Update the session start time and send a telemetry measurement
    * for the number of days elapsed since the session was started.
    *
    * @param state
    *        The session state.
    */
   _updateSessionStartTime: function ssi_updateSessionStartTime(state) {
     // Attempt to load the session start time from the session state
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -44,17 +44,17 @@ var gFrameTree = new FrameTree(this);
 Cu.import("resource:///modules/sessionstore/ContentRestore.jsm", this);
 XPCOMUtils.defineLazyGetter(this, "gContentRestore",
                             () => { return new ContentRestore(this) });
 
 // The current epoch.
 var gCurrentEpoch = 0;
 
 // A bound to the size of data to store for DOM Storage.
-const DOM_STORAGE_MAX_CHARS = 10000000; // 10M characters
+const DOM_STORAGE_LIMIT_PREF = "browser.sessionstore.dom_storage_limit";
 
 // This pref controls whether or not we send updates to the parent on a timeout
 // or not, and should only be used for tests or debugging.
 const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates";
 
 const kNoIndex = Number.MAX_SAFE_INTEGER;
 const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
 
@@ -575,108 +575,78 @@ var SessionStorageListener = {
   },
 
   observe() {
     // Collect data on the next tick so that any other observer
     // that needs to purge data can do its work first.
     setTimeout(() => this.collect(), 0);
   },
 
-  // Before DOM Storage can be written to disk, it needs to be serialized
-  // for sending across frames/processes, then again to be sent across
-  // threads, then again to be put in a buffer for the disk. Each of these
-  // serializations is an opportunity to OOM and (depending on the site of
-  // the OOM), either crash, lose all data for the frame or lose all data
-  // for the application.
-  //
-  // In order to avoid this, compute an estimate of the size of the
-  // object, and block SessionStorage items that are too large. As
-  // we also don't want to cause an OOM here, we use a quick and memory-
-  // efficient approximation: we compute the total sum of string lengths
-  // involved in this object.
-  estimateStorageSize(collected) {
-    if (!collected) {
-      return 0;
-    }
-
-    let size = 0;
-    for (let host of Object.keys(collected)) {
-      size += host.length;
-      let perHost = collected[host];
-      for (let key of Object.keys(perHost)) {
-        size += key.length;
-        let perKey = perHost[key];
-        size += perKey.length;
-      }
-    }
-
-    return size;
-  },
-
   // We don't want to send all the session storage data for all the frames
   // for every change. So if only a few value changed we send them over as
   // a "storagechange" event. If however for some reason before we send these
   // changes we have to send over the entire sessions storage data, we just
   // reset these changes.
   _changes: undefined,
 
   resetChanges() {
     this._changes = undefined;
   },
 
   collectFromEvent(event) {
-    // TODO: we should take browser.sessionstore.dom_storage_limit into an account here.
-    if (docShell) {
-      let {url, key, newValue} = event;
-      let uri = Services.io.newURI(url);
-      let domain = uri.prePath;
-      if (!this._changes) {
-        this._changes = {};
-      }
-      if (!this._changes[domain]) {
-        this._changes[domain] = {};
-      }
-      this._changes[domain][key] = newValue;
+    if (!docShell) {
+      return;
+    }
+
+    // How much data does DOMSessionStorage contain?
+    let usage = content.QueryInterface(Ci.nsIInterfaceRequestor)
+                       .getInterface(Ci.nsIDOMWindowUtils)
+                       .getStorageUsage(event.storageArea);
+    Services.telemetry.getHistogramById("FX_SESSION_RESTORE_DOM_STORAGE_SIZE_ESTIMATE_CHARS").add(usage);
+
+    // Don't store any data if we exceed the limit. Wipe any data we previously
+    // collected so that we don't confuse websites with partial state.
+    if (usage > Preferences.get(DOM_STORAGE_LIMIT_PREF)) {
+      MessageQueue.push("storage", () => null);
+      return;
+    }
 
-      MessageQueue.push("storagechange", () => {
-        let tmp = this._changes;
-        // If there were multiple changes we send them merged.
-        // First one will collect all the changes the rest of
-        // these messages will be ignored.
-        this.resetChanges();
-        return tmp;
-      });
+    let {url, key, newValue} = event;
+    let uri = Services.io.newURI(url);
+    let domain = uri.prePath;
+    if (!this._changes) {
+      this._changes = {};
+    }
+    if (!this._changes[domain]) {
+      this._changes[domain] = {};
     }
+    this._changes[domain][key] = newValue;
+
+    MessageQueue.push("storagechange", () => {
+      let tmp = this._changes;
+      // If there were multiple changes we send them merged.
+      // First one will collect all the changes the rest of
+      // these messages will be ignored.
+      this.resetChanges();
+      return tmp;
+    });
   },
 
   collect() {
-    if (docShell) {
-      // We need the entire session storage, let's reset the pending individual change
-      // messages.
-      this.resetChanges();
-      MessageQueue.push("storage", () => {
-        let collected = SessionStorage.collect(docShell, gFrameTree);
-
-        if (collected == null) {
-          return collected;
-        }
+    if (!docShell) {
+      return;
+    }
 
-        let size = this.estimateStorageSize(collected);
-        Services.telemetry.getHistogramById("FX_SESSION_RESTORE_DOM_STORAGE_SIZE_ESTIMATE_CHARS").add(size);
+    // We need the entire session storage, let's reset the pending individual change
+    // messages.
+    this.resetChanges();
 
-        if (size > Preferences.get("browser.sessionstore.dom_storage_limit", DOM_STORAGE_MAX_CHARS)) {
-          // Rather than keeping the old storage, which wouldn't match the rest
-          // of the state of the page, empty the storage. DOM storage will be
-          // recollected the next time and stored if it is now small enough.
-          return {};
-        }
-
-        return collected;
-      });
-    }
+    MessageQueue.push("storage", () => {
+      return SessionStorage.collect(docShell, gFrameTree);
+    });
   },
 
   onFrameTreeCollected() {
     this.collect();
   },
 
   onFrameTreeReset() {
     this.collect();
--- a/browser/components/sessionstore/test/browser_423132.js
+++ b/browser/components/sessionstore/test/browser_423132.js
@@ -47,10 +47,10 @@ add_task(function*() {
       break;
   }
   is(cookie.name, cookie2.name, "cookie name successfully restored");
   is(cookie.value, cookie2.value, "cookie value successfully restored");
   is(cookie.path, cookie2.path, "cookie path successfully restored");
 
   // clean up
   Services.cookies.removeAll();
-  yield promiseRemoveTab(tab);
+  yield promiseRemoveTab(gBrowser.tabs[1]);
 });
--- a/browser/components/sessionstore/test/browser_586147.js
+++ b/browser/components/sessionstore/test/browser_586147.js
@@ -17,36 +17,36 @@ function test() {
   let [origTab] = gBrowser.visibleTabs;
   let hiddenTab = gBrowser.addTab();
 
   is(gBrowser.visibleTabs.length, 2, "should have 2 tabs before hiding");
   gBrowser.showOnlyTheseTabs([origTab]);
   is(gBrowser.visibleTabs.length, 1, "only 1 after hiding");
   ok(hiddenTab.hidden, "sanity check that it's hidden");
 
-  let extraTab = gBrowser.addTab();
+  gBrowser.addTab();
   let state = ss.getBrowserState();
   let stateObj = JSON.parse(state);
   let tabs = stateObj.windows[0].tabs;
   is(tabs.length, 3, "just checking that browser state is correct");
   ok(!tabs[0].hidden, "first tab is visible");
   ok(tabs[1].hidden, "second is hidden");
   ok(!tabs[2].hidden, "third is visible");
 
   // Make the third tab hidden and then restore the modified state object
   tabs[2].hidden = true;
 
   observeOneRestore(function() {
-    let testWindow = Services.wm.getEnumerator("navigator:browser").getNext();
-    is(testWindow.gBrowser.visibleTabs.length, 1, "only restored 1 visible tab");
-    let restoredTabs = testWindow.gBrowser.tabs;
+    is(gBrowser.visibleTabs.length, 1, "only restored 1 visible tab");
+    let restoredTabs = gBrowser.tabs;
+
     ok(!restoredTabs[0].hidden, "first is still visible");
     ok(restoredTabs[1].hidden, "second tab is still hidden");
     ok(restoredTabs[2].hidden, "third tab is now hidden");
 
     // Restore the original state and clean up now that we're done
-    gBrowser.removeTab(hiddenTab);
-    gBrowser.removeTab(extraTab);
+    gBrowser.removeTab(gBrowser.tabs[1]);
+    gBrowser.removeTab(gBrowser.tabs[1]);
 
     finish();
   });
   ss.setBrowserState(JSON.stringify(stateObj));
 }
--- a/browser/components/sessionstore/test/browser_590563.js
+++ b/browser/components/sessionstore/test/browser_590563.js
@@ -51,21 +51,24 @@ function middleClickTest(win) {
      "The total number of visible tabs should be 3 after restoring 2 tabs by middle click");
 }
 
 function newWindowWithState(state, callback) {
   let opts = "chrome,all,dialog=no,height=800,width=800";
   let win = window.openDialog(getBrowserURL(), "_blank", opts);
 
   win.addEventListener("load", function() {
-    let tab = win.gBrowser.selectedTab;
-
     // The form data will be restored before SSTabRestored, so we want to listen
-    // for that on the currently selected tab (it will be reused)
-    tab.addEventListener("SSTabRestored", function() {
-      callback(win);
-    }, {capture: true, once: true});
+    // for that on the currently selected tab
+    let onSSTabRestored = event => {
+      let tab = event.target;
+      if (tab.selected) {
+        win.gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, true);
+        callback(win);
+      }
+    };
+    win.gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, true);
 
     executeSoon(function() {
       ss.setWindowState(win, JSON.stringify(state), true);
     });
   }, {once: true});
 }
--- a/browser/components/sessionstore/test/browser_624727.js
+++ b/browser/components/sessionstore/test/browser_624727.js
@@ -16,20 +16,18 @@ add_task(function* () {
   assertNumberOfTabs(1, "we start off with one tab");
   assertNumberOfPinnedTabs(0, "no pinned tabs so far");
 
   // setup
   gBrowser.addTab("about:blank");
   assertNumberOfTabs(2, "there are two tabs, now");
 
   let [tab1, tab2] = gBrowser.tabs;
-  let linkedBrowser = tab1.linkedBrowser;
   gBrowser.pinTab(tab1);
   gBrowser.pinTab(tab2);
   assertNumberOfPinnedTabs(2, "both tabs are now pinned");
 
   // run the test
   yield promiseBrowserState(TEST_STATE);
 
   assertNumberOfTabs(1, "one tab left after setBrowserState()");
   assertNumberOfPinnedTabs(0, "there are no pinned tabs");
-  is(gBrowser.tabs[0].linkedBrowser, linkedBrowser, "first tab's browser got re-used");
 });
--- a/browser/components/sessionstore/test/browser_parentProcessRestoreHash.js
+++ b/browser/components/sessionstore/test/browser_parentProcessRestoreHash.js
@@ -21,16 +21,17 @@ let TestAboutPage = {
   },
 
   newChannel(aURI, aLoadInfo) {
     // about: page inception!
     let newURI = Services.io.newURI(SELFCHROMEURL);
     let channel = Services.io.newChannelFromURIWithLoadInfo(newURI,
                                                             aLoadInfo);
     channel.originalURI = aURI;
+    aLoadInfo.resultPrincipalURI = aURI;
     return channel;
   },
 
   createInstance(outer, iid) {
     if (outer != null) {
       throw Cr.NS_ERROR_NO_AGGREGATION;
     }
     return this.QueryInterface(iid);
--- a/browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js
+++ b/browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js
@@ -81,40 +81,28 @@ const PINNED_STATE = {
  * stateToRestore:
  *   A JS Object for the state to send down to the window.
  *
  * selectedTab:
  *   The 1-based index of the tab that we want to select for the
  *   restored window. Leave this at 0 if you don't want to change
  *   the selection from the initial window state.
  *
- * expectedFlips:
- *   an Array that represents the window that we end up with after
- *   restoring state. Each bool in the Array represents the window tabs,
- *   in order. A "true" indicates that the tab should have flipped
- *   its remoteness once. "false" indicates that the tab should never
- *   have flipped remoteness. Note that any tab that flips its remoteness
- *   more than once will cause a test failure.
- *
  * expectedRemoteness:
  *   an Array that represents the window that we end up with after
  *   restoring state. Each bool in the Array represents the window
  *   tabs in order. A "true" indicates that the tab be remote, and
  *   a "false" indicates that the tab should be "non-remote". We
  *   need this Array in order to test pinned tabs which will also
  *   be loaded by default, and therefore should end up remote.
  *
  */
 function* runScenarios(scenarios) {
   for (let [scenarioIndex, scenario] of scenarios.entries()) {
     info("Running scenario " + scenarioIndex);
-    // Let's make sure our scenario is sane first.
-    Assert.equal(scenario.expectedFlips.length,
-                 scenario.expectedRemoteness.length,
-                 "All expected flips and remoteness needs to be supplied");
     Assert.ok(scenario.initialSelectedTab > 0,
               "You must define an initially selected tab");
 
     // First, we need to create the initial conditions, so we
     // open a new window to put into our starting state...
     let win = yield BrowserTestUtils.openNewBrowserWindow();
     let tabbrowser = win.gBrowser;
     Assert.ok(tabbrowser.selectedBrowser.isRemoteBrowser,
@@ -137,86 +125,38 @@ function* runScenarios(scenarios) {
     }
 
     // And select the requested tab.
     let tabToSelect = tabbrowser.tabs[scenario.initialSelectedTab - 1];
     if (tabbrowser.selectedTab != tabToSelect) {
       yield BrowserTestUtils.switchTab(tabbrowser, tabToSelect);
     }
 
-    // Hook up an event listener to make sure that the right
-    // tabs flip remoteness, and only once.
-    let flipListener = {
-      seenBeforeTabs: new Set(),
-      seenAfterTabs: new Set(),
-      handleEvent(e) {
-        let index = Array.from(tabbrowser.tabs).indexOf(e.target);
-        switch (e.type) {
-          case "BeforeTabRemotenessChange":
-            info(`Saw tab at index ${index} before remoteness flip`);
-            if (this.seenBeforeTabs.has(e.target)) {
-              Assert.ok(false, "Saw tab before remoteness flip more than once");
-            }
-            this.seenBeforeTabs.add(e.target);
-            break;
-          case "TabRemotenessChange":
-            info(`Saw tab at index ${index} after remoteness flip`);
-            if (this.seenAfterTabs.has(e.target)) {
-              Assert.ok(false, "Saw tab after remoteness flip more than once");
-            }
-            this.seenAfterTabs.add(e.target);
-            break;
-        }
-      },
-    };
-
-    win.addEventListener("BeforeTabRemotenessChange", flipListener);
-    win.addEventListener("TabRemotenessChange", flipListener);
-
     // Okay, time to test!
     let state = prepareState(scenario.stateToRestore,
                              scenario.selectedTab);
 
     SessionStore.setWindowState(win, state, true);
 
-    win.removeEventListener("BeforeTabRemotenessChange", flipListener);
-    win.removeEventListener("TabRemotenessChange", flipListener);
-
-    // Because we know that scenario.expectedFlips and
-    // scenario.expectedRemoteness have the same length, we
-    // can check that we satisfied both with the same loop.
-    for (let i = 0; i < scenario.expectedFlips.length; ++i) {
-      let expectedToFlip = scenario.expectedFlips[i];
+    for (let i = 0; i < scenario.expectedRemoteness.length; ++i) {
       let expectedRemoteness = scenario.expectedRemoteness[i];
       let tab = tabbrowser.tabs[i];
-      if (expectedToFlip) {
-        Assert.ok(flipListener.seenBeforeTabs.has(tab),
-                  `We should have seen tab at index ${i} before remoteness flip`);
-        Assert.ok(flipListener.seenAfterTabs.has(tab),
-                  `We should have seen tab at index ${i} after remoteness flip`);
-      } else {
-        Assert.ok(!flipListener.seenBeforeTabs.has(tab),
-                  `We should not have seen tab at index ${i} before remoteness flip`);
-        Assert.ok(!flipListener.seenAfterTabs.has(tab),
-                  `We should not have seen tab at index ${i} after remoteness flip`);
-      }
 
       Assert.equal(tab.linkedBrowser.isRemoteBrowser, expectedRemoteness,
                    "Should have gotten the expected remoteness " +
                    `for the tab at index ${i}`);
     }
 
     yield BrowserTestUtils.closeWindow(win);
   }
 }
 
 /**
  * Tests that if we restore state to browser windows with
- * a variety of initial remoteness states, that we only flip
- * the remoteness on the necessary tabs. For this particular
+ * a variety of initial remoteness states. For this particular
  * set of tests, we assume that tabs are restoring on demand.
  */
 add_task(function*() {
   // This test opens and closes windows, which might bog down
   // a debug build long enough to time out the test, so we
   // extend the tolerance on timeouts.
   requestLongerTimeout(5);
 
@@ -228,132 +168,83 @@ add_task(function*() {
     // Only one tab in the new window, and it's remote. This
     // is the common case, since this is how restoration occurs
     // when the restored window is being opened.
     {
       initialRemoteness: [true],
       initialSelectedTab: 1,
       stateToRestore: SIMPLE_STATE,
       selectedTab: 3,
-      // The initial tab is remote and should go into
-      // the background state. The second and third tabs
-      // are new and should initialize remotely as well.
-      // There should therefore be no remoteness flips.
-      expectedFlips: [false, false, false],
       // All tabs should now be remote.
       expectedRemoteness: [true, true, true],
     },
 
     // A single remote tab, and this is the one that's going
     // to be selected once state is restored.
     {
       initialRemoteness: [true],
       initialSelectedTab: 1,
       stateToRestore: SIMPLE_STATE,
       selectedTab: 1,
-      // The initial tab is remote and selected, so it should
-      // not flip remoteness. The other two new tabs should
-      // initialize as remote unrestored background tabs.
-      expectedFlips: [false, false, false],
       // All tabs should now be remote.
       expectedRemoteness: [true, true, true],
     },
 
     // A single remote tab which starts selected. We set the
     // selectedTab to 0 which is equivalent to "don't change
     // the tab selection in the window".
     {
       initialRemoteness: [true],
       initialSelectedTab: 1,
       stateToRestore: SIMPLE_STATE,
       selectedTab: 0,
-      // The initial tab is remote and selected, so it should
-      // not flip remoteness. The other two new tabs should
-      // initialize as remote unrestored background tabs.
-      expectedFlips: [false, false, false],
       // All tabs should now be remote.
       expectedRemoteness: [true, true, true],
     },
 
     // An initially remote tab, but we're going to load
     // some pinned tabs now, and the pinned tabs should load
     // right away.
     {
       initialRemoteness: [true],
       initialSelectedTab: 1,
       stateToRestore: PINNED_STATE,
       selectedTab: 3,
-      // The initial tab is pinned and will load right away,
-      // so it should stay remote. The second tab is new
-      // and pinned, so it should start remote and not flip.
-      // The third tab is not pinned, but it is selected,
-      // so it will start remote.
-      expectedFlips: [false, false, false],
       // Both pinned tabs and the selected tabs should all
       // end up being remote.
       expectedRemoteness: [true, true, true],
     },
 
     // A single non-remote tab.
     {
       initialRemoteness: [false],
       initialSelectedTab: 1,
       stateToRestore: SIMPLE_STATE,
       selectedTab: 2,
-      // The initial tab is non-remote and will flip. The two tabs that
-      // are added after should be initialized as remote. Since the selected
-      // tab is changing, SessionStore should swap the initial tab with the
-      // tab thats in the selectedTab slot. That'll result in a remoteness
-      // state like this:
-      //
-      // [true, false, true]
-      expectedFlips: [false, true, false],
       // All tabs should now be remote.
       expectedRemoteness: [true, true, true],
     },
 
     // A mixture of remote and non-remote tabs.
     {
       initialRemoteness: [true, false, true],
       initialSelectedTab: 1,
       stateToRestore: SIMPLE_STATE,
       selectedTab: 3,
-      // The initial tab is remote and should stay that way, even
-      // when put into the background. The initial tab is going to be
-      // swapped into the selectedTab slot, and both of those tabs are
-      // remote already. That will result in the tabs remoteness being
-      // in this order still:
-      //
-      // [true, false, true]
-      //
-      // This means that we'll only need to flip the second tab.
-      expectedFlips: [false, true, false],
       // All tabs should now be remote.
       expectedRemoteness: [true, true, true],
     },
 
     // An initially non-remote tab, but we're going to load
     // some pinned tabs now, and the pinned tabs should load
     // right away.
     {
       initialRemoteness: [false],
       initialSelectedTab: 1,
       stateToRestore: PINNED_STATE,
       selectedTab: 3,
-      // The initial tab is going to swap into the selected slot,
-      // and so after the other two tabs from the PINNED_STATE are
-      // inserted, we'll have a remoteness state like this:
-      //
-      // [true, true, false]
-      //
-      // The two pinned tabs at the start of the PINNED_STATE should
-      // load right away, but should not flip remoteness.
-      //
-      // The third tab is selected, and should load right away, so
-      // it should flip remoteness.
-      expectedFlips: [false, false, true],
       // All tabs should now be remote.
       expectedRemoteness: [true, true, true],
     },
   ];
 
   yield* runScenarios(TEST_SCENARIOS);
 });
--- a/browser/components/sessionstore/test/browser_sessionStorage_size.js
+++ b/browser/components/sessionstore/test/browser_sessionStorage_size.js
@@ -22,17 +22,17 @@ add_task(function* test_telemetry() {
   yield promiseBrowserLoaded(browser);
 
   // Flush to make sure we submitted telemetry data.
   yield TabStateFlusher.flush(browser);
 
   // There is no good way to make sure that the parent received the histogram entries from the child processes.
   // Let's stick to the ugly, spinning the event loop until we have a good approach (Bug 1357509).
   yield BrowserTestUtils.waitForCondition(() => {
-    return histogram.snapshot().counts[5] > snap1.counts[5];
+    return histogram.snapshot().counts[4] > snap1.counts[4];
   });
 
   Assert.ok(true);
   yield promiseRemoveTab(tab);
   Services.telemetry.canRecordExtended = false;
 });
 
 // Lower the size limit for DOM Storage content. Check that DOM Storage
--- a/browser/config/mozconfigs/linux32/devedition
+++ b/browser/config/mozconfigs/linux32/devedition
@@ -1,8 +1,13 @@
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+  MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
+  MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
 . "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
 
 # Add-on signing is not required for DevEdition
 MOZ_REQUIRE_SIGNING=0
 
 ac_add_options --enable-verify-mar
 
 # This will overwrite the default of stripping everything and keep the symbol table.
--- a/browser/config/mozconfigs/linux64/code-coverage
+++ b/browser/config/mozconfigs/linux64/code-coverage
@@ -2,13 +2,14 @@
 
 TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
 
 ac_add_options --disable-install-strip
 ac_add_options --disable-jemalloc
 ac_add_options --disable-crashreporter
 ac_add_options --disable-elf-hack
 ac_add_options --enable-debug
+ac_add_options --disable-sandbox
 
 MOZ_CODE_COVERAGE=1
 export CFLAGS="--coverage"
 export CXXFLAGS="--coverage"
 export LDFLAGS="--coverage -L$TOOLTOOL_DIR/gtk3/usr/local/lib"
--- a/browser/config/mozconfigs/linux64/devedition
+++ b/browser/config/mozconfigs/linux64/devedition
@@ -1,8 +1,13 @@
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+  MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
+  MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
 . "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
 
 # Add-on signing is not required for DevEdition
 MOZ_REQUIRE_SIGNING=0
 
 ac_add_options --enable-verify-mar
 
 # This will overwrite the default of stripping everything and keep the symbol table.
--- a/browser/config/mozconfigs/macosx64/devedition
+++ b/browser/config/mozconfigs/macosx64/devedition
@@ -1,8 +1,13 @@
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+  MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+  MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
 . "$topsrcdir/browser/config/mozconfigs/macosx64/common-opt"
 
 # Add-on signing is not required for DevEdition
 MOZ_REQUIRE_SIGNING=0
 
 ac_add_options --disable-install-strip
 ac_add_options --enable-verify-mar
 
--- a/browser/config/mozconfigs/win32/devedition
+++ b/browser/config/mozconfigs/win32/devedition
@@ -1,8 +1,13 @@
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+  MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+  MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
 
 # Add-on signing is not required for DevEdition
 MOZ_REQUIRE_SIGNING=0
 
 ac_add_options --enable-verify-mar
 
--- a/browser/config/mozconfigs/win64/devedition
+++ b/browser/config/mozconfigs/win64/devedition
@@ -1,8 +1,13 @@
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+  MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+  MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
 . "$topsrcdir/browser/config/mozconfigs/win64/common-opt"
 
 # Add-on signing is not required for DevEdition
 MOZ_REQUIRE_SIGNING=0
 
 ac_add_options --enable-verify-mar
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -13,16 +13,17 @@ if test "$OS_ARCH" = "WINNT" -o \
   MOZ_BUNDLED_FONTS=1
 fi
 
 if test "$OS_ARCH" = "WINNT"; then
   MOZ_MAINTENANCE_SERVICE=1
   if ! test "$HAVE_64BIT_BUILD"; then
     if test "$MOZ_UPDATE_CHANNEL" = "nightly" -o \
             "$MOZ_UPDATE_CHANNEL" = "aurora" -o \
+            "$MOZ_UPDATE_CHANNEL" = "aurora-dev" -o \
             "$MOZ_UPDATE_CHANNEL" = "beta" -o \
             "$MOZ_UPDATE_CHANNEL" = "beta-dev" -o \
             "$MOZ_UPDATE_CHANNEL" = "release" -o \
             "$MOZ_UPDATE_CHANNEL" = "release-dev"; then
       if ! test "$MOZ_DEBUG"; then
         MOZ_STUB_INSTALLER=1
       fi
     fi
@@ -42,22 +43,27 @@ MOZ_APP_VERSION_DISPLAY=$FIREFOX_VERSION
 # MOZ_BRANDING_DIRECTORY is the default branding directory used when none is
 # specified. It should never point to the "official" branding directory.
 # For mozilla-beta, mozilla-release, or mozilla-central repositories, use
 # "unofficial" branding.
 # For the mozilla-aurora repository, use "aurora".
 MOZ_BRANDING_DIRECTORY=browser/branding/unofficial
 MOZ_OFFICIAL_BRANDING_DIRECTORY=browser/branding/official
 MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
-# This should usually be the same as the value MAR_CHANNEL_ID.
+# ACCEPTED_MAR_CHANNEL_IDS should usually be the same as the value MAR_CHANNEL_ID.
 # If more than one ID is needed, then you should use a comma separated list
 # of values.
-ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-central
 # The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t "
-MAR_CHANNEL_ID=firefox-mozilla-central
+if test "$MOZ_UPDATE_CHANNEL" = "aurora"; then
+  ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-aurora
+  MAR_CHANNEL_ID=firefox-mozilla-aurora
+else
+  ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-central
+  MAR_CHANNEL_ID=firefox-mozilla-central
+fi
 MOZ_PROFILE_MIGRATOR=1
 MOZ_JSDOWNLOADS=1
 
 # Enable checking that add-ons are signed by the trusted root
 MOZ_ADDON_SIGNING=1
 
 # Include the DevTools client, not just the server (which is the default)
 MOZ_DEVTOOLS=all
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -405,16 +405,23 @@ var FormAutofillContent = {
     // Collects root forms from inputs.
     for (let field of doc.getElementsByTagName("input")) {
       // We only consider text-like fields for now until we support radio and
       // checkbox buttons in the future.
       if (!field.mozIsTextField(true)) {
         continue;
       }
 
+      // For now skip consider fields in forms we've already seen before even
+      // if the specific field wasn't seen before. Ideally whether the field is
+      // already in the handler's form details would be considered.
+      if (this.getFormHandler(field)) {
+        continue;
+      }
+
       let formLike = FormLikeFactory.createFromField(field);
       if (!forms.some(form => form.rootElement === formLike.rootElement)) {
         forms.push(formLike);
       }
     }
 
     this.log.debug("Found", forms.length, "forms");
 
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -89,21 +89,27 @@ FormAutofillParent.prototype = {
     this._setStatus(this._getStatus());
     this._updateSavedFieldNames();
   },
 
   observe(subject, topic, data) {
     log.debug("observe:", topic, "with data:", data);
     switch (topic) {
       case "advanced-pane-loaded": {
-        let formAutofillPreferences = new FormAutofillPreferences();
+        let useOldOrganization = Services.prefs.getBoolPref("browser.preferences.useOldOrganization",
+                                                            false);
+        let formAutofillPreferences = new FormAutofillPreferences({useOldOrganization});
         let document = subject.document;
         let prefGroup = formAutofillPreferences.init(document);
-        let parentNode = document.getElementById("mainPrefPane");
-        let insertBeforeNode = document.getElementById("locationBarGroup");
+        let parentNode = useOldOrganization ?
+                         document.getElementById("mainPrefPane") :
+                         document.getElementById("passwordsGroup");
+        let insertBeforeNode = useOldOrganization ?
+                               document.getElementById("locationBarGroup") :
+                               document.getElementById("passwordGrid");
         parentNode.insertBefore(prefGroup, insertBeforeNode);
         break;
       }
 
       case "nsPref:changed": {
         // Observe pref changes and update _enabled cache if status is changed.
         let currentStatus = this._getStatus();
         if (currentStatus !== this._enabled) {
--- a/browser/extensions/formautofill/FormAutofillPreferences.jsm
+++ b/browser/extensions/formautofill/FormAutofillPreferences.jsm
@@ -9,48 +9,41 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["FormAutofillPreferences"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 const PREF_AUTOFILL_ENABLED = "browser.formautofill.enabled";
 const BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
 const MANAGE_PROFILES_URL = "chrome://formautofill/content/manageProfiles.xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
-function FormAutofillPreferences() {
+function FormAutofillPreferences({useOldOrganization}) {
+  this.useOldOrganization = useOldOrganization;
   this.bundle = Services.strings.createBundle(BUNDLE_URI);
 }
 
 FormAutofillPreferences.prototype = {
   /**
    * Check if Form Autofill feature is enabled.
    *
    * @returns {boolean}
    */
   get isAutofillEnabled() {
     return Services.prefs.getBoolPref(PREF_AUTOFILL_ENABLED);
   },
 
   /**
-   * Check if the current page is Preferences/Privacy.
-   *
-   * @returns {boolean}
-   */
-  get isPrivacyPane() {
-    return this.refs.document.location.href == "about:preferences#privacy";
-  },
-
-  /**
    * Create the Form Autofill preference group.
    *
    * @param   {XULDocument} document
    * @returns {XULElement}
    */
   init(document) {
     this.createPreferenceGroup(document);
     this.attachEventListeners();
@@ -67,68 +60,73 @@ FormAutofillPreferences.prototype = {
   },
 
   /**
    * Create Form Autofill preference group
    *
    * @param  {XULDocument} document
    */
   createPreferenceGroup(document) {
-    const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-    let formAutofillGroup = document.createElementNS(XUL_NS, "groupbox");
-    let caption = document.createElementNS(XUL_NS, "caption");
-    let captionLabel = document.createElementNS(XUL_NS, "label");
-    let hbox = document.createElementNS(XUL_NS, "hbox");
-    let enabledCheckbox = document.createElementNS(XUL_NS, "checkbox");
+    let formAutofillGroup;
+    let profileAutofill = document.createElementNS(XUL_NS, "hbox");
+    let profileAutofillCheckbox = document.createElementNS(XUL_NS, "checkbox");
     let spacer = document.createElementNS(XUL_NS, "spacer");
     let savedProfilesBtn = document.createElementNS(XUL_NS, "button");
 
+    if (this.useOldOrganization) {
+      let caption = document.createElementNS(XUL_NS, "caption");
+      let captionLabel = document.createElementNS(XUL_NS, "label");
+
+      formAutofillGroup = document.createElementNS(XUL_NS, "groupbox");
+      formAutofillGroup.hidden = document.location.href != "about:preferences#privacy";
+      // Use .setAttribute because HTMLElement.dataset is not available on XUL elements
+      formAutofillGroup.setAttribute("data-category", "panePrivacy");
+      formAutofillGroup.appendChild(caption);
+      caption.appendChild(captionLabel);
+      captionLabel.textContent = this.bundle.GetStringFromName("preferenceGroupTitle");
+    } else {
+      formAutofillGroup = document.createElementNS(XUL_NS, "vbox");
+      savedProfilesBtn.className = "accessory-button";
+    }
+
     this.refs = {
-      document,
       formAutofillGroup,
-      enabledCheckbox,
+      profileAutofillCheckbox,
       savedProfilesBtn,
     };
 
     formAutofillGroup.id = "formAutofillGroup";
-    formAutofillGroup.hidden = !this.isPrivacyPane;
-    // Use .setAttribute because HTMLElement.dataset is not available on XUL elements
-    formAutofillGroup.setAttribute("data-category", "panePrivacy");
-
-    captionLabel.textContent = this.bundle.GetStringFromName("preferenceGroupTitle");
+    profileAutofill.id = "profileAutofill";
     savedProfilesBtn.setAttribute("label", this.bundle.GetStringFromName("savedProfiles"));
-    enabledCheckbox.setAttribute("label", this.bundle.GetStringFromName("enableProfileAutofill"));
+    profileAutofillCheckbox.setAttribute("label", this.bundle.GetStringFromName("enableProfileAutofill"));
 
     // Manually set the checked state
     if (this.isAutofillEnabled) {
-      enabledCheckbox.setAttribute("checked", true);
+      profileAutofillCheckbox.setAttribute("checked", true);
     }
 
     spacer.flex = 1;
 
-    formAutofillGroup.appendChild(caption);
-    caption.appendChild(captionLabel);
-    formAutofillGroup.appendChild(hbox);
-    hbox.appendChild(enabledCheckbox);
-    hbox.appendChild(spacer);
-    hbox.appendChild(savedProfilesBtn);
+    formAutofillGroup.appendChild(profileAutofill);
+    profileAutofill.appendChild(profileAutofillCheckbox);
+    profileAutofill.appendChild(spacer);
+    profileAutofill.appendChild(savedProfilesBtn);
   },
 
   /**
    * Handle events
    *
    * @param  {DOMEvent} event
    */
   handleEvent(event) {
     switch (event.type) {
       case "command": {
         let target = event.target;
 
-        if (target == this.refs.enabledCheckbox) {
+        if (target == this.refs.profileAutofillCheckbox) {
           // Set preference directly instead of relying on <Preference>
           Services.prefs.setBoolPref(PREF_AUTOFILL_ENABLED, target.checked);
         } else if (target == this.refs.savedProfilesBtn) {
           target.ownerGlobal.gSubDialog.open(MANAGE_PROFILES_URL);
         }
         break;
       }
     }
--- a/browser/extensions/formautofill/content/FormAutofillFrameScript.js
+++ b/browser/extensions/formautofill/content/FormAutofillFrameScript.js
@@ -18,37 +18,37 @@ Cu.import("resource://formautofill/FormA
 
 /**
  * Handles content's interactions for the frame.
  *
  * NOTE: Declares it by "var" to make it accessible in unit tests.
  */
 var FormAutofillFrameScript = {
   init() {
-    addEventListener("DOMContentLoaded", this);
+    addEventListener("focusin", this);
     addMessageListener("FormAutofill:PreviewProfile", this);
     addMessageListener("FormAutoComplete:PopupClosed", this);
   },
 
   handleEvent(evt) {
     if (!evt.isTrusted) {
       return;
     }
 
     if (!Services.prefs.getBoolPref("browser.formautofill.enabled")) {
       return;
     }
 
     switch (evt.type) {
-      case "DOMContentLoaded": {
-        let doc = evt.target;
-        if (!(doc instanceof Ci.nsIDOMHTMLDocument)) {
+      case "focusin": {
+        let element = evt.target;
+        if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
           return;
         }
-        FormAutofillContent.identifyAutofillFields(doc);
+        FormAutofillContent.identifyAutofillFields(element.ownerDocument);
         break;
       }
     }
   },
 
   receiveMessage(message) {
     if (!Services.prefs.getBoolPref("browser.formautofill.enabled")) {
       return;
--- a/browser/extensions/formautofill/content/editProfile.xhtml
+++ b/browser/extensions/formautofill/content/editProfile.xhtml
@@ -11,27 +11,27 @@
   <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
   <link rel="stylesheet" href="chrome://browser/skin/preferences/in-content/dialog.css" />
   <link rel="stylesheet" href="chrome://formautofill-shared/skin/editProfile.css" />
   <link rel="stylesheet" href="chrome://formautofill/skin/editProfile.css" />
   <script src="chrome://formautofill/content/editProfile.js"></script>
 </head>
 <body>
   <form>
-    <label id="first-name-container">
+    <label id="given-name-container">
       <span>First Name</span>
-      <input id="first-name" type="text"/>
+      <input id="given-name" type="text"/>
     </label>
-    <label id="middle-name-container">
+    <label id="additional-name-container">
       <span>Middle Name</span>
-      <input id="middle-name" type="text"/>
+      <input id="additional-name" type="text"/>
     </label>
-    <label id="last-name-container">
+    <label id="family-name-container">
       <span>Last Name</span>
-      <input id="last-name" type="text"/>
+      <input id="family-name" type="text"/>
     </label>
     <label id="organization-container">
       <span>Company</span>
       <input id="organization" type="text"/>
     </label>
     <label id="street-address-container">
       <span>Street Address</span>
       <textarea id="street-address"/>
--- a/browser/extensions/formautofill/content/manageProfiles.js
+++ b/browser/extensions/formautofill/content/manageProfiles.js
@@ -134,16 +134,17 @@ ManageProfileDialog.prototype = {
    * @param  {object} profile
    * @returns {string}
    */
   getProfileLabel(profile) {
     // TODO: Implement a smarter way for deciding what to display
     //       as option text. Possibly improve the algorithm in
     //       ProfileAutoCompleteResult.jsm and reuse it here.
     const fieldOrder = [
+      "name",
       "street-address",  // Street address
       "address-level2",  // City/Town
       "organization",    // Company or organization name
       "address-level1",  // Province/State (Standardized code if possible)
       "country",         // Country
       "postal-code",     // Postal code
       "tel",             // Phone number
       "email",           // Email address
@@ -164,17 +165,17 @@ ManageProfileDialog.prototype = {
 
   /**
    * Open the edit profile dialog to create/edit a profile.
    *
    * @param  {object} profile [optional]
    */
   openEditDialog(profile) {
     window.openDialog(EDIT_PROFILE_URL, null,
-                      "chrome,centerscreen,modal,width=600,height=370",
+                      "chrome,centerscreen,modal,width=600,height=450",
                       profile);
   },
 
   /**
    * Enable/disable the Edit and Remove buttons based on number of selected
    * options.
    *
    * @param  {number} selectedCount
--- a/browser/extensions/formautofill/skin/shared/editProfile.css
+++ b/browser/extensions/formautofill/skin/shared/editProfile.css
@@ -47,49 +47,42 @@ textarea {
 button {
   padding: 3px 2em;
 }
 
 #country-container {
   width: 15em;
 }
 
-#first-name-container,
-#middle-name-container,
+#given-name-container,
+#additional-name-container,
 #address-level1-container,
 #postal-code-container,
 #country-container {
   flex: 0 1 50%;
 }
 
-#last-name-container,
+#family-name-container,
 #organization-container,
 #street-address-container,
 #address-level2-container,
 #email-container,
 #tel-container,
 #controls-container {
   flex: 0 1 100%;
 }
 
 #controls-container {
   justify-content: end;
 }
 
-#last-name,
+#family-name,
 #organization,
 #address-level2,
 #tel{
   flex: 0 0 auto;
   width: calc(50% - 10em);
 }
 
 #street-address,
 #email {
   flex: 1 0 auto;
 }
-
-#first-name-container,
-#middle-name-container,
-#last-name-container {
-  /* Hide until we support names */
-  display: none;
-}
--- a/browser/extensions/formautofill/test/browser/browser.ini
+++ b/browser/extensions/formautofill/test/browser/browser.ini
@@ -1,5 +1,7 @@
 [DEFAULT]
+head = head.js
 
 [browser_check_installed.js]
+[browser_editProfileDialog.js]
 [browser_privacyPreferences.js]
 [browser_manageProfilesDialog.js]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_editProfileDialog.js
@@ -0,0 +1,90 @@
+"use strict";
+
+registerCleanupFunction(function* () {
+  let profiles = yield getProfiles();
+  if (profiles.length) {
+    yield removeProfiles(profiles.map(profile => profile.guid));
+  }
+});
+
+add_task(function* test_cancelEditProfileDialog() {
+  yield new Promise(resolve => {
+    let win = window.openDialog(EDIT_PROFILE_DIALOG_URL, null, null, null);
+    win.addEventListener("load", () => {
+      win.addEventListener("unload", () => {
+        ok(true, "Edit profile dialog is closed");
+        resolve();
+      }, {once: true});
+      win.document.querySelector("#cancel").click();
+    }, {once: true});
+  });
+});
+
+add_task(function* test_saveProfile() {
+  yield new Promise(resolve => {
+    let win = window.openDialog(EDIT_PROFILE_DIALOG_URL, null, null, null);
+    win.addEventListener("load", () => {
+      win.addEventListener("unload", () => {
+        ok(true, "Edit profile dialog is closed");
+        resolve();
+      }, {once: true});
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_PROFILE_1["given-name"], {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_PROFILE_1["additional-name"], {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_PROFILE_1["family-name"], {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_PROFILE_1.organization, {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_PROFILE_1["street-address"], {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_PROFILE_1["address-level2"], {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_PROFILE_1["address-level1"], {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_PROFILE_1["postal-code"], {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_PROFILE_1.country, {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_PROFILE_1.email, {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey(TEST_PROFILE_1.tel, {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      info("saving profile");
+      EventUtils.synthesizeKey("VK_RETURN", {}, win);
+    }, {once: true});
+  });
+  let profiles = yield getProfiles();
+
+  is(profiles.length, 1, "only one profile is in storage");
+  is(Object.keys(TEST_PROFILE_1).length, 11, "Sanity check number of properties");
+  for (let [fieldName, fieldValue] of Object.entries(TEST_PROFILE_1)) {
+    is(profiles[0][fieldName], fieldValue, "check " + fieldName);
+  }
+});
+
+add_task(function* test_editProfile() {
+  let profiles = yield getProfiles();
+  yield new Promise(resolve => {
+    let win = window.openDialog(EDIT_PROFILE_DIALOG_URL, null, null, profiles[0]);
+    win.addEventListener("load", () => {
+      win.addEventListener("unload", () => {
+        ok(true, "Edit profile dialog is closed");
+        resolve();
+      }, {once: true});
+      EventUtils.synthesizeKey("VK_TAB", {}, win);
+      EventUtils.synthesizeKey("test", {}, win);
+      win.document.querySelector("#save").click();
+    }, {once: true});
+  });
+  profiles = yield getProfiles();
+
+  is(profiles.length, 1, "only one profile is in storage");
+  is(profiles[0]["given-name"], TEST_PROFILE_1["given-name"] + "test", "given-name changed");
+  yield removeProfiles([profiles[0].guid]);
+
+  profiles = yield getProfiles();
+  is(profiles.length, 0, "Profile storage is empty");
+});
--- a/browser/extensions/formautofill/test/browser/browser_manageProfilesDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_manageProfilesDialog.js
@@ -1,64 +1,29 @@
 "use strict";
 
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-const MANAGE_PROFILES_DIALOG_URL = "chrome://formautofill/content/manageProfiles.xhtml";
 const TEST_SELECTORS = {
   selProfiles: "#profiles",
   btnRemove: "#remove",
   btnAdd: "#add",
   btnEdit: "#edit",
 };
 
-const TEST_PROFILE_1 = {
-  organization: "World Wide Web Consortium",
-  "street-address": "32 Vassar Street\nMIT Room 32-G524",
-  "address-level2": "Cambridge",
-  "address-level1": "MA",
-  "postal-code": "02139",
-  country: "US",
-  tel: "+1 617 253 5702",
-  email: "timbl@w3.org",
-};
-
-const TEST_PROFILE_2 = {
-  "street-address": "Some Address",
-  country: "US",
-};
-
-const TEST_PROFILE_3 = {
-  "street-address": "Other Address",
-  "postal-code": "12345",
-};
-
-function saveProfile(profile) {
-  Services.cpmm.sendAsyncMessage("FormAutofill:SaveProfile", {profile});
-  return TestUtils.topicObserved("formautofill-storage-changed");
-}
-
-function removeProfiles(guids) {
-  Services.cpmm.sendAsyncMessage("FormAutofill:RemoveProfiles", {guids});
-  return TestUtils.topicObserved("formautofill-storage-changed");
-}
-
 function waitForProfiles() {
   return new Promise(resolve => {
     Services.cpmm.addMessageListener("FormAutofill:Profiles", function getResult(result) {
       Services.cpmm.removeMessageListener("FormAutofill:Profiles", getResult);
       // Wait for the next tick for elements to get rendered.
       SimpleTest.executeSoon(resolve.bind(null, result.data));
     });
   });
 }
 
 registerCleanupFunction(function* () {
-  Services.cpmm.sendAsyncMessage("FormAutofill:GetProfiles", {});
-  let profiles = yield waitForProfiles();
+  let profiles = yield getProfiles();
   if (profiles.length) {
     yield removeProfiles(profiles.map(profile => profile.guid));
   }
 });
 
 add_task(function* test_manageProfilesInitialState() {
   yield BrowserTestUtils.withNewTab({gBrowser, url: MANAGE_PROFILES_DIALOG_URL}, function* (browser) {
     yield ContentTask.spawn(browser, TEST_SELECTORS, (args) => {
--- a/browser/extensions/formautofill/test/browser/browser_privacyPreferences.js
+++ b/browser/extensions/formautofill/test/browser/browser_privacyPreferences.js
@@ -1,14 +1,14 @@
 "use strict";
 
 const PAGE_PREFS = "about:preferences";
 const PAGE_PRIVACY = PAGE_PREFS + "#privacy";
-const GROUP_AUTOFILL = "#formAutofillGroup";
-const CHECKBOX_AUTOFILL = GROUP_AUTOFILL + " checkbox";
+const GROUP_AUTOFILL = "#passwordsGroup";
+const CHECKBOX_AUTOFILL = "#profileAutofill checkbox";
 const PREF_AUTOFILL_ENABLED = "browser.formautofill.enabled";
 const TEST_SELECTORS = {
   group: GROUP_AUTOFILL,
   checkbox: CHECKBOX_AUTOFILL,
 };
 
 // Visibility of form autofill group should be hidden when opening
 // preferences page.
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -0,0 +1,52 @@
+/* exported MANAGE_PROFILES_DIALOG_URL, EDIT_PROFILE_DIALOG_URL,
+            TEST_PROFILE_1, TEST_PROFILE_2, TEST_PROFILE_3,
+            getProfiles, saveProfile, removeProfiles */
+
+"use strict";
+
+const MANAGE_PROFILES_DIALOG_URL = "chrome://formautofill/content/manageProfiles.xhtml";
+const EDIT_PROFILE_DIALOG_URL = "chrome://formautofill/content/editProfile.xhtml";
+
+const TEST_PROFILE_1 = {
+  "given-name": "John",
+  "additional-name": "R.",
+  "family-name": "Smith",
+  organization: "World Wide Web Consortium",
+  "street-address": "32 Vassar Street\nMIT Room 32-G524",
+  "address-level2": "Cambridge",
+  "address-level1": "MA",
+  "postal-code": "02139",
+  country: "US",
+  tel: "+1 617 253 5702",
+  email: "timbl@w3.org",
+};
+
+const TEST_PROFILE_2 = {
+  "street-address": "Some Address",
+  country: "US",
+};
+
+const TEST_PROFILE_3 = {
+  "street-address": "Other Address",
+  "postal-code": "12345",
+};
+
+function getProfiles() {
+  return new Promise(resolve => {
+    Services.cpmm.addMessageListener("FormAutofill:Profiles", function getResult(result) {
+      Services.cpmm.removeMessageListener("FormAutofill:Profiles", getResult);
+      resolve(result.data);
+    });
+    Services.cpmm.sendAsyncMessage("FormAutofill:GetProfiles", {});
+  });
+}
+
+function saveProfile(profile) {
+  Services.cpmm.sendAsyncMessage("FormAutofill:SaveProfile", {profile});
+  return TestUtils.topicObserved("formautofill-storage-changed");
+}
+
+function removeProfiles(guids) {
+  Services.cpmm.sendAsyncMessage("FormAutofill:RemoveProfiles", {guids});
+  return TestUtils.topicObserved("formautofill-storage-changed");
+}
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,5 +1,5 @@
 This is the PDF.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.8.290
+Current extension version is: 1.8.331
 
-Taken from upstream commit: 60c232bc
+Taken from upstream commit: 0dbc68a6
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -998,16 +998,17 @@ PdfStreamConverter.prototype = {
             domWindow.frameElement.className === "previewPluginContentFrame";
           PdfJsTelemetry.onEmbed(isObjectEmbed);
         }
       }
     };
 
     // Keep the URL the same so the browser sees it as the same.
     channel.originalURI = aRequest.URI;
+    channel.loadInfo.resultPrincipalURI = aRequest.loadInfo.resultPrincipalURI;
     channel.loadGroup = aRequest.loadGroup;
     channel.loadInfo.originAttributes = aRequest.loadInfo.originAttributes;
 
     // We can use the resource principal when data is fetched by the chrome,
     // e.g. useful for NoScript. Make make sure we reuse the origin attributes
     // from the request channel to keep isolation consistent.
     var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
                 .getService(Ci.nsIScriptSecurityManager);
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -93,17 +93,23 @@ return /******/ (function(modules) { // 
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 /* WEBPACK VAR INJECTION */(function(global) {
 
-var compatibility = __w_pdfjs_require__(14);
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.warn = exports.utf8StringToString = exports.stringToUTF8String = exports.stringToPDFString = exports.stringToBytes = exports.string32 = exports.shadow = exports.setVerbosityLevel = exports.removeNullCharacters = exports.readUint32 = exports.readUint16 = exports.readInt8 = exports.log2 = exports.loadJpegStream = exports.isEvalSupported = exports.isLittleEndian = exports.createValidAbsoluteUrl = exports.isSameOrigin = exports.isNodeJS = exports.isSpace = exports.isString = exports.isNum = exports.isInt = exports.isEmptyObj = exports.isBool = exports.isArrayBuffer = exports.isArray = exports.info = exports.globalScope = exports.getVerbosityLevel = exports.getLookupTableFactory = exports.error = exports.deprecated = exports.createObjectURL = exports.createPromiseCapability = exports.createBlob = exports.bytesToString = exports.assert = exports.arraysToBytes = exports.arrayByteLength = exports.XRefParseException = exports.Util = exports.UnknownErrorException = exports.UnexpectedResponseException = exports.TextRenderingMode = exports.StreamType = exports.StatTimer = exports.PasswordResponses = exports.PasswordException = exports.PageViewport = exports.NotImplementedException = exports.MissingPDFException = exports.MissingDataException = exports.MessageHandler = exports.InvalidPDFException = exports.CMapCompressionType = exports.ImageKind = exports.FontType = exports.AnnotationType = exports.AnnotationFlag = exports.AnnotationFieldFlag = exports.AnnotationBorderStyleType = exports.UNSUPPORTED_FEATURES = exports.VERBOSITY_LEVELS = exports.OPS = exports.IDENTITY_MATRIX = exports.FONT_IDENTITY_MATRIX = undefined;
+
+__w_pdfjs_require__(14);
+
 var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : undefined;
 var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
 var TextRenderingMode = {
   FILL: 0,
   STROKE: 1,
   FILL_STROKE: 2,
   INVISIBLE: 3,
   FILL_ADD_TO_PATH: 4,
@@ -987,17 +993,17 @@ var createObjectURL = function createObj
 function MessageHandler(sourceName, targetName, comObj) {
   this.sourceName = sourceName;
   this.targetName = targetName;
   this.comObj = comObj;
   this.callbackIndex = 1;
   this.postMessageTransfers = true;
   var callbacksCapabilities = this.callbacksCapabilities = Object.create(null);
   var ah = this.actionHandler = Object.create(null);
-  this._onComObjOnMessage = function messageHandlerComObjOnMessage(event) {
+  this._onComObjOnMessage = event => {
     var data = event.data;
     if (data.targetName !== this.sourceName) {
       return;
     }
     if (data.isReply) {
       var callbackId = data.callbackId;
       if (data.callbackId in callbacksCapabilities) {
         var callback = callbacksCapabilities[callbackId];
@@ -1038,17 +1044,17 @@ function MessageHandler(sourceName, targ
           });
         });
       } else {
         action[0].call(action[1], data.data);
       }
     } else {
       error('Unknown action from worker: ' + data.action);
     }
-  }.bind(this);
+  };
   comObj.addEventListener('message', this._onComObjOnMessage);
 }
 MessageHandler.prototype = {
   on(actionName, handler, scope) {
     var ah = this.actionHandler;
     if (ah[actionName]) {
       error('There is already an actionName called "' + actionName + '"');
     }
@@ -1220,24 +1226,24 @@ var DOMCMapReaderFactory = function DOMC
     this.isCompressed = params.isCompressed || false;
   }
   DOMCMapReaderFactory.prototype = {
     fetch(params) {
       var name = params.name;
       if (!name) {
         return Promise.reject(new Error('CMap name must be specified.'));
       }
-      return new Promise(function (resolve, reject) {
+      return new Promise((resolve, reject) => {
         var url = this.baseUrl + name + (this.isCompressed ? '.bcmap' : '');
         var request = new XMLHttpRequest();
         request.open('GET', url, true);
         if (this.isCompressed) {
           request.responseType = 'arraybuffer';
         }
-        request.onreadystatechange = function () {
+        request.onreadystatechange = () => {
           if (request.readyState !== XMLHttpRequest.DONE) {
             return;
           }
           if (request.status === 200 || request.status === 0) {
             var data;
             if (this.isCompressed && request.response) {
               data = new Uint8Array(request.response);
             } else if (!this.isCompressed && request.responseText) {
@@ -1247,19 +1253,19 @@ var DOMCMapReaderFactory = function DOMC
               resolve({
                 cMapData: data,
                 compressionType: this.isCompressed ? _util.CMapCompressionType.BINARY : _util.CMapCompressionType.NONE
               });
               return;
             }
           }
           reject(new Error('Unable to load ' + (this.isCompressed ? 'binary ' : '') + 'CMap at: ' + url));
-        }.bind(this);
+        };
         request.send(null);
-      }.bind(this));
+      });
     }
   };
   return DOMCMapReaderFactory;
 }();
 var CustomStyle = function CustomStyleClosure() {
   var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
   var _cache = Object.create(null);
   function CustomStyle() {}
@@ -1593,34 +1599,32 @@ var LinkAnnotationElement = function Lin
           this._bindNamedAction(link, this.data.action);
         } else {
           this._bindLink(link, this.data.dest);
         }
       }
       this.container.appendChild(link);
       return this.container;
     },
-    _bindLink: function LinkAnnotationElement_bindLink(link, destination) {
-      var self = this;
+    _bindLink(link, destination) {
       link.href = this.linkService.getDestinationHash(destination);
-      link.onclick = function () {
+      link.onclick = () => {
         if (destination) {
-          self.linkService.navigateTo(destination);
+          this.linkService.navigateTo(destination);
         }
         return false;
       };
       if (destination) {
         link.className = 'internalLink';
       }
     },
-    _bindNamedAction: function LinkAnnotationElement_bindNamedAction(link, action) {
-      var self = this;
+    _bindNamedAction(link, action) {
       link.href = this.linkService.getAnchorUrl('');
-      link.onclick = function () {
-        self.linkService.executeNamedAction(action);
+      link.onclick = () => {
+        this.linkService.executeNamedAction(action);
         return false;
       };
       link.className = 'internalLink';
     }
   });
   return LinkAnnotationElement;
 }();
 var TextAnnotationElement = function TextAnnotationElementClosure() {
@@ -2077,17 +2081,17 @@ exports.AnnotationLayer = AnnotationLaye
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.build = exports.version = exports._UnsupportedManager = exports.PDFPageProxy = exports.PDFDocumentProxy = exports.PDFWorker = exports.PDFDataRangeTransport = exports.getDocument = undefined;
+exports.build = exports.version = exports._UnsupportedManager = exports.PDFPageProxy = exports.PDFDocumentProxy = exports.PDFWorker = exports.PDFDataRangeTransport = exports.LoopbackPort = exports.getDocument = undefined;
 
 var _util = __w_pdfjs_require__(0);
 
 var _dom_utils = __w_pdfjs_require__(1);
 
 var _font_loader = __w_pdfjs_require__(11);
 
 var _canvas = __w_pdfjs_require__(10);
@@ -2234,23 +2238,23 @@ var PDFDocumentLoadingTask = function PD
   }
   PDFDocumentLoadingTask.prototype = {
     get promise() {
       return this._capability.promise;
     },
     destroy() {
       this.destroyed = true;
       var transportDestroyed = !this._transport ? Promise.resolve() : this._transport.destroy();
-      return transportDestroyed.then(function () {
+      return transportDestroyed.then(() => {
         this._transport = null;
         if (this._worker) {
           this._worker.destroy();
           this._worker = null;
         }
-      }.bind(this));
+      });
     },
     then: function PDFDocumentLoadingTask_then(onFulfilled, onRejected) {
       return this.promise.then.apply(this.promise, arguments);
     }
   };
   return PDFDocumentLoadingTask;
 }();
 var PDFDataRangeTransport = function pdfDataRangeTransportClosure() {
@@ -2274,30 +2278,30 @@ var PDFDataRangeTransport = function pdf
     },
     onDataRange: function PDFDataRangeTransport_onDataRange(begin, chunk) {
       var listeners = this._rangeListeners;
       for (var i = 0, n = listeners.length; i < n; ++i) {
         listeners[i](begin, chunk);
       }
     },
     onDataProgress: function PDFDataRangeTransport_onDataProgress(loaded) {
-      this._readyCapability.promise.then(function () {
+      this._readyCapability.promise.then(() => {
         var listeners = this._progressListeners;
         for (var i = 0, n = listeners.length; i < n; ++i) {
           listeners[i](loaded);
         }
-      }.bind(this));
+      });
     },
     onDataProgressiveRead: function PDFDataRangeTransport_onDataProgress(chunk) {
-      this._readyCapability.promise.then(function () {
+      this._readyCapability.promise.then(() => {
         var listeners = this._progressiveReadListeners;
         for (var i = 0, n = listeners.length; i < n; ++i) {
           listeners[i](chunk);
         }
-      }.bind(this));
+      });
     },
     transportReady: function PDFDataRangeTransport_transportReady() {
       this._readyCapability.resolve();
     },
     requestDataRange: function PDFDataRangeTransport_requestDataRange(begin, end) {
       throw new Error('Abstract method PDFDataRangeTransport.requestDataRange');
     },
     abort: function PDFDataRangeTransport_abort() {}
@@ -2426,56 +2430,53 @@ var PDFPageProxy = function PDFPageProxy
         };
         this.stats.time('Page Request');
         this.transport.messageHandler.send('RenderPageRequest', {
           pageIndex: this.pageNumber - 1,
           intent: renderingIntent,
           renderInteractiveForms: params.renderInteractiveForms === true
         });
       }
+      var complete = error => {
+        var i = intentState.renderTasks.indexOf(internalRenderTask);
+        if (i >= 0) {
+          intentState.renderTasks.splice(i, 1);
+        }
+        if (this.cleanupAfterRender) {
+          this.pendingCleanup = true;
+        }
+        this._tryCleanup();
+        if (error) {
+          internalRenderTask.capability.reject(error);
+        } else {
+          internalRenderTask.capability.resolve();
+        }
+        stats.timeEnd('Rendering');
+        stats.timeEnd('Overall');
+      };
       var internalRenderTask = new InternalRenderTask(complete, params, this.objs, this.commonObjs, intentState.operatorList, this.pageNumber, canvasFactory);
       internalRenderTask.useRequestAnimationFrame = renderingIntent !== 'print';
       if (!intentState.renderTasks) {
         intentState.renderTasks = [];
       }
       intentState.renderTasks.push(internalRenderTask);
       var renderTask = internalRenderTask.task;
       if (params.continueCallback) {
         (0, _util.deprecated)('render is used with continueCallback parameter');
         renderTask.onContinue = params.continueCallback;
       }
-      var self = this;
-      intentState.displayReadyCapability.promise.then(function pageDisplayReadyPromise(transparency) {
-        if (self.pendingCleanup) {
+      intentState.displayReadyCapability.promise.then(transparency => {
+        if (this.pendingCleanup) {
           complete();
           return;
         }
         stats.time('Rendering');
         internalRenderTask.initializeGraphics(transparency);
         internalRenderTask.operatorListChanged();
-      }, function pageDisplayReadPromiseError(reason) {
-        complete(reason);
-      });
-      function complete(error) {
-        var i = intentState.renderTasks.indexOf(internalRenderTask);
-        if (i >= 0) {
-          intentState.renderTasks.splice(i, 1);
-        }
-        if (self.cleanupAfterRender) {
-          self.pendingCleanup = true;
-        }
-        self._tryCleanup();
-        if (error) {
-          internalRenderTask.capability.reject(error);
-        } else {
-          internalRenderTask.capability.resolve();
-        }
-        stats.timeEnd('Rendering');
-        stats.timeEnd('Overall');
-      }
+      }, complete);
       return renderTask;
     },
     getOperatorList: function PDFPageProxy_getOperatorList() {
       function operatorListChanged() {
         if (intentState.operatorList.lastChunk) {
           intentState.opListReadCapability.resolve(intentState.operatorList);
           var i = intentState.renderTasks.indexOf(opListTask);
           if (i >= 0) {
@@ -2578,16 +2579,84 @@ var PDFPageProxy = function PDFPageProxy
       if (operatorListChunk.lastChunk) {
         intentState.receivingOperatorList = false;
         this._tryCleanup();
       }
     }
   };
   return PDFPageProxy;
 }();
+class LoopbackPort {
+  constructor(defer) {
+    this._listeners = [];
+    this._defer = defer;
+    this._deferred = Promise.resolve(undefined);
+  }
+  postMessage(obj, transfers) {
+    function cloneValue(value) {
+      if (typeof value !== 'object' || value === null) {
+        return value;
+      }
+      if (cloned.has(value)) {
+        return cloned.get(value);
+      }
+      var result;
+      var buffer;
+      if ((buffer = value.buffer) && (0, _util.isArrayBuffer)(buffer)) {
+        var transferable = transfers && transfers.indexOf(buffer) >= 0;
+        if (value === buffer) {
+          result = value;
+        } else if (transferable) {
+          result = new value.constructor(buffer, value.byteOffset, value.byteLength);
+        } else {
+          result = new value.constructor(value);
+        }
+        cloned.set(value, result);
+        return result;
+      }
+      result = (0, _util.isArray)(value) ? [] : {};
+      cloned.set(value, result);
+      for (var i in value) {
+        var desc,
+            p = value;
+        while (!(desc = Object.getOwnPropertyDescriptor(p, i))) {
+          p = Object.getPrototypeOf(p);
+        }
+        if (typeof desc.value === 'undefined' || typeof desc.value === 'function') {
+          continue;
+        }
+        result[i] = cloneValue(desc.value);
+      }
+      return result;
+    }
+    if (!this._defer) {
+      this._listeners.forEach(function (listener) {
+        listener.call(this, { data: obj });
+      }, this);
+      return;
+    }
+    var cloned = new WeakMap();
+    var e = { data: cloneValue(obj) };
+    this._deferred.then(() => {
+      this._listeners.forEach(function (listener) {
+        listener.call(this, e);
+      }, this);
+    });
+  }
+  addEventListener(name, listener) {
+    this._listeners.push(listener);
+  }
+  removeEventListener(name, listener) {
+    var i = this._listeners.indexOf(listener);
+    this._listeners.splice(i, 1);
+  }
+  terminate() {
+    this._listeners = [];
+  }
+}
 var PDFWorker = function PDFWorkerClosure() {
   var nextFakeWorkerId = 0;
   function getWorkerSrc() {
     if (typeof workerSrc !== 'undefined') {
       return workerSrc;
     }
     if ((0, _dom_utils.getDefaultSetting)('workerSrc')) {
       return (0, _dom_utils.getDefaultSetting)('workerSrc');
@@ -2604,84 +2673,16 @@ var PDFWorker = function PDFWorkerClosur
     var loader = fakeWorkerFilesLoader || function (callback) {
       _util.Util.loadScript(getWorkerSrc(), function () {
         callback(window.pdfjsDistBuildPdfWorker.WorkerMessageHandler);
       });
     };
     loader(fakeWorkerFilesLoadedCapability.resolve);
     return fakeWorkerFilesLoadedCapability.promise;
   }
-  function FakeWorkerPort(defer) {
-    this._listeners = [];
-    this._defer = defer;
-    this._deferred = Promise.resolve(undefined);
-  }
-  FakeWorkerPort.prototype = {
-    postMessage(obj, transfers) {
-      function cloneValue(value) {
-        if (typeof value !== 'object' || value === null) {
-          return value;
-        }
-        if (cloned.has(value)) {
-          return cloned.get(value);
-        }
-        var result;
-        var buffer;
-        if ((buffer = value.buffer) && (0, _util.isArrayBuffer)(buffer)) {
-          var transferable = transfers && transfers.indexOf(buffer) >= 0;
-          if (value === buffer) {
-            result = value;
-          } else if (transferable) {
-            result = new value.constructor(buffer, value.byteOffset, value.byteLength);
-          } else {
-            result = new value.constructor(value);
-          }
-          cloned.set(value, result);
-          return result;
-        }
-        result = (0, _util.isArray)(value) ? [] : {};
-        cloned.set(value, result);
-        for (var i in value) {
-          var desc,
-              p = value;
-          while (!(desc = Object.getOwnPropertyDescriptor(p, i))) {
-            p = Object.getPrototypeOf(p);
-          }
-          if (typeof desc.value === 'undefined' || typeof desc.value === 'function') {
-            continue;
-          }
-          result[i] = cloneValue(desc.value);
-        }
-        return result;
-      }
-      if (!this._defer) {
-        this._listeners.forEach(function (listener) {
-          listener.call(this, { data: obj });
-        }, this);
-        return;
-      }
-      var cloned = new WeakMap();
-      var e = { data: cloneValue(obj) };
-      this._deferred.then(function () {
-        this._listeners.forEach(function (listener) {
-          listener.call(this, e);
-        }, this);
-      }.bind(this));
-    },
-    addEventListener(name, listener) {
-      this._listeners.push(listener);
-    },
-    removeEventListener(name, listener) {
-      var i = this._listeners.indexOf(listener);
-      this._listeners.splice(i, 1);
-    },
-    terminate() {
-      this._listeners = [];
-    }
-  };
   function createCDNWrapper(url) {
     var wrapper = 'importScripts(\'' + url + '\');';
     return URL.createObjectURL(new Blob([wrapper]));
   }
   function PDFWorker(name, port) {
     this.name = name;
     this.destroyed = false;
     this._readyCapability = (0, _util.createPromiseCapability)();
@@ -2711,33 +2712,33 @@ var PDFWorker = function PDFWorkerClosur
       this._readyCapability.resolve();
     },
     _initialize: function PDFWorker_initialize() {
       if (!isWorkerDisabled && !(0, _dom_utils.getDefaultSetting)('disableWorker') && typeof Worker !== 'undefined') {
         var workerSrc = getWorkerSrc();
         try {
           var worker = new Worker(workerSrc);
           var messageHandler = new _util.MessageHandler('main', 'worker', worker);
-          var terminateEarly = function () {
+          var terminateEarly = () => {
             worker.removeEventListener('error', onWorkerError);
             messageHandler.destroy();
             worker.terminate();
             if (this.destroyed) {
               this._readyCapability.reject(new Error('Worker was destroyed'));
             } else {
               this._setupFakeWorker();
             }
-          }.bind(this);
-          var onWorkerError = function (event) {
+          };
+          var onWorkerError = () => {
             if (!this._webWorker) {
               terminateEarly();
             }
-          }.bind(this);
+          };
           worker.addEventListener('error', onWorkerError);
-          messageHandler.on('test', function PDFWorker_test(data) {
+          messageHandler.on('test', data => {
             worker.removeEventListener('error', onWorkerError);
             if (this.destroyed) {
               terminateEarly();
               return;
             }
             var supportTypedArray = data && data.supportTypedArray;
             if (supportTypedArray) {
               this._messageHandler = messageHandler;
@@ -2748,35 +2749,35 @@ var PDFWorker = function PDFWorkerClosur
               }
               this._readyCapability.resolve();
               messageHandler.send('configure', { verbosity: (0, _util.getVerbosityLevel)() });
             } else {
               this._setupFakeWorker();
               messageHandler.destroy();
               worker.terminate();
             }
-          }.bind(this));
+          });
           messageHandler.on('console_log', function (data) {
             console.log.apply(console, data);
           });
           messageHandler.on('console_error', function (data) {
             console.error.apply(console, data);
           });
-          messageHandler.on('ready', function (data) {
+          messageHandler.on('ready', data => {
             worker.removeEventListener('error', onWorkerError);
             if (this.destroyed) {
               terminateEarly();
               return;
             }
             try {
               sendTest();
             } catch (e) {
               this._setupFakeWorker();
             }
-          }.bind(this));
+          });
           var sendTest = function () {
             var postMessageTransfers = (0, _dom_utils.getDefaultSetting)('postMessageTransfers') && !isPostMessageTransfersDisabled;
             var testObj = new Uint8Array([postMessageTransfers ? 255 : 0]);
             try {
               messageHandler.send('test', testObj, [testObj.buffer]);
             } catch (ex) {
               (0, _util.info)('Cannot use postMessage transfers');
               testObj[0] = 0;
@@ -2791,31 +2792,31 @@ var PDFWorker = function PDFWorkerClosur
       }
       this._setupFakeWorker();
     },
     _setupFakeWorker: function PDFWorker_setupFakeWorker() {
       if (!isWorkerDisabled && !(0, _dom_utils.getDefaultSetting)('disableWorker')) {
         (0, _util.warn)('Setting up fake worker.');
         isWorkerDisabled = true;
       }
-      setupFakeWorkerGlobal().then(function (WorkerMessageHandler) {
+      setupFakeWorkerGlobal().then(WorkerMessageHandler => {
         if (this.destroyed) {
           this._readyCapability.reject(new Error('Worker was destroyed'));
           return;
         }
         var isTypedArraysPresent = Uint8Array !== Float32Array;
-        var port = new FakeWorkerPort(isTypedArraysPresent);
+        var port = new LoopbackPort(isTypedArraysPresent);
         this._port = port;
         var id = 'fake' + nextFakeWorkerId++;
         var workerHandler = new _util.MessageHandler(id + '_worker', id, port);
         WorkerMessageHandler.setup(workerHandler, port);
         var messageHandler = new _util.MessageHandler(id, id + '_worker', port);
         this._messageHandler = messageHandler;
         this._readyCapability.resolve();
-      }.bind(this));
+      });
     },
     destroy: function PDFWorker_destroy() {
       this.destroyed = true;
       if (this._webWorker) {
         this._webWorker.terminate();
         this._webWorker = null;
       }
       this._port = null;
@@ -2859,30 +2860,29 @@ var WorkerTransport = function WorkerTra
       var waitOn = [];
       this.pageCache.forEach(function (page) {
         if (page) {
           waitOn.push(page._destroy());
         }
       });
       this.pageCache = [];
       this.pagePromises = [];
-      var self = this;
       var terminated = this.messageHandler.sendWithPromise('Terminate', null);
       waitOn.push(terminated);
-      Promise.all(waitOn).then(function () {
-        self.fontLoader.clear();
-        if (self.pdfDataRangeTransport) {
-          self.pdfDataRangeTransport.abort();
-          self.pdfDataRangeTransport = null;
+      Promise.all(waitOn).then(() => {
+        this.fontLoader.clear();
+        if (this.pdfDataRangeTransport) {
+          this.pdfDataRangeTransport.abort();
+          this.pdfDataRangeTransport = null;
         }
-        if (self.messageHandler) {
-          self.messageHandler.destroy();
-          self.messageHandler = null;
+        if (this.messageHandler) {
+          this.messageHandler.destroy();
+          this.messageHandler = null;
         }
-        self.destroyCapability.resolve();
+        this.destroyCapability.resolve();
       }, this.destroyCapability.reject);
       return this.destroyCapability.promise;
     },
     setupMessageHandler: function WorkerTransport_setupMessageHandler() {
       var messageHandler = this.messageHandler;
       var loadingTask = this.loadingTask;
       var pdfDataRangeTransport = this.pdfDataRangeTransport;
       if (pdfDataRangeTransport) {
@@ -2986,19 +2986,20 @@ var WorkerTransport = function WorkerTra
                 }
               };
             }
             var font = new _font_loader.FontFaceObject(exportedData, {
               isEvalSuported: (0, _dom_utils.getDefaultSetting)('isEvalSupported'),
               disableFontFace: (0, _dom_utils.getDefaultSetting)('disableFontFace'),
               fontRegistry
             });
-            this.fontLoader.bind([font], function fontReady(fontObjs) {
+            var fontReady = fontObjs => {
               this.commonObjs.resolve(id, font);
-            }.bind(this));
+            };
+            this.fontLoader.bind([font], fontReady);
             break;
           case 'FontPath':
             this.commonObjs.resolve(id, data[2]);
             break;
           default:
             (0, _util.error)('Got unknown common object type ' + type);
         }
       }, this);
@@ -3135,24 +3136,24 @@ var WorkerTransport = function WorkerTra
     getPage: function WorkerTransport_getPage(pageNumber, capability) {
       if (!(0, _util.isInt)(pageNumber) || pageNumber <= 0 || pageNumber > this.numPages) {
         return Promise.reject(new Error('Invalid page request'));
       }
       var pageIndex = pageNumber - 1;
       if (pageIndex in this.pagePromises) {
         return this.pagePromises[pageIndex];
       }
-      var promise = this.messageHandler.sendWithPromise('GetPage', { pageIndex }).then(function (pageInfo) {
+      var promise = this.messageHandler.sendWithPromise('GetPage', { pageIndex }).then(pageInfo => {
         if (this.destroyed) {
           throw new Error('Transport destroyed');
         }
         var page = new PDFPageProxy(pageIndex, pageInfo, this);
         this.pageCache[pageIndex] = page;
         return page;
-      }.bind(this));
+      });
       this.pagePromises[pageIndex] = promise;
       return promise;
     },
     getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
       return this.messageHandler.sendWithPromise('GetPageIndex', { ref }).catch(function (reason) {
         return Promise.reject(new Error(reason));
       });
     },
@@ -3187,26 +3188,26 @@ var WorkerTransport = function WorkerTra
           metadata: results[1] ? new _metadata.Metadata(results[1]) : null
         };
       });
     },
     getStats: function WorkerTransport_getStats() {
       return this.messageHandler.sendWithPromise('GetStats', null);
     },
     startCleanup: function WorkerTransport_startCleanup() {
-      this.messageHandler.sendWithPromise('Cleanup', null).then(function endCleanup() {
+      this.messageHandler.sendWithPromise('Cleanup', null).then(() => {
         for (var i = 0, ii = this.pageCache.length; i < ii; i++) {
           var page = this.pageCache[i];
           if (page) {
             page.cleanup();
           }
         }
         this.commonObjs.clear();
         this.fontLoader.clear();
-      }.bind(this));
+      });
     }
   };
   return WorkerTransport;
 }();
 var PDFObjects = function PDFObjectsClosure() {
   function PDFObjects() {
     this.objs = Object.create(null);
   }
@@ -3386,20 +3387,21 @@ var _UnsupportedManager = function Unsup
       for (var i = 0, ii = listeners.length; i < ii; i++) {
         listeners[i](featureId);
       }
     }
   };
 }();
 var version, build;
 {
-  exports.version = version = '1.8.290';
-  exports.build = build = '60c232bc';
+  exports.version = version = '1.8.331';
+  exports.build = build = '0dbc68a6';
 }
 exports.getDocument = getDocument;
+exports.LoopbackPort = LoopbackPort;
 exports.PDFDataRangeTransport = PDFDataRangeTransport;
 exports.PDFWorker = PDFWorker;
 exports.PDFDocumentProxy = PDFDocumentProxy;
 exports.PDFPageProxy = PDFPageProxy;
 exports._UnsupportedManager = _UnsupportedManager;
 exports.version = version;
 exports.build = build;
 
@@ -3840,20 +3842,19 @@ var renderTextLayer = function renderTex
       var textItems = this._textContent.items;
       var textStyles = this._textContent.styles;
       for (var i = 0, len = textItems.length; i < len; i++) {
         appendText(this, textItems[i], textStyles);
       }
       if (!timeout) {
         render(this);
       } else {
-        var self = this;
-        this._renderTimer = setTimeout(function () {
-          render(self);
-          self._renderTimer = null;
+        this._renderTimer = setTimeout(() => {
+          render(this);
+          this._renderTimer = null;
         }, timeout);
       }
     },
     expandTextDivs: function TextLayer_expandTextDivs(expandDivs) {
       if (!this._enhanceTextSelection || !this._renderingDone) {
         return;
       }
       if (this._bounds !== null) {
@@ -4389,18 +4390,18 @@ var _text_layer = __w_pdfjs_require__(5)
 var _svg = __w_pdfjs_require__(4);
 
 var isWorker = typeof window === 'undefined';
 if (!_util.globalScope.PDFJS) {
   _util.globalScope.PDFJS = {};
 }
 var PDFJS = _util.globalScope.PDFJS;
 {
-  PDFJS.version = '1.8.290';
-  PDFJS.build = '60c232bc';
+  PDFJS.version = '1.8.331';
+  PDFJS.build = '0dbc68a6';
 }
 PDFJS.pdfBug = false;
 if (PDFJS.verbosity !== undefined) {
   (0, _util.setVerbosityLevel)(PDFJS.verbosity);
 }
 delete PDFJS.verbosity;
 Object.defineProperty(PDFJS, 'verbosity', {
   get() {
@@ -4453,16 +4454,17 @@ PDFJS.postMessageTransfers = PDFJS.postM
 PDFJS.disableCreateObjectURL = PDFJS.disableCreateObjectURL === undefined ? false : PDFJS.disableCreateObjectURL;
 PDFJS.disableWebGL = PDFJS.disableWebGL === undefined ? true : PDFJS.disableWebGL;
 PDFJS.externalLinkTarget = PDFJS.externalLinkTarget === undefined ? _dom_utils.LinkTarget.NONE : PDFJS.externalLinkTarget;
 PDFJS.externalLinkRel = PDFJS.externalLinkRel === undefined ? _dom_utils.DEFAULT_LINK_REL : PDFJS.externalLinkRel;
 PDFJS.isEvalSupported = PDFJS.isEvalSupported === undefined ? true : PDFJS.isEvalSupported;
 PDFJS.pdfjsNext = PDFJS.pdfjsNext === undefined ? false : PDFJS.pdfjsNext;
 ;
 PDFJS.getDocument = _api.getDocument;
+PDFJS.LoopbackPort = _api.LoopbackPort;
 PDFJS.PDFDataRangeTransport = _api.PDFDataRangeTransport;
 PDFJS.PDFWorker = _api.PDFWorker;
 PDFJS.hasCanvasTypedArrays = true;
 PDFJS.CustomStyle = _dom_utils.CustomStyle;
 PDFJS.LinkTarget = _dom_utils.LinkTarget;
 PDFJS.addLinkAttributes = _dom_utils.addLinkAttributes;
 PDFJS.getFilenameFromUrl = _dom_utils.getFilenameFromUrl;
 PDFJS.isExternalLinkTargetSet = _dom_utils.isExternalLinkTargetSet;
@@ -5728,17 +5730,17 @@ var CanvasGraphics = function CanvasGrap
           spacingLength = spacingDir * glyph * fontSize / 1000;
           this.ctx.translate(spacingLength, 0);
           current.x += spacingLength * textHScale;
           continue;
         }
         var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
         var operatorList = font.charProcOperatorList[glyph.operatorListId];
         if (!operatorList) {
-          (0, _util.warn)('Type3 character \"' + glyph.operatorListId + '\" is not available');
+          (0, _util.warn)(`Type3 character "${glyph.operatorListId}" is not available.`);
           continue;
         }
         this.processingType3 = glyph;
         this.save();
         ctx.scale(fontSize, fontSize);
         ctx.transform.apply(ctx, fontMatrix);
         this.executeOperatorList(operatorList);
         this.restore();
@@ -6703,29 +6705,30 @@ exports.TilingPattern = TilingPattern;
 
 /***/ }),
 /* 13 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.8.290';
-var pdfjsBuild = '60c232bc';
+var pdfjsVersion = '1.8.331';
+var pdfjsBuild = '0dbc68a6';
 var pdfjsSharedUtil = __w_pdfjs_require__(0);
 var pdfjsDisplayGlobal = __w_pdfjs_require__(8);
 var pdfjsDisplayAPI = __w_pdfjs_require__(3);
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(5);
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(2);
 var pdfjsDisplayDOMUtils = __w_pdfjs_require__(1);
 var pdfjsDisplaySVG = __w_pdfjs_require__(4);
 exports.PDFJS = pdfjsDisplayGlobal.PDFJS;
 exports.build = pdfjsDisplayAPI.build;
 exports.version = pdfjsDisplayAPI.version;
 exports.getDocument = pdfjsDisplayAPI.getDocument;
+exports.LoopbackPort = pdfjsDisplayAPI.LoopbackPort;
 exports.PDFDataRangeTransport = pdfjsDisplayAPI.PDFDataRangeTransport;
 exports.PDFWorker = pdfjsDisplayAPI.PDFWorker;
 exports.renderTextLayer = pdfjsDisplayTextLayer.renderTextLayer;
 exports.AnnotationLayer = pdfjsDisplayAnnotationLayer.AnnotationLayer;
 exports.CustomStyle = pdfjsDisplayDOMUtils.CustomStyle;
 exports.createPromiseCapability = pdfjsSharedUtil.createPromiseCapability;
 exports.PasswordResponses = pdfjsSharedUtil.PasswordResponses;
 exports.InvalidPDFException = pdfjsSharedUtil.InvalidPDFException;
@@ -6746,11 +6749,13 @@ exports.addLinkAttributes = pdfjsDisplay
 
 /***/ }),
 /* 14 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
+;
+
 /***/ })
 /******/ ]);
 });
\ No newline at end of file
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -93,17 +93,23 @@ return /******/ (function(modules) { // 
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 /* WEBPACK VAR INJECTION */(function(global) {
 
-var compatibility = __w_pdfjs_require__(36);
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.warn = exports.utf8StringToString = exports.stringToUTF8String = exports.stringToPDFString = exports.stringToBytes = exports.string32 = exports.shadow = exports.setVerbosityLevel = exports.removeNullCharacters = exports.readUint32 = exports.readUint16 = exports.readInt8 = exports.log2 = exports.loadJpegStream = exports.isEvalSupported = exports.isLittleEndian = exports.createValidAbsoluteUrl = exports.isSameOrigin = exports.isNodeJS = exports.isSpace = exports.isString = exports.isNum = exports.isInt = exports.isEmptyObj = exports.isBool = exports.isArrayBuffer = exports.isArray = exports.info = exports.globalScope = exports.getVerbosityLevel = exports.getLookupTableFactory = exports.error = exports.deprecated = exports.createObjectURL = exports.createPromiseCapability = exports.createBlob = exports.bytesToString = exports.assert = exports.arraysToBytes = exports.arrayByteLength = exports.XRefParseException = exports.Util = exports.UnknownErrorException = exports.UnexpectedResponseException = exports.TextRenderingMode = exports.StreamType = exports.StatTimer = exports.PasswordResponses = exports.PasswordException = exports.PageViewport = exports.NotImplementedException = exports.MissingPDFException = exports.MissingDataException = exports.MessageHandler = exports.InvalidPDFException = exports.CMapCompressionType = exports.ImageKind = exports.FontType = exports.AnnotationType = exports.AnnotationFlag = exports.AnnotationFieldFlag = exports.AnnotationBorderStyleType = exports.UNSUPPORTED_FEATURES = exports.VERBOSITY_LEVELS = exports.OPS = exports.IDENTITY_MATRIX = exports.FONT_IDENTITY_MATRIX = undefined;
+
+__w_pdfjs_require__(36);
+
 var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : undefined;
 var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
 var TextRenderingMode = {
   FILL: 0,
   STROKE: 1,
   FILL_STROKE: 2,
   INVISIBLE: 3,
   FILL_ADD_TO_PATH: 4,
@@ -987,17 +993,17 @@ var createObjectURL = function createObj
 function MessageHandler(sourceName, targetName, comObj) {
   this.sourceName = sourceName;
   this.targetName = targetName;
   this.comObj = comObj;
   this.callbackIndex = 1;
   this.postMessageTransfers = true;
   var callbacksCapabilities = this.callbacksCapabilities = Object.create(null);
   var ah = this.actionHandler = Object.create(null);
-  this._onComObjOnMessage = function messageHandlerComObjOnMessage(event) {
+  this._onComObjOnMessage = event => {
     var data = event.data;
     if (data.targetName !== this.sourceName) {
       return;
     }
     if (data.isReply) {
       var callbackId = data.callbackId;
       if (data.callbackId in callbacksCapabilities) {
         var callback = callbacksCapabilities[callbackId];
@@ -1038,17 +1044,17 @@ function MessageHandler(sourceName, targ
           });
         });
       } else {
         action[0].call(action[1], data.data);
       }
     } else {
       error('Unknown action from worker: ' + data.action);
     }
-  }.bind(this);
+  };
   comObj.addEventListener('message', this._onComObjOnMessage);
 }
 MessageHandler.prototype = {
   on(actionName, handler, scope) {
     var ah = this.actionHandler;
     if (ah[actionName]) {
       error('There is already an actionName called "' + actionName + '"');
     }
@@ -4664,19 +4670,23 @@ var Lexer = function LexerClosure() {
         }
       } else if (ch === 0x2B) {
         ch = this.nextChar();
       }
       if (ch === 0x2E) {
         divideBy = 10;
         ch = this.nextChar();
       }
+      if (ch === 0x0A || ch === 0x0D) {
+        do {
+          ch = this.nextChar();
+        } while (ch === 0x0A || ch === 0x0D);
+      }
       if (ch < 0x30 || ch > 0x39) {
-        error('Invalid number: ' + String.fromCharCode(ch));
-        return 0;
+        error(`Invalid number: ${String.fromCharCode(ch)} (charCode ${ch})`);
       }
       var baseValue = ch - 0x30;
       var powerValue = 0;
       var powerValueSign = 1;
       while ((ch = this.nextChar()) >= 0) {
         if (0x30 <= ch && ch <= 0x39) {
           var currentDigit = ch - 0x30;
           if (eNotation) {
@@ -14551,25 +14561,25 @@ var ChunkedStreamManager = function Chun
             chunks = null;
             resolve(chunkData);
           } catch (e) {
             reject(e);
           }
         };
         rangeReader.read().then(readChunk, reject);
       });
-      promise.then(function (data) {
+      promise.then(data => {
         if (this.aborted) {
           return;
         }
         this.onReceiveData({
           chunk: data,
           begin
         });
-      }.bind(this));
+      });
     },
     requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
       var missingChunks = this.stream.getMissingChunks();
       this._requestChunks(missingChunks);
       return this._loadedStreamCapability.promise;
     },
     _requestChunks: function ChunkedStreamManager_requestChunks(chunks) {
       var requestId = this.currRequestId++;
@@ -16431,17 +16441,17 @@ var getEncoding = coreEncodings.getEncod
 var getStdFontMap = coreStandardFonts.getStdFontMap;
 var getSerifFonts = coreStandardFonts.getSerifFonts;
 var getSymbolsFonts = coreStandardFonts.getSymbolsFonts;
 var getNormalizedUnicodes = coreUnicode.getNormalizedUnicodes;
 var reverseIfRtl = coreUnicode.reverseIfRtl;
 var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph;
 var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
 var PartialEvaluator = function PartialEvaluatorClosure() {
-  var DefaultPartialEvaluatorOptions = {
+  const DefaultPartialEvaluatorOptions = {
     forceDataSchema: false,
     maxImageSize: -1,
     disableFontFace: false,
     disableNativeImageDecoder: false,
     ignoreErrors: false
   };
   function NativeImageDecoder(xref, resources, handler, forceDataSchema) {
     this.xref = xref;
@@ -16476,31 +16486,31 @@ var PartialEvaluator = function PartialE
   NativeImageDecoder.isDecodable = function NativeImageDecoder_isDecodable(image, xref, res) {
     var dict = image.dict;
     if (dict.has('DecodeParms') || dict.has('DP')) {
       return false;
     }
     var cs = ColorSpace.parse(dict.get('ColorSpace', 'CS'), xref, res);
     return (cs.numComps === 1 || cs.numComps === 3) && cs.isDefaultDecode(dict.getArray('Decode', 'D'));
   };
-  function PartialEvaluator(pdfManager, xref, handler, pageIndex, idFactory, fontCache, builtInCMapCache, options) {
+  function PartialEvaluator({ pdfManager, xref, handler, pageIndex, idFactory, fontCache, builtInCMapCache, options = null }) {
     this.pdfManager = pdfManager;
     this.xref = xref;
     this.handler = handler;
     this.pageIndex = pageIndex;
     this.idFactory = idFactory;
     this.fontCache = fontCache;
     this.builtInCMapCache = builtInCMapCache;
     this.options = options || DefaultPartialEvaluatorOptions;
     this.fetchBuiltInCMap = name => {
       var cachedCMap = this.builtInCMapCache[name];
       if (cachedCMap) {
         return Promise.resolve(cachedCMap);
       }
-      return handler.sendWithPromise('FetchBuiltInCMap', { name }).then(data => {
+      return this.handler.sendWithPromise('FetchBuiltInCMap', { name }).then(data => {
         if (data.compressionType !== CMapCompressionType.NONE) {
           this.builtInCMapCache[name] = data;
         }
         return data;
       });
     };
   }
   var TIME_SLOT_DURATION_MS = 20;
@@ -16653,25 +16663,30 @@ var PartialEvaluator = function PartialE
         }
         if (smask && smask.backdrop) {
           colorSpace = colorSpace || ColorSpace.singletons.rgb;
           smask.backdrop = colorSpace.getRgb(smask.backdrop, 0);
         }
         operatorList.addOp(OPS.beginGroup, [groupOptions]);
       }
       operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]);
-      return this.getOperatorList(xobj, task, dict.get('Resources') || resources, operatorList, initialState).then(function () {
+      return this.getOperatorList({
+        stream: xobj,
+        task,
+        resources: dict.get('Resources') || resources,
+        operatorList,
+        initialState
+      }).then(function () {
         operatorList.addOp(OPS.paintFormXObjectEnd, []);
         if (group) {
           operatorList.addOp(OPS.endGroup, [groupOptions]);
         }
       });
     },
     buildPaintImageXObject: function PartialEvaluator_buildPaintImageXObject(resources, image, inline, operatorList, cacheKey, imageCache) {
-      var self = this;
       var dict = image.dict;
       var w = dict.get('Width', 'W');
       var h = dict.get('Height', 'H');
       if (!(w && isNum(w)) || !(h && isNum(h))) {
         warn('Image dimensions are missing, or not numbers.');
         return;
       }
       var maxImageSize = this.options.maxImageSize;
@@ -16715,24 +16730,24 @@ var PartialEvaluator = function PartialE
       args = [objId, w, h];
       if (useNativeImageDecoder && !softMask && !mask && image instanceof JpegStream && NativeImageDecoder.isSupported(image, this.xref, resources)) {
         operatorList.addOp(OPS.paintJpegXObject, args);
         this.handler.send('obj', [objId, this.pageIndex, 'JpegStream', image.getIR(this.options.forceDataSchema)]);
         return;
       }
       var nativeImageDecoder = null;
       if (useNativeImageDecoder && (image instanceof JpegStream || mask instanceof JpegStream || softMask instanceof JpegStream)) {
-        nativeImageDecoder = new NativeImageDecoder(self.xref, resources, self.handler, self.options.forceDataSchema);
-      }
-      PDFImage.buildImage(self.handler, self.xref, resources, image, inline, nativeImageDecoder).then(function (imageObj) {
+        nativeImageDecoder = new NativeImageDecoder(this.xref, resources, this.handler, this.options.forceDataSchema);
+      }
+      PDFImage.buildImage(this.handler, this.xref, resources, image, inline, nativeImageDecoder).then(imageObj => {
         var imgData = imageObj.createImageData(false);
-        self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData], [imgData.data.buffer]);
-      }).then(undefined, function (reason) {
+        this.handler.send('obj', [objId, this.pageIndex, 'Image', imgData], [imgData.data.buffer]);
+      }).catch(reason => {
         warn('Unable to decode image: ' + reason);
-        self.handler.send('obj', [objId, self.pageIndex, 'Image', null]);
+        this.handler.send('obj', [objId, this.pageIndex, 'Image', null]);
       });
       operatorList.addOp(OPS.paintImageXObject, args);
       if (cacheKey) {
         imageCache[cacheKey] = {
           fn: OPS.paintImageXObject,
           args
         };
       }
@@ -16756,111 +16771,114 @@ var PartialEvaluator = function PartialE
         smaskOptions.transferMap = transferMap;
       }
       return this.buildFormXObject(resources, smaskContent, smaskOptions, operatorList, task, stateManager.state.clone());
     },
     handleTilingType: function PartialEvaluator_handleTilingType(fn, args, resources, pattern, patternDict, operatorList, task) {
       var tilingOpList = new OperatorList();
       var resourcesArray = [patternDict.get('Resources'), resources];
       var patternResources = Dict.merge(this.xref, resourcesArray);
-      return this.getOperatorList(pattern, task, patternResources, tilingOpList).then(function () {
+      return this.getOperatorList({
+        stream: pattern,
+        task,
+        resources: patternResources,
+        operatorList: tilingOpList
+      }).then(function () {
         operatorList.addDependencies(tilingOpList.dependencies);
         operatorList.addOp(fn, getTilingPatternIR({
           fnArray: tilingOpList.fnArray,
           argsArray: tilingOpList.argsArray
         }, patternDict, args));
       });
     },
     handleSetFont: function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef, operatorList, task, state) {
       var fontName;
       if (fontArgs) {
         fontArgs = fontArgs.slice();
         fontName = fontArgs[0].name;
       }
-      var self = this;
-      return this.loadFont(fontName, fontRef, resources).then(function (translated) {
+      return this.loadFont(fontName, fontRef, resources).then(translated => {
         if (!translated.font.isType3Font) {
           return translated;
         }
-        return translated.loadType3Data(self, resources, operatorList, task).then(function () {
+        return translated.loadType3Data(this, resources, operatorList, task).then(function () {
           return translated;
-        }, function (reason) {
-          self.handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.font });
+        }).catch(reason => {
+          this.handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.font });
           return new TranslatedFont('g_font_error', new ErrorFont('Type3 font load error: ' + reason), translated.font);
         });
-      }).then(function (translated) {
+      }).then(translated => {
         state.font = translated.font;
-        translated.send(self.handler);
+        translated.send(this.handler);
         return translated.loadedName;
       });
     },
     handleText: function PartialEvaluator_handleText(chars, state) {
       var font = state.font;
       var glyphs = font.charsToGlyphs(chars);
       var isAddToPathSet = !!(state.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG);
       if (font.data && (isAddToPathSet || this.options.disableFontFace)) {
-        var buildPath = function (fontChar) {
+        var buildPath = fontChar => {
           if (!font.renderer.hasBuiltPath(fontChar)) {
             var path = font.renderer.getPathJs(fontChar);
             this.handler.send('commonobj', [font.loadedName + '_path_' + fontChar, 'FontPath', path]);
           }
-        }.bind(this);
+        };
         for (var i = 0, ii = glyphs.length; i < ii; i++) {
           var glyph = glyphs[i];
           buildPath(glyph.fontChar);
           var accent = glyph.accent;
           if (accent && accent.fontChar) {
             buildPath(accent.fontChar);
           }
         }
       }
       return glyphs;
     },
     setGState: function PartialEvaluator_setGState(resources, gState, operatorList, task, stateManager) {
       var gStateObj = [];
       var gStateKeys = gState.getKeys();
-      var self = this;
       var promise = Promise.resolve();
       for (var i = 0, ii = gStateKeys.length; i < ii; i++) {
-        var key = gStateKeys[i];
-        var value = gState.get(key);
+        let key = gStateKeys[i];
+        let value = gState.get(key);
         switch (key) {
           case 'Type':
             break;
           case 'LW':
           case 'LC':
           case 'LJ':
           case 'ML':
           case 'D':
           case 'RI':
           case 'FL':
           case 'CA':
           case 'ca':
             gStateObj.push([key, value]);
             break;
           case 'Font':
-            promise = promise.then(function () {
-              return self.handleSetFont(resources, null, value[0], operatorList, task, stateManager.state).then(function (loadedName) {
+            promise = promise.then(() => {
+              return this.handleSetFont(resources, null, value[0], operatorList, task, stateManager.state).then(function (loadedName) {
                 operatorList.addDependency(loadedName);
                 gStateObj.push([key, [loadedName, value[1]]]);
               });
             });
             break;
           case 'BM':
             gStateObj.push([key, normalizeBlendMode(value)]);
             break;
           case 'SMask':
             if (isName(value, 'None')) {
               gStateObj.push([key, false]);
               break;
             }
             if (isDict(value)) {
-              promise = promise.then(function (dict) {
-                return self.handleSMask(dict, resources, operatorList, task, stateManager);
-              }.bind(this, value));
+              promise = promise.then(() => {
+                return this.handleSMask(value, resources, operatorList, task, stateManager);
+              });
               gStateObj.push([key, true]);
             } else {
               warn('Unsupported SMask type');
             }
             break;
           case 'OP':
           case 'op':
           case 'OPM':
@@ -16960,25 +16978,24 @@ var PartialEvaluator = function PartialE
       font.loadedName = 'g_' + this.pdfManager.docId + '_f' + fontID;
       font.translated = fontCapability.promise;
       var translatedPromise;
       try {
         translatedPromise = this.translateFont(preEvaluatedFont);
       } catch (e) {
         translatedPromise = Promise.reject(e);
       }
-      var self = this;
       translatedPromise.then(function (translatedFont) {
         if (translatedFont.fontType !== undefined) {
           var xrefFontStats = xref.stats.fontTypes;
           xrefFontStats[translatedFont.fontType] = true;
         }
         fontCapability.resolve(new TranslatedFont(font.loadedName, translatedFont, font));
-      }, function (reason) {
-        self.handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.font });
+      }).catch(reason => {
+        this.handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.font });
         try {
           var descriptor = preEvaluatedFont.descriptor;
           var fontFile3 = descriptor && descriptor.get('FontFile3');
           var subtype = fontFile3 && fontFile3.get('Subtype');
           var fontType = getFontType(preEvaluatedFont.type, subtype && subtype.name);
           var xrefFontStats = xref.stats.fontTypes;
           xrefFontStats[fontType] = true;
         } catch (ex) {}
@@ -17015,25 +17032,26 @@ var PartialEvaluator = function PartialE
           operatorList.addOp(fn, pattern.getIR());
           return Promise.resolve();
         }
         return Promise.reject(new Error('Unknown PatternType: ' + typeNum));
       }
       operatorList.addOp(fn, args);
       return Promise.resolve();
     },
-    getOperatorList: function PartialEvaluator_getOperatorList(stream, task, resources, operatorList, initialState) {
+    getOperatorList({ stream, task, resources, operatorList, initialState = null }) {
+      resources = resources || Dict.empty;
+      initialState = initialState || new EvalState();
+      assert(operatorList, 'getOperatorList: missing "operatorList" parameter');
       var self = this;
       var xref = this.xref;
       var imageCache = Object.create(null);
-      assert(operatorList);
-      resources = resources || Dict.empty;
       var xobjs = resources.get('XObject') || Dict.empty;
       var patterns = resources.get('Pattern') || Dict.empty;
-      var stateManager = new StateManager(initialState || new EvalState());
+      var stateManager = new StateManager(initialState);
       var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
       var timeSlotManager = new TimeSlotManager();
       function closePendingRestoreOPS(argument) {
         for (var i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) {
           operatorList.addOp(OPS.restore, []);
         }
       }
       return new Promise(function promiseBody(resolve, reject) {
@@ -17268,27 +17286,28 @@ var PartialEvaluator = function PartialE
           operatorList.addOp(fn, args);
         }
         if (stop) {
           next(deferred);
           return;
         }
         closePendingRestoreOPS();
         resolve();
-      }).catch(function (reason) {
+      }).catch(reason => {
         if (this.options.ignoreErrors) {
           this.handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.unknown });
           warn('getOperatorList - ignoring errors during task: ' + task.name);
           closePendingRestoreOPS();
           return;
         }
         throw reason;
-      }.bind(this));
-    },
-    getTextContent: function PartialEvaluator_getTextContent(stream, task, resources, stateManager, normalizeWhitespace, combineTextItems) {
+      });
+    },
+    getTextContent({ stream, task, resources, stateManager = null, normalizeWhitespace = false, combineTextItems = false }) {
+      resources = resources || Dict.empty;
       stateManager = stateManager || new StateManager(new TextState());
       var WhitespaceRegexp = /\s/g;
       var textContent = {
         items: [],
         styles: Object.create(null)
       };
       var textContentItem = {
         initialized: false,
@@ -17307,17 +17326,16 @@ var PartialEvaluator = function PartialE
         transform: null,
         fontName: null
       };
       var SPACE_FACTOR = 0.3;
       var MULTI_SPACE_FACTOR = 1.5;
       var MULTI_SPACE_FACTOR_MAX = 4;
       var self = this;
       var xref = this.xref;
-      resources = xref.fetchIfRef(resources) || Dict.empty;
       var xobjs = null;
       var xobjsCache = Object.create(null);
       var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
       var textState;
       function ensureTextContentItem() {
         if (textContentItem.initialized) {
           return textContentItem;
         }
@@ -17655,17 +17673,24 @@ var PartialEvaluator = function PartialE
                 break;
               }
               var currentState = stateManager.state.clone();
               var xObjStateManager = new StateManager(currentState);
               var matrix = xobj.dict.getArray('Matrix');
               if (isArray(matrix) && matrix.length === 6) {
                 xObjStateManager.transform(matrix);
               }
-              next(self.getTextContent(xobj, task, xobj.dict.get('Resources') || resources, xObjStateManager, normalizeWhitespace, combineTextItems).then(function (formTextContent) {
+              next(self.getTextContent({
+                stream: xobj,
+                task,
+                resources: xobj.dict.get('Resources') || resources,
+                stateManager: xObjStateManager,
+                normalizeWhitespace,
+                combineTextItems
+              }).then(function (formTextContent) {
                 Util.appendToArray(textContent.items, formTextContent.items);
                 Util.extendObj(textContent.styles, formTextContent.styles);
                 xobjsCache.key = name;
                 xobjsCache.texts = formTextContent;
               }));
               return;
             case OPS.setGState:
               flushTextContentItem();
@@ -17689,24 +17714,24 @@ var PartialEvaluator = function PartialE
           }
         }
         if (stop) {
           next(deferred);
           return;
         }
         flushTextContentItem();
         resolve(textContent);
-      }).catch(function (reason) {
+      }).catch(reason => {
         if (this.options.ignoreErrors) {
           warn('getTextContent - ignoring errors during task: ' + task.name);
           flushTextContentItem();
           return textContent;
         }
         throw reason;
-      }.bind(this));
+      });
     },
     extractDataStructures: function PartialEvaluator_extractDataStructures(dict, baseDict, properties) {
       var xref = this.xref;
       var toUnicode = dict.get('ToUnicode') || baseDict.get('ToUnicode');
       var toUnicodePromise = toUnicode ? this.readToUnicode(toUnicode) : Promise.resolve(undefined);
       if (properties.composite) {
         var cidSystemInfo = dict.get('CIDSystemInfo');
         if (isDict(cidSystemInfo)) {
@@ -17772,20 +17797,20 @@ var PartialEvaluator = function PartialE
           }
         }
         properties.defaultEncoding = encoding;
       }
       properties.differences = differences;
       properties.baseEncodingName = baseEncodingName;
       properties.hasEncoding = !!baseEncodingName || differences.length > 0;
       properties.dict = dict;
-      return toUnicodePromise.then(function (toUnicode) {
+      return toUnicodePromise.then(toUnicode => {
         properties.toUnicode = toUnicode;
         return this.buildToUnicode(properties);
-      }.bind(this)).then(function (toUnicode) {
+      }).then(function (toUnicode) {
         properties.toUnicode = toUnicode;
         return properties;
       });
     },
     buildToUnicode: function PartialEvaluator_buildToUnicode(properties) {
       properties.hasIncludedToUnicodeMap = !!properties.toUnicode && properties.toUnicode.length > 0;
       if (properties.hasIncludedToUnicodeMap) {
         return Promise.resolve(properties.toUnicode);
@@ -18155,20 +18180,20 @@ var PartialEvaluator = function PartialE
             type,
             name: baseFontName,
             widths: metrics.widths,
             defaultWidth: metrics.defaultWidth,
             flags,
             firstChar: 0,
             lastChar: maxCharIndex
           };
-          return this.extractDataStructures(dict, dict, properties).then(function (properties) {
+          return this.extractDataStructures(dict, dict, properties).then(properties => {
             properties.widths = this.buildCharCodeToWidth(metrics.widths, properties);
             return new Font(baseFontName, null, properties);
-          }.bind(this));
+          });
         }
       }
       var firstChar = dict.get('FirstChar') || 0;
       var lastChar = dict.get('LastChar') || maxCharIndex;
       var fontName = descriptor.get('FontName');
       var baseFont = dict.get('BaseFont');
       if (isString(fontName)) {
         fontName = Name.get(fontName);
@@ -18236,25 +18261,25 @@ var PartialEvaluator = function PartialE
           useCMap: null
         }).then(function (cMap) {
           properties.cMap = cMap;
           properties.vertical = properties.cMap.vertical;
         });
       } else {
         cMapPromise = Promise.resolve(undefined);
       }
-      return cMapPromise.then(function () {
+      return cMapPromise.then(() => {
         return this.extractDataStructures(dict, baseDict, properties);
-      }.bind(this)).then(function (properties) {
+      }).then(properties => {
         this.extractWidths(dict, descriptor, properties);
         if (type === 'Type3') {
           properties.isType3Font = true;
         }
         return new Font(fontName.name, fontFile, properties);
-      }.bind(this));
+      });
     }
   };
   return PartialEvaluator;
 }();
 var TranslatedFont = function TranslatedFontClosure() {
   function TranslatedFont(loadedName, font, dict) {
     this.loadedName = loadedName;
     this.font = font;
@@ -18281,28 +18306,34 @@ var TranslatedFont = function Translated
       var type3Evaluator = evaluator.clone(type3Options);
       var translatedFont = this.font;
       var loadCharProcsPromise = Promise.resolve();
       var charProcs = this.dict.get('CharProcs');
       var fontResources = this.dict.get('Resources') || resources;
       var charProcKeys = charProcs.getKeys();
       var charProcOperatorList = Object.create(null);
       for (var i = 0, n = charProcKeys.length; i < n; ++i) {
-        loadCharProcsPromise = loadCharProcsPromise.then(function (key) {
+        let key = charProcKeys[i];
+        loadCharProcsPromise = loadCharProcsPromise.then(function () {
           var glyphStream = charProcs.get(key);
           var operatorList = new OperatorList();
-          return type3Evaluator.getOperatorList(glyphStream, task, fontResources, operatorList).then(function () {
+          return type3Evaluator.getOperatorList({
+            stream: glyphStream,
+            task,
+            resources: fontResources,
+            operatorList
+          }).then(function () {
             charProcOperatorList[key] = operatorList.getIR();
             parentOperatorList.addDependencies(operatorList.dependencies);
-          }, function (reason) {
-            warn('Type3 font resource \"' + key + '\" is not available');
+          }).catch(function (reason) {
+            warn(`Type3 font resource "${key}" is not available.`);
             var operatorList = new OperatorList();
             charProcOperatorList[key] = operatorList.getIR();
           });
-        }.bind(this, charProcKeys[i]));
+        });
       }
       this.type3Loaded = loadCharProcsPromise.then(function () {
         translatedFont.charProcOperatorList = charProcOperatorList;
       });
       return this.type3Loaded;
     }
   };
   return TranslatedFont;
@@ -21599,32 +21630,30 @@ var Catalog = function CatalogClosure() 
       return shadow(this, 'javaScript', javaScript);
     },
     cleanup: function Catalog_cleanup() {
       this.pageKidsCountCache.clear();
       var promises = [];
       this.fontCache.forEach(function (promise) {
         promises.push(promise);
       });
-      return Promise.all(promises).then(function (translatedFonts) {
+      return Promise.all(promises).then(translatedFonts => {
         for (var i = 0, ii = translatedFonts.length; i < ii; i++) {
           var font = translatedFonts[i].dict;
           delete font.translated;
         }
         this.fontCache.clear();
         this.builtInCMapCache = Object.create(null);
-      }.bind(this));
+      });
     },
     getPage: function Catalog_getPage(pageIndex) {
       if (!(pageIndex in this.pagePromises)) {
-        this.pagePromises[pageIndex] = this.getPageDict(pageIndex).then(function (a) {
-          var dict = a[0];
-          var ref = a[1];
+        this.pagePromises[pageIndex] = this.getPageDict(pageIndex).then(([dict, ref]) => {
           return this.pageFactory.createPage(pageIndex, dict, ref, this.fontCache, this.builtInCMapCache);
-        }.bind(this));
+        });
       }
       return this.pagePromises[pageIndex];
     },
     getPageDict: function Catalog_getPageDict(pageIndex) {
       var capability = createPromiseCapability();
       var nodesToVisit = [this.catDict.getRaw('Pages')];
       var count,
           currentPageIndex = 0;
@@ -22659,26 +22688,26 @@ var ObjectLoader = function () {
           }
           if (foundMissingData) {
             nodesToRevisit.push(currentNode);
           }
         }
         addChildren(currentNode, nodesToVisit);
       }
       if (pendingRequests.length) {
-        this.xref.stream.manager.requestRanges(pendingRequests).then(function pendingRequestCallback() {
+        this.xref.stream.manager.requestRanges(pendingRequests).then(() => {
           nodesToVisit = nodesToRevisit;
           for (var i = 0; i < nodesToRevisit.length; i++) {
             var node = nodesToRevisit[i];
             if (isRef(node)) {
               this.refSet.remove(node);
             }
           }
           this._walk(nodesToVisit);
-        }.bind(this), this.capability.reject);
+        }, this.capability.reject);
         return;
       }
       this.refSet = null;
       this.capability.resolve();
     }
   };
   return ObjectLoader;
 }();
@@ -23591,17 +23620,17 @@ var PDFWorkerStream = function PDFWorker
   };
   return PDFWorkerStream;
 }();
 var PDFNetworkStream;
 function setPDFNetworkStreamClass(cls) {
   PDFNetworkStream = cls;
 }
 var WorkerMessageHandler = {
-  setup: function wphSetup(handler, port) {
+  setup(handler, port) {
     var testMessageProcessed = false;
     handler.on('test', function wphSetupTest(data) {
       if (testMessageProcessed) {
         return;
       }
       testMessageProcessed = true;
       if (!(data instanceof Uint8Array)) {
         handler.send('test', 'main', false);
@@ -23627,17 +23656,17 @@ var WorkerMessageHandler = {
     });
     handler.on('configure', function wphConfigure(data) {
       setVerbosityLevel(data.verbosity);
     });
     handler.on('GetDocRequest', function wphSetupDoc(data) {
       return WorkerMessageHandler.createDocumentHandler(data, port);
     });
   },
-  createDocumentHandler: function wphCreateDocumentHandler(docParams, port) {
+  createDocumentHandler(docParams, port) {
     var pdfManager;
     var terminated = false;
     var cancelXHRs = null;
     var WorkerTasks = [];
     var docId = docParams.docId;
     var docBaseUrl = docParams.docBaseUrl;
     var workerHandlerName = docParams.docId + '_worker';
     var handler = new MessageHandler(workerHandlerName, docId, port);
@@ -23908,17 +23937,22 @@ var WorkerMessageHandler = {
     });
     handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
       var pageIndex = data.pageIndex;
       pdfManager.getPage(pageIndex).then(function (page) {
         var task = new WorkerTask('RenderPageRequest: page ' + pageIndex);
         startWorkerTask(task);
         var pageNum = pageIndex + 1;
         var start = Date.now();
-        page.getOperatorList(handler, task, data.intent, data.renderInteractiveForms).then(function (operatorList) {
+        page.getOperatorList({
+          handler,
+          task,
+          intent: data.intent,
+          renderInteractiveForms: data.renderInteractiveForms
+        }).then(function (operatorList) {
           finishWorkerTask(task);
           info('page=' + pageNum + ' - getOperatorList: time=' + (Date.now() - start) + 'ms, len=' + operatorList.totalLength);
         }, function (e) {
           finishWorkerTask(task);
           if (task.terminated) {
             return;
           }
           handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.unknown });
@@ -23950,17 +23984,22 @@ var WorkerMessageHandler = {
     }, this);
     handler.on('GetTextContent', function wphExtractText(data) {
       var pageIndex = data.pageIndex;
       return pdfManager.getPage(pageIndex).then(function (page) {
         var task = new WorkerTask('GetTextContent: page ' + pageIndex);
         startWorkerTask(task);
         var pageNum = pageIndex + 1;
         var start = Date.now();
-        return page.extractTextContent(handler, task, data.normalizeWhitespace, data.combineTextItems).then(function (textContent) {
+        return page.extractTextContent({
+          handler,
+          task,
+          normalizeWhitespace: data.normalizeWhitespace,
+          combineTextItems: data.combineTextItems
+        }).then(function (textContent) {
           finishWorkerTask(task);
           info('text indexing: page=' + pageNum + ' - time=' + (Date.now() - start) + 'ms');
           return textContent;
         }, function (reason) {
           finishWorkerTask(task);
           if (task.terminated) {
             return;
           }
@@ -23990,25 +24029,28 @@ var WorkerMessageHandler = {
         handler = null;
       });
     });
     handler.on('Ready', function wphReady(data) {
       setupDoc(docParams);
       docParams = null;
     });
     return workerHandlerName;
+  },
+  initializeFromPort(port) {
+    var handler = new MessageHandler('worker', 'main', port);
+    WorkerMessageHandler.setup(handler, port);
+    handler.send('ready', null);
   }
 };
-function initializeWorker() {
-  var handler = new MessageHandler('worker', 'main', self);
-  WorkerMessageHandler.setup(handler, self);
-  handler.send('ready', null);
-}
-if (typeof window === 'undefined' && !isNodeJS()) {
-  initializeWorker();
+function isMessagePort(maybePort) {
+  return typeof maybePort.postMessage === 'function' && 'onmessage' in maybePort;
+}
+if (typeof window === 'undefined' && !isNodeJS() && typeof self !== 'undefined' && isMessagePort(self)) {
+  WorkerMessageHandler.initializeFromPort(self);
 }
 exports.setPDFNetworkStreamClass = setPDFNetworkStreamClass;
 exports.WorkerTask = WorkerTask;
 exports.WorkerMessageHandler = WorkerMessageHandler;
 
 /***/ }),
 /* 18 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
@@ -24265,46 +24307,47 @@ var Annotation = function AnnotationClos
       if (!dict.has('C')) {
         this.data.color = null;
       }
       this.data.hasPopup = dict.has('Popup');
       this.data.title = stringToPDFString(dict.get('T') || '');
       this.data.contents = stringToPDFString(dict.get('Contents') || '');
     },
     loadResources: function Annotation_loadResources(keys) {
-      return new Promise(function (resolve, reject) {
-        this.appearance.dict.getAsync('Resources').then(function (resources) {
-          if (!resources) {
-            resolve();
-            return;
-          }
-          var objectLoader = new ObjectLoader(resources.map, keys, resources.xref);
-          objectLoader.load().then(function () {
-            resolve(resources);
-          }, reject);
-        }, reject);
-      }.bind(this));
+      return this.appearance.dict.getAsync('Resources').then(resources => {
+        if (!resources) {
+          return;
+        }
+        var objectLoader = new ObjectLoader(resources.map, keys, resources.xref);
+        return objectLoader.load().then(function () {
+          return resources;
+        });
+      });
     },
     getOperatorList: function Annotation_getOperatorList(evaluator, task, renderForms) {
       if (!this.appearance) {
         return Promise.resolve(new OperatorList());
       }
       var data = this.data;
       var appearanceDict = this.appearance.dict;
       var resourcesPromise = this.loadResources(['ExtGState', 'ColorSpace', 'Pattern', 'Shading', 'XObject', 'Font']);
       var bbox = appearanceDict.getArray('BBox') || [0, 0, 1, 1];
       var matrix = appearanceDict.getArray('Matrix') || [1, 0, 0, 1, 0, 0];
       var transform = getTransformMatrix(data.rect, bbox, matrix);
-      var self = this;
-      return resourcesPromise.then(function (resources) {
+      return resourcesPromise.then(resources => {
         var opList = new OperatorList();
         opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
-        return evaluator.getOperatorList(self.appearance, task, resources, opList).then(function () {
+        return evaluator.getOperatorList({
+          stream: this.appearance,
+          task,
+          resources,
+          operatorList: opList
+        }).then(() => {
           opList.addOp(OPS.endAnnotation, []);
-          self.appearance.reset();
+          this.appearance.reset();
           return opList;
         });
       });
     }
   };
   return Annotation;
 }();
 var AnnotationBorderStyle = function AnnotationBorderStyleClosure() {
@@ -24459,17 +24502,22 @@ var TextWidgetAnnotation = function Text
       }
       if (this.appearance) {
         return Annotation.prototype.getOperatorList.call(this, evaluator, task, renderForms);
       }
       if (!this.data.defaultAppearance) {
         return Promise.resolve(operatorList);
       }
       var stream = new Stream(stringToBytes(this.data.defaultAppearance));
-      return evaluator.getOperatorList(stream, task, this.fieldResources, operatorList).then(function () {
+      return evaluator.getOperatorList({
+        stream,
+        task,
+        resources: this.fieldResources,
+        operatorList
+      }).then(function () {
         return operatorList;
       });
     }
   });
   return TextWidgetAnnotation;
 }();
 var ButtonWidgetAnnotation = function ButtonWidgetAnnotationClosure() {
   function ButtonWidgetAnnotation(params) {
@@ -25805,44 +25853,53 @@ var Page = function PageClosure() {
         stream = new NullStream();
       }
       return stream;
     },
     loadResources: function Page_loadResources(keys) {
       if (!this.resourcesPromise) {
         this.resourcesPromise = this.pdfManager.ensure(this, 'resources');
       }
-      return this.resourcesPromise.then(function resourceSuccess() {
+      return this.resourcesPromise.then(() => {
         var objectLoader = new ObjectLoader(this.resources.map, keys, this.xref);
         return objectLoader.load();
-      }.bind(this));
-    },
-    getOperatorList: function Page_getOperatorList(handler, task, intent, renderInteractiveForms) {
-      var self = this;
-      var pdfManager = this.pdfManager;
-      var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', []);
+      });
+    },
+    getOperatorList({ handler, task, intent, renderInteractiveForms }) {
+      var contentStreamPromise = this.pdfManager.ensure(this, 'getContentStream');
       var resourcesPromise = this.loadResources(['ExtGState', 'ColorSpace', 'Pattern', 'Shading', 'XObject', 'Font']);
-      var partialEvaluator = new PartialEvaluator(pdfManager, this.xref, handler, this.pageIndex, this.idFactory, this.fontCache, this.builtInCMapCache, this.evaluatorOptions);
+      var partialEvaluator = new PartialEvaluator({
+        pdfManager: this.pdfManager,
+        xref: this.xref,
+        handler,
+        pageIndex: this.pageIndex,
+        idFactory: this.idFactory,
+        fontCache: this.fontCache,
+        builtInCMapCache: this.builtInCMapCache,
+        options: this.evaluatorOptions
+      });
       var dataPromises = Promise.all([contentStreamPromise, resourcesPromise]);
-      var pageListPromise = dataPromises.then(function (data) {
-        var contentStream = data[0];
-        var opList = new OperatorList(intent, handler, self.pageIndex);
+      var pageListPromise = dataPromises.then(([contentStream]) => {
+        var opList = new OperatorList(intent, handler, this.pageIndex);
         handler.send('StartRenderPage', {
-          transparency: partialEvaluator.hasBlendModes(self.resources),
-          pageIndex: self.pageIndex,
+          transparency: partialEvaluator.hasBlendModes(this.resources),
+          pageIndex: this.pageIndex,
           intent
         });
-        return partialEvaluator.getOperatorList(contentStream, task, self.resources, opList).then(function () {
+        return partialEvaluator.getOperatorList({
+          stream: contentStream,
+          task,
+          resources: this.resources,
+          operatorList: opList
+        }).then(function () {
           return opList;
         });
       });
-      var annotationsPromise = pdfManager.ensure(this, 'annotations');
-      return Promise.all([pageListPromise, annotationsPromise]).then(function (datas) {
-        var pageOpList = datas[0];
-        var annotations = datas[1];
+      var annotationsPromise = this.pdfManager.ensure(this, 'annotations');
+      return Promise.all([pageListPromise, annotationsPromise]).then(function ([pageOpList, annotations]) {
         if (annotations.length === 0) {
           pageOpList.flush(true);
           return pageOpList;
         }
         var i,
             ii,
             opListPromises = [];
         for (i = 0, ii = annotations.length; i < ii; i++) {
@@ -25856,26 +25913,38 @@ var Page = function PageClosure() {
             pageOpList.addOpList(opLists[i]);
           }
           pageOpList.addOp(OPS.endAnnotations, []);
           pageOpList.flush(true);
           return pageOpList;
         });
       });
     },
-    extractTextContent: function Page_extractTextContent(handler, task, normalizeWhitespace, combineTextItems) {
-      var self = this;
-      var pdfManager = this.pdfManager;
-      var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', []);
+    extractTextContent({ handler, task, normalizeWhitespace, combineTextItems }) {
+      var contentStreamPromise = this.pdfManager.ensure(this, 'getContentStream');
       var resourcesPromise = this.loadResources(['ExtGState', 'XObject', 'Font']);
       var dataPromises = Promise.all([contentStreamPromise, resourcesPromise]);
-      return dataPromises.then(function (data) {
-        var contentStream = data[0];
-        var partialEvaluator = new PartialEvaluator(pdfManager, self.xref, handler, self.pageIndex, self.idFactory, self.fontCache, self.builtInCMapCache, self.evaluatorOptions);
-        return partialEvaluator.getTextContent(contentStream, task, self.resources, null, normalizeWhitespace, combineTextItems);
+      return dataPromises.then(([contentStream]) => {
+        var partialEvaluator = new PartialEvaluator({
+          pdfManager: this.pdfManager,
+          xref: this.xref,
+          handler,
+          pageIndex: this.pageIndex,
+          idFactory: this.idFactory,
+          fontCache: this.fontCache,
+          builtInCMapCache: this.builtInCMapCache,
+          options: this.evaluatorOptions
+        });
+        return partialEvaluator.getTextContent({
+          stream: contentStream,
+          task,
+          resources: this.resources,
+          normalizeWhitespace,
+          combineTextItems
+        });
       });
     },
     getAnnotationsData: function Page_getAnnotationsData(intent) {
       var annotations = this.annotations;
       var annotationsData = [];
       for (var i = 0, n = annotations.length; i < n; ++i) {
         if (!intent || isAnnotationRenderable(annotations[i], intent)) {
           annotationsData.push(annotations[i].data);
@@ -27288,26 +27357,26 @@ var Font = function FontClosure() {
         if (/Wingdings/i.test(name)) {
           warn('Non-embedded Wingdings font, falling back to ZapfDingbats.');
         }
         this.toFontChar = buildToFontChar(ZapfDingbatsEncoding, getDingbatsGlyphsUnicode(), properties.differences);
       } else if (isStandardFont) {
         this.toFontChar = buildToFontChar(properties.defaultEncoding, getGlyphsUnicode(), properties.differences);
       } else {
         glyphsUnicodeMap = getGlyphsUnicode();
-        this.toUnicode.forEach(function (charCode, unicodeCharCode) {
+        this.toUnicode.forEach((charCode, unicodeCharCode) => {
           if (!this.composite) {
             glyphName = properties.differences[charCode] || properties.defaultEncoding[charCode];
             unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
             if (unicode !== -1) {
               unicodeCharCode = unicode;
             }
           }
           this.toFontChar[charCode] = unicodeCharCode;
-        }.bind(this));
+        });
       }
       this.loadedName = fontName.split('-')[0];
       this.loading = false;
       this.fontType = getFontType(type, subtype);
       return;
     }
     if (subtype === 'Type1C') {
       if (type !== 'Type1' && type !== 'MMType1') {
@@ -36558,24 +36627,26 @@ exports.Type1Parser = Type1Parser;
 
 /***/ }),
 /* 35 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.8.290';
-var pdfjsBuild = '60c232bc';
+var pdfjsVersion = '1.8.331';
+var pdfjsBuild = '0dbc68a6';
 var pdfjsCoreWorker = __w_pdfjs_require__(17);
 ;
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 36 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
+;
+
 /***/ })
 /******/ ]);
 });
\ No newline at end of file
--- a/browser/extensions/pdfjs/content/web/debugger.js
+++ b/browser/extensions/pdfjs/content/web/debugger.js
@@ -144,21 +144,21 @@ var FontInspector = (function FontInspec
       font.appendChild(document.createTextNode(' '));
       font.appendChild(download);
       font.appendChild(document.createTextNode(' '));
       font.appendChild(logIt);
       font.appendChild(moreInfo);
       fonts.appendChild(font);
       // Somewhat of a hack, should probably add a hook for when the text layer
       // is done rendering.
-      setTimeout(function() {
+      setTimeout(() => {
         if (this.active) {
           resetSelection();
         }
-      }.bind(this), 2000);
+      }, 2000);
     }
   };
 })();
 
 var opMap;
 
 // Manages all the page steppers.
 var StepperManager = (function StepperManagerClosure() {
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -86,17 +86,17 @@
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.localized = exports.animationStarted = exports.normalizeWheelEventDelta = exports.binarySearchFirstItem = exports.watchScroll = exports.scrollIntoView = exports.getOutputScale = exports.approximateFraction = exports.roundToDivide = exports.getVisibleElements = exports.parseQueryString = exports.noContextMenuHandler = exports.getPDFFileNameFromURL = exports.ProgressBar = exports.EventBus = exports.mozL10n = exports.RendererType = exports.VERTICAL_PADDING = exports.SCROLLBAR_PADDING = exports.MAX_AUTO_SCALE = exports.UNKNOWN_SCALE = exports.MAX_SCALE = exports.MIN_SCALE = exports.DEFAULT_SCALE = exports.DEFAULT_SCALE_VALUE = exports.CSS_UNITS = undefined;
+exports.localized = exports.animationStarted = exports.normalizeWheelEventDelta = exports.binarySearchFirstItem = exports.watchScroll = exports.scrollIntoView = exports.getOutputScale = exports.approximateFraction = exports.roundToDivide = exports.getVisibleElements = exports.parseQueryString = exports.noContextMenuHandler = exports.getPDFFileNameFromURL = exports.ProgressBar = exports.EventBus = exports.mozL10n = exports.RendererType = exports.cloneObj = exports.VERTICAL_PADDING = exports.SCROLLBAR_PADDING = exports.MAX_AUTO_SCALE = exports.UNKNOWN_SCALE = exports.MAX_SCALE = exports.MIN_SCALE = exports.DEFAULT_SCALE = exports.DEFAULT_SCALE_VALUE = exports.CSS_UNITS = undefined;
 
 var _pdfjs = __webpack_require__(1);
 
 var CSS_UNITS = 96.0 / 72.0;
 var DEFAULT_SCALE_VALUE = 'auto';
 var DEFAULT_SCALE = 1.0;
 var MIN_SCALE = 0.25;
 var MAX_SCALE = 10.0;
@@ -354,16 +354,25 @@ function normalizeWheelEventDelta(evt) {
   var MOUSE_LINES_PER_PAGE = 30;
   if (evt.deltaMode === MOUSE_DOM_DELTA_PIXEL_MODE) {
     delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
   } else if (evt.deltaMode === MOUSE_DOM_DELTA_LINE_MODE) {
     delta /= MOUSE_LINES_PER_PAGE;
   }
   return delta;
 }
+function cloneObj(obj) {
+  var result = {};
+  for (var i in obj) {
+    if (Object.prototype.hasOwnProperty.call(obj, i)) {
+      result[i] = obj[i];
+    }
+  }
+  return result;
+}
 var animationStarted = new Promise(function (resolve) {
   window.requestAnimationFrame(resolve);
 });
 var localized = new Promise(function (resolve, reject) {
   if (!mozL10n) {
     resolve();
     return;
   }
@@ -474,16 +483,17 @@ exports.CSS_UNITS = CSS_UNITS;
 exports.DEFAULT_SCALE_VALUE = DEFAULT_SCALE_VALUE;
 exports.DEFAULT_SCALE = DEFAULT_SCALE;
 exports.MIN_SCALE = MIN_SCALE;
 exports.MAX_SCALE = MAX_SCALE;
 exports.UNKNOWN_SCALE = UNKNOWN_SCALE;
 exports.MAX_AUTO_SCALE = MAX_AUTO_SCALE;
 exports.SCROLLBAR_PADDING = SCROLLBAR_PADDING;
 exports.VERTICAL_PADDING = VERTICAL_PADDING;
+exports.cloneObj = cloneObj;
 exports.RendererType = RendererType;
 exports.mozL10n = mozL10n;
 exports.EventBus = EventBus;
 exports.ProgressBar = ProgressBar;
 exports.getPDFFileNameFromURL = getPDFFileNameFromURL;
 exports.noContextMenuHandler = noContextMenuHandler;
 exports.parseQueryString = parseQueryString;
 exports.getVisibleElements = getVisibleElements;
@@ -1162,16 +1172,17 @@ var PDFViewerApplication = {
     }
     var promise = this.pdfLoadingTask.destroy();
     this.pdfLoadingTask = null;
     if (this.pdfDocument) {
       this.pdfDocument = null;
       this.pdfThumbnailViewer.setDocument(null);
       this.pdfViewer.setDocument(null);
       this.pdfLinkService.setDocument(null, null);
+      this.pdfDocumentProperties.setDocument(null, null);
     }
     this.store = null;
     this.isInitialViewSet = false;
     this.pdfSidebar.reset();
     this.pdfOutlineViewer.reset();
     this.pdfAttachmentViewer.reset();
     this.findController.reset();
     this.findBar.reset();
@@ -1305,39 +1316,39 @@ var PDFViewerApplication = {
     if (percent > this.loadingBar.percent || isNaN(percent)) {
       this.loadingBar.percent = percent;
       if (_pdfjs.PDFJS.disableAutoFetch && percent) {
         if (this.disableAutoFetchLoadingBarTimeout) {
           clearTimeout(this.disableAutoFetchLoadingBarTimeout);
           this.disableAutoFetchLoadingBarTimeout = null;
         }
         this.loadingBar.show();
-        this.disableAutoFetchLoadingBarTimeout = setTimeout(function () {
+        this.disableAutoFetchLoadingBarTimeout = setTimeout(() => {
           this.loadingBar.hide();
           this.disableAutoFetchLoadingBarTimeout = null;
-        }.bind(this), DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT);
+        }, DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT);
       }
     }
   },
   load: function pdfViewLoad(pdfDocument, scale) {
     var self = this;
     scale = scale || _ui_utils.UNKNOWN_SCALE;
     this.pdfDocument = pdfDocument;
-    this.pdfDocumentProperties.setDocumentAndUrl(pdfDocument, this.url);
     var downloadedPromise = pdfDocument.getDownloadInfo().then(function () {
       self.downloadComplete = true;
       self.loadingBar.hide();
     });
     this.toolbar.setPagesCount(pdfDocument.numPages, false);
     this.secondaryToolbar.setPagesCount(pdfDocument.numPages);
     var id = this.documentFingerprint = pdfDocument.fingerprint;
     var store = this.store = new _view_history.ViewHistory(id);
     var baseDocumentUrl;
     baseDocumentUrl = this.baseUrl;
     this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl);
+    this.pdfDocumentProperties.setDocument(pdfDocument, this.url);
     var pdfViewer = this.pdfViewer;
     pdfViewer.currentScale = scale;
     pdfViewer.setDocument(pdfDocument);
     var firstPagePromise = pdfViewer.firstPagePromise;
     var pagesPromise = pdfViewer.pagesPromise;
     var onePageRendered = pdfViewer.onePageRendered;
     this.pageRotation = 0;
     var pdfThumbnailViewer = this.pdfThumbnailViewer;
@@ -2268,88 +2279,88 @@ exports.PDFPrintServiceFactory = PDFPrin
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 var OverlayManager = {
   overlays: {},
   active: null,
-  register: function overlayManagerRegister(name, element, callerCloseMethod, canForceClose) {
-    return new Promise(function (resolve) {
+  register(name, element, callerCloseMethod, canForceClose) {
+    return new Promise(resolve => {
       var container;
       if (!name || !element || !(container = element.parentNode)) {
         throw new Error('Not enough parameters.');
       } else if (this.overlays[name]) {
         throw new Error('The overlay is already registered.');
       }
       this.overlays[name] = {
         element,
         container,
         callerCloseMethod: callerCloseMethod || null,
         canForceClose: canForceClose || false
       };
       resolve();
-    }.bind(this));
+    });
   },
-  unregister: function overlayManagerUnregister(name) {
-    return new Promise(function (resolve) {
+  unregister(name) {
+    return new Promise(resolve => {
       if (!this.overlays[name]) {
         throw new Error('The overlay does not exist.');
       } else if (this.active === name) {
         throw new Error('The overlay cannot be removed while it is active.');
       }
       delete this.overlays[name];
       resolve();
-    }.bind(this));
+    });
   },
-  open: function overlayManagerOpen(name) {
-    return new Promise(function (resolve) {
+  open(name) {
+    return new Promise(resolve => {
       if (!this.overlays[name]) {
         throw new Error('The overlay does not exist.');
       } else if (this.active) {
         if (this.overlays[name].canForceClose) {
           this._closeThroughCaller();
         } else if (this.active === name) {
           throw new Error('The overlay is already active.');
         } else {
           throw new Error('Another overlay is currently active.');
         }
       }
       this.active = name;
       this.overlays[this.active].element.classList.remove('hidden');
       this.overlays[this.active].container.classList.remove('hidden');
       window.addEventListener('keydown', this._keyDown);
       resolve();
-    }.bind(this));
+    });
   },
-  close: function overlayManagerClose(name) {
-    return new Promise(function (resolve) {
+  close(name) {
+    return new Promise(resolve => {
       if (!this.overlays[name]) {
         throw new Error('The overlay does not exist.');
       } else if (!this.active) {
         throw new Error('The overlay is currently not active.');
       } else if (this.active !== name) {
         throw new Error('Another overlay is currently active.');
       }
       this.overlays[this.active].container.classList.add('hidden');
       this.overlays[this.active].element.classList.add('hidden');
       this.active = null;
       window.removeEventListener('keydown', this._keyDown);
       resolve();
-    }.bind(this));
+    });
   },
-  _keyDown: function overlayManager_keyDown(evt) {
+  _keyDown(evt) {
     var self = OverlayManager;
     if (self.active && evt.keyCode === 27) {
       self._closeThroughCaller();
       evt.preventDefault();
     }
   },
-  _closeThroughCaller: function overlayManager_closeThroughCaller() {
+  _closeThroughCaller() {
     if (this.overlays[this.active].callerCloseMethod) {
       this.overlays[this.active].callerCloseMethod();
     }
     if (this.active) {
       this.close(this.active);
     }
   }
 };
@@ -2890,25 +2901,25 @@ var PDFFindController = function PDFFind
       extractPageText(0);
     },
     executeCommand: function PDFFindController_executeCommand(cmd, state) {
       if (this.state === null || cmd !== 'findagain') {
         this.dirtyMatch = true;
       }
       this.state = state;
       this.updateUIState(FindStates.FIND_PENDING);
-      this._firstPagePromise.then(function () {
+      this._firstPagePromise.then(() => {
         this.extractText();
         clearTimeout(this.findTimeout);
         if (cmd === 'find') {
           this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
         } else {
           this.nextMatch();
         }
-      }.bind(this));
+      });
     },
     updatePage: function PDFFindController_updatePage(index) {
       if (this.selected.pageIdx === index) {
         this.pdfViewer.currentPageNumber = index + 1;
       }
       var page = this.pdfViewer.getPageView(index);
       if (page.textLayer) {
         page.textLayer.updateMatches();
@@ -3207,27 +3218,28 @@ var DownloadManager = function DownloadM
       FirefoxCom.request('download', {
         blobUrl,
         originalUrl: blobUrl,
         filename,
         isAttachment: true
       });
     },
     download: function DownloadManager_download(blob, url, filename) {
-      var blobUrl = window.URL.createObjectURL(blob);
+      let blobUrl = window.URL.createObjectURL(blob);
+      let onResponse = err => {
+        if (err && this.onerror) {
+          this.onerror(err);
+        }
+        window.URL.revokeObjectURL(blobUrl);
+      };
       FirefoxCom.request('download', {
         blobUrl,
         originalUrl: url,
         filename
-      }, function response(err) {
-        if (err && this.onerror) {
-          this.onerror(err);
-        }
-        window.URL.revokeObjectURL(blobUrl);
-      }.bind(this));
+      }, onResponse);
     }
   };
   return DownloadManager;
 }();
 class FirefoxPreferences extends _preferences.BasePreferences {
   _writeToStorage(prefObj) {
     return new Promise(function (resolve) {
       FirefoxCom.request('setPreferences', prefObj, resolve);
@@ -3603,26 +3615,26 @@ var HandTool = function HandToolClosure(
       }
     });
     this.eventBus.on('togglehandtool', this.toggle.bind(this));
     Promise.all([_ui_utils.localized, preferences.get('enableHandToolOnLoad')]).then(values => {
       if (values[1] === true) {
         this.handTool.activate();
       }
     }).catch(function rejected(reason) {});
-    this.eventBus.on('presentationmodechanged', function (e) {
-      if (e.switchInProgress) {
+    this.eventBus.on('presentationmodechanged', evt => {
+      if (evt.switchInProgress) {
         return;
       }
-      if (e.active) {
+      if (evt.active) {
         this.enterPresentationMode();
       } else {
         this.exitPresentationMode();
       }
-    }.bind(this));
+    });
   }
   HandTool.prototype = {
     get isActive() {
       return !!this.handTool.active;
     },
     toggle: function HandTool_toggle() {
       this.handTool.toggle();
     },
@@ -3795,18 +3807,18 @@ class PDFAttachmentViewer {
       } else {
         this._bindLink(button, item.content, filename);
       }
       div.appendChild(button);
       this.container.appendChild(div);
     }
     this._dispatchEvent(attachmentsCount);
   }
-  _appendAttachment(item) {
-    this._renderedCapability.promise.then(function (id, filename, content) {
+  _appendAttachment({ id, filename, content }) {
+    this._renderedCapability.promise.then(() => {
       var attachments = this.attachments;
       if (!attachments) {
         attachments = Object.create(null);
       } else {
         for (var name in attachments) {
           if (id === name) {
             return;
           }
@@ -3815,17 +3827,17 @@ class PDFAttachmentViewer {
       attachments[id] = {
         filename,
         content
       };
       this.render({
         attachments,
         keepRenderedCapability: true
       });
-    }.bind(this, item.id, item.filename, item.content));
+    });
   }
 }
 exports.PDFAttachmentViewer = PDFAttachmentViewer;
 
 /***/ }),
 /* 15 */
 /***/ (function(module, exports, __webpack_require__) {
 
@@ -3838,105 +3850,129 @@ Object.defineProperty(exports, "__esModu
 exports.PDFDocumentProperties = undefined;
 
 var _ui_utils = __webpack_require__(0);
 
 var _pdfjs = __webpack_require__(1);
 
 var _overlay_manager = __webpack_require__(5);
 
+const DEFAULT_FIELD_CONTENT = '-';
 class PDFDocumentProperties {
-  constructor(options) {
-    this.overlayName = options.overlayName;
-    this.fields = options.fields;
-    this.container = options.container;
-    this.rawFileSize = 0;
-    this.url = null;
-    this.pdfDocument = null;
-    if (options.closeButton) {
-      options.closeButton.addEventListener('click', this.close.bind(this));
-    }
-    this._dataAvailableCapability = (0, _pdfjs.createPromiseCapability)();
+  constructor({ overlayName, fields, container, closeButton }) {
+    this.overlayName = overlayName;
+    this.fields = fields;
+    this.container = container;
+    this._reset();
+    if (closeButton) {
+      closeButton.addEventListener('click', this.close.bind(this));
+    }
     _overlay_manager.OverlayManager.register(this.overlayName, this.container, this.close.bind(this));
   }
   open() {
+    let freezeFieldData = data => {
+      Object.defineProperty(this, 'fieldData', {
+        value: Object.freeze(data),
+        writable: false,
+        enumerable: true,
+        configurable: true
+      });
+    };
     Promise.all([_overlay_manager.OverlayManager.open(this.overlayName), this._dataAvailableCapability.promise]).then(() => {
-      this._getProperties();
+      if (this.fieldData) {
+        this._updateUI();
+        return;
+      }
+      this.pdfDocument.getMetadata().then(({ info, metadata }) => {
+        freezeFieldData({
+          'fileName': (0, _ui_utils.getPDFFileNameFromURL)(this.url),
+          'fileSize': this._parseFileSize(this.maybeFileSize),
+          'title': info.Title,
+          'author': info.Author,
+          'subject': info.Subject,
+          'keywords': info.Keywords,
+          'creationDate': this._parseDate(info.CreationDate),
+          'modificationDate': this._parseDate(info.ModDate),
+          'creator': info.Creator,
+          'producer': info.Producer,
+          'version': info.PDFFormatVersion,
+          'pageCount': this.pdfDocument.numPages
+        });
+        this._updateUI();
+        return this.pdfDocument.getDownloadInfo();
+      }).then(({ length }) => {
+        let data = (0, _ui_utils.cloneObj)(this.fieldData);
+        data['fileSize'] = this._parseFileSize(length);
+        freezeFieldData(data);
+        this._updateUI();
+      });
     });
   }
   close() {
     _overlay_manager.OverlayManager.close(this.overlayName);
   }
-  setFileSize(fileSize) {
-    if (fileSize > 0) {
-      this.rawFileSize = fileSize;
-    }
-  }
-  setDocumentAndUrl(pdfDocument, url) {
+  setDocument(pdfDocument, url) {
+    if (this.pdfDocument) {
+      this._reset();
+      this._updateUI(true);
+    }
+    if (!pdfDocument) {
+      return;
+    }
     this.pdfDocument = pdfDocument;
     this.url = url;
     this._dataAvailableCapability.resolve();
   }
-  _getProperties() {
-    if (!_overlay_manager.OverlayManager.active) {
+  setFileSize(fileSize) {
+    if (typeof fileSize === 'number' && fileSize > 0) {
+      this.maybeFileSize = fileSize;
+    }
+  }
+  _reset() {
+    this.pdfDocument = null;
+    this.url = null;
+    this.maybeFileSize = 0;
+    delete this.fieldData;
+    this._dataAvailableCapability = (0, _pdfjs.createPromiseCapability)();
+  }
+  _updateUI(reset = false) {
+    if (reset || !this.fieldData) {
+      for (let id in this.fields) {
+        this.fields[id].textContent = DEFAULT_FIELD_CONTENT;
+      }
       return;
     }
-    this.pdfDocument.getDownloadInfo().then(data => {
-      if (data.length === this.rawFileSize) {
-        return;
-      }
-      this.setFileSize(data.length);
-      this._updateUI(this.fields['fileSize'], this._parseFileSize());
-    });
-    this.pdfDocument.getMetadata().then(data => {
-      var content = {
-        'fileName': (0, _ui_utils.getPDFFileNameFromURL)(this.url),
-        'fileSize': this._parseFileSize(),
-        'title': data.info.Title,
-        'author': data.info.Author,
-        'subject': data.info.Subject,
-        'keywords': data.info.Keywords,
-        'creationDate': this._parseDate(data.info.CreationDate),
-        'modificationDate': this._parseDate(data.info.ModDate),
-        'creator': data.info.Creator,
-        'producer': data.info.Producer,
-        'version': data.info.PDFFormatVersion,
-        'pageCount': this.pdfDocument.numPages
-      };
-      for (var identifier in content) {
-        this._updateUI(this.fields[identifier], content[identifier]);
-      }
-    });
-  }
-  _updateUI(field, content) {
-    if (field && content !== undefined && content !== '') {
-      field.textContent = content;
-    }
-  }
-  _parseFileSize() {
-    var fileSize = this.rawFileSize,
-        kb = fileSize / 1024;
+    if (_overlay_manager.OverlayManager.active !== this.overlayName) {
+      return;
+    }
+    for (let id in this.fields) {
+      let content = this.fieldData[id];
+      this.fields[id].textContent = content || content === 0 ? content : DEFAULT_FIELD_CONTENT;
+    }
+  }
+  _parseFileSize(fileSize = 0) {
+    let kb = fileSize / 1024;
     if (!kb) {
       return;
     } else if (kb < 1024) {
       return _ui_utils.mozL10n.get('document_properties_kb', {
         size_kb: (+kb.toPrecision(3)).toLocaleString(),
         size_b: fileSize.toLocaleString()
       }, '{{size_kb}} KB ({{size_b}} bytes)');
     }
     return _ui_utils.mozL10n.get('document_properties_mb', {
       size_mb: (+(kb / 1024).toPrecision(3)).toLocaleString(),
       size_b: fileSize.toLocaleString()
     }, '{{size_mb}} MB ({{size_b}} bytes)');
   }
   _parseDate(inputDate) {
-    var dateToParse = inputDate;
-    if (dateToParse === undefined) {
-      return '';
-    }
+    if (!inputDate) {
+      return;
+    }
+    let dateToParse = inputDate;
     if (dateToParse.substring(0, 2) === 'D:') {
       dateToParse = dateToParse.substring(2);
     }
     var year = parseInt(dateToParse.substring(0, 4), 10);
     var month = parseInt(dateToParse.substring(4, 6), 10) - 1;
     var day = parseInt(dateToParse.substring(6, 8), 10);
     var hours = parseInt(dateToParse.substring(8, 10), 10);
     var minutes = parseInt(dateToParse.substring(10, 12), 10);
@@ -6015,31 +6051,31 @@ var PDFThumbnailViewer = function PDFThu
       if (this.pdfDocument) {
         this._cancelRendering();
         this._resetView();
       }
       this.pdfDocument = pdfDocument;
       if (!pdfDocument) {
         return Promise.resolve();
       }
-      return pdfDocument.getP