merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 30 Sep 2016 12:02:16 +0200
changeset 315970 5ffed033557e5b6f9694123f1948f867f913ede3
parent 315814 59db3b0781908d9e2145477ff8dc5a85729e25bf (current diff)
parent 315969 3b4b49952871c7fb905aaed928e183a8a26191df (diff)
child 315971 afe79b010d138c608f5490572e3303647b452d74
child 315982 3040be42dde9169f98248ede39d3e7b7619e774a
child 316054 33d05c77b5c6aa7556f0ac6d95f873e2bff61ca7
push id20634
push usercbook@mozilla.com
push dateFri, 30 Sep 2016 10:10:13 +0000
treeherderfx-team@afe79b010d13 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
merge mozilla-inbound to mozilla-central a=merge
dom/presentation/PresentationServiceBase.cpp
js/src/ctypes/CTypes.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
testing/talos/talos/tests/scroll/scroll-test.js
testing/talos/talos/tests/scroll/scroll.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -97,17 +97,17 @@ devtools/client/shared/webgl-utils.js
 devtools/client/shared/developer-toolbar.js
 devtools/client/shared/components/test/**
 devtools/client/shared/redux/middleware/test/**
 devtools/client/shared/test/**
 !devtools/client/shared/test/test-actor-registry.js
 devtools/client/shared/widgets/*.jsm
 devtools/client/sourceeditor/test/*.js
 devtools/client/webaudioeditor/**
-devtools/client/webconsole/**
+#devtools/client/webconsole/**
 !devtools/client/webconsole/panel.js
 !devtools/client/webconsole/jsterm.js
 !devtools/client/webconsole/console-commands.js
 devtools/client/webide/**
 !devtools/client/webide/components/webideCli.js
 devtools/server/*.js
 devtools/server/*.jsm
 !devtools/server/child.js
--- a/.flake8
+++ b/.flake8
@@ -1,3 +1,5 @@
 [flake8]
+# See http://pep8.readthedocs.io/en/latest/intro.html#configuration
+ignore = E121, E123, E126, E133, E226, E241, E242, E704, W503, E402
 max-line-length = 99
 filename = *.py, +.lint
--- a/accessible/base/AccEvent.h
+++ b/accessible/base/AccEvent.h
@@ -5,16 +5,17 @@
 
 #ifndef _AccEvent_H_
 #define _AccEvent_H_
 
 #include "nsIAccessibleEvent.h"
 
 #include "mozilla/a11y/Accessible.h"
 
+class nsEventShell;
 namespace mozilla {
 
 namespace dom {
 class Selection;
 }
 
 namespace a11y {
 
@@ -126,16 +127,17 @@ protected:
 
   bool mIsFromUserInput;
   uint32_t mEventType;
   EEventRule mEventRule;
   RefPtr<Accessible> mAccessible;
 
   friend class EventQueue;
   friend class EventTree;
+  friend class ::nsEventShell;
 };
 
 
 /**
  * Accessible state change event.
  */
 class AccStateChangeEvent: public AccEvent
 {
@@ -285,17 +287,20 @@ public:
   virtual unsigned int GetEventGroups() const override
   {
     return AccMutationEvent::GetEventGroups() | (1U << eShowEvent);
   }
 
   uint32_t InsertionIndex() const { return mInsertionIndex; }
 
 private:
+  nsTArray<RefPtr<AccHideEvent>> mPrecedingEvents;
   uint32_t mInsertionIndex;
+
+  friend class EventTree;
 };
 
 
 /**
  * Class for reorder accessible event. Takes care about
  */
 class AccReorderEvent : public AccEvent
 {
--- a/accessible/base/EventTree.cpp
+++ b/accessible/base/EventTree.cpp
@@ -149,16 +149,34 @@ TreeMutation::PrefixLog(void* aData, Acc
 }
 #endif
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // EventTree
 
 void
+EventTree::Shown(Accessible* aChild)
+{
+  RefPtr<AccShowEvent> ev = new AccShowEvent(aChild);
+  Controller(aChild)->WithdrawPrecedingEvents(&ev->mPrecedingEvents);
+  Mutated(ev);
+}
+
+void
+EventTree::Hidden(Accessible* aChild, bool aNeedsShutdown)
+{
+  RefPtr<AccHideEvent> ev = new AccHideEvent(aChild, aNeedsShutdown);
+  if (!aNeedsShutdown) {
+    Controller(aChild)->StorePrecedingEvent(ev);
+  }
+  Mutated(ev);
+}
+
+void
 EventTree::Process(const RefPtr<DocAccessible>& aDeathGrip)
 {
   while (mFirst) {
     // Skip a node and its subtree if its container is not in the document.
     if (mFirst->mContainer->IsInDocument()) {
       mFirst->Process(aDeathGrip);
       if (aDeathGrip->IsDefunct()) {
         return;
@@ -172,20 +190,29 @@ EventTree::Process(const RefPtr<DocAcces
   MOZ_ASSERT(!mContainer || !mContainer->IsDefunct(),
              "Processing events for defunct container");
   MOZ_ASSERT(!mFireReorder || mContainer, "No target for reorder event");
 
   // Fire mutation events.
   uint32_t eventsCount = mDependentEvents.Length();
   for (uint32_t jdx = 0; jdx < eventsCount; jdx++) {
     AccMutationEvent* mtEvent = mDependentEvents[jdx];
-    MOZ_ASSERT(mtEvent->mEventRule != AccEvent::eDoNotEmit,
-               "The event shouldn't be presented in the tree");
     MOZ_ASSERT(mtEvent->Document(), "No document for event target");
 
+    // Fire all hide events that has to be fired before this show event.
+    if (mtEvent->IsShow()) {
+      AccShowEvent* showEv = downcast_accEvent(mtEvent);
+      for (uint32_t i = 0; i < showEv->mPrecedingEvents.Length(); i++) {
+        nsEventShell::FireEvent(showEv->mPrecedingEvents[i]);
+        if (aDeathGrip->IsDefunct()) {
+          return;
+        }
+      }
+    }
+
     nsEventShell::FireEvent(mtEvent);
     if (aDeathGrip->IsDefunct()) {
       return;
     }
 
     if (mtEvent->mTextChangeEvent) {
       nsEventShell::FireEvent(mtEvent->mTextChangeEvent);
       if (aDeathGrip->IsDefunct()) {
@@ -410,23 +437,32 @@ EventTree::Log(uint32_t aLevel) const
   for (uint32_t i = 0; i < aLevel; i++) {
     printf("  ");
   }
   logging::AccessibleInfo("container", mContainer);
 
   for (uint32_t i = 0; i < mDependentEvents.Length(); i++) {
     AccMutationEvent* ev = mDependentEvents[i];
     if (ev->IsShow()) {
-      for (uint32_t i = 0; i < aLevel; i++) {
+      for (uint32_t i = 0; i < aLevel + 1; i++) {
         printf("  ");
       }
       logging::AccessibleInfo("shown", ev->mAccessible);
+
+      AccShowEvent* showEv = downcast_accEvent(ev);
+      for (uint32_t i = 0; i < showEv->mPrecedingEvents.Length(); i++) {
+        for (uint32_t j = 0; j < aLevel + 1; j++) {
+          printf("  ");
+        }
+        logging::AccessibleInfo("preceding",
+                                showEv->mPrecedingEvents[i]->mAccessible);
+      }
     }
     else {
-      for (uint32_t i = 0; i < aLevel; i++) {
+      for (uint32_t i = 0; i < aLevel + 1; i++) {
         printf("  ");
       }
       logging::AccessibleInfo("hidden", ev->mAccessible);
     }
   }
 
   if (mFirst) {
     mFirst->Log(aLevel + 1);
--- a/accessible/base/EventTree.h
+++ b/accessible/base/EventTree.h
@@ -62,27 +62,19 @@ class EventTree final {
 public:
   EventTree() :
     mFirst(nullptr), mNext(nullptr), mContainer(nullptr), mFireReorder(false) { }
   explicit EventTree(Accessible* aContainer, bool aFireReorder) :
     mFirst(nullptr), mNext(nullptr), mContainer(aContainer),
     mFireReorder(aFireReorder) { }
   ~EventTree() { Clear(); }
 
-  void Shown(Accessible* aChild)
-  {
-    RefPtr<AccShowEvent> ev = new AccShowEvent(aChild);
-    Mutated(ev);
-  }
+  void Shown(Accessible* aChild);
 
-  void Hidden(Accessible* aChild, bool aNeedsShutdown = true)
-  {
-    RefPtr<AccHideEvent> ev = new AccHideEvent(aChild, aNeedsShutdown);
-    Mutated(ev);
-  }
+  void Hidden(Accessible* aChild, bool aNeedsShutdown = true);
 
   /**
    * Return an event tree node for the given accessible.
    */
   const EventTree* Find(const Accessible* aContainer) const;
 
 #ifdef A11Y_LOG
   void Log(uint32_t aLevel = UINT32_MAX) const;
@@ -104,16 +96,19 @@ private:
 
   UniquePtr<EventTree> mFirst;
   UniquePtr<EventTree> mNext;
 
   Accessible* mContainer;
   nsTArray<RefPtr<AccMutationEvent>> mDependentEvents;
   bool mFireReorder;
 
+  static NotificationController* Controller(Accessible* aAcc)
+    { return aAcc->Document()->Controller(); }
+
   friend class NotificationController;
 };
 
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_EventQueue_h_
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -21,16 +21,20 @@ using namespace mozilla::a11y;
 // NotificationCollector
 ////////////////////////////////////////////////////////////////////////////////
 
 NotificationController::NotificationController(DocAccessible* aDocument,
                                                nsIPresShell* aPresShell) :
   EventQueue(aDocument), mObservingState(eNotObservingRefresh),
   mPresShell(aPresShell)
 {
+#ifdef DEBUG
+  mMoveGuardOnStack = false;
+#endif
+
   // Schedule initial accessible tree construction.
   ScheduleProcessing();
 }
 
 NotificationController::~NotificationController()
 {
   NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
   if (mDocument)
--- a/accessible/base/NotificationController.h
+++ b/accessible/base/NotificationController.h
@@ -127,16 +127,39 @@ public:
   }
 
   /**
    * Returns existing event tree for the given the accessible or creates one if
    * it doesn't exists yet.
    */
   EventTree* QueueMutation(Accessible* aContainer);
 
+  class MoveGuard final {
+  public:
+    explicit MoveGuard(NotificationController* aController) :
+      mController(aController)
+    {
+#ifdef DEBUG
+      MOZ_ASSERT(!mController->mMoveGuardOnStack,
+                 "Move guard is on stack already!");
+      mController->mMoveGuardOnStack = true;
+#endif
+    }
+    ~MoveGuard() {
+#ifdef DEBUG
+      MOZ_ASSERT(mController->mMoveGuardOnStack, "No move guard on stack!");
+      mController->mMoveGuardOnStack = false;
+#endif
+      mController->mPrecedingEvents.Clear();
+    }
+
+  private:
+    NotificationController* mController;
+  };
+
 #ifdef A11Y_LOG
   const EventTree& RootEventTree() const { return mEventTree; };
 #endif
 
   /**
    * Schedule binding the child document to the tree of this document.
    */
   void ScheduleChildDocBinding(DocAccessible* aDocument);
@@ -242,16 +265,36 @@ protected:
 
 private:
   NotificationController(const NotificationController&);
   NotificationController& operator = (const NotificationController&);
 
   // nsARefreshObserver
   virtual void WillRefresh(mozilla::TimeStamp aTime) override;
 
+  /**
+   * Set and returns a hide event, paired with a show event, for the move.
+   */
+  void WithdrawPrecedingEvents(nsTArray<RefPtr<AccHideEvent>>* aEvs)
+  {
+    if (mPrecedingEvents.Length() > 0) {
+      aEvs->AppendElements(mozilla::Move(mPrecedingEvents));
+    }
+  }
+  void StorePrecedingEvent(AccHideEvent* aEv)
+  {
+    MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!");
+    mPrecedingEvents.AppendElement(aEv);
+  }
+  void StorePrecedingEvents(nsTArray<RefPtr<AccHideEvent>>&& aEvs)
+  {
+    MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!");
+    mPrecedingEvents.InsertElementsAt(0, aEvs);
+  }
+
 private:
   /**
    * Indicates whether we're waiting on an event queue processing from our
    * notification controller to flush events.
    */
   enum eObservingState {
     eNotObservingRefresh,
     eRefreshObserving,
@@ -315,14 +358,27 @@ private:
    * Holds all scheduled relocations.
    */
   nsTArray<RefPtr<Accessible> > mRelocations;
 
   /**
    * Holds all mutation events.
    */
   EventTree mEventTree;
+
+  /**
+   * A temporary collection of hide events that should be fired before related
+   * show event. Used by EventTree.
+   */
+  nsTArray<RefPtr<AccHideEvent>> mPrecedingEvents;
+
+#ifdef DEBUG
+  bool mMoveGuardOnStack;
+#endif
+
+  friend class MoveGuard;
+  friend class EventTree;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_NotificationController_h_
--- a/accessible/base/nsEventShell.cpp
+++ b/accessible/base/nsEventShell.cpp
@@ -14,17 +14,17 @@ using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsEventShell
 ////////////////////////////////////////////////////////////////////////////////
 
 void
 nsEventShell::FireEvent(AccEvent* aEvent)
 {
-  if (!aEvent)
+  if (!aEvent || aEvent->mEventRule == AccEvent::eDoNotEmit)
     return;
 
   Accessible* accessible = aEvent->GetAccessible();
   NS_ENSURE_TRUE_VOID(accessible);
 
   nsINode* node = accessible->GetNode();
   if (node) {
     sEventTargetNode = node;
@@ -38,16 +38,17 @@ nsEventShell::FireEvent(AccEvent* aEvent
     GetAccService()->GetStringEventType(aEvent->GetEventType(), type);
     logging::MsgEntry("type: %s", NS_ConvertUTF16toUTF8(type).get());
     logging::AccessibleInfo("target", aEvent->GetAccessible());
     logging::MsgEnd();
   }
 #endif
 
   accessible->HandleAccEvent(aEvent);
+  aEvent->mEventRule = AccEvent::eDoNotEmit;
 
   sEventTargetNode = nullptr;
 }
 
 void
 nsEventShell::FireEvent(uint32_t aEventType, Accessible* aAccessible,
                         EIsFromUserInput aIsFromUserInput)
 {
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -2175,16 +2175,18 @@ DocAccessible::MoveChild(Accessible* aCh
 #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);
   }
 
+  NotificationController::MoveGuard mguard(mNotificationController);
+
   if (curParent == aNewParent) {
     MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
     curParent->MoveChild(aIdxInParent, aChild);
 
 #ifdef A11Y_LOG
     logging::TreeInfo("move child: parent tree after",
                       logging::eVerbose, curParent);
 #endif
--- a/accessible/tests/mochitest/events/test_coalescence.html
+++ b/accessible/tests/mochitest/events/test_coalescence.html
@@ -496,17 +496,17 @@
      * <div id="t7_c">
      *   <div id="t7_c_directchild">ha</div>
      *   <div><div id="t7_c_grandchild">ha</div></div>
      * </div>
      */
     function test7()
     {
       this.eventSeq = [
-        new todo_invokerChecker(EVENT_HIDE, getNode('t7_c')),
+        new invokerChecker(EVENT_HIDE, getNode('t7_c')),
         new invokerChecker(EVENT_SHOW, getNode('t7_c')),
         new invokerChecker(EVENT_REORDER, getNode('t7')),
         new unexpectedInvokerChecker(EVENT_REORDER, getNode('t7_c_directchild')),
         new unexpectedInvokerChecker(EVENT_REORDER, getNode('t7_c_grandchild')),
         new unexpectedInvokerChecker(EVENT_SHOW, () => getNode('t7_c_directchild').firstChild),
         new unexpectedInvokerChecker(EVENT_SHOW, () => getNode('t7_c_grandchild').firstChild)
       ];
 
@@ -517,21 +517,57 @@
         getNode('t7_moveplace').setAttribute('aria-owns', 't7_c');
       };
 
       this.getID = function test7_getID() {
         return "Show child accessibles and then hide their container";
       };
     }
 
+    /**
+     * Move a node by aria-owns from right to left in the tree, so that
+     * the eventing looks this way:
+     * reorder for 't8_c1'
+     *   hide for 't8_c1_child'
+     *   show for 't8_c2_moved'
+     * reorder for 't8_c2'
+     *   hide for 't8_c2_moved'
+     *
+     * The hide event should be delivered before the paired show event.
+     */
+    function test8()
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getNode('t8_c1_child')),
+        new invokerChecker(EVENT_HIDE, 't8_c2_moved'),
+        new invokerChecker(EVENT_SHOW, 't8_c2_moved'),
+        new invokerChecker(EVENT_REORDER, 't8_c1'),
+        new invokerChecker(EVENT_REORDER, 't8_c2')
+      ];
+
+      this.invoke = function test8_invoke()
+      {
+        // Remove a node from 't8_c1' container to give the event tree a
+        // desired structure (the 't8_c1' container node goes first in the event
+        // tree)
+        getNode('t8_c1_child').remove();
+        // then move 't8_c2_moved' from 't8_c2' to 't8_c1'.
+        getNode('t8_c1').setAttribute('aria-owns', 't8_c2_moved');
+      };
+
+      this.getID = function test8_getID() {
+        return "Move a node by aria-owns to left within the tree";
+      };
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Do tests.
 
     //gA11yEventDumpToConsole = true; // debug stuff
-    //enableLogging("tree,eventTree,verbose");
+    //enableLogging("eventTree");
 
     var gQueue = null;
     function doTests()
     {
       gQueue = new eventQueue();
 
       gQueue.push(new removeChildNParent("option1", "select1"));
       gQueue.push(new removeParentNChild("option2", "select2"));
@@ -550,16 +586,17 @@
       gQueue.push(new showParentNAddChild("select12", true));
 
       gQueue.push(new removeGrandChildrenNHideParent("t1_child1", "t1_child2", "t1_parent"));
       gQueue.push(new test3());
       gQueue.push(new test4());
       gQueue.push(new test5());
       gQueue.push(new test6());
       gQueue.push(new test7());
+      gQueue.push(new test8());
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
@@ -576,17 +613,16 @@
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
     Mozilla Bug 570275
   </a>
 
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
-  <div id="eventdump"></div>
 
   <div id="testContainer">
     <select id="select1">
       <option id="option1">option</option>
     </select>
     <select id="select2">
       <option id="option2">option</option>
     </select>
@@ -651,10 +687,17 @@
 
   <div id="t7">
     <div id="t7_moveplace"></div>
     <div id="t7_c">
       <div><div id="t7_c_grandchild"></div></div>
       <div id="t7_c_directchild"></div>
     </div>
   </div>
+
+  <div id="t8">
+    <div id="t8_c1"><div id="t8_c1_child"></div></div>
+    <div id="t8_c2">
+      <div id="t8_c2_moved"></div>
+    </div>
+  </div>
 </body>
 </html>
--- a/accessible/tests/mochitest/treeupdate/test_ariaowns.html
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -68,20 +68,20 @@
       {
         return "Change @aria-owns attribute";
       }
     }
 
     function removeARIAOwns()
     {
       this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
         new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
         new invokerChecker(EVENT_HIDE, getNode("t1_button")),
         new invokerChecker(EVENT_SHOW, getNode("t1_button")),
-        new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
         new invokerChecker(EVENT_REORDER, getNode("t1_container"))
       ];
 
       this.invoke = function removeARIAOwns_invoke()
       {
         getNode("t1_container").removeAttribute("aria-owns");
       }
 
@@ -137,18 +137,18 @@
       {
         return "Set @aria-owns attribute";
       }
     }
 
     function addIdToARIAOwns()
     {
       this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getNode("t1_group")),
         new invokerChecker(EVENT_SHOW, getNode("t1_group")),
-        new invokerChecker(EVENT_HIDE, getNode("t1_group")),
         new invokerChecker(EVENT_REORDER, document)
       ];
 
       this.invoke = function addIdToARIAOwns_invoke()
       {
         getNode("t1_container").
           setAttribute("aria-owns", "t1_button t1_subdiv t1_group");
       }
@@ -270,18 +270,18 @@
       {
         return "Remove ID from ARIA owned element";
       }
     }
 
     function setId()
     {
       this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")),
         new invokerChecker(EVENT_SHOW, getNode("t1_grouptmp")),
-        new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")),
         new invokerChecker(EVENT_REORDER, document)
       ];
 
       this.invoke = function setId_invoke()
       {
         getNode("t1_grouptmp").setAttribute("id", "t1_group");
       }
 
--- a/addon-sdk/source/lib/sdk/panel.js
+++ b/addon-sdk/source/lib/sdk/panel.js
@@ -8,17 +8,17 @@
 module.metadata = {
   "stability": "stable",
   "engines": {
     "Firefox": "*",
     "SeaMonkey": "*"
   }
 };
 
-const { Ci } = require("chrome");
+const { Cu, Ci } = require("chrome");
 const { setTimeout } = require('./timers');
 const { Class } = require("./core/heritage");
 const { merge } = require("./util/object");
 const { WorkerHost } = require("./content/utils");
 const { Worker } = require("./deprecated/sync-worker");
 const { Disposable } = require("./core/disposable");
 const { WeakReference } = require('./core/reference');
 const { contract: loaderContract } = require("./content/loader");
@@ -106,42 +106,83 @@ var workers = new WeakMap();
 var styles = new WeakMap();
 
 const viewFor = (panel) => views.get(panel);
 const modelFor = (panel) => models.get(panel);
 const panelFor = (view) => panels.get(view);
 const workerFor = (panel) => workers.get(panel);
 const styleFor = (panel) => styles.get(panel);
 
-// Utility function takes `panel` instance and makes sure it will be
-// automatically hidden as soon as other panel is shown.
-var setupAutoHide = new function() {
-  let refs = new WeakMap();
+function getPanelFromWeakRef(weakRef) {
+  if (!weakRef) {
+    return null;
+  }
+  let panel = weakRef.get();
+  if (!panel) {
+    return null;
+  }
+  if (isDisposed(panel)) {
+    return null;
+  }
+  return panel;
+}
 
-  return function setupAutoHide(panel) {
-    // Create system event listener that reacts to any panel showing and
-    // hides given `panel` if it's not the one being shown.
-    function listener({subject}) {
-      // It could be that listener is not GC-ed in the same cycle as
-      // panel in such case we remove listener manually.
-      let view = viewFor(panel);
-      if (!view) systemEvents.off("popupshowing", listener);
-      else if (subject !== view) panel.hide();
+var SinglePanelManager = {
+  visiblePanel: null,
+  enqueuedPanel: null,
+  enqueuedPanelCallback: null,
+  // Calls |callback| with no arguments when the panel may be shown.
+  requestOpen: function(panelToOpen, callback) {
+    let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
+    if (currentPanel || SinglePanelManager.enqueuedPanel) {
+      SinglePanelManager.enqueuedPanel = Cu.getWeakReference(panelToOpen);
+      SinglePanelManager.enqueuedPanelCallback = callback;
+      if (currentPanel && currentPanel.isShowing) {
+        currentPanel.hide();
+      }
+    } else {
+      SinglePanelManager.notifyPanelCanOpen(panelToOpen, callback);
     }
-
-    // system event listener is intentionally weak this way we'll allow GC
-    // to claim panel if it's no longer referenced by an add-on code. This also
-    // helps minimizing cleanup required on unload.
-    systemEvents.on("popupshowing", listener);
-    // To make sure listener is not claimed by GC earlier than necessary we
-    // associate it with `panel` it's associated with. This way it won't be
-    // GC-ed earlier than `panel` itself.
-    refs.set(panel, listener);
+  },
+  notifyPanelCanOpen: function(panel, callback) {
+    let view = viewFor(panel);
+    // Can't pass an arrow function as the event handler because we need to be
+    // able to call |removeEventListener| later.
+    view.addEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
+    view.addEventListener("popupshown", SinglePanelManager.onVisiblePanelShown, false);
+    SinglePanelManager.enqueuedPanel = null;
+    SinglePanelManager.enqueuedPanelCallback = null;
+    SinglePanelManager.visiblePanel = Cu.getWeakReference(panel);
+    callback();
+  },
+  onVisiblePanelShown: function(event) {
+    let panel = panelFor(event.target);
+    if (SinglePanelManager.enqueuedPanel) {
+      // Another panel started waiting for |panel| to close before |panel| was
+      // even done opening.
+      panel.hide();
+    }
+  },
+  onVisiblePanelHidden: function(event) {
+    let view = event.target;
+    let panel = panelFor(view);
+    let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
+    if (currentPanel && currentPanel != panel) {
+      return;
+    }
+    SinglePanelManager.visiblePanel = null;
+    view.removeEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
+    view.removeEventListener("popupshown", SinglePanelManager.onVisiblePanelShown, false);
+    let nextPanel = getPanelFromWeakRef(SinglePanelManager.enqueuedPanel);
+    let nextPanelCallback = SinglePanelManager.enqueuedPanelCallback;
+    if (nextPanel) {
+      SinglePanelManager.notifyPanelCanOpen(nextPanel, nextPanelCallback);
+    }
   }
-}
+};
 
 const Panel = Class({
   implements: [
     // Generate accessors for the validated properties that update model on
     // set and return values from model on get.
     panelContract.properties(modelFor),
     EventTarget,
     Disposable,
@@ -173,18 +214,16 @@ const Panel = Class({
     views.set(this, view);
 
     // Load panel content.
     domPanel.setURL(view, model.contentURL);
 
     // Allow context menu
     domPanel.allowContextMenu(view, model.contextMenu);
 
-    setupAutoHide(this);
-
     // Setup listeners.
     setListeners(this, options);
     let worker = new Worker(stripListeners(options));
     workers.set(this, worker);
 
     // pipe events from worker to a panel.
     pipe(worker, this);
   },
@@ -257,45 +296,47 @@ const Panel = Class({
 
   /* Public API: Panel.isShowing */
   get isShowing() {
     return !isDisposed(this) && domPanel.isOpen(viewFor(this));
   },
 
   /* Public API: Panel.show */
   show: function show(options={}, anchor) {
-    if (options instanceof Ci.nsIDOMElement) {
-      [anchor, options] = [options, null];
-    }
+    SinglePanelManager.requestOpen(this, () => {
+      if (options instanceof Ci.nsIDOMElement) {
+        [anchor, options] = [options, null];
+      }
 
-    if (anchor instanceof Ci.nsIDOMElement) {
-      console.warn(
-        "Passing a DOM node to Panel.show() method is an unsupported " +
-        "feature that will be soon replaced. " +
-        "See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877"
-      );
-    }
+      if (anchor instanceof Ci.nsIDOMElement) {
+        console.warn(
+          "Passing a DOM node to Panel.show() method is an unsupported " +
+          "feature that will be soon replaced. " +
+          "See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877"
+        );
+      }
 
-    let model = modelFor(this);
-    let view = viewFor(this);
-    let anchorView = getNodeView(anchor || options.position || model.position);
+      let model = modelFor(this);
+      let view = viewFor(this);
+      let anchorView = getNodeView(anchor || options.position || model.position);
 
-    options = merge({
-      position: model.position,
-      width: model.width,
-      height: model.height,
-      defaultWidth: model.defaultWidth,
-      defaultHeight: model.defaultHeight,
-      focus: model.focus,
-      contextMenu: model.contextMenu
-    }, displayContract(options));
+      options = merge({
+        position: model.position,
+        width: model.width,
+        height: model.height,
+        defaultWidth: model.defaultWidth,
+        defaultHeight: model.defaultHeight,
+        focus: model.focus,
+        contextMenu: model.contextMenu
+      }, displayContract(options));
 
-    if (!isDisposed(this))
-      domPanel.show(view, options, anchorView);
-
+      if (!isDisposed(this)) {
+        domPanel.show(view, options, anchorView);
+      }
+    });
     return this;
   },
 
   /* Public API: Panel.hide */
   hide: function hide() {
     // Quit immediately if panel is disposed or there is no state change.
     domPanel.close(viewFor(this));
 
--- a/addon-sdk/source/test/test-panel.js
+++ b/addon-sdk/source/test/test-panel.js
@@ -292,18 +292,21 @@ exports["test Hide Before Show"] = funct
       hideCalled = true;
     }
   });
   panel1.show();
   panel1.hide();
 
   let panel2 = Panel({
     onShow: function () {
-      assert.ok(!showCalled, 'should not emit show');
-      assert.ok(!hideCalled, 'should not emit hide');
+      if (showCalled) {
+        assert.ok(hideCalled, 'should not emit show without also emitting hide');
+      } else {
+        assert.ok(!hideCalled, 'should not emit hide without also emitting show');
+      }
       panel1.destroy();
       panel2.destroy();
       done();
     },
   });
   panel2.show();
 };
 
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -255,22 +255,36 @@ var StarUI = {
         }
         parent = parent.parentNode;
       }
       if (parent) {
         this._anchorToolbarButton = parent;
         parent.setAttribute("open", "true");
       }
     }
-    this.panel.openPopup(aAnchorElement, aPosition);
+    let panel = this.panel;
+    let target = panel;
+    if (target.parentNode) {
+      // By targeting the panel's parent and using a capturing listener, we
+      // can have our listener called before others waiting for the panel to
+      // be shown (which probably expect the panel to be fully initialized)
+      target = target.parentNode;
+    }
+    target.addEventListener("popupshown", function shownListener(event) {
+      if (event.target == panel) {
+        target.removeEventListener("popupshown", shownListener, true);
 
-    gEditItemOverlay.initPanel({ node: aNode
-                               , hiddenRows: ["description", "location",
-                                              "loadInSidebar", "keyword"]
-                               , focusedElement: "preferred" });
+        gEditItemOverlay.initPanel({ node: aNode
+                                   , hiddenRows: ["description", "location",
+                                                  "loadInSidebar", "keyword"]
+                                   , focusedElement: "preferred"});
+      }
+    }, true);
+
+    this.panel.openPopup(aAnchorElement, aPosition);
   }),
 
   panelShown:
   function SU_panelShown(aEvent) {
     if (aEvent.target == this.panel) {
       if (this._element("editBookmarkPanelContent").hidden) {
         // Note this isn't actually used anymore, we should remove this
         // once we decide not to bring back the page bookmarked notification
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3300,16 +3300,21 @@ var PrintPreviewListener = {
   getSimplifiedSourceBrowser: function () {
     return this._simplifyPageTab ?
       gBrowser.getBrowserForTab(this._simplifyPageTab) : null;
   },
   getNavToolbox: function () {
     return gNavToolbox;
   },
   onEnter: function () {
+    // We might have accidentally switched tabs since the user invoked print
+    // preview
+    if (gBrowser.selectedTab != this._printPreviewTab) {
+      gBrowser.selectedTab = this._printPreviewTab;
+    }
     gInPrintPreviewMode = true;
     this._toggleAffectedChrome();
   },
   onExit: function () {
     gBrowser.selectedTab = this._tabBeforePrintPreview;
     this._tabBeforePrintPreview = null;
     gInPrintPreviewMode = false;
     this._toggleAffectedChrome();
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3044,16 +3044,19 @@
       </method>
 
       <property name="selectedTab">
         <getter>
           return this.mCurrentTab;
         </getter>
         <setter>
           <![CDATA[
+          if (gNavToolbox.collapsed) {
+            return this.mTabBox.selectedTab;
+          }
           // Update the tab
           this.mTabBox.selectedTab = val;
           return val;
           ]]>
         </setter>
       </property>
 
       <property name="selectedBrowser"
@@ -5984,17 +5987,18 @@
         }
       ]]></handler>
 
       <handler event="click"><![CDATA[
         if (event.button != 1)
           return;
 
         if (event.target.localName == "tab") {
-          this.tabbrowser.removeTab(event.target, {animate: true, byMouse: true});
+          this.tabbrowser.removeTab(event.target, {animate: true,
+                byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE});
         } else if (event.originalTarget.localName == "box") {
           // The user middleclicked an open space on the tabstrip. This could
           // be because they intend to open a new tab, but it could also be
           // because they just removed a tab and they now middleclicked on the
           // resulting space while that tab is closing. In that case, we don't
           // want to open a tab. So if we're removing one or more tabs, and
           // the tab click is before the end of the last visible tab, we do
           // nothing.
@@ -6406,17 +6410,18 @@
        element (in both cases, when they are anonymous nodes of <tabbrowser>).
   -->
   <binding id="tabbrowser-close-tab-button"
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
     <handlers>
       <handler event="click" button="0"><![CDATA[
         var bindingParent = document.getBindingParent(this);
         var tabContainer = bindingParent.parentNode;
-        tabContainer.tabbrowser.removeTab(bindingParent, {animate: true, byMouse: true});
+        tabContainer.tabbrowser.removeTab(bindingParent, {animate: true,
+                byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE});
         // This enables double-click protection for the tab container
         // (see tabbrowser-tabs 'click' handler).
         tabContainer._blockDblClick = true;
       ]]></handler>
 
       <handler event="dblclick" button="0" phase="capturing">
         // for the one-close-button case
         event.stopPropagation();
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -127,16 +127,17 @@ support-files =
   !/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html
   !/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs
   !/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/theme.xpi
+  !/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs
 
 [browser_aboutAccounts.js]
 skip-if = os == "linux" # Bug 958026
 support-files =
   content_aboutAccounts.js
 [browser_aboutCertError.js]
 [browser_aboutNetError.js]
 [browser_aboutSupport_newtab_security_state.js]
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -5,1154 +5,1147 @@
 const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const TESTROOT2 = "http://example.org/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const SECUREROOT = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
 const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
 const PROGRESS_NOTIFICATION = "addon-progress";
 
 const { REQUIRE_SIGNING } = Cu.import("resource://gre/modules/addons/AddonConstants.jsm", {});
+const { Task } = Cu.import("resource://gre/modules/Task.jsm");
 
 var rootDir = getRootDirectory(gTestPath);
 var path = rootDir.split('/');
 var chromeName = path[0] + '//' + path[2];
 var croot = chromeName + "/content/browser/toolkit/mozapps/extensions/test/xpinstall/";
 var jar = getJar(croot);
 if (jar) {
   var tmpdir = extractJarToTmp(jar);
   croot = 'file://' + tmpdir.path + '/';
 }
 const CHROMEROOT = croot;
 
 var gApp = document.getElementById("bundle_brand").getString("brandShortName");
 var gVersion = Services.appinfo.version;
 
-function get_observer_topic(aNotificationId) {
+function getObserverTopic(aNotificationId) {
   let topic = aNotificationId;
   if (topic == "xpinstall-disabled")
     topic = "addon-install-disabled";
   else if (topic == "addon-progress")
     topic = "addon-install-started";
   else if (topic == "addon-install-restart")
     topic = "addon-install-complete";
   return topic;
 }
 
-function wait_for_progress_notification(aCallback, aExpectedCount = 1) {
-  wait_for_notification(PROGRESS_NOTIFICATION, aCallback, aExpectedCount, "popupshowing");
+function waitForProgressNotification(aPanelOpen = false, aExpectedCount = 1) {
+  return Task.spawn(function* () {
+    let notificationId = PROGRESS_NOTIFICATION;
+    info("Waiting for " + notificationId + " notification");
+
+    let topic = getObserverTopic(notificationId);
+
+    let observerPromise = new Promise(resolve => {
+      Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+        // Ignore the progress notification unless that is the notification we want
+        if (notificationId != PROGRESS_NOTIFICATION &&
+            aTopic == getObserverTopic(PROGRESS_NOTIFICATION)) {
+          return;
+        }
+        Services.obs.removeObserver(observer, topic);
+        resolve();
+      }, topic, false);
+    });
+
+    let panelEventPromise;
+    if (aPanelOpen) {
+      panelEventPromise = Promise.resolve();
+    } else {
+      panelEventPromise = new Promise(resolve => {
+        PopupNotifications.panel.addEventListener("popupshowing", function eventListener() {
+          PopupNotifications.panel.removeEventListener("popupshowing", eventListener);
+          resolve();
+        });
+      });
+    }
+
+    yield observerPromise;
+    yield panelEventPromise;
+
+    info("Saw a notification");
+    ok(PopupNotifications.isPanelOpen, "Panel should be open");
+    is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications");
+    if (PopupNotifications.panel.childNodes.length) {
+      let nodes = Array.from(PopupNotifications.panel.childNodes);
+      let notification = nodes.find(n => n.id == notificationId + "-notification");
+      ok(notification, `Should have seen the right notification`);
+    }
+
+    return PopupNotifications.panel;
+  });
 }
 
-function wait_for_notification(aId, aCallback, aExpectedCount = 1, aEvent = "popupshown") {
-  info("Waiting for " + aId + " notification");
+function waitForNotification(aId, aExpectedCount = 1) {
+  return Task.spawn(function* () {
+    info("Waiting for " + aId + " notification");
 
-  let topic = get_observer_topic(aId);
-  function observer(aSubject, aTopic, aData) {
-    // Ignore the progress notification unless that is the notification we want
-    if (aId != PROGRESS_NOTIFICATION &&
-        aTopic == get_observer_topic(PROGRESS_NOTIFICATION))
-      return;
-
-    Services.obs.removeObserver(observer, topic);
+    let topic = getObserverTopic(aId);
 
-    if (PopupNotifications.isPanelOpen)
-      executeSoon(verify);
-    else
-      PopupNotifications.panel.addEventListener(aEvent, event_listener);
-  }
+    let observerPromise = new Promise(resolve => {
+      Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+        // Ignore the progress notification unless that is the notification we want
+        if (aId != PROGRESS_NOTIFICATION &&
+            aTopic == getObserverTopic(PROGRESS_NOTIFICATION)) {
+          return;
+        }
+        Services.obs.removeObserver(observer, topic);
+        resolve();
+      }, topic, false);
+    });
 
-  function event_listener() {
-    // Ignore the progress notification unless that is the notification we want
-    if (aId != PROGRESS_NOTIFICATION &&
-        PopupNotifications.panel.childNodes[0].id == PROGRESS_NOTIFICATION + "-notification")
-      return;
+    let panelEventPromise = new Promise(resolve => {
+      PopupNotifications.panel.addEventListener("PanelUpdated", function eventListener(e) {
+        // Skip notifications that are not the one that we are supposed to be looking for
+        if (e.detail.indexOf(aId) == -1) {
+          return;
+        }
+        PopupNotifications.panel.removeEventListener("PanelUpdated", eventListener);
+        resolve();
+      });
+    });
 
-    PopupNotifications.panel.removeEventListener(aEvent, event_listener);
+    yield observerPromise;
+    yield panelEventPromise;
 
-    verify();
-  }
-
-  function verify() {
     info("Saw a notification");
     ok(PopupNotifications.isPanelOpen, "Panel should be open");
     is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications");
     if (PopupNotifications.panel.childNodes.length) {
       let nodes = Array.from(PopupNotifications.panel.childNodes);
       let notification = nodes.find(n => n.id == aId + "-notification");
-      ok(notification, "Should have seen the right notification");
+      ok(notification, `Should have seen the right notification`);
     }
-    aCallback(PopupNotifications.panel);
-  }
 
-  Services.obs.addObserver(observer, topic, false);
-}
-
-function wait_for_notification_close(aCallback) {
-  info("Waiting for notification to close");
-  PopupNotifications.panel.addEventListener("popuphidden", function() {
-    PopupNotifications.panel.removeEventListener("popuphidden", arguments.callee, false);
-    executeSoon(aCallback);
-  }, false);
+    return PopupNotifications.panel;
+  });
 }
 
-function wait_for_install_dialog(aCallback) {
-  if (Preferences.get("xpinstall.customConfirmationUI", false)) {
-    wait_for_notification("addon-install-confirmation", function(aPanel) {
-      aCallback();
-    });
-    return;
-  }
-
-  info("Waiting for install dialog");
-
-  Services.wm.addListener({
-    onOpenWindow: function(aXULWindow) {
-      info("Install dialog opened, waiting for focus");
-      Services.wm.removeListener(this);
-
-      var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                                .getInterface(Ci.nsIDOMWindow);
-      waitForFocus(function() {
-        info("Saw install dialog");
-        is(domwindow.document.location.href, XPINSTALL_URL, "Should have seen the right window open");
-
-        // Override the countdown timer on the accept button
-        var button = domwindow.document.documentElement.getButton("accept");
-        button.disabled = false;
-
-        aCallback();
-      }, domwindow);
-    },
-
-    onCloseWindow: function(aXULWindow) {
-    },
-
-    onWindowTitleChange: function(aXULWindow, aNewTitle) {
-    }
+function waitForNotificationClose() {
+  return new Promise(resolve => {
+    info("Waiting for notification to close");
+    PopupNotifications.panel.addEventListener("popuphidden", function listener() {
+      PopupNotifications.panel.removeEventListener("popuphidden", listener, false);
+      resolve();
+    }, false);
   });
 }
 
-function remove_tab_and_run_next_test() {
-  let eventCount = 0;
-  let nextTest = () => {
-    if (++eventCount == 2) {
-      runNextTest();
+function waitForInstallDialog() {
+  return Task.spawn(function* () {
+    if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+      yield waitForNotification("addon-install-confirmation");
+      return;
     }
-  }
-  wait_for_notification_close(nextTest);
-  BrowserTestUtils.removeTab(gBrowser.selectedTab).then(nextTest);
+
+    info("Waiting for install dialog");
+
+    yield new Promise(resolve => {
+      Services.wm.addListener({
+        onOpenWindow: function(aXULWindow) {
+          Services.wm.removeListener(this);
+          resolve();
+        },
+        onCloseWindow: function(aXULWindow) {
+        },
+        onWindowTitleChange: function(aXULWindow, aNewTitle) {
+        }
+      });
+    });
+    info("Install dialog opened, waiting for focus");
+
+    let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIDOMWindow);
+    yield new Promise(resolve => {
+      waitForFocus(function() {
+        resolve();
+      }, domwindow);
+    });
+    info("Saw install dialog");
+    is(domwindow.document.location.href, XPINSTALL_URL, "Should have seen the right window open");
+
+    // Override the countdown timer on the accept button
+    let button = domwindow.document.documentElement.getButton("accept");
+    button.disabled = false;
+
+    return;
+  });
 }
 
-function accept_install_dialog() {
+function removeTab() {
+  return Promise.all([
+    waitForNotificationClose(),
+    BrowserTestUtils.removeTab(gBrowser.selectedTab)
+  ]);
+}
+
+function acceptInstallDialog() {
   if (Preferences.get("xpinstall.customConfirmationUI", false)) {
     document.getElementById("addon-install-confirmation-accept").click();
   } else {
     let win = Services.wm.getMostRecentWindow("Addons:Install");
     win.document.documentElement.acceptDialog();
   }
 }
 
-function cancel_install_dialog() {
+function cancelInstallDialog() {
   if (Preferences.get("xpinstall.customConfirmationUI", false)) {
     document.getElementById("addon-install-confirmation-cancel").click();
   } else {
     let win = Services.wm.getMostRecentWindow("Addons:Install");
     win.document.documentElement.cancelDialog();
   }
 }
 
-function wait_for_single_notification(aCallback) {
-  function inner_waiter() {
-    info("Waiting for single notification");
-    // Notification should never close while we wait
-    ok(PopupNotifications.isPanelOpen, "Notification should still be open");
-    if (PopupNotifications.panel.childNodes.length == 2) {
-      executeSoon(inner_waiter);
-      return;
+function waitForSingleNotification(aCallback) {
+  return Task.spawn(function* () {
+    while (PopupNotifications.panel.childNodes.length == 2) {
+      yield new Promise(resolve => executeSoon(resolve));
+
+      info("Waiting for single notification");
+      // Notification should never close while we wait
+      ok(PopupNotifications.isPanelOpen, "Notification should still be open");
     }
-
-    aCallback();
-  }
-
-  executeSoon(inner_waiter);
+  });
 }
 
-function setup_redirect(aSettings) {
+function setupRedirect(aSettings) {
   var url = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs?mode=setup";
   for (var name in aSettings) {
     url += "&" + name + "=" + aSettings[name];
   }
 
   var req = new XMLHttpRequest();
   req.open("GET", url, false);
   req.send(null);
 }
 
-var TESTS = [
-function test_disabled_install() {
-  Services.prefs.setBoolPref("xpinstall.enabled", false);
+function getInstalls() {
+  return new Promise(resolve => {
+    AddonManager.getAllInstalls(installs => resolve(installs));
+  });
+}
 
-  // Wait for the disabled notification
-  wait_for_notification("xpinstall-disabled", function(aPanel) {
-    let notification = aPanel.childNodes[0];
+var TESTS = [
+function test_disabledInstall() {
+  return Task.spawn(function* () {
+    Services.prefs.setBoolPref("xpinstall.enabled", false);
+
+    let notificationPromise = waitForNotification("xpinstall-disabled");
+    let triggers = encodeURIComponent(JSON.stringify({
+      "XPI": "amosigned.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    let panel = yield notificationPromise;
+
+    let notification = panel.childNodes[0];
     is(notification.button.label, "Enable", "Should have seen the right button");
     is(notification.getAttribute("label"),
        "Software installation is currently disabled. Click Enable and try again.");
 
-    wait_for_notification_close(function() {
-      try {
-        ok(Services.prefs.getBoolPref("xpinstall.enabled"), "Installation should be enabled");
-      }
-      catch (e) {
-        ok(false, "xpinstall.enabled should be set");
-      }
-
-      BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => {
-        AddonManager.getAllInstalls(function(aInstalls) {
-          is(aInstalls.length, 0, "Shouldn't be any pending installs");
-
-          runNextTest();
-        });
-      });
-    });
-
+    let closePromise = waitForNotificationClose();
     // Click on Enable
     EventUtils.synthesizeMouseAtCenter(notification.button, {});
-  });
+    yield closePromise;
 
-  var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "amosigned.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    try {
+      ok(Services.prefs.getBoolPref("xpinstall.enabled"), "Installation should be enabled");
+    }
+    catch (e) {
+      ok(false, "xpinstall.enabled should be set");
+    }
+
+    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+    let installs = yield getInstalls();
+    is(installs.length, 0, "Shouldn't be any pending installs");
+  });
 },
 
-function test_blocked_install() {
-  // Wait for the blocked notification
-  wait_for_notification("addon-install-blocked", function(aPanel) {
-    let notification = aPanel.childNodes[0];
+function test_blockedInstall() {
+  return Task.spawn(function* () {
+    let notificationPromise = waitForNotification("addon-install-blocked");
+    let triggers = encodeURIComponent(JSON.stringify({
+      "XPI": "amosigned.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    let panel = yield notificationPromise;
+
+    let notification = panel.childNodes[0];
     is(notification.button.label, "Allow", "Should have seen the right button");
     is(notification.getAttribute("origin"), "example.com",
        "Should have seen the right origin host");
     is(notification.getAttribute("label"),
        gApp + " prevented this site from asking you to install software on your computer.",
        "Should have seen the right message");
 
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(function() {
-      // Wait for the complete notification
-      wait_for_notification("addon-install-restart", function(aPanel) {
-        let notification = aPanel.childNodes[0];
-        is(notification.button.label, "Restart Now", "Should have seen the right button");
-        is(notification.getAttribute("label"),
-           "XPI Test will be installed after you restart " + gApp + ".",
-           "Should have seen the right message");
-
-        AddonManager.getAllInstalls(function(aInstalls) {
-        is(aInstalls.length, 1, "Should be one pending install");
-          aInstalls[0].cancel();
-
-          remove_tab_and_run_next_test();
-        });
-      });
-
-      accept_install_dialog();
-    });
-
+    let dialogPromise = waitForInstallDialog();
     // Click on Allow
     EventUtils.synthesizeMouse(notification.button, 20, 10, {});
-
     // Notification should have changed to progress notification
     ok(PopupNotifications.isPanelOpen, "Notification should still be open");
-    notification = aPanel.childNodes[0];
+    notification = panel.childNodes[0];
     is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
-  });
+    yield dialogPromise;
+
+    notificationPromise = waitForNotification("addon-install-restart");
+    acceptInstallDialog();
+    panel = yield notificationPromise;
 
-  var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "amosigned.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    notification = panel.childNodes[0];
+    is(notification.button.label, "Restart Now", "Should have seen the right button");
+    is(notification.getAttribute("label"),
+       "XPI Test will be installed after you restart " + gApp + ".",
+       "Should have seen the right message");
+
+    let installs = yield getInstalls();
+    is(installs.length, 1, "Should be one pending install");
+    installs[0].cancel();
+    yield removeTab();
+  });
 },
 
-function test_whitelisted_install() {
-  var originalTab = gBrowser.selectedTab;
-  var tab;
-
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
+function test_whitelistedInstall() {
+  return Task.spawn(function* () {
+    let originalTab = gBrowser.selectedTab;
+    let tab;
     gBrowser.selectedTab = originalTab;
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(function() {
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    let triggers = encodeURIComponent(JSON.stringify({
+      "XPI": "amosigned.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?"
+      + triggers).then(newTab => tab = newTab);
+    yield progressPromise;
+    yield dialogPromise;
+    yield BrowserTestUtils.waitForCondition(() => !!tab, "tab should be present");
 
-      BrowserTestUtils.waitForCondition(() => !!tab, "tab should be present").then(() => {
-        is(gBrowser.selectedTab, tab,
-           "tab selected in response to the addon-install-confirmation notification");
+    is(gBrowser.selectedTab, tab,
+       "tab selected in response to the addon-install-confirmation notification");
 
-        // Wait for the complete notification
-        wait_for_notification("addon-install-restart", function(aPanel) {
-          let notification = aPanel.childNodes[0];
-          is(notification.button.label, "Restart Now", "Should have seen the right button");
-          is(notification.getAttribute("label"),
+    let notificationPromise = waitForNotification("addon-install-restart");
+    acceptInstallDialog();
+    let panel = yield notificationPromise;
+
+    let notification = panel.childNodes[0];
+    is(notification.button.label, "Restart Now", "Should have seen the right button");
+    is(notification.getAttribute("label"),
              "XPI Test will be installed after you restart " + gApp + ".",
              "Should have seen the right message");
 
-          AddonManager.getAllInstalls(function(aInstalls) {
-            is(aInstalls.length, 1, "Should be one pending install");
-            aInstalls[0].cancel();
-
-            Services.perms.remove(makeURI("http://example.com/"), "install");
-            remove_tab_and_run_next_test();
-          });
-        });
+    let installs = yield getInstalls();
+    is(installs.length, 1, "Should be one pending install");
+    installs[0].cancel();
 
-        accept_install_dialog();
-      });
-    });
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield removeTab();
   });
-
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
-
-  var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "amosigned.xpi"
-  }));
-
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?"
-    + triggers).then(newTab => tab = newTab);
 },
 
-function test_failed_download() {
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the failed notification
-    wait_for_notification("addon-install-failed", function(aPanel) {
-      let notification = aPanel.childNodes[0];
-      is(notification.getAttribute("label"),
-         "The add-on could not be downloaded because of a connection failure.",
-         "Should have seen the right message");
+function test_failedDownload() {
+  return Task.spawn(function* () {
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-      Services.perms.remove(makeURI("http://example.com/"), "install");
-      remove_tab_and_run_next_test();
-    });
-  });
+    let progressPromise = waitForProgressNotification();
+    let failPromise = waitForNotification("addon-install-failed");
+    let triggers = encodeURIComponent(JSON.stringify({
+      "XPI": "missing.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    let panel = yield failPromise;
 
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+    let notification = panel.childNodes[0];
+    is(notification.getAttribute("label"),
+       "The add-on could not be downloaded because of a connection failure.",
+       "Should have seen the right message");
 
-  var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "missing.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield removeTab();
+  });
 },
 
-function test_corrupt_file() {
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the failed notification
-    wait_for_notification("addon-install-failed", function(aPanel) {
-      let notification = aPanel.childNodes[0];
-      is(notification.getAttribute("label"),
-         "The add-on downloaded from this site could not be installed " +
-         "because it appears to be corrupt.",
-         "Should have seen the right message");
+function test_corruptFile() {
+  return Task.spawn(function* () {
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-      Services.perms.remove(makeURI("http://example.com/"), "install");
-      remove_tab_and_run_next_test();
-    });
-  });
+    let progressPromise = waitForProgressNotification();
+    let failPromise = waitForNotification("addon-install-failed");
+    let triggers = encodeURIComponent(JSON.stringify({
+      "XPI": "corrupt.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    let panel = yield failPromise;
 
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+    let notification = panel.childNodes[0];
+    is(notification.getAttribute("label"),
+       "The add-on downloaded from this site could not be installed " +
+       "because it appears to be corrupt.",
+       "Should have seen the right message");
 
-  var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "corrupt.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield removeTab();
+  });
 },
 
 function test_incompatible() {
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the failed notification
-    wait_for_notification("addon-install-failed", function(aPanel) {
-      let notification = aPanel.childNodes[0];
-      is(notification.getAttribute("label"),
-         "XPI Test could not be installed because it is not compatible with " +
-         gApp + " " + gVersion + ".",
-         "Should have seen the right message");
+  return Task.spawn(function* () {
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-      Services.perms.remove(makeURI("http://example.com/"), "install");
-      remove_tab_and_run_next_test();
-    });
-  });
+    let progressPromise = waitForProgressNotification();
+    let failPromise = waitForNotification("addon-install-failed");
+    let triggers = encodeURIComponent(JSON.stringify({
+      "XPI": "incompatible.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    let panel = yield failPromise;
 
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+    let notification = panel.childNodes[0];
+    is(notification.getAttribute("label"),
+       "XPI Test could not be installed because it is not compatible with " +
+       gApp + " " + gVersion + ".",
+       "Should have seen the right message");
 
-  var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "incompatible.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield removeTab();
+  });
 },
 
 function test_restartless() {
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(function() {
-      // Wait for the complete notification
-      wait_for_notification("addon-install-complete", function(aPanel) {
-        let notification = aPanel.childNodes[0];
-        is(notification.getAttribute("label"),
-           "XPI Test has been installed successfully.",
-           "Should have seen the right message");
+  return Task.spawn(function* () {
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-        AddonManager.getAllInstalls(function(aInstalls) {
-          is(aInstalls.length, 0, "Should be no pending installs");
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    let triggers = encodeURIComponent(JSON.stringify({
+      "XPI": "restartless.xpi"
+    }));
+    gBrowser.selectedTab = gBrowser.addTab();
+    gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    yield dialogPromise;
 
-          AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(aAddon) {
-            aAddon.uninstall();
+    let notificationPromise = waitForNotification("addon-install-complete");
+    acceptInstallDialog();
+    let panel = yield notificationPromise;
 
-            Services.perms.remove(makeURI("http://example.com/"), "install");
-            wait_for_notification_close(runNextTest);
-            gBrowser.removeTab(gBrowser.selectedTab);
-          });
-        });
+    let notification = panel.childNodes[0];
+    is(notification.getAttribute("label"),
+       "XPI Test has been installed successfully.",
+       "Should have seen the right message");
+
+    let installs = yield getInstalls();
+    is(installs.length, 0, "Should be no pending installs");
+
+    let addon = yield new Promise(resolve => {
+      AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", addon => {
+        resolve(addon);
       });
+    });
+    addon.uninstall();
 
-      accept_install_dialog();
-    });
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+
+    let closePromise = waitForNotificationClose();
+    gBrowser.removeTab(gBrowser.selectedTab);
+    yield closePromise;
   });
-
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
-
-  var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "restartless.xpi"
-  }));
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_multiple() {
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(function() {
-      // Wait for the complete notification
-      wait_for_notification("addon-install-restart", function(aPanel) {
-        let notification = aPanel.childNodes[0];
-        is(notification.button.label, "Restart Now", "Should have seen the right button");
-        is(notification.getAttribute("label"),
-           "2 add-ons will be installed after you restart " + gApp + ".",
-           "Should have seen the right message");
+  return Task.spawn(function* () {
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-        AddonManager.getAllInstalls(function(aInstalls) {
-          is(aInstalls.length, 1, "Should be one pending install");
-          aInstalls[0].cancel();
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    let triggers = encodeURIComponent(JSON.stringify({
+      "Unsigned XPI": "amosigned.xpi",
+      "Restartless XPI": "restartless.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    let panel = yield progressPromise;
+    yield dialogPromise;
 
-          AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(aAddon) {
-            aAddon.uninstall();
+    let notificationPromise = waitForNotification("addon-install-restart");
+    acceptInstallDialog();
+    yield notificationPromise;
 
-            Services.perms.remove(makeURI("http://example.com/"), "install");
-            remove_tab_and_run_next_test();
-          });
-        });
+    let notification = panel.childNodes[0];
+    is(notification.button.label, "Restart Now", "Should have seen the right button");
+    is(notification.getAttribute("label"),
+       "2 add-ons will be installed after you restart " + gApp + ".",
+       "Should have seen the right message");
+
+    let installs = yield getInstalls();
+    is(installs.length, 1, "Should be one pending install");
+    installs[0].cancel();
+
+    let addon = yield new Promise(resolve => {
+      AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function (addon) {
+        resolve(addon);
       });
-
-      accept_install_dialog();
     });
+    addon.uninstall();
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield removeTab();
   });
-
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
-
-  var triggers = encodeURIComponent(JSON.stringify({
-    "Unsigned XPI": "amosigned.xpi",
-    "Restartless XPI": "restartless.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_sequential() {
-  // This test is only relevant if using the new doorhanger UI
-  if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
-    runNextTest();
-    return;
-  }
-
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(function() {
-      // Wait for the progress notification
-
-      // Should see the right add-on
-      let container = document.getElementById("addon-install-confirmation-content");
-      is(container.childNodes.length, 1, "Should be one item listed");
-      is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+  return Task.spawn(function* () {
+    // This test is only relevant if using the new doorhanger UI
+    if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
+      return;
+    }
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-      wait_for_progress_notification(function(aPanel) {
-        // Should still have the right add-on in the confirmation notification
-        is(container.childNodes.length, 1, "Should be one item listed");
-        is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    let triggers = encodeURIComponent(JSON.stringify({
+      "Restartless XPI": "restartless.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    yield dialogPromise;
 
-        // Wait for the install to complete, we won't see a new confirmation
-        // notification
-        Services.obs.addObserver(function observer() {
-          Services.obs.removeObserver(observer, "addon-install-confirmation");
+    // Should see the right add-on
+    let container = document.getElementById("addon-install-confirmation-content");
+    is(container.childNodes.length, 1, "Should be one item listed");
+    is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
 
-          // Make sure browser-addons.js executes first
-          executeSoon(function () {
-            // Should have dropped the progress notification
-            is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications");
-            is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification",
-               "Should only be showing one install confirmation");
+    progressPromise = waitForProgressNotification(true, 2);
+    triggers = encodeURIComponent(JSON.stringify({
+      "Theme XPI": "theme.xpi"
+    }));
+    gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+
+    // Should still have the right add-on in the confirmation notification
+    is(container.childNodes.length, 1, "Should be one item listed");
+    is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
 
-            // Should still have the right add-on in the confirmation notification
-            is(container.childNodes.length, 1, "Should be one item listed");
-            is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
-
-            cancel_install_dialog();
+    // Wait for the install to complete, we won't see a new confirmation
+    // notification
+    yield new Promise(resolve => {
+      Services.obs.addObserver(function observer() {
+        Services.obs.removeObserver(observer, "addon-install-confirmation");
+        resolve();
+      }, "addon-install-confirmation", false);
+    });
 
-            ok(PopupNotifications.isPanelOpen, "Panel should still be open");
-            is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications");
-            is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification",
-               "Should still have an install confirmation open");
+    // Make sure browser-addons.js executes first
+    yield new Promise(resolve => executeSoon(resolve));
 
-            // Should have the next add-on's confirmation dialog
-            is(container.childNodes.length, 1, "Should be one item listed");
-            is(container.childNodes[0].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
-
-            Services.perms.remove(makeURI("http://example.com"), "install");
-            wait_for_notification_close(() => {
-              BrowserTestUtils.removeTab(gBrowser.selectedTab).then(runNextTest);
-            });
+    // Should have dropped the progress notification
+    is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications");
+    is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification",
+       "Should only be showing one install confirmation");
 
-            cancel_install_dialog();
-          });
-        }, "addon-install-confirmation", false);
-      }, 2);
+    // Should still have the right add-on in the confirmation notification
+    is(container.childNodes.length, 1, "Should be one item listed");
+    is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+
+    cancelInstallDialog();
+
+    ok(PopupNotifications.isPanelOpen, "Panel should still be open");
+    is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications");
+    is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification",
+       "Should still have an install confirmation open");
 
-      var triggers = encodeURIComponent(JSON.stringify({
-        "Theme XPI": "theme.xpi"
-      }));
-      gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
-    });
-  });
+    // Should have the next add-on's confirmation dialog
+    is(container.childNodes.length, 1, "Should be one item listed");
+    is(container.childNodes[0].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
 
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
-
-  var triggers = encodeURIComponent(JSON.stringify({
-    "Restartless XPI": "restartless.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    Services.perms.remove(makeURI("http://example.com"), "install");
+    let closePromise = waitForNotificationClose();
+    cancelInstallDialog();
+    yield closePromise;
+    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  });
 },
 
-function test_someunverified() {
-  // This test is only relevant if using the new doorhanger UI and allowing
-  // unsigned add-ons
-  if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
-      Preferences.get("xpinstall.signatures.required", true) ||
-      REQUIRE_SIGNING) {
-    runNextTest();
-    return;
-  }
+function test_someUnverified() {
+  return Task.spawn(function* () {
+    // This test is only relevant if using the new doorhanger UI and allowing
+    // unsigned add-ons
+    if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
+        Preferences.get("xpinstall.signatures.required", true) ||
+        REQUIRE_SIGNING) {
+      return;
+    }
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(function() {
-      let notification = document.getElementById("addon-install-confirmation-notification");
-      let message = notification.getAttribute("label");
-      is(message, "Caution: This site would like to install 2 add-ons in " + gApp +
-         ", some of which are unverified. Proceed at your own risk.",
-         "Should see the right message");
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    let triggers = encodeURIComponent(JSON.stringify({
+      "Extension XPI": "restartless-unsigned.xpi",
+      "Theme XPI": "theme.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    yield dialogPromise;
 
-      let container = document.getElementById("addon-install-confirmation-content");
-      is(container.childNodes.length, 2, "Should be two items listed");
-      is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
-      is(container.childNodes[0].lastChild.getAttribute("class"),
-         "addon-install-confirmation-unsigned", "Should have the unverified marker");
-      is(container.childNodes[1].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
-      is(container.childNodes[1].childNodes.length, 1, "Shouldn't have the unverified marker");
+    let notification = document.getElementById("addon-install-confirmation-notification");
+    let message = notification.getAttribute("label");
+    is(message, "Caution: This site would like to install 2 add-ons in " + gApp +
+       ", some of which are unverified. Proceed at your own risk.",
+       "Should see the right message");
 
-      // Wait for the complete notification
-      wait_for_notification("addon-install-restart", function(aPanel) {
-        AddonManager.getAddonsByIDs(["restartless-xpi@tests.mozilla.org",
-                                     "theme-xpi@tests.mozilla.org"], function([a, t]) {
-          a.uninstall();
-          // Installing a new theme tries to switch to it, switch back to the
-          // default theme.
-          t.userDisabled = true;
-          t.uninstall();
+    let container = document.getElementById("addon-install-confirmation-content");
+    is(container.childNodes.length, 2, "Should be two items listed");
+    is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+    is(container.childNodes[0].lastChild.getAttribute("class"),
+       "addon-install-confirmation-unsigned", "Should have the unverified marker");
+    is(container.childNodes[1].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
+    is(container.childNodes[1].childNodes.length, 1, "Shouldn't have the unverified marker");
 
-          Services.perms.remove(makeURI("http://example.com/"), "install");
-          remove_tab_and_run_next_test();
-        });
+    let notificationPromise = waitForNotification("addon-install-restart");
+    acceptInstallDialog();
+    yield notificationPromise;
+
+    let [addon, theme] = yield new Promise(resolve => {
+      AddonManager.getAddonsByIDs(["restartless-xpi@tests.mozilla.org",
+                                  "theme-xpi@tests.mozilla.org"],
+                                  function(addons) {
+        resolve(addons);
       });
-
-      accept_install_dialog();
     });
-  });
+    addon.uninstall();
+    // Installing a new theme tries to switch to it, switch back to the
+    // default theme.
+    theme.userDisabled = true;
+    theme.uninstall();
 
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
-
-  var triggers = encodeURIComponent(JSON.stringify({
-    "Extension XPI": "restartless-unsigned.xpi",
-    "Theme XPI": "theme.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield removeTab();
+  });
 },
 
-function test_allunverified() {
-  // This test is only relevant if using the new doorhanger UI and allowing
-  // unsigned add-ons
-  if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
-      Preferences.get("xpinstall.signatures.required", true) ||
-      REQUIRE_SIGNING) {
-    runNextTest();
-    return;
-  }
+function test_allUnverified() {
+  return Task.spawn(function* () {
+    // This test is only relevant if using the new doorhanger UI and allowing
+    // unsigned add-ons
+    if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
+        Preferences.get("xpinstall.signatures.required", true) ||
+        REQUIRE_SIGNING) {
+      return;
+    }
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(function() {
-      let notification = document.getElementById("addon-install-confirmation-notification");
-      let message = notification.getAttribute("label");
-      is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk.");
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    let triggers = encodeURIComponent(JSON.stringify({
+      "Extension XPI": "restartless-unsigned.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    yield dialogPromise;
 
-      let container = document.getElementById("addon-install-confirmation-content");
-      is(container.childNodes.length, 1, "Should be one item listed");
-      is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
-      is(container.childNodes[0].childNodes.length, 1, "Shouldn't have the unverified marker");
+    let notification = document.getElementById("addon-install-confirmation-notification");
+    let message = notification.getAttribute("label");
+    is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk.");
 
-      // Wait for the complete notification
-      wait_for_notification("addon-install-complete", function(aPanel) {
-        AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(aAddon) {
-          aAddon.uninstall();
+    let container = document.getElementById("addon-install-confirmation-content");
+    is(container.childNodes.length, 1, "Should be one item listed");
+    is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+    is(container.childNodes[0].childNodes.length, 1, "Shouldn't have the unverified marker");
 
-          Services.perms.remove(makeURI("http://example.com/"), "install");
-          remove_tab_and_run_next_test();
-        });
+    let notificationPromise = waitForNotification("addon-install-complete");
+    acceptInstallDialog();
+    yield notificationPromise;
+
+    let addon = yield new Promise(resolve => {
+      AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(addon) {
+        resolve(addon);
       });
+    });
+    addon.uninstall();
 
-      accept_install_dialog();
-    });
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield removeTab();
   });
-
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
-
-  var triggers = encodeURIComponent(JSON.stringify({
-    "Extension XPI": "restartless-unsigned.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_url() {
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(function() {
-      // Wait for the complete notification
-      wait_for_notification("addon-install-restart", function(aPanel) {
-        let notification = aPanel.childNodes[0];
-        is(notification.button.label, "Restart Now", "Should have seen the right button");
-        is(notification.getAttribute("label"),
-           "XPI Test will be installed after you restart " + gApp + ".",
-           "Should have seen the right message");
+  return Task.spawn(function* () {
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    gBrowser.selectedTab = gBrowser.addTab("about:blank");
+    yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+    gBrowser.loadURI(TESTROOT + "amosigned.xpi");
+    yield progressPromise;
+    yield dialogPromise;
 
-        AddonManager.getAllInstalls(function(aInstalls) {
-          is(aInstalls.length, 1, "Should be one pending install");
-          aInstalls[0].cancel();
-
-          remove_tab_and_run_next_test();
-        });
-      });
+    let notificationPromise = waitForNotification("addon-install-restart");
+    acceptInstallDialog();
+    let panel = yield notificationPromise;
 
-      accept_install_dialog();
-    });
-  });
+    let notification = panel.childNodes[0];
+    is(notification.button.label, "Restart Now", "Should have seen the right button");
+    is(notification.getAttribute("label"),
+       "XPI Test will be installed after you restart " + gApp + ".",
+       "Should have seen the right message");
 
-  gBrowser.selectedTab = gBrowser.addTab("about:blank");
-  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
-    gBrowser.loadURI(TESTROOT + "amosigned.xpi");
+    let installs = yield getInstalls();
+    is(installs.length, 1, "Should be one pending install");
+    installs[0].cancel();
+
+    yield removeTab();
   });
 },
 
-function test_localfile() {
-  // Wait for the install to fail
-  Services.obs.addObserver(function() {
-    Services.obs.removeObserver(arguments.callee, "addon-install-failed");
+function test_localFile() {
+  return Task.spawn(function* () {
+    let cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+                       .getService(Components.interfaces.nsIChromeRegistry);
+    let path;
+    try {
+      path = cr.convertChromeURL(makeURI(CHROMEROOT + "corrupt.xpi")).spec;
+    } catch (ex) {
+      path = CHROMEROOT + "corrupt.xpi";
+    }
+
+    let failPromise = new Promise(resolve => {
+      Services.obs.addObserver(function observer() {
+        Services.obs.removeObserver(observer, "addon-install-failed");
+        resolve();
+      }, "addon-install-failed", false);
+    });
+    gBrowser.selectedTab = gBrowser.addTab("about:blank");
+    yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+    gBrowser.loadURI(path);
+    yield failPromise;
 
     // Wait for the browser code to add the failure notification
-    wait_for_single_notification(function() {
-      let notification = PopupNotifications.panel.childNodes[0];
-      is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
-      is(notification.getAttribute("label"),
-         "This add-on could not be installed because it appears to be corrupt.",
-         "Should have seen the right message");
-
-      remove_tab_and_run_next_test();
-    });
-  }, "addon-install-failed", false);
+    yield waitForSingleNotification();
 
-  var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
-                     .getService(Components.interfaces.nsIChromeRegistry);
-  try {
-    var path = cr.convertChromeURL(makeURI(CHROMEROOT + "corrupt.xpi")).spec;
-  } catch (ex) {
+    let notification = PopupNotifications.panel.childNodes[0];
+    is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+    is(notification.getAttribute("label"),
+       "This add-on could not be installed because it appears to be corrupt.",
+       "Should have seen the right message");
+
+    yield removeTab();
+  });
     path = CHROMEROOT + "corrupt.xpi";
-  }
-  gBrowser.selectedTab = gBrowser.addTab("about:blank");
-  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
-    gBrowser.loadURI(path);
-  });
 },
 
-function test_tabclose() {
-  if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
-    runNextTest();
-    return;
-  }
-
-  // Wait for the progress notification
-  wait_for_progress_notification(aPanel => {
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(() => {
-      AddonManager.getAllInstalls(aInstalls => {
-        is(aInstalls.length, 1, "Should be one pending install");
+function test_tabClose() {
+  return Task.spawn(function* () {
+    if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
+      runNextTest();
+      return;
+    }
 
-        let eventCount = 0;
-        let nextTest = () => {
-          if (++eventCount == 2) {
-            runNextTest();
-          }
-        }
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    gBrowser.selectedTab = gBrowser.addTab("about:blank");
+    yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+    gBrowser.loadURI(TESTROOT + "amosigned.xpi");
+    yield progressPromise;
+    yield dialogPromise;
 
-        wait_for_notification_close(() => {
-          AddonManager.getAllInstalls(aInstalls => {
-            is(aInstalls.length, 0, "Should be no pending install since the tab is closed");
-            nextTest();
-          });
-        });
+    let installs = yield getInstalls();
+    is(installs.length, 1, "Should be one pending install");
 
-        BrowserTestUtils.removeTab(gBrowser.selectedTab).then(nextTest);
-      });
-    });
-  });
+    let closePromise = waitForNotificationClose();
+    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+    yield closePromise;
 
-  gBrowser.selectedTab = gBrowser.addTab("about:blank");
-  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
-    gBrowser.loadURI(TESTROOT + "amosigned.xpi");
+    installs = yield getInstalls();
+    is(installs.length, 0, "Should be no pending install since the tab is closed");
   });
 },
 
 // Add-ons should be cancelled and the install notification destroyed when
 // navigating to a new origin
-function test_tabnavigate() {
-  if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
-    runNextTest();
-    return;
-  }
-
-  // Wait for the progress notification
-  wait_for_progress_notification(aPanel => {
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(() => {
-      wait_for_notification_close(() => {
-        AddonManager.getAllInstalls(aInstalls => {
-          is(aInstalls.length, 0, "Should be no pending install");
+function test_tabNavigate() {
+  return Task.spawn(function* () {
+    if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
+      return;
+    }
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-          Services.perms.remove(makeURI("http://example.com/"), "install");
-          loadPromise.then(() => {
-            BrowserTestUtils.removeTab(gBrowser.selectedTab).then(runNextTest);
-          });
-        });
-      });
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    let triggers = encodeURIComponent(JSON.stringify({
+      "Extension XPI": "amosigned.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    yield dialogPromise;
 
-      let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
-      gBrowser.loadURI("about:blank");
-    });
-  });
+    let closePromise = waitForNotificationClose();
+    let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+    gBrowser.loadURI("about:blank");
+    yield closePromise;
 
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+    let installs = yield getInstalls();
+    is(installs.length, 0, "Should be no pending install");
 
-  var triggers = encodeURIComponent(JSON.stringify({
-    "Extension XPI": "amosigned.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield loadPromise;
+    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  });
 },
 
-function test_urlbar() {
-  wait_for_notification("addon-install-origin-blocked", function(aPanel) {
-    let notification = aPanel.childNodes[0];
-
-    is(notification.button.label, "", "Button to allow install should be hidden.");
-
-    remove_tab_and_run_next_test();
-  });
-
-  gBrowser.selectedTab = gBrowser.addTab("about:blank");
-  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+function test_urlBar() {
+  return Task.spawn(function* () {
+    let notificationPromise = waitForNotification("addon-install-origin-blocked");
+    gBrowser.selectedTab = gBrowser.addTab("about:blank");
+    yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
     gURLBar.value = TESTROOT + "amosigned.xpi";
     gURLBar.focus();
     EventUtils.synthesizeKey("VK_RETURN", {});
+    let panel = yield notificationPromise;
+
+    let notification = panel.childNodes[0];
+    is(notification.button.label, "", "Button to allow install should be hidden.");
+    yield removeTab();
   });
 },
 
-function test_wronghost() {
-  gBrowser.selectedTab = gBrowser.addTab();
+function test_wrongHost() {
+  return Task.spawn(function* () {
+    let requestedUrl = TESTROOT2 + "enabled.html";
+    gBrowser.selectedTab = gBrowser.addTab();
+
+    let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl);
+    gBrowser.loadURI(TESTROOT2 + "enabled.html");
+    yield loadedPromise;
 
-  let requestedUrl = TESTROOT2 + "enabled.html";
-  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl).then(() => {
-    // Wait for the progress notification
-    wait_for_progress_notification(function(aPanel) {
-      // Wait for the complete notification
-      wait_for_notification("addon-install-failed", function(aPanel) {
-        let notification = aPanel.childNodes[0];
-        is(notification.getAttribute("label"),
-           "The add-on downloaded from this site could not be installed " +
-           "because it appears to be corrupt.",
-           "Should have seen the right message");
+    let progressPromise = waitForProgressNotification();
+    let notificationPromise = waitForNotification("addon-install-failed");
+    gBrowser.loadURI(TESTROOT + "corrupt.xpi");
+    yield progressPromise;
+    let panel = yield notificationPromise;
 
-        remove_tab_and_run_next_test();
-      });
-    });
+    let notification = panel.childNodes[0];
+    is(notification.getAttribute("label"),
+       "The add-on downloaded from this site could not be installed " +
+       "because it appears to be corrupt.",
+       "Should have seen the right message");
 
-    gBrowser.loadURI(TESTROOT + "corrupt.xpi");
+    yield removeTab();
   });
-  gBrowser.loadURI(TESTROOT2 + "enabled.html");
 },
 
 function test_reload() {
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(function() {
-      // Wait for the complete notification
-      wait_for_notification("addon-install-restart", function(aPanel) {
-        let notification = aPanel.childNodes[0];
-        is(notification.button.label, "Restart Now", "Should have seen the right button");
-        is(notification.getAttribute("label"),
-           "XPI Test will be installed after you restart " + gApp + ".",
-           "Should have seen the right message");
+  return Task.spawn(function* () {
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-        function test_fail() {
-          ok(false, "Reloading should not have hidden the notification");
-        }
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    let triggers = encodeURIComponent(JSON.stringify({
+      "Unsigned XPI": "amosigned.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    yield dialogPromise;
 
-        PopupNotifications.panel.addEventListener("popuphiding", test_fail, false);
-
-        let requestedUrl = TESTROOT2 + "enabled.html";
-        BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl).then(() => {
-          PopupNotifications.panel.removeEventListener("popuphiding", test_fail, false);
+    let notificationPromise = waitForNotification("addon-install-restart");
+    acceptInstallDialog();
+    let panel = yield notificationPromise;
 
-          AddonManager.getAllInstalls(function(aInstalls) {
-            is(aInstalls.length, 1, "Should be one pending install");
-            aInstalls[0].cancel();
-
-            Services.perms.remove(makeURI("http://example.com/"), "install");
-            remove_tab_and_run_next_test();
-          });
-        });
-        gBrowser.loadURI(TESTROOT2 + "enabled.html");
-      });
+    let notification = panel.childNodes[0];
+    is(notification.button.label, "Restart Now", "Should have seen the right button");
+    is(notification.getAttribute("label"),
+       "XPI Test will be installed after you restart " + gApp + ".",
+       "Should have seen the right message");
 
-      accept_install_dialog();
-    });
-  });
+    function testFail() {
+      ok(false, "Reloading should not have hidden the notification");
+    }
+    PopupNotifications.panel.addEventListener("popuphiding", testFail, false);
+    let requestedUrl = TESTROOT2 + "enabled.html";
+    let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl);
+    gBrowser.loadURI(TESTROOT2 + "enabled.html");
+    yield loadedPromise;
+    PopupNotifications.panel.removeEventListener("popuphiding", testFail, false);
 
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+    let installs = yield getInstalls();
+    is(installs.length, 1, "Should be one pending install");
+    installs[0].cancel();
 
-  var triggers = encodeURIComponent(JSON.stringify({
-    "Unsigned XPI": "amosigned.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield removeTab();
+  });
 },
 
 function test_theme() {
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(function() {
-      // Wait for the complete notification
-      wait_for_notification("addon-install-restart", function(aPanel) {
-        let notification = aPanel.childNodes[0];
-        is(notification.button.label, "Restart Now", "Should have seen the right button");
-        is(notification.getAttribute("label"),
-           "Theme Test will be installed after you restart " + gApp + ".",
-           "Should have seen the right message");
+  return Task.spawn(function* () {
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-        AddonManager.getAddonByID("{972ce4c6-7e08-4474-a285-3208198ce6fd}", function(aAddon) {
-          ok(aAddon.userDisabled, "Should be switching away from the default theme.");
-          // Undo the pending theme switch
-          aAddon.userDisabled = false;
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    let triggers = encodeURIComponent(JSON.stringify({
+      "Theme XPI": "theme.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    yield dialogPromise;
+
+    let notificationPromise = waitForNotification("addon-install-restart");
+    acceptInstallDialog();
+    let panel = yield notificationPromise;
 
-          AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) {
-            isnot(aAddon, null, "Test theme will have been installed");
-            aAddon.uninstall();
-
-            Services.perms.remove(makeURI("http://example.com/"), "install");
-            remove_tab_and_run_next_test();
-          });
-        });
-      });
+    let notification = panel.childNodes[0];
+    is(notification.button.label, "Restart Now", "Should have seen the right button");
+    is(notification.getAttribute("label"),
+       "Theme Test will be installed after you restart " + gApp + ".",
+       "Should have seen the right message");
 
-      accept_install_dialog();
+    let addon = yield new Promise(resolve => {
+      AddonManager.getAddonByID("{972ce4c6-7e08-4474-a285-3208198ce6fd}", function(addon) {
+        resolve(addon);
+      });
     });
-  });
+    ok(addon.userDisabled, "Should be switching away from the default theme.");
+    // Undo the pending theme switch
+    addon.userDisabled = false;
 
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+    addon = yield new Promise(resolve => {
+      AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(addon) {
+        resolve(addon);
+      });
+    });
+    isnot(addon, null, "Test theme will have been installed");
+    addon.uninstall();
 
-  var triggers = encodeURIComponent(JSON.stringify({
-    "Theme XPI": "theme.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield removeTab();
+  });
 },
 
-function test_renotify_blocked() {
-  // Wait for the blocked notification
-  wait_for_notification("addon-install-blocked", function(aPanel) {
-    let notification = aPanel.childNodes[0];
+function test_renotifyBlocked() {
+  return Task.spawn(function* () {
+    let notificationPromise = waitForNotification("addon-install-blocked");
+    let triggers = encodeURIComponent(JSON.stringify({
+      "XPI": "amosigned.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    let panel = yield notificationPromise;
 
-    wait_for_notification_close(function () {
-      info("Timeouts after this probably mean bug 589954 regressed");
-      executeSoon(function () {
-        wait_for_notification("addon-install-blocked", function(aPanel) {
-          AddonManager.getAllInstalls(function(aInstalls) {
-            is(aInstalls.length, 2, "Should be two pending installs");
+    let closePromise = waitForNotificationClose();
+    // hide the panel (this simulates the user dismissing it)
+    panel.hidePopup();
+    yield closePromise;
 
-            let eventCount = 0;
-            let nextTest = () => {
-              if (++eventCount == 2) {
-                runNextTest();
-              }
-            }
+    info("Timeouts after this probably mean bug 589954 regressed");
 
-            wait_for_notification_close(() => {
-              AddonManager.getAllInstalls(function(aInstalls) {
-                is(aInstalls.length, 0, "Should have cancelled the installs");
-                nextTest();
-              });
-            });
+    yield new Promise(resolve => executeSoon(resolve));
 
-            info("Closing browser tab");
-            BrowserTestUtils.removeTab(gBrowser.selectedTab).then(nextTest);
-          });
-        });
+    notificationPromise = waitForNotification("addon-install-blocked");
+    gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+    yield notificationPromise;
 
-        gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
-      });
-    });
+    let installs = yield getInstalls();
+    is(installs.length, 2, "Should be two pending installs");
 
-    // hide the panel (this simulates the user dismissing it)
-    aPanel.hidePopup();
-  });
+    closePromise = waitForNotificationClose();
+    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+    yield closePromise;
 
-  var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "amosigned.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    installs = yield getInstalls();
+    is(installs.length, 0, "Should have cancelled the installs");
+  });
 },
 
-function test_renotify_installed() {
-  // Wait for the progress notification
-  wait_for_progress_notification(function(aPanel) {
-    // Wait for the install confirmation dialog
-    wait_for_install_dialog(function() {
-      // Wait for the complete notification
-      wait_for_notification("addon-install-restart", function(aPanel) {
-        // Dismiss the notification
-        wait_for_notification_close(function () {
-          // Install another
-          executeSoon(function () {
-            // Wait for the progress notification
-            wait_for_progress_notification(function(aPanel) {
-              // Wait for the install confirmation dialog
-              wait_for_install_dialog(function() {
-                info("Timeouts after this probably mean bug 589954 regressed");
+function test_renotifyInstalled() {
+  return Task.spawn(function* () {
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
-                // Wait for the complete notification
-                wait_for_notification("addon-install-restart", function(aPanel) {
-                  AddonManager.getAllInstalls(function(aInstalls) {
-                  is(aInstalls.length, 1, "Should be one pending installs");
-                    aInstalls[0].cancel();
+    let progressPromise = waitForProgressNotification();
+    let dialogPromise = waitForInstallDialog();
+    let triggers = encodeURIComponent(JSON.stringify({
+      "XPI": "amosigned.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    yield dialogPromise;
+
+    // Wait for the complete notification
+    let notificationPromise = waitForNotification("addon-install-restart");
+    acceptInstallDialog();
+    let panel = yield notificationPromise;
+
+    let closePromise = waitForNotificationClose();
+    // hide the panel (this simulates the user dismissing it)
+    panel.hidePopup();
+    yield closePromise;
 
-                    Services.perms.remove(makeURI("http://example.com/"), "install");
-                    remove_tab_and_run_next_test();
-                  });
-                });
+    // Install another
+    yield new Promise(resolve => executeSoon(resolve));
 
-                accept_install_dialog();
-              });
-            });
+    progressPromise = waitForProgressNotification();
+    dialogPromise = waitForInstallDialog();
+    gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+    yield progressPromise;
+    yield dialogPromise;
 
-            gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
-          });
-        });
+    info("Timeouts after this probably mean bug 589954 regressed");
 
-        // hide the panel (this simulates the user dismissing it)
-        aPanel.hidePopup();
-      });
-
-      accept_install_dialog();
-    });
-  });
+    // Wait for the complete notification
+    notificationPromise = waitForNotification("addon-install-restart");
+    acceptInstallDialog();
+    yield notificationPromise;
 
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+    let installs = yield getInstalls();
+    is(installs.length, 1, "Should be one pending installs");
+    installs[0].cancel();
 
-  var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "amosigned.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield removeTab();
+  });
 },
 
 function test_cancel() {
-  function complete_install(callback) {
-    let url = TESTROOT + "slowinstall.sjs?continue=true"
-    NetUtil.asyncFetch({
-      uri: url,
-      loadUsingSystemPrincipal: true
-    }, callback || (() => {}));
-  }
+  return Task.spawn(function* () {
+    function complete_install(callback) {
+      let url = TESTROOT + "slowinstall.sjs?continue=true"
+      NetUtil.asyncFetch({
+        uri: url,
+        loadUsingSystemPrincipal: true
+      }, callback || (() => {}));
+    }
 
-  // Wait for the progress notification
-  wait_for_notification(PROGRESS_NOTIFICATION, function(aPanel) {
-    let notification = aPanel.childNodes[0];
+    let pm = Services.perms;
+    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+    let notificationPromise = waitForNotification(PROGRESS_NOTIFICATION);
+    let triggers = encodeURIComponent(JSON.stringify({
+      "XPI": "slowinstall.sjs?file=amosigned.xpi"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+    let panel = yield notificationPromise;
+
+    let notification = panel.childNodes[0];
     // Close the notification
     let anchor = document.getElementById("addons-notification-icon");
     anchor.click();
     // Reopen the notification
     anchor.click();
 
     ok(PopupNotifications.isPanelOpen, "Notification should still be open");
     is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
-    notification = aPanel.childNodes[0];
+    notification = panel.childNodes[0];
     is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
     let button = document.getElementById("addon-progress-cancel");
 
-    // Wait for the install to fully cancel
+    // Cancel the download
     let install = notification.notification.options.installs[0];
-    install.addListener({
-      onDownloadCancelled: function() {
-        install.removeListener(this);
+    let cancelledPromise = new Promise(resolve => {
+      install.addListener({
+        onDownloadCancelled: function() {
+          install.removeListener(this);
+          resolve();
+        }
+      });
+    });
+    EventUtils.synthesizeMouseAtCenter(button, {});
+    yield cancelledPromise;
 
-        executeSoon(function() {
-          ok(!PopupNotifications.isPanelOpen, "Notification should be closed");
+    yield new Promise(resolve => executeSoon(resolve));
 
-          AddonManager.getAllInstalls(function(aInstalls) {
-            is(aInstalls.length, 0, "Should be no pending install");
+    ok(!PopupNotifications.isPanelOpen, "Notification should be closed");
+
+    let installs = yield getInstalls();
+    is(installs.length, 0, "Should be no pending install");
 
-            Services.perms.remove(makeURI("http://example.com/"), "install");
-            BrowserTestUtils.removeTab(gBrowser.selectedTab).then(runNextTest);
-          });
-        });
-      }
+    Services.perms.remove(makeURI("http://example.com/"), "install");
+    yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  });
+},
+
+function test_failedSecurity() {
+  return Task.spawn(function* () {
+    Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false);
+    setupRedirect({
+      "Location": TESTROOT + "amosigned.xpi"
     });
 
-    // Cancel the download
-    EventUtils.synthesizeMouseAtCenter(button, {});
-  });
-
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+    let notificationPromise = waitForNotification("addon-install-blocked");
+    let triggers = encodeURIComponent(JSON.stringify({
+      "XPI": "redirect.sjs?mode=redirect"
+    }));
+    BrowserTestUtils.openNewForegroundTab(gBrowser, SECUREROOT + "installtrigger.html?" + triggers);
+    let panel = yield notificationPromise;
 
-  var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "slowinstall.sjs?file=amosigned.xpi"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
-},
-
-function test_failed_security() {
-  Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false);
-
-  setup_redirect({
-    "Location": TESTROOT + "amosigned.xpi"
-  });
-
-  // Wait for the blocked notification
-  wait_for_notification("addon-install-blocked", function(aPanel) {
-    let notification = aPanel.childNodes[0];
-
+    let notification = panel.childNodes[0];
     // Click on Allow
     EventUtils.synthesizeMouse(notification.button, 20, 10, {});
 
     // Notification should have changed to progress notification
     ok(PopupNotifications.isPanelOpen, "Notification should still be open");
     is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
-    notification = aPanel.childNodes[0];
+    notification = panel.childNodes[0];
     is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
 
     // Wait for it to fail
-    Services.obs.addObserver(function() {
-      Services.obs.removeObserver(arguments.callee, "addon-install-failed");
-
-      // Allow the browser code to add the failure notification and then wait
-      // for the progress notification to dismiss itself
-      wait_for_single_notification(function() {
-        is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
-        notification = aPanel.childNodes[0];
-        is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+    yield new Promise(resolve => {
+      Services.obs.addObserver(function observer() {
+        Services.obs.removeObserver(observer, "addon-install-failed");
+        resolve();
+      }, "addon-install-failed", false);
+    });
 
-        Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true);
-        remove_tab_and_run_next_test();
-      });
-    }, "addon-install-failed", false);
+    // Allow the browser code to add the failure notification and then wait
+    // for the progress notification to dismiss itself
+    yield waitForSingleNotification();
+    is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+    notification = panel.childNodes[0];
+    is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+
+    Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true);
+    yield removeTab();
   });
-
-  var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "redirect.sjs?mode=redirect"
-  }));
-  BrowserTestUtils.openNewForegroundTab(gBrowser, SECUREROOT + "installtrigger.html?" + triggers);
 }
 ];
 
 var gTestStart = null;
 
-function runNextTest() {
-  if (gTestStart)
-    info("Test part took " + (Date.now() - gTestStart) + "ms");
-
-  ok(!PopupNotifications.isPanelOpen, "Notification should be closed");
-
-  AddonManager.getAllInstalls(function(aInstalls) {
-    is(aInstalls.length, 0, "Should be no active installs");
-
-    if (TESTS.length == 0) {
-      finish();
-      return;
-    }
-
-    info("Running " + TESTS[0].name);
-    gTestStart = Date.now();
-    TESTS.shift()();
-  });
-}
-
 var XPInstallObserver = {
   observe: function (aSubject, aTopic, aData) {
     var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
     info("Observed " + aTopic + " for " + installInfo.installs.length + " installs");
     installInfo.installs.forEach(function(aInstall) {
       info("Install of " + aInstall.sourceURI.spec + " was in state " + aInstall.state);
     });
   }
 };
 
-function test() {
+add_task(function* () {
   requestLongerTimeout(4);
-  waitForExplicitFinish();
 
   Services.prefs.setBoolPref("extensions.logging.enabled", true);
   Services.prefs.setBoolPref("extensions.strictCompatibility", true);
   Services.prefs.setBoolPref("extensions.install.requireSecureOrigin", false);
   Services.prefs.setIntPref("security.dialog_enable_delay", 0);
 
   Services.obs.addObserver(XPInstallObserver, "addon-install-started", false);
   Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);
@@ -1175,10 +1168,26 @@ function test() {
     Services.prefs.clearUserPref("security.dialog_enable_delay");
 
     Services.obs.removeObserver(XPInstallObserver, "addon-install-started");
     Services.obs.removeObserver(XPInstallObserver, "addon-install-blocked");
     Services.obs.removeObserver(XPInstallObserver, "addon-install-failed");
     Services.obs.removeObserver(XPInstallObserver, "addon-install-complete");
   });
 
-  runNextTest();
-}
+  for (let i = 0; i < TESTS.length; ++i) {
+    if (gTestStart)
+      info("Test part took " + (Date.now() - gTestStart) + "ms");
+
+    ok(!PopupNotifications.isPanelOpen, "Notification should be closed");
+
+    let installs = yield new Promise(resolve => {
+      AddonManager.getAllInstalls(function(aInstalls) {
+        resolve(aInstalls);
+      });
+    });
+
+    is(installs.length, 0, "Should be no active installs");
+    info("Running " + TESTS[i].name);
+    gTestStart = Date.now();
+    yield TESTS[i]();
+  }
+});
--- a/browser/base/content/test/general/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/general/browser_misused_characters_in_strings.js
@@ -103,16 +103,20 @@ let gWhitelist = [{
   }, {
     file: "pocket.properties",
     key: "tos",
     type: "double-quote"
   }, {
     file: "pocket.properties",
     key: "tos",
     type: "apostrophe"
+  }, {
+    file: "aboutNetworking.dtd",
+    key: "aboutNetworking.logTutorial",
+    type: "single-quote"
   }
 ];
 
 var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
 var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
 
 /**
  * Check if an error should be ignored due to matching one of the whitelist
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js
@@ -20,16 +20,24 @@ function checkCheckbox(checkbox, label, 
 
 function checkMainAction(notification, disabled=false) {
   let mainAction = notification.button;
   let warningLabel = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-warning");
   is(warningLabel.hidden, !disabled, "Warning label should be shown");
   is(mainAction.disabled, disabled, "MainAction should be disabled");
 }
 
+function promiseElementVisible(element) {
+  // HTMLElement.offsetParent is null when the element is not visisble
+  // (or if the element has |position: fixed|). See:
+  // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
+  return BrowserTestUtils.waitForCondition(() => element.offsetParent !== null,
+                                          "Waiting for element to be visible");
+}
+
 var gNotification;
 
 var tests = [
   // Test that passing the checkbox field shows the checkbox.
   { id: "show_checkbox",
     run: function () {
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.options.checkbox = {
@@ -70,21 +78,22 @@ var tests = [
     run: function () {
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.mainAction.callback = ({checkboxChecked}) => this.mainActionChecked = checkboxChecked;
       this.notifyObj.options.checkbox = {
         label: "This is a checkbox",
       };
       gNotification = showNotification(this.notifyObj);
     },
-    onShown: function (popup) {
+    onShown: function* (popup) {
       checkPopup(popup, this.notifyObj);
       let notification = popup.childNodes[0];
       let checkbox = notification.checkbox;
       checkCheckbox(checkbox, "This is a checkbox");
+      yield promiseElementVisible(checkbox);
       EventUtils.synthesizeMouseAtCenter(checkbox, {});
       checkCheckbox(checkbox, "This is a checkbox", true);
       triggerMainCommand(popup);
     },
     onHidden: function () {
       is(this.mainActionChecked, true, "mainAction callback is passed the correct checkbox value");
     }
   },
@@ -98,21 +107,22 @@ var tests = [
         accessKey: "T",
         callback: ({checkboxChecked}) => this.secondaryActionChecked = checkboxChecked,
       }];
       this.notifyObj.options.checkbox = {
         label: "This is a checkbox",
       };
       gNotification = showNotification(this.notifyObj);
     },
-    onShown: function (popup) {
+    onShown: function* (popup) {
       checkPopup(popup, this.notifyObj);
       let notification = popup.childNodes[0];
       let checkbox = notification.checkbox;
       checkCheckbox(checkbox, "This is a checkbox");
+      yield promiseElementVisible(checkbox);
       EventUtils.synthesizeMouseAtCenter(checkbox, {});
       checkCheckbox(checkbox, "This is a checkbox", true);
       triggerSecondaryCommand(popup, 0);
     },
     onHidden: function () {
       is(this.secondaryActionChecked, true, "secondaryAction callback is passed the correct checkbox value");
     }
   },
@@ -125,21 +135,22 @@ var tests = [
         label: "This is a checkbox",
         checkedState: {
           disableMainAction: true,
           warningLabel: "Testing disable",
         },
       };
       gNotification = showNotification(this.notifyObj);
     },
-    onShown: function (popup) {
+    onShown: function* (popup) {
       checkPopup(popup, this.notifyObj);
       let notification = popup.childNodes[0];
       let checkbox = notification.checkbox;
       checkCheckbox(checkbox, "This is a checkbox");
+      yield promiseElementVisible(checkbox);
       EventUtils.synthesizeMouseAtCenter(checkbox, {});
       dismissNotification(popup);
     },
     onHidden: function (popup) {
       let icon = document.getElementById("default-notification-icon");
       EventUtils.synthesizeMouseAtCenter(icon, {});
       let notification = popup.childNodes[0];
       let checkbox = notification.checkbox;
@@ -162,25 +173,26 @@ var tests = [
             checked: checked,
             [state]: {
               disableMainAction: true,
               warningLabel: "Testing disable",
             },
           };
           gNotification = showNotification(this.notifyObj);
         },
-        onShown: function (popup) {
+        onShown: function* (popup) {
           checkPopup(popup, this.notifyObj);
           let notification = popup.childNodes[0];
           let checkbox = notification.checkbox;
           let disabled = (state === "checkedState" && checked) ||
                          (state === "uncheckedState" && !checked);
 
           checkCheckbox(checkbox, "This is a checkbox", checked);
           checkMainAction(notification, disabled);
+          yield promiseElementVisible(checkbox);
           EventUtils.synthesizeMouseAtCenter(checkbox, {});
           checkCheckbox(checkbox, "This is a checkbox", !checked);
           checkMainAction(notification, !disabled);
           EventUtils.synthesizeMouseAtCenter(checkbox, {});
           checkCheckbox(checkbox, "This is a checkbox", checked);
           checkMainAction(notification, disabled);
 
           // Unblock the main command if it's currently disabled.
--- a/browser/base/content/test/urlbar/browser_locationBarCommand.js
+++ b/browser/base/content/test/urlbar/browser_locationBarCommand.js
@@ -36,17 +36,21 @@ add_task(function* shift_left_click_test
   info("Running test: Shift left click");
 
   let newWindowPromise = BrowserTestUtils.waitForNewWindow();
   triggerCommand(true, {shiftKey: true});
   let win = yield newWindowPromise;
 
   // Wait for the initial browser to load.
   let browser = win.gBrowser.selectedBrowser;
-  yield BrowserTestUtils.browserLoaded(browser);
+  let destinationURL = "http://" + TEST_VALUE + "/";
+  yield Promise.all([
+    BrowserTestUtils.browserLoaded(browser),
+    BrowserTestUtils.waitForLocationChange(win.gBrowser, destinationURL)
+  ]);
 
   info("URL should be loaded in a new window");
   is(gURLBar.value, "", "Urlbar reverted to original value");
   yield promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser);
   is(document.activeElement, gBrowser.selectedBrowser, "Content window should be focused");
   is(win.gURLBar.textValue, TEST_VALUE, "New URL is loaded in new window");
 
   // Cleanup.
--- a/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js
+++ b/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js
@@ -40,22 +40,25 @@ add_task(function* testAddOnBeforeCreate
   ok(onBeforeCreatedCalled, "onBeforeCreated should have been called");
 
   let widgetNode = document.getElementById(kWidgetId);
   ok(widgetNode, "Widget should exist");
   if (widgetNode) {
     try {
       widgetNode.click();
 
+      let tempPanel = document.getElementById("customizationui-widget-panel");
+      let panelShownPromise = promisePanelElementShown(window, tempPanel);
+
       let shownTimeout = setTimeout(() => viewShownDeferred.reject("Panel not shown within 20s"), 20000);
       yield viewShownDeferred.promise;
+      yield panelShownPromise;
       clearTimeout(shownTimeout);
       ok(true, "Found view shown");
 
-      let tempPanel = document.getElementById("customizationui-widget-panel");
       let panelHiddenPromise = promisePanelElementHidden(window, tempPanel);
       tempPanel.hidePopup();
       yield panelHiddenPromise;
 
       CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_PANEL);
       yield PanelUI.show();
 
       viewShownDeferred = Promise.defer();
--- a/browser/components/customizableui/test/browser_988072_sidebar_events.js
+++ b/browser/components/customizableui/test/browser_988072_sidebar_events.js
@@ -77,19 +77,21 @@ function compareList(original, displayed
     compareElements(displayed[i], original[i]);
   }
 }
 
 var showSidebarPopup = Task.async(function*() {
   let button = document.getElementById("sidebar-button");
   let subview = document.getElementById("PanelUI-sidebar");
 
+  let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
+
   let subviewShownPromise = subviewShown(subview);
   EventUtils.synthesizeMouseAtCenter(button, {});
-  return subviewShownPromise;
+  return Promise.all([subviewShownPromise, popupShownPromise]);
 });
 
 // Check the sidebar widget shows the default items
 add_task(function*() {
   addWidget();
 
   yield showSidebarPopup();
 
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
@@ -1,16 +1,26 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+requestLongerTimeout(2);
+
+function add_tasks(task) {
+  add_task(task.bind(null, {embedded: false}));
+
+  add_task(task.bind(null, {embedded: true}));
+}
+
 function* loadExtension(options) {
   let extension = ExtensionTestUtils.loadExtension({
     useAddonManager: "temporary",
 
+    embedded: options.embedded,
+
     manifest: Object.assign({
       "permissions": ["tabs"],
     }, options.manifest),
 
     files: {
       "options.html": `<!DOCTYPE html>
         <html>
           <head>
@@ -32,20 +42,22 @@ function* loadExtension(options) {
     background: options.background,
   });
 
   yield extension.startup();
 
   return extension;
 }
 
-add_task(function* test_inline_options() {
+add_tasks(function* test_inline_options(extraOptions) {
+  info(`Test options opened inline (${JSON.stringify(extraOptions)})`);
+
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
 
-  let extension = yield loadExtension({
+  let extension = yield loadExtension(Object.assign({}, extraOptions, {
     manifest: {
       applications: {gecko: {id: "inline_options@tests.mozilla.org"}},
       "options_ui": {
         "page": "options.html",
       },
     },
 
     background: function() {
@@ -118,28 +130,30 @@ add_task(function* test_inline_options()
         return browser.tabs.remove(tab.id);
       }).then(() => {
         browser.test.notifyPass("options-ui");
       }).catch(error => {
         browser.test.log(`Error: ${error} :: ${error.stack}`);
         browser.test.notifyFail("options-ui");
       });
     },
-  });
+  }));
 
   yield extension.awaitFinish("options-ui");
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab);
 });
 
-add_task(function* test_tab_options() {
+add_tasks(function* test_tab_options(extraOptions) {
+  info(`Test options opened in a tab (${JSON.stringify(extraOptions)})`);
+
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
 
-  let extension = yield loadExtension({
+  let extension = yield loadExtension(Object.assign({}, extraOptions, {
     manifest: {
       applications: {gecko: {id: "tab_options@tests.mozilla.org"}},
       "options_ui": {
         "page": "options.html",
         "open_in_tab": true,
       },
     },
 
@@ -216,26 +230,28 @@ add_task(function* test_tab_options() {
         return browser.tabs.remove(tab.id);
       }).then(() => {
         browser.test.notifyPass("options-ui-tab");
       }).catch(error => {
         browser.test.log(`Error: ${error} :: ${error.stack}`);
         browser.test.notifyFail("options-ui-tab");
       });
     },
-  });
+  }));
 
   yield extension.awaitFinish("options-ui-tab");
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab);
 });
 
-add_task(function* test_options_no_manifest() {
-  let extension = yield loadExtension({
+add_tasks(function* test_options_no_manifest(extraOptions) {
+  info(`Test with no manifest key (${JSON.stringify(extraOptions)})`);
+
+  let extension = yield loadExtension(Object.assign({}, extraOptions, {
     manifest: {
       applications: {gecko: {id: "no_options@tests.mozilla.org"}},
     },
 
     background: function() {
       browser.test.log("Try to open options page when not specified in the manifest.");
 
       browser.runtime.openOptionsPage().then(
@@ -251,13 +267,13 @@ add_task(function* test_options_no_manif
         }
       ).then(() => {
         browser.test.notifyPass("options-no-manifest");
       }).catch(error => {
         browser.test.log(`Error: ${error} :: ${error.stack}`);
         browser.test.notifyFail("options-no-manifest");
       });
     },
-  });
+  }));
 
   yield extension.awaitFinish("options-no-manifest");
   yield extension.unload();
 });
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -172,16 +172,28 @@ var gEditItemOverlay = {
    *          - hiddenRows (Strings array): list of rows to be hidden regardless
    *            of the item edited. Possible values: "title", "location",
    *            "description", "keyword", "loadInSidebar", "feedLocation",
    *            "siteLocation", folderPicker"
    */
   initPanel(aInfo) {
     if (typeof(aInfo) != "object" || aInfo === null)
       throw new Error("aInfo must be an object.");
+    if ("node" in aInfo) {
+      try {
+        aInfo.node.type;
+      } catch (e) {
+        // If the lazy loader for |type| generates an exception, it means that
+        // this bookmark could not be loaded. This sometimes happens when tests
+        // create a bookmark by clicking the bookmark star, then try to cleanup
+        // before the bookmark panel has finished opening. Either way, if we
+        // cannot retrieve the bookmark information, we cannot open the panel.
+        return;
+      }
+    }
 
     // For sanity ensure that the implementer has uninited the panel before
     // trying to init it again, or we could end up leaking due to observers.
     if (this.initialized)
       this.uninitPanel(false);
 
     let { itemId, itemGuid, isItem,
           isURI, uri, title,
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -648,47 +648,16 @@ EqualOrSubdomain(nsIURI* aProbeArg, nsIU
             return false;
         }
         NS_ENSURE_SUCCESS(rv, false);
         rv = probe->SetHost(newHost);
         NS_ENSURE_SUCCESS(rv, false);
     }
 }
 
-static bool
-AllSchemesMatch(nsIURI* aURI, nsIURI* aOtherURI)
-{
-    auto stringComparator = nsCaseInsensitiveCStringComparator();
-    nsCOMPtr<nsIURI> currentURI = aURI;
-    nsCOMPtr<nsIURI> currentOtherURI = aOtherURI;
-    while (currentURI && currentOtherURI) {
-        nsAutoCString scheme, otherScheme;
-        currentURI->GetScheme(scheme);
-        currentOtherURI->GetScheme(otherScheme);
-        if (!scheme.Equals(otherScheme, stringComparator)) {
-            return false;
-        }
-        nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(currentURI);
-        nsCOMPtr<nsINestedURI> nestedOtherURI = do_QueryInterface(currentOtherURI);
-        // If neither are nested and all schemes have matched so far
-        // (or we would have bailed already), we're the same:
-        if (!nestedURI && !nestedOtherURI) {
-            return true;
-        }
-        // If one is nested and the other not, they're not equal:
-        if (!nestedURI != !nestedOtherURI) {
-            return false;
-        }
-        // At this stage, both are still nested URIs, so let's play again:
-        nestedURI->GetInnerURI(getter_AddRefs(currentURI));
-        nestedOtherURI->GetInnerURI(getter_AddRefs(currentOtherURI));
-    }
-    return false;
-}
-
 NS_IMETHODIMP
 nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
                                                    nsIURI *aTargetURI,
                                                    uint32_t aFlags)
 {
     NS_PRECONDITION(aPrincipal, "CheckLoadURIWithPrincipal must have a principal");
     // If someone passes a flag that we don't understand, we should
     // fail, because they may need a security check that we don't
@@ -801,63 +770,60 @@ nsScriptSecurityManager::CheckLoadURIWit
     bool targetIsViewSource = false;
 
     if (sourceScheme.LowerCaseEqualsLiteral(NS_NULLPRINCIPAL_SCHEME)) {
         // A null principal can target its own URI.
         if (sourceURI == aTargetURI) {
             return NS_OK;
         }
     }
-    else if (AllSchemesMatch(sourceURI, aTargetURI) ||
-             (sViewSourceReachableFromInner &&
-              sourceScheme.EqualsIgnoreCase(targetScheme.get()) &&
-              NS_SUCCEEDED(aTargetURI->SchemeIs("view-source", &targetIsViewSource)) &&
-              targetIsViewSource))
+    else if (sViewSourceReachableFromInner &&
+             sourceScheme.EqualsIgnoreCase(targetScheme.get()) &&
+             NS_SUCCEEDED(aTargetURI->SchemeIs("view-source", &targetIsViewSource)) &&
+             targetIsViewSource)
     {
-        // every scheme can access another URI from the same scheme,
-        // as long as they don't represent null principals...
-        // Or they don't require an special permission to do so
-        // See bug#773886
-        rv = NS_URIChainHasFlags(targetBaseURI,
-                                 nsIProtocolHandler::URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM,
-                                 &hasFlags);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        if (hasFlags) {
-            // Let apps load the whitelisted theme resources even if they don't
-            // have the webapps-manage permission but have the themeable one.
-            // Resources from the theme origin are also allowed to load from
-            // the theme origin (eg. stylesheets using images from the theme).
-            auto themeOrigin = Preferences::GetCString("b2g.theme.origin");
-            if (themeOrigin) {
-                nsAutoCString targetOrigin;
-                nsPrincipal::GetOriginForURI(targetBaseURI, targetOrigin);
-                if (targetOrigin.Equals(themeOrigin)) {
-                    nsAutoCString pOrigin;
-                    aPrincipal->GetOrigin(pOrigin);
-                    return nsContentUtils::IsExactSitePermAllow(aPrincipal, "themeable") ||
-                           pOrigin.Equals(themeOrigin)
-                        ? NS_OK : NS_ERROR_DOM_BAD_URI;
-                }
-            }
-            // In this case, we allow opening only if the source and target URIS
-            // are on the same domain, or the opening URI has the webapps
-            // permision granted
-            if (!SecurityCompareURIs(sourceBaseURI, targetBaseURI) &&
-                !nsContentUtils::IsExactSitePermAllow(aPrincipal, WEBAPPS_PERM_NAME)) {
-                return NS_ERROR_DOM_BAD_URI;
-            }
-        }
+        // exception for foo: linking to view-source:foo for reftests...
         return NS_OK;
     }
 
-    // If the schemes don't match, the policy is specified by the protocol
-    // flags on the target URI.
-    return CheckLoadURIFlags(sourceURI, aTargetURI, sourceBaseURI,
-                             targetBaseURI, aFlags);
+    // If we get here, check all the schemes can link to each other, from the top down:
+    nsCaseInsensitiveCStringComparator stringComparator;
+    nsCOMPtr<nsIURI> currentURI = sourceURI;
+    nsCOMPtr<nsIURI> currentOtherURI = aTargetURI;
+    while (currentURI && currentOtherURI) {
+        nsAutoCString scheme, otherScheme;
+        currentURI->GetScheme(scheme);
+        currentOtherURI->GetScheme(otherScheme);
+
+        // If schemes are not equal, check if the URI flags of the current
+        // target URI allow the current source URI to link to it.
+        // The policy is specified by the protocol flags on both URIs.
+        if (!scheme.Equals(otherScheme, stringComparator)) {
+            return CheckLoadURIFlags(currentURI, currentOtherURI,
+                                     sourceBaseURI, targetBaseURI, aFlags);
+        }
+        // Otherwise... check if we can nest another level:
+        nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(currentURI);
+        nsCOMPtr<nsINestedURI> nestedOtherURI = do_QueryInterface(currentOtherURI);
+
+        // If schemes match and neither URI is nested further, we're OK.
+        if (!nestedURI && !nestedOtherURI) {
+            return NS_OK;
+        }
+        // If one is nested and the other isn't, something is wrong.
+        if (!nestedURI != !nestedOtherURI) {
+            return NS_ERROR_DOM_BAD_URI;
+        }
+        // Otherwise, both should be nested and we'll go through the loop again.
+        nestedURI->GetInnerURI(getter_AddRefs(currentURI));
+        nestedOtherURI->GetInnerURI(getter_AddRefs(currentOtherURI));
+    }
+
+    // We should never get here. We should always return from inside the loop.
+    return NS_ERROR_DOM_BAD_URI;
 }
 
 /**
  * Helper method to check whether the target URI and its innermost ("base") URI
  * has protocol flags that should stop it from being loaded by the source URI
  * (and/or the source URI's innermost ("base") URI), taking into account any
  * nsIScriptSecurityManager flags originally passed to
  * CheckLoadURIWithPrincipal and friends.
@@ -1198,16 +1164,17 @@ nsScriptSecurityManager::GetAppCodebaseP
 }
 
 NS_IMETHODIMP
 nsScriptSecurityManager::
   GetLoadContextCodebasePrincipal(nsIURI* aURI,
                                   nsILoadContext* aLoadContext,
                                   nsIPrincipal** aPrincipal)
 {
+  NS_ENSURE_STATE(aLoadContext);
   DocShellOriginAttributes docShellAttrs;
   bool result = aLoadContext->GetOriginAttributes(docShellAttrs);;
   NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
 
   PrincipalOriginAttributes attrs;
   attrs.InheritFromDocShellToDoc(docShellAttrs, aURI);
 
   nsresult rv = MaybeSetAddonIdFromURI(attrs, aURI);
--- a/caps/tests/mochitest/browser_checkloaduri.js
+++ b/caps/tests/mochitest/browser_checkloaduri.js
@@ -10,45 +10,51 @@ const URLs = new Map([
   // - whether the URI can be created at all (some protocol handlers will
   //   refuse to create certain variants)
     ["http://www.example2.com", true, true, true],
     ["feed:http://www.example2.com", false, false, true],
     ["https://www.example2.com", true, true, true],
     ["chrome://foo/content/bar.xul", false, false, true],
     ["feed:chrome://foo/content/bar.xul", false, false, false],
     ["view-source:http://www.example2.com", false, false, true],
+    ["view-source:https://www.example2.com", false, false, true],
     ["view-source:feed:http://www.example2.com", false, false, true],
     ["feed:view-source:http://www.example2.com", false, false, false],
     ["data:text/html,Hi", true, false, true],
+    ["view-source:data:text/html,Hi", false, false, true],
     ["javascript:alert('hi')", true, false, true],
   ]],
   ["feed:http://www.example.com", [
     ["http://www.example2.com", true, true, true],
     ["feed:http://www.example2.com", true, true, true],
     ["https://www.example2.com", true, true, true],
-    ["feed:https://www.example2.com", false, false, true],
+    ["feed:https://www.example2.com", true, true, true],
     ["chrome://foo/content/bar.xul", false, false, true],
     ["feed:chrome://foo/content/bar.xul", false, false, false],
     ["view-source:http://www.example2.com", false, false, true],
+    ["view-source:https://www.example2.com", false, false, true],
     ["view-source:feed:http://www.example2.com", false, false, true],
     ["feed:view-source:http://www.example2.com", false, false, false],
     ["data:text/html,Hi", true, false, true],
+    ["view-source:data:text/html,Hi", false, false, true],
     ["javascript:alert('hi')", true, false, true],
   ]],
   ["view-source:http://www.example.com", [
     ["http://www.example2.com", true, true, true],
     ["feed:http://www.example2.com", false, false, true],
     ["https://www.example2.com", true, true, true],
     ["feed:https://www.example2.com", false, false, true],
     ["chrome://foo/content/bar.xul", false, false, true],
     ["feed:chrome://foo/content/bar.xul", false, false, false],
     ["view-source:http://www.example2.com", true, true, true],
+    ["view-source:https://www.example2.com", true, true, true],
     ["view-source:feed:http://www.example2.com", false, false, true],
     ["feed:view-source:http://www.example2.com", false, false, false],
     ["data:text/html,Hi", true, false, true],
+    ["view-source:data:text/html,Hi", true, false, true],
     ["javascript:alert('hi')", true, false, true],
   ]],
 ]);
 
 function testURL(source, target, canLoad, canLoadWithoutInherit, canCreate, flags) {
   let threw = false;
   let targetURI;
   try {
--- a/devtools/client/webconsole/test/browser_console_variables_view_dom_nodes.js
+++ b/devtools/client/webconsole/test/browser_console_variables_view_dom_nodes.js
@@ -3,54 +3,57 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that ensures DOM nodes are rendered correctly in VariablesView.
 
 "use strict";
 
 function test() {
-  const TEST_URI = 'data:text/html;charset=utf-8,                           \
-                    <html>                                                  \
-                      <head>                                                \
-                        <title>Test for DOM nodes in variables view</title> \
-                      </head>                                               \
-                      <body>                                                \
-                        <div></div>                                         \
-                        <div id="testID"></div>                             \
-                        <div class="single-class"></div>                    \
-                        <div class="multiple-classes another-class"></div>  \
-                        <div class="class-and-id" id="class-and-id"></div>  \
-                        <div class="multiple-classes-and-id another-class"  \
-                             id="multiple-classes-and-id"></div>            \
-                        <div class="   whitespace-start"></div>             \
-                        <div class="whitespace-end     "></div>             \
-                        <div class="multiple    spaces"></div>              \
-                      </body>                                               \
-                    </html>';
+  const TEST_URI = `
+    data:text/html;charset=utf-8,
+    <html>
+      <head>
+        <title>Test for DOM nodes in variables view</title>
+      </head>
+      <body>
+        <div></div>
+        <div id="testID"></div>
+        <div class="single-class"></div>
+        <div class="multiple-classes another-class"></div>
+        <div class="class-and-id" id="class-and-id"></div>
+        <div class="multiple-classes-and-id another-class"
+          id="multiple-classes-and-id"></div>
+        <div class="   whitespace-start"></div>
+        <div class="whitespace-end     "></div>
+        <div class="multiple    spaces"></div>
+      </body>
+    </html>
+`;
 
   Task.spawn(runner).then(finishTest);
 
   function* runner() {
     const {tab} = yield loadTab(TEST_URI);
     const hud = yield openConsole(tab);
     const jsterm = hud.jsterm;
 
     let deferred = promise.defer();
-    jsterm.once("variablesview-fetched", (_, aVar) => deferred.resolve(aVar));
+    jsterm.once("variablesview-fetched", (_, val) => deferred.resolve(val));
     jsterm.execute("inspect(document.querySelectorAll('div'))");
 
     let variableScope = yield deferred.promise;
     ok(variableScope, "Variables view opened");
 
     yield findVariableViewProperties(variableScope, [
       { name: "0", value: "<div>"},
       { name: "1", value: "<div#testID>"},
       { name: "2", value: "<div.single-class>"},
       { name: "3", value: "<div.multiple-classes.another-class>"},
       { name: "4", value: "<div#class-and-id.class-and-id>"},
-      { name: "5", value: "<div#multiple-classes-and-id.multiple-classes-and-id.another-class>"},
+      { name: "5", value: "<div#multiple-classes-and-id." +
+                          "multiple-classes-and-id.another-class>"},
       { name: "6", value: "<div.whitespace-start>"},
       { name: "7", value: "<div.whitespace-end>"},
       { name: "8", value: "<div.multiple.spaces>"},
     ], { webconsole: hud});
   }
 }
--- a/docshell/base/nsDefaultURIFixup.cpp
+++ b/docshell/base/nsDefaultURIFixup.cpp
@@ -907,17 +907,17 @@ nsDefaultURIFixup::KeywordURIFixup(const
              (*iter >= 'A' && *iter <= 'F') ||
              nsCRT::IsAsciiDigit(*iter)))) {
         looksLikeIpv6 = false;
       }
     }
 
     // If we're at the end of the string or this is the first slash,
     // check if the thing before the slash looks like ipv4:
-    if ((iter.size_forward() == 1 ||
+    if ((iterEnd - iter == 1 ||
          (lastSlashLoc == uint32_t(kNotFound) && *iter == '/')) &&
         // Need 2 or 3 dots + only digits
         (foundDots == 2 || foundDots == 3) &&
         // and they should be all that came before now:
         (foundDots + foundDigits == pos ||
          // or maybe there was also exactly 1 colon that came after the last dot,
          // and the digits, dots and colon were all that came before now:
          (foundColons == 1 && firstColonLoc > lastDotLoc &&
--- a/dom/base/BodyUtil.cpp
+++ b/dom/base/BodyUtil.cpp
@@ -75,19 +75,20 @@ public:
   GetText()
   {
     return mDecoded;
   }
 };
 
 // Reads over a CRLF and positions start after it.
 static bool
-PushOverLine(nsACString::const_iterator& aStart)
+PushOverLine(nsACString::const_iterator& aStart,
+	     const nsACString::const_iterator& aEnd)
 {
-  if (*aStart == nsCRT::CR && (aStart.size_forward() > 1) && *(++aStart) == nsCRT::LF) {
+  if (*aStart == nsCRT::CR && (aEnd - aStart > 1) && *(++aStart) == nsCRT::LF) {
     ++aStart; // advance to after CRLF
     return true;
   }
 
   return false;
 }
 
 class MOZ_STACK_CLASS FillFormIterator final
@@ -389,29 +390,29 @@ public:
           if (start != end && *start == '-') {
             // End of data.
             if (!mFormData) {
               mFormData = new FormData();
             }
             return true;
           }
 
-          if (!PushOverLine(start)) {
+          if (!PushOverLine(start, end)) {
             return false;
           }
           mState = PARSE_HEADER;
           break;
 
         case PARSE_HEADER:
           bool emptyHeader;
           if (!ParseHeader(start, end, &emptyHeader)) {
             return false;
           }
 
-          if (emptyHeader && !PushOverLine(start)) {
+          if (emptyHeader && !PushOverLine(start, end)) {
             return false;
           }
 
           mState = emptyHeader ? PARSE_BODY : PARSE_HEADER;
           break;
 
         case PARSE_BODY:
           if (mName.IsVoid()) {
--- a/dom/base/ThirdPartyUtil.cpp
+++ b/dom/base/ThirdPartyUtil.cpp
@@ -116,17 +116,18 @@ ThirdPartyUtil::IsThirdPartyWindow(mozID
   NS_ASSERTION(aResult, "null outparam pointer");
 
   bool result;
 
   // Get the URI of the window, and its base domain.
   nsresult rv;
   nsCOMPtr<nsIURI> currentURI;
   rv = GetURIFromWindow(aWindow, getter_AddRefs(currentURI));
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_FAILED(rv))
+    return rv;
 
   nsCString bottomDomain;
   rv = GetBaseDomain(currentURI, bottomDomain);
   if (NS_FAILED(rv))
     return rv;
 
   if (aURI) {
     // Determine whether aURI is foreign with respect to currentURI.
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -787,33 +787,22 @@ nsContentUtils::InitializeTouchEventTabl
   }
 }
 
 static bool
 Is8bit(const nsAString& aString)
 {
   static const char16_t EIGHT_BIT = char16_t(~0x00FF);
 
-  nsAString::const_iterator done_reading;
-  aString.EndReading(done_reading);
-
-  // for each chunk of |aString|...
-  uint32_t fragmentLength = 0;
-  nsAString::const_iterator iter;
-  for (aString.BeginReading(iter); iter != done_reading;
-       iter.advance(int32_t(fragmentLength))) {
-    fragmentLength = uint32_t(iter.size_forward());
-    const char16_t* c = iter.get();
-    const char16_t* fragmentEnd = c + fragmentLength;
-
-    // for each character in this chunk...
-    while (c < fragmentEnd) {
-      if (*c++ & EIGHT_BIT) {
-        return false;
-      }
+  for (nsAString::const_char_iterator start = aString.BeginReading(),
+         end = aString.EndReading();
+       start != end;
+       ++start) {
+    if (*start & EIGHT_BIT) {
+      return false;
     }
   }
 
   return true;
 }
 
 nsresult
 nsContentUtils::Btoa(const nsAString& aBinaryData,
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3495,16 +3495,29 @@ NS_IMETHODIMP
 nsDOMWindowUtils::GetIsHandlingUserInput(bool* aHandlingUserInput)
 {
   *aHandlingUserInput = EventStateManager::IsHandlingUserInput();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::GetMillisSinceLastUserInput(double* aMillisSinceLastUserInput)
+{
+  TimeStamp lastInput = EventStateManager::LatestUserInputStart();
+  if (lastInput.IsNull()) {
+    *aMillisSinceLastUserInput = 0;
+    return NS_OK;
+  }
+
+  *aMillisSinceLastUserInput = (TimeStamp::Now() - lastInput).ToMilliseconds();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::AllowScriptsToClose()
 {
   nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
   NS_ENSURE_STATE(window);
   nsGlobalWindow::Cast(window)->AllowScriptsToClose();
   return NS_OK;
 }
 
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -879,16 +879,17 @@ GK_ATOM(onpairingconsentreq, "onpairingc
 GK_ATOM(onpaste, "onpaste")
 GK_ATOM(onpendingchange, "onpendingchange")
 GK_ATOM(onpichange, "onpichange")
 GK_ATOM(onpicture, "onpicture")
 GK_ATOM(onpointerlockchange, "onpointerlockchange")
 GK_ATOM(onpointerlockerror, "onpointerlockerror")
 GK_ATOM(onpopuphidden, "onpopuphidden")
 GK_ATOM(onpopuphiding, "onpopuphiding")
+GK_ATOM(onpopuppositioned, "onpopuppositioned")
 GK_ATOM(onpopupshowing, "onpopupshowing")
 GK_ATOM(onpopupshown, "onpopupshown")
 GK_ATOM(onposter, "onposter")
 GK_ATOM(onpreviewstatechange, "onpreviewstatechange")
 GK_ATOM(onpullphonebookreq, "onpullphonebookreq")
 GK_ATOM(onpullvcardentryreq, "onpullvcardentryreq")
 GK_ATOM(onpullvcardlistingreq, "onpullvcardlistingreq")
 GK_ATOM(onpush, "onpush")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -6680,26 +6680,28 @@ nsGlobalWindow::EnsureReflowFlushAndPain
   }
 
   // Unsuppress painting.
   presShell->UnsuppressPainting();
 }
 
 // static
 void
-nsGlobalWindow::MakeScriptDialogTitle(nsAString &aOutTitle)
-{
+nsGlobalWindow::MakeScriptDialogTitle(nsAString& aOutTitle,
+                                      nsIPrincipal* aSubjectPrincipal)
+{
+  MOZ_ASSERT(aSubjectPrincipal);
+
   aOutTitle.Truncate();
 
   // Try to get a host from the running principal -- this will do the
   // right thing for javascript: and data: documents.
 
-  nsCOMPtr<nsIPrincipal> principal = nsContentUtils::SubjectPrincipal();
   nsCOMPtr<nsIURI> uri;
-  nsresult rv = principal->GetURI(getter_AddRefs(uri));
+  nsresult rv = aSubjectPrincipal->GetURI(getter_AddRefs(uri));
   // Note - The check for the current JSContext here isn't necessarily sensical.
   // It's just designed to preserve existing behavior during a mass-conversion
   // patch.
   if (NS_SUCCEEDED(rv) && uri && nsContentUtils::GetCurrentJSContext()) {
     // remove user:pass for privacy and spoof prevention
 
     nsCOMPtr<nsIURIFixup> fixup(do_GetService(NS_URIFIXUP_CONTRACTID));
     if (fixup) {
@@ -6800,21 +6802,23 @@ nsGlobalWindow::CanMoveResizeWindows(boo
     }
   }
   return true;
 }
 
 bool
 nsGlobalWindow::AlertOrConfirm(bool aAlert,
                                const nsAString& aMessage,
-                               mozilla::ErrorResult& aError)
+                               const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                               ErrorResult& aError)
 {
   // XXX This method is very similar to nsGlobalWindow::Prompt, make
   // sure any modifications here don't need to happen over there!
   MOZ_ASSERT(IsOuterWindow());
+  MOZ_ASSERT(aSubjectPrincipal.isSome());
 
   if (!AreDialogsEnabled()) {
     // Just silently return.  In the case of alert(), the return value is
     // ignored.  In the case of confirm(), returning false is the same thing as
     // would happen if the user cancels.
     return false;
   }
 
@@ -6823,17 +6827,17 @@ nsGlobalWindow::AlertOrConfirm(bool aAle
   // the whole time a modal dialog is open.
   nsAutoPopupStatePusher popupStatePusher(openAbused, true);
 
   // Before bringing up the window, unsuppress painting and flush
   // pending reflows.
   EnsureReflowFlushAndPaint();
 
   nsAutoString title;
-  MakeScriptDialogTitle(title);
+  MakeScriptDialogTitle(title, aSubjectPrincipal.value());
 
   // Remove non-terminating null characters from the
   // string. See bug #310037.
   nsAutoString final;
   nsContentUtils::StripNullChars(aMessage, final);
 
   nsresult rv;
   nsCOMPtr<nsIPromptFactory> promptFac =
@@ -6876,63 +6880,79 @@ nsGlobalWindow::AlertOrConfirm(bool aAle
                prompt->Alert(title.get(), final.get()) :
                prompt->Confirm(title.get(), final.get(), &result);
   }
 
   return result;
 }
 
 void
-nsGlobalWindow::Alert(mozilla::ErrorResult& aError)
-{
-  MOZ_ASSERT(IsInnerWindow());
-  Alert(EmptyString(), aError);
-}
-
-void
-nsGlobalWindow::AlertOuter(const nsAString& aMessage, mozilla::ErrorResult& aError)
+nsGlobalWindow::Alert(const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                      ErrorResult& aError)
+{
+  MOZ_ASSERT(IsInnerWindow());
+  Alert(EmptyString(), aSubjectPrincipal, aError);
+}
+
+void
+nsGlobalWindow::AlertOuter(const nsAString& aMessage,
+                           const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                           ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
-  AlertOrConfirm(/* aAlert = */ true, aMessage, aError);
-}
-
-void
-nsGlobalWindow::Alert(const nsAString& aMessage, mozilla::ErrorResult& aError)
-{
-  FORWARD_TO_OUTER_OR_THROW(AlertOuter, (aMessage, aError), aError, );
+  AlertOrConfirm(/* aAlert = */ true, aMessage, aSubjectPrincipal, aError);
+}
+
+void
+nsGlobalWindow::Alert(const nsAString& aMessage,
+                      const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                      ErrorResult& aError)
+{
+  FORWARD_TO_OUTER_OR_THROW(AlertOuter, (aMessage, aSubjectPrincipal, aError),
+                            aError, );
 }
 
 bool
-nsGlobalWindow::ConfirmOuter(const nsAString& aMessage, ErrorResult& aError)
+nsGlobalWindow::ConfirmOuter(const nsAString& aMessage,
+                             const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                             ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
-  return AlertOrConfirm(/* aAlert = */ false, aMessage, aError);
+  return AlertOrConfirm(/* aAlert = */ false, aMessage, aSubjectPrincipal,
+                        aError);
 }
 
 bool
-nsGlobalWindow::Confirm(const nsAString& aMessage, ErrorResult& aError)
-{
-  FORWARD_TO_OUTER_OR_THROW(ConfirmOuter, (aMessage, aError), aError, false);
+nsGlobalWindow::Confirm(const nsAString& aMessage,
+                        const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                        ErrorResult& aError)
+{
+  FORWARD_TO_OUTER_OR_THROW(ConfirmOuter, (aMessage, aSubjectPrincipal, aError),
+                            aError, false);
 }
 
 already_AddRefed<Promise>
 nsGlobalWindow::Fetch(const RequestOrUSVString& aInput,
                       const RequestInit& aInit, ErrorResult& aRv)
 {
   return FetchRequest(this, aInput, aInit, aRv);
 }
 
 void
-nsGlobalWindow::PromptOuter(const nsAString& aMessage, const nsAString& aInitial,
-                            nsAString& aReturn, ErrorResult& aError)
+nsGlobalWindow::PromptOuter(const nsAString& aMessage,
+                            const nsAString& aInitial,
+                            nsAString& aReturn,
+                            const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                            ErrorResult& aError)
 {
   // XXX This method is very similar to nsGlobalWindow::AlertOrConfirm, make
   // sure any modifications here don't need to happen over there!
   MOZ_RELEASE_ASSERT(IsOuterWindow());
+  MOZ_ASSERT(aSubjectPrincipal.isSome());
 
   SetDOMStringToNull(aReturn);
 
   if (!AreDialogsEnabled()) {
     // Return null, as if the user just canceled the prompt.
     return;
   }
 
@@ -6941,17 +6961,17 @@ nsGlobalWindow::PromptOuter(const nsAStr
   // the whole time a modal dialog is open.
   nsAutoPopupStatePusher popupStatePusher(openAbused, true);
 
   // Before bringing up the window, unsuppress painting and flush
   // pending reflows.
   EnsureReflowFlushAndPaint();
 
   nsAutoString title;
-  MakeScriptDialogTitle(title);
+  MakeScriptDialogTitle(title, aSubjectPrincipal.value());
 
   // Remove non-terminating null characters from the
   // string. See bug #310037.
   nsAutoString fixedMessage, fixedInitial;
   nsContentUtils::StripNullChars(aMessage, fixedMessage);
   nsContentUtils::StripNullChars(aInitial, fixedInitial);
 
   nsresult rv;
@@ -7001,19 +7021,23 @@ nsGlobalWindow::PromptOuter(const nsAStr
 
   if (ok && outValue) {
     aReturn.Assign(outValue);
   }
 }
 
 void
 nsGlobalWindow::Prompt(const nsAString& aMessage, const nsAString& aInitial,
-                       nsAString& aReturn, ErrorResult& aError)
-{
-  FORWARD_TO_OUTER_OR_THROW(PromptOuter, (aMessage, aInitial, aReturn, aError),
+                       nsAString& aReturn,
+                       const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                       ErrorResult& aError)
+{
+  FORWARD_TO_OUTER_OR_THROW(PromptOuter,
+                            (aMessage, aInitial, aReturn, aSubjectPrincipal,
+                             aError),
                             aError, );
 }
 
 void
 nsGlobalWindow::FocusOuter(ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
@@ -8244,19 +8268,21 @@ nsGlobalWindow::CallerInnerWindow()
   nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
   return nsGlobalWindow::Cast(win);
 }
 
 void
 nsGlobalWindow::PostMessageMozOuter(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                                     const nsAString& aTargetOrigin,
                                     JS::Handle<JS::Value> aTransfer,
+                                    const Maybe<nsIPrincipal*>& aSubjectPrincipal,
                                     ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
+  MOZ_ASSERT(aSubjectPrincipal.isSome());
 
   //
   // Window.postMessage is an intentional subversion of the same-origin policy.
   // As such, this code must be particularly careful in the information it
   // exposes to calling code.
   //
   // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html
   //
@@ -8328,20 +8354,18 @@ nsGlobalWindow::PostMessageMozOuter(JSCo
       return;
     }
 
     if (NS_FAILED(originURI->SetUserPass(EmptyCString())) ||
         NS_FAILED(originURI->SetPath(EmptyCString()))) {
       return;
     }
 
-    nsCOMPtr<nsIPrincipal> principal = nsContentUtils::SubjectPrincipal();
-    MOZ_ASSERT(principal);
-
-    PrincipalOriginAttributes attrs = BasePrincipal::Cast(principal)->OriginAttributesRef();
+    PrincipalOriginAttributes attrs =
+      BasePrincipal::Cast(aSubjectPrincipal.value())->OriginAttributesRef();
     // Create a nsIPrincipal inheriting the app/browser attributes from the
     // caller.
     providedPrincipal = BasePrincipal::CreateCodebasePrincipal(originURI, attrs);
     if (NS_WARN_IF(!providedPrincipal)) {
       return;
     }
   }
 
@@ -8369,27 +8393,30 @@ nsGlobalWindow::PostMessageMozOuter(JSCo
 
   aError = NS_DispatchToCurrentThread(event);
 }
 
 void
 nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                                const nsAString& aTargetOrigin,
                                JS::Handle<JS::Value> aTransfer,
+                               const Maybe<nsIPrincipal*>& aSubjectPrincipal,
                                ErrorResult& aError)
 {
   FORWARD_TO_OUTER_OR_THROW(PostMessageMozOuter,
-                            (aCx, aMessage, aTargetOrigin, aTransfer, aError),
+                            (aCx, aMessage, aTargetOrigin, aTransfer,
+                             aSubjectPrincipal, aError),
                             aError, );
 }
 
 void
 nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                                const nsAString& aTargetOrigin,
-                               const Optional<Sequence<JS::Value > >& aTransfer,
+                               const Optional<Sequence<JS::Value>>& aTransfer,
+                               const Maybe<nsIPrincipal*>& aSubjectPrincipal,
                                ErrorResult& aError)
 {
   JS::Rooted<JS::Value> transferArray(aCx, JS::UndefinedValue());
   if (aTransfer.WasPassed()) {
     const Sequence<JS::Value >& values = aTransfer.Value();
 
     // The input sequence only comes from the generated bindings code, which
     // ensures it is rooted.
@@ -8398,17 +8425,18 @@ nsGlobalWindow::PostMessageMoz(JSContext
 
     transferArray = JS::ObjectOrNullValue(JS_NewArrayObject(aCx, elements));
     if (transferArray.isNull()) {
       aError.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
     }
   }
 
-  PostMessageMoz(aCx, aMessage, aTargetOrigin, transferArray, aError);
+  PostMessageMoz(aCx, aMessage, aTargetOrigin, transferArray,
+                 aSubjectPrincipal, aError);
 }
 
 class nsCloseEvent : public Runnable {
 
   RefPtr<nsGlobalWindow> mWindow;
   bool mIndirect;
 
   nsCloseEvent(nsGlobalWindow *aWindow, bool aIndirect)
@@ -9006,42 +9034,45 @@ nsGlobalWindow::CacheXBLPrototypeHandler
     mCachedXBLPrototypeHandlers = new nsJSThingHashtable<nsPtrHashKey<nsXBLPrototypeHandler>, JSObject*>();
     PreserveWrapper(ToSupports(this));
   }
 
   mCachedXBLPrototypeHandlers->Put(aKey, aHandler);
 }
 
 Element*
-nsGlobalWindow::GetFrameElementOuter()
+nsGlobalWindow::GetFrameElementOuter(const Maybe<nsIPrincipal*>& aSubjectPrincipal)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
+  MOZ_ASSERT(aSubjectPrincipal.isSome());
 
   if (!mDocShell || mDocShell->GetIsMozBrowserOrApp()) {
     return nullptr;
   }
 
   // Per HTML5, the frameElement getter returns null in cross-origin situations.
   Element* element = GetRealFrameElementOuter();
   if (!element) {
     return nullptr;
   }
 
-  if (!nsContentUtils::SubjectPrincipal()->
+  if (!aSubjectPrincipal.value()->
          SubsumesConsideringDomain(element->NodePrincipal())) {
     return nullptr;
   }
 
   return element;
 }
 
 Element*
-nsGlobalWindow::GetFrameElement(ErrorResult& aError)
-{
-  FORWARD_TO_OUTER_OR_THROW(GetFrameElementOuter, (), aError, nullptr);
+nsGlobalWindow::GetFrameElement(const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                                ErrorResult& aError)
+{
+  FORWARD_TO_OUTER_OR_THROW(GetFrameElementOuter, (aSubjectPrincipal), aError,
+                            nullptr);
 }
 
 Element*
 nsGlobalWindow::GetRealFrameElementOuter()
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   if (!mDocShell) {
@@ -9203,32 +9234,36 @@ nsGlobalWindow::ConvertDialogOptions(con
         !TokenizeDialogOptions(token, iter, end) ||
         !token.EqualsLiteral(";")) {
       break;
     }
   }
 }
 
 already_AddRefed<nsIVariant>
-nsGlobalWindow::ShowModalDialogOuter(const nsAString& aUrl, nsIVariant* aArgument,
-                                     const nsAString& aOptions, ErrorResult& aError)
+nsGlobalWindow::ShowModalDialogOuter(const nsAString& aUrl,
+                                     nsIVariant* aArgument,
+                                     const nsAString& aOptions,
+                                     const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                                     ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
+  MOZ_ASSERT(aSubjectPrincipal.isSome());
 
   if (mDoc) {
     mDoc->WarnOnceAbout(nsIDocument::eShowModalDialog);
   }
 
   if (!IsShowModalDialogEnabled()) {
     aError.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   RefPtr<DialogValueHolder> argHolder =
-    new DialogValueHolder(nsContentUtils::SubjectPrincipal(), aArgument);
+    new DialogValueHolder(aSubjectPrincipal.value(), aArgument);
 
   // Before bringing up the window/dialog, unsuppress painting and flush
   // pending reflows.
   EnsureReflowFlushAndPaint();
 
   if (!AreDialogsEnabled()) {
     // We probably want to keep throwing here; silently doing nothing is a bit
     // weird given the typical use cases of showModalDialog().
@@ -9274,41 +9309,45 @@ nsGlobalWindow::ShowModalDialogOuter(con
   aError = dialog->GetReturnValue(getter_AddRefs(retVal));
   MOZ_ASSERT(!aError.Failed());
 
   return retVal.forget();
 }
 
 already_AddRefed<nsIVariant>
 nsGlobalWindow::ShowModalDialog(const nsAString& aUrl, nsIVariant* aArgument,
-                                const nsAString& aOptions, ErrorResult& aError)
+                                const nsAString& aOptions,
+                                const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                                ErrorResult& aError)
 {
   FORWARD_TO_OUTER_OR_THROW(ShowModalDialogOuter,
-                            (aUrl, aArgument, aOptions, aError), aError,
-                            nullptr);
+                            (aUrl, aArgument, aOptions, aSubjectPrincipal,
+                             aError), aError, nullptr);
 }
 
 void
 nsGlobalWindow::ShowModalDialog(JSContext* aCx, const nsAString& aUrl,
                                 JS::Handle<JS::Value> aArgument,
                                 const nsAString& aOptions,
                                 JS::MutableHandle<JS::Value> aRetval,
+                                const Maybe<nsIPrincipal*>& aSubjectPrincipal,
                                 ErrorResult& aError)
 {
   MOZ_ASSERT(IsInnerWindow());
 
   nsCOMPtr<nsIVariant> args;
   aError = nsContentUtils::XPConnect()->JSToVariant(aCx,
                                                     aArgument,
                                                     getter_AddRefs(args));
   if (aError.Failed()) {
     return;
   }
 
-  nsCOMPtr<nsIVariant> retVal = ShowModalDialog(aUrl, args, aOptions, aError);
+  nsCOMPtr<nsIVariant> retVal =
+    ShowModalDialog(aUrl, args, aOptions, aSubjectPrincipal, aError);
   if (aError.Failed()) {
     return;
   }
 
   JS::Rooted<JS::Value> result(aCx);
   if (retVal) {
     aError = nsContentUtils::XPConnect()->VariantToJS(aCx,
                                                       FastGetGlobalJSObject(),
@@ -10971,17 +11010,17 @@ nsGlobalWindow::ShowSlowScriptDialog()
   if (Preferences::GetBool("dom.always_stop_slow_scripts")) {
     return KillSlowScript;
   }
 
   // If it isn't safe to run script, then it isn't safe to bring up the prompt
   // (since that spins the event loop). In that (rare) case, we just kill the
   // script and report a warning.
   if (!nsContentUtils::IsSafeToRunScript()) {
-    JS_ReportWarning(cx, "A long running script was terminated");
+    JS_ReportWarningASCII(cx, "A long running script was terminated");
     return KillSlowScript;
   }
 
   // If our document is not active, just kill the script: we've been unloaded
   if (!AsInner()->HasActiveDocument()) {
     return KillSlowScript;
   }
 
@@ -13944,42 +13983,46 @@ NS_INTERFACE_MAP_END_INHERITING(nsGlobal
 
 NS_IMPL_ADDREF_INHERITED(nsGlobalModalWindow, nsGlobalWindow)
 NS_IMPL_RELEASE_INHERITED(nsGlobalModalWindow, nsGlobalWindow)
 
 
 void
 nsGlobalWindow::GetDialogArgumentsOuter(JSContext* aCx,
                                         JS::MutableHandle<JS::Value> aRetval,
+                                        const Maybe<nsIPrincipal*>& aSubjectPrincipal,
                                         ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
+  MOZ_ASSERT(aSubjectPrincipal.isSome());
   MOZ_ASSERT(IsModalContentWindow(),
              "This should only be called on modal windows!");
 
   if (!mDialogArguments) {
     MOZ_ASSERT(mIsClosed, "This window should be closed!");
     aRetval.setUndefined();
     return;
   }
 
   // This does an internal origin check, and returns undefined if the subject
   // does not subsumes the origin of the arguments.
   JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());
   JSAutoCompartment ac(aCx, wrapper);
-  mDialogArguments->Get(aCx, wrapper, nsContentUtils::SubjectPrincipal(),
+  mDialogArguments->Get(aCx, wrapper, aSubjectPrincipal.value(),
                         aRetval, aError);
 }
 
 void
 nsGlobalWindow::GetDialogArguments(JSContext* aCx,
                                    JS::MutableHandle<JS::Value> aRetval,
+                                   const Maybe<nsIPrincipal*>& aSubjectPrincipal,
                                    ErrorResult& aError)
 {
-  FORWARD_TO_OUTER_OR_THROW(GetDialogArgumentsOuter, (aCx, aRetval, aError),
+  FORWARD_TO_OUTER_OR_THROW(GetDialogArgumentsOuter,
+                            (aCx, aRetval, aSubjectPrincipal, aError),
                             aError, );
 }
 
 /* static */ already_AddRefed<nsGlobalModalWindow>
 nsGlobalModalWindow::Create(nsGlobalWindow *aOuterWindow)
 {
   RefPtr<nsGlobalModalWindow> window = new nsGlobalModalWindow(aOuterWindow);
   window->InitWasOffline();
@@ -14009,38 +14052,42 @@ void
 nsGlobalWindow::InitWasOffline()
 {
   mWasOffline = NS_IsOffline() || NS_IsAppOffline(GetPrincipal());
 }
 
 void
 nsGlobalWindow::GetReturnValueOuter(JSContext* aCx,
                                     JS::MutableHandle<JS::Value> aReturnValue,
+                                    const Maybe<nsIPrincipal*>& aSubjectPrincipal,
                                     ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
+  MOZ_ASSERT(aSubjectPrincipal.isSome());
   MOZ_ASSERT(IsModalContentWindow(),
              "This should only be called on modal windows!");
 
   if (mReturnValue) {
     JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());
     JSAutoCompartment ac(aCx, wrapper);
-    mReturnValue->Get(aCx, wrapper, nsContentUtils::SubjectPrincipal(),
+    mReturnValue->Get(aCx, wrapper, aSubjectPrincipal.value(),
                       aReturnValue, aError);
   } else {
     aReturnValue.setUndefined();
   }
 }
 
 void
 nsGlobalWindow::GetReturnValue(JSContext* aCx,
                                JS::MutableHandle<JS::Value> aReturnValue,
+                               const Maybe<nsIPrincipal*>& aSubjectPrincipal,
                                ErrorResult& aError)
 {
-  FORWARD_TO_OUTER_OR_THROW(GetReturnValueOuter, (aCx, aReturnValue, aError),
+  FORWARD_TO_OUTER_OR_THROW(GetReturnValueOuter,
+                            (aCx, aReturnValue, aSubjectPrincipal, aError),
                             aError, );
 }
 
 NS_IMETHODIMP
 nsGlobalModalWindow::GetReturnValue(nsIVariant **aRetVal)
 {
   FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(GetReturnValue, (aRetVal), NS_OK);
 
@@ -14050,38 +14097,42 @@ nsGlobalModalWindow::GetReturnValue(nsIV
     return NS_OK;
   }
   return mReturnValue->Get(nsContentUtils::SubjectPrincipal(), aRetVal);
 }
 
 void
 nsGlobalWindow::SetReturnValueOuter(JSContext* aCx,
                                     JS::Handle<JS::Value> aReturnValue,
+                                    const Maybe<nsIPrincipal*>& aSubjectPrincipal,
                                     ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
+  MOZ_ASSERT(aSubjectPrincipal.isSome());
   MOZ_ASSERT(IsModalContentWindow(),
              "This should only be called on modal windows!");
 
   nsCOMPtr<nsIVariant> returnValue;
   aError =
     nsContentUtils::XPConnect()->JSToVariant(aCx, aReturnValue,
                                              getter_AddRefs(returnValue));
   if (!aError.Failed()) {
-    mReturnValue = new DialogValueHolder(nsContentUtils::SubjectPrincipal(),
+    mReturnValue = new DialogValueHolder(aSubjectPrincipal.value(),
                                          returnValue);
   }
 }
 
 void
 nsGlobalWindow::SetReturnValue(JSContext* aCx,
                                JS::Handle<JS::Value> aReturnValue,
+                               const Maybe<nsIPrincipal*>& aSubjectPrincipal,
                                ErrorResult& aError)
 {
-  FORWARD_TO_OUTER_OR_THROW(SetReturnValueOuter, (aCx, aReturnValue, aError),
+  FORWARD_TO_OUTER_OR_THROW(SetReturnValueOuter,
+                            (aCx, aReturnValue, aSubjectPrincipal, aError),
                             aError, );
 }
 
 NS_IMETHODIMP
 nsGlobalModalWindow::SetReturnValue(nsIVariant *aRetVal)
 {
   FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(SetReturnValue, (aRetVal), NS_OK);
 
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -927,18 +927,21 @@ public:
   already_AddRefed<nsPIDOMWindowOuter> GetOpener() override;
   void SetOpener(JSContext* aCx, JS::Handle<JS::Value> aOpener,
                  mozilla::ErrorResult& aError);
   already_AddRefed<nsPIDOMWindowOuter> GetParentOuter();
   already_AddRefed<nsPIDOMWindowOuter> GetParent(mozilla::ErrorResult& aError);
   already_AddRefed<nsPIDOMWindowOuter> GetParent() override;
   nsPIDOMWindowOuter* GetScriptableParent() override;
   nsPIDOMWindowOuter* GetScriptableParentOrNull() override;
-  mozilla::dom::Element* GetFrameElementOuter();
-  mozilla::dom::Element* GetFrameElement(mozilla::ErrorResult& aError);
+  mozilla::dom::Element*
+  GetFrameElementOuter(const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal);
+  mozilla::dom::Element*
+  GetFrameElement(const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                  mozilla::ErrorResult& aError);
   already_AddRefed<nsIDOMElement> GetFrameElement() override;
   already_AddRefed<nsPIDOMWindowOuter>
   OpenOuter(const nsAString& aUrl,
             const nsAString& aName,
             const nsAString& aOptions,
             mozilla::ErrorResult& aError);
   already_AddRefed<nsPIDOMWindowOuter>
   Open(const nsAString& aUrl,
@@ -970,42 +973,58 @@ public:
   static bool
   TokenizeDialogOptions(nsAString& aToken, nsAString::const_iterator& aIter,
                         nsAString::const_iterator aEnd);
   static void
   ConvertDialogOptions(const nsAString& aOptions, nsAString& aResult);
 
 protected:
   bool AlertOrConfirm(bool aAlert, const nsAString& aMessage,
+                      const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                       mozilla::ErrorResult& aError);
 
 public:
-  void Alert(mozilla::ErrorResult& aError);
-  void AlertOuter(const nsAString& aMessage, mozilla::ErrorResult& aError);
-  void Alert(const nsAString& aMessage, mozilla::ErrorResult& aError);
+  void Alert(const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+             mozilla::ErrorResult& aError);
+  void AlertOuter(const nsAString& aMessage,
+                  const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                  mozilla::ErrorResult& aError);
+  void Alert(const nsAString& aMessage,
+             const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+             mozilla::ErrorResult& aError);
+  bool ConfirmOuter(const nsAString& aMessage,
+                    const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                    mozilla::ErrorResult& aError);
+  bool Confirm(const nsAString& aMessage,
+               const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+               mozilla::ErrorResult& aError);
+  void PromptOuter(const nsAString& aMessage, const nsAString& aInitial,
+                   nsAString& aReturn,
+                   const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                   mozilla::ErrorResult& aError);
+  void Prompt(const nsAString& aMessage, const nsAString& aInitial,
+              nsAString& aReturn,
+              const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+              mozilla::ErrorResult& aError);
   already_AddRefed<mozilla::dom::cache::CacheStorage> GetCaches(mozilla::ErrorResult& aRv);
-  bool ConfirmOuter(const nsAString& aMessage, mozilla::ErrorResult& aError);
-  bool Confirm(const nsAString& aMessage, mozilla::ErrorResult& aError);
   already_AddRefed<mozilla::dom::Promise> Fetch(const mozilla::dom::RequestOrUSVString& aInput,
                                                 const mozilla::dom::RequestInit& aInit,
                                                 mozilla::ErrorResult& aRv);
-  void PromptOuter(const nsAString& aMessage, const nsAString& aInitial,
-                   nsAString& aReturn, mozilla::ErrorResult& aError);
-  void Prompt(const nsAString& aMessage, const nsAString& aInitial,
-              nsAString& aReturn, mozilla::ErrorResult& aError);
   void PrintOuter(mozilla::ErrorResult& aError);
   void Print(mozilla::ErrorResult& aError);
   void ShowModalDialog(JSContext* aCx, const nsAString& aUrl,
                        JS::Handle<JS::Value> aArgument,
                        const nsAString& aOptions,
                        JS::MutableHandle<JS::Value> aRetval,
+                       const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                        mozilla::ErrorResult& aError);
   void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                       const nsAString& aTargetOrigin,
                       const mozilla::dom::Optional<mozilla::dom::Sequence<JS::Value > >& aTransfer,
+                      const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                       mozilla::ErrorResult& aError);
   int32_t SetTimeout(JSContext* aCx, mozilla::dom::Function& aFunction,
                      int32_t aTimeout,
                      const mozilla::dom::Sequence<JS::Value>& aArguments,
                      mozilla::ErrorResult& aError);
   int32_t SetTimeout(JSContext* aCx, const nsAString& aHandler,
                      int32_t aTimeout,
                      const mozilla::dom::Sequence<JS::Value>& /* unused */,
@@ -1240,26 +1259,32 @@ public:
   nsIMessageBroadcaster* GetMessageManager(mozilla::ErrorResult& aError);
   nsIMessageBroadcaster* GetGroupMessageManager(const nsAString& aGroup,
                                                 mozilla::ErrorResult& aError);
   void BeginWindowMove(mozilla::dom::Event& aMouseDownEvent,
                        mozilla::dom::Element* aPanel,
                        mozilla::ErrorResult& aError);
 
   void GetDialogArgumentsOuter(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
+                               const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                                mozilla::ErrorResult& aError);
   void GetDialogArguments(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
+                          const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                           mozilla::ErrorResult& aError);
   void GetReturnValueOuter(JSContext* aCx, JS::MutableHandle<JS::Value> aReturnValue,
+                           const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                            mozilla::ErrorResult& aError);
   void GetReturnValue(JSContext* aCx, JS::MutableHandle<JS::Value> aReturnValue,
+                      const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                       mozilla::ErrorResult& aError);
   void SetReturnValueOuter(JSContext* aCx, JS::Handle<JS::Value> aReturnValue,
+                           const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                            mozilla::ErrorResult& aError);
   void SetReturnValue(JSContext* aCx, JS::Handle<JS::Value> aReturnValue,
+                      const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                       mozilla::ErrorResult& aError);
 
   void GetInterface(JSContext* aCx, nsIJSID* aIID,
                     JS::MutableHandle<JS::Value> aRetval,
                     mozilla::ErrorResult& aError);
 
   already_AddRefed<nsWindowRoot> GetWindowRootOuter();
   already_AddRefed<nsWindowRoot> GetWindowRoot(mozilla::ErrorResult& aError);
@@ -1540,17 +1565,18 @@ public:
   // Outer windows only.
   // Arguments to this function should have values in app units
   void SetCSSViewportWidthAndHeight(nscoord width, nscoord height);
   // Arguments to this function should have values in device pixels
   nsresult SetDocShellWidthAndHeight(int32_t width, int32_t height);
 
   static bool CanSetProperty(const char *aPrefName);
 
-  static void MakeScriptDialogTitle(nsAString &aOutTitle);
+  static void MakeScriptDialogTitle(nsAString& aOutTitle,
+                                    nsIPrincipal* aSubjectPrincipal);
 
   // Outer windows only.
   bool CanMoveResizeWindows(bool aCallerIsChrome);
 
   // If aDoFlush is true, we'll flush our own layout; otherwise we'll try to
   // just flush our parent and only flush ourselves if we think we need to.
   // Outer windows only.
   mozilla::CSSIntPoint GetScrollXY(bool aDoFlush);
@@ -1671,29 +1697,35 @@ protected:
   // Returns CSS pixels based on primary screen.  Outer windows only.
   mozilla::CSSIntPoint GetScreenXY(mozilla::ErrorResult& aError);
 
   nsGlobalWindow* InnerForSetTimeoutOrInterval(mozilla::ErrorResult& aError);
 
   void PostMessageMozOuter(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                            const nsAString& aTargetOrigin,
                            JS::Handle<JS::Value> aTransfer,
+                           const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                            mozilla::ErrorResult& aError);
   void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                       const nsAString& aTargetOrigin,
                       JS::Handle<JS::Value> aTransfer,
+                      const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                       mozilla::ErrorResult& aError);
 
   already_AddRefed<nsIVariant>
     ShowModalDialogOuter(const nsAString& aUrl, nsIVariant* aArgument,
-                         const nsAString& aOptions, mozilla::ErrorResult& aError);
+                         const nsAString& aOptions,
+                         const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                         mozilla::ErrorResult& aError);
 
   already_AddRefed<nsIVariant>
     ShowModalDialog(const nsAString& aUrl, nsIVariant* aArgument,
-                    const nsAString& aOptions, mozilla::ErrorResult& aError);
+                    const nsAString& aOptions,
+                    const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+                    mozilla::ErrorResult& aError);
 
   // Ask the user if further dialogs should be blocked, if dialogs are currently
   // being abused. This is used in the cases where we have no modifiable UI to
   // show, in that case we show a separate dialog to ask this question.
   bool ConfirmDialogIfNeeded();
 
   // Helper called after moving/resizing, to update docShell's presContext
   // if we have caused a resolution change by moving across monitors.
--- a/dom/base/nsHTMLContentSerializer.cpp
+++ b/dom/base/nsHTMLContentSerializer.cpp
@@ -526,17 +526,17 @@ nsHTMLContentSerializer::AppendAndTransl
     nsReadingIterator<char16_t> iter;
 
     const uint8_t* entityTable = mInAttribute ? kAttrEntities : kEntities;
     nsAutoCString entityReplacement;
 
     for (aStr.BeginReading(iter);
          iter != done_reading;
          iter.advance(int32_t(advanceLength))) {
-      uint32_t fragmentLength = iter.size_forward();
+      uint32_t fragmentLength = done_reading - iter;
       uint32_t lengthReplaced = 0; // the number of UTF-16 codepoints
                                     //  replaced by a particular entity
       const char16_t* c = iter.get();
       const char16_t* fragmentStart = c;
       const char16_t* fragmentEnd = c + fragmentLength;
       const char* entityText = nullptr;
       const char* fullConstEntityText = nullptr;
       char* fullEntityText = nullptr;
--- a/dom/base/nsNodeInfoManager.cpp
+++ b/dom/base/nsNodeInfoManager.cpp
@@ -6,16 +6,17 @@
 
 /*
  * A class for handing out nodeinfos and ensuring sharing of them as needed.
  */
 
 #include "nsNodeInfoManager.h"
 
 #include "mozilla/DebugOnly.h"
+#include "mozilla/Telemetry.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "mozilla/dom/NodeInfoInlines.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsIAtom.h"
 #include "nsIDocument.h"
 #include "nsIPrincipal.h"
 #include "nsIURI.h"
@@ -384,16 +385,21 @@ void
 nsNodeInfoManager::SetDocumentPrincipal(nsIPrincipal *aPrincipal)
 {
   mPrincipal = nullptr;
   if (!aPrincipal) {
     aPrincipal = mDefaultPrincipal;
   }
 
   NS_ASSERTION(aPrincipal, "Must have principal by this point!");
+  MOZ_DIAGNOSTIC_ASSERT(!nsContentUtils::IsExpandedPrincipal(aPrincipal),
+                        "Documents shouldn't have an expanded principal");
+  if (nsContentUtils::IsExpandedPrincipal(aPrincipal)) {
+    Telemetry::Accumulate(Telemetry::DOCUMENT_WITH_EXPANDED_PRINCIPAL, 1);
+  }
 
   mPrincipal = aPrincipal;
 }
 
 void
 nsNodeInfoManager::RemoveNodeInfo(NodeInfo *aNodeInfo)
 {
   NS_PRECONDITION(aNodeInfo, "Trying to remove null nodeinfo from manager!");
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -23,16 +23,17 @@
 
 class nsGlobalWindow;
 class nsIArray;
 class nsIContent;
 class nsICSSDeclaration;
 class nsIDocShell;
 class nsIDocument;
 class nsIIdleObserver;
+class nsIPrincipal;
 class nsIScriptTimeoutHandler;
 class nsIURI;
 class nsPIDOMWindowInner;
 class nsPIDOMWindowOuter;
 class nsPIWindowRoot;
 class nsXBLPrototypeHandler;
 struct nsTimeout;
 
--- a/dom/base/nsPlainTextSerializer.cpp
+++ b/dom/base/nsPlainTextSerializer.cpp
@@ -1577,17 +1577,17 @@ nsPlainTextSerializer::Write(const nsASt
         break;
     }
   }
 
   // We have two major codepaths here. One that does preformatted text and one
   // that does normal formatted text. The one for preformatted text calls
   // Output directly while the other code path goes through AddToLine.
   if ((mPreFormattedMail && !mWrapColumn) || (IsInPre() && !mPreFormattedMail)
-      || (mSpanLevel > 0 && mEmptyLines >= 0 && str.First() == char16_t('>'))) {
+      || (mSpanLevel > 0 && mEmptyLines >= 0 && IsQuotedLine(str))) {
     // No intelligent wrapping.
 
     // This mustn't be mixed with intelligent wrapping without clearing
     // the mCurrentLine buffer before!!!
     NS_ASSERTION(mCurrentLine.IsEmpty() || (IsInPre() && !mPreFormattedMail),
                  "Mixed wrapping data and nonwrapping data on the same line");
     if (!mCurrentLine.IsEmpty()) {
       FlushLine();
@@ -1652,20 +1652,21 @@ nsPlainTextSerializer::Write(const nsASt
           // over the LF.
           bol++;
         }
       }
 
       mCurrentLine.Truncate();
       if (mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
         if ((outputLineBreak || !spacesOnly) && // bugs 261467,125928
+            !IsQuotedLine(stringpart) &&
             !stringpart.EqualsLiteral("-- ") &&
             !stringpart.EqualsLiteral("- -- "))
           stringpart.Trim(" ", false, true, true);
-        if (IsSpaceStuffable(stringpart.get()) && stringpart[0] != '>')
+        if (IsSpaceStuffable(stringpart.get()) && !IsQuotedLine(stringpart))
           mCurrentLine.Append(char16_t(' '));
       }
       mCurrentLine.Append(stringpart);
 
       if (outputQuotes) {
         // Note: this call messes with mAtFirstColumn
         OutputQuotesAndIndent();
       }
--- a/dom/base/nsPlainTextSerializer.h
+++ b/dom/base/nsPlainTextSerializer.h
@@ -105,16 +105,21 @@ private:
     return !(mFlags & nsIDocumentEncoder::OutputDisallowLineBreaking);
   }
 
   inline bool DoOutput()
   {
     return mHeadLevel == 0;
   }
 
+  inline bool IsQuotedLine(const nsAString& aLine)
+  {
+    return !aLine.IsEmpty() && aLine.First() == char16_t('>');
+  }
+
   // Stack handling functions
   bool GetLastBool(const nsTArray<bool>& aStack);
   void SetLastBool(nsTArray<bool>& aStack, bool aValue);
   void PushBool(nsTArray<bool>& aStack, bool aValue);
   bool PopBool(nsTArray<bool>& aStack);
 
   bool ShouldReplaceContainerWithPlaceholder(nsIAtom* aTag);
   bool IsIgnorableRubyAnnotation(nsIAtom* aTag);
--- a/dom/base/nsXMLContentSerializer.cpp
+++ b/dom/base/nsXMLContentSerializer.cpp
@@ -653,36 +653,34 @@ nsXMLContentSerializer::SerializeAttr(co
     // character entity references, ignoring the value of aDoEscapeEntities.
     // See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2.2 for
     // the standard on character entity references in values.  We also have to
     // make sure to escape any '&' characters.
 
     bool bIncludesSingle = false;
     bool bIncludesDouble = false;
     nsAString::const_iterator iCurr, iEnd;
-    uint32_t uiSize, i;
     aValue.BeginReading(iCurr);
     aValue.EndReading(iEnd);
-    for ( ; iCurr != iEnd; iCurr.advance(uiSize) ) {
-      const char16_t * buf = iCurr.get();
-      uiSize = iCurr.size_forward();
-      for ( i = 0; i < uiSize; i++, buf++ ) {
-        if ( *buf == char16_t('\'') )
-        {
-          bIncludesSingle = true;
-          if ( bIncludesDouble ) break;
+    for ( ; iCurr != iEnd; ++iCurr) {
+      if (*iCurr == char16_t('\'')) {
+        bIncludesSingle = true;
+        if (bIncludesDouble) {
+          break;
         }
-        else if ( *buf == char16_t('"') )
-        {
-          bIncludesDouble = true;
-          if ( bIncludesSingle ) break;
+      } else if (*iCurr == char16_t('"')) {
+        bIncludesDouble = true;
+        if (bIncludesSingle) {
+          break;
         }
       }
       // if both have been found we don't need to search further
-      if ( bIncludesDouble && bIncludesSingle ) break;
+      if (bIncludesDouble && bIncludesSingle) {
+        break;
+      }
     }
 
     // Delimiter and escaping is according to the following table
     //    bIncludesDouble     bIncludesSingle     Delimiter       Escape Double Quote
     //    FALSE               FALSE               "               FALSE
     //    FALSE               TRUE                "               FALSE
     //    TRUE                FALSE               '               FALSE
     //    TRUE                TRUE                "               TRUE
@@ -1241,17 +1239,17 @@ nsXMLContentSerializer::AppendAndTransla
   uint32_t advanceLength = 0;
   nsReadingIterator<char16_t> iter;
 
   const uint8_t* entityTable = mInAttribute ? kAttrEntities : kEntities;
 
   for (aStr.BeginReading(iter);
        iter != done_reading;
        iter.advance(int32_t(advanceLength))) {
-    uint32_t fragmentLength = iter.size_forward();
+    uint32_t fragmentLength = done_reading - iter;
     const char16_t* c = iter.get();
     const char16_t* fragmentStart = c;
     const char16_t* fragmentEnd = c + fragmentLength;
     const char* entityText = nullptr;
 
     advanceLength = 0;
     // for each character in this chunk, check if it
     // needs to be replaced
--- a/dom/base/test/TestPlainTextSerializer.cpp
+++ b/dom/base/test/TestPlainTextSerializer.cpp
@@ -125,16 +125,53 @@ TestCJKWithDisallowLineBreaking()
     return NS_ERROR_FAILURE;
   }
 
   passed("HTML to CJK text serialization with OutputDisallowLineBreaking");
 
   return NS_OK;
 }
 
+// Test for ASCII with format=flowed; and quoted lines in preformatted span.
+nsresult
+TestPreformatFlowedQuotes()
+{
+  nsString test;
+  nsString result;
+
+  test.AssignLiteral("<html><body>"
+                     "<span style=\"white-space: pre-wrap;\">"
+                     "&gt; Firefox Firefox Firefox Firefox <br>"
+                     "&gt; Firefox Firefox Firefox Firefox<br>"
+                     "&gt;<br>"
+                     "&gt;&gt; Firefox Firefox Firefox Firefox <br>"
+                     "&gt;&gt; Firefox Firefox Firefox Firefox<br>"
+                     "</span></body></html>");
+
+  ConvertBufToPlainText(test, nsIDocumentEncoder::OutputFormatted |
+                              nsIDocumentEncoder::OutputCRLineBreak |
+                              nsIDocumentEncoder::OutputLFLineBreak |
+                              nsIDocumentEncoder::OutputFormatFlowed);
+
+  // create result case
+  result.AssignLiteral("> Firefox Firefox Firefox Firefox \r\n"
+                       "> Firefox Firefox Firefox Firefox\r\n"
+                       ">\r\n"
+                       ">> Firefox Firefox Firefox Firefox \r\n"
+                       ">> Firefox Firefox Firefox Firefox\r\n");
+  if (!test.Equals(result)) {
+    fail("Wrong HTML to ASCII text serialization with format=flowed; and quoted lines");
+    return NS_ERROR_FAILURE;
+  }
+
+  passed("HTML to ASCII text serialization with format=flowed; and quoted lines");
+
+  return NS_OK;
+}
+
 nsresult
 TestPrettyPrintedHtml()
 {
   nsString test;
   test.AppendLiteral(
     "<html>" NS_LINEBREAK
     "<body>" NS_LINEBREAK
     "  first<br>" NS_LINEBREAK
@@ -262,16 +299,19 @@ TestPlainTextSerializer()
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = TestPreWrapElementForThunderbird();
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = TestCJKWithDisallowLineBreaking();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = TestPreformatFlowedQuotes();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // Add new tests here...
   return NS_OK;
 }
 
 int main(int argc, char** argv)
 {
   ScopedXPCOM xpcom("PlainTextSerializer");
   if (xpcom.failed())
--- a/dom/base/test/browser_use_counters.js
+++ b/dom/base/test/browser_use_counters.js
@@ -35,16 +35,27 @@ add_task(function* () {
   // Check that use counters are incremented by SVGs loaded directly in iframes.
   yield check_use_counter_iframe("file_use_counter_svg_getElementById.svg",
                                  "SVGSVGELEMENT_GETELEMENTBYID");
   yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg",
                                  "SVGSVGELEMENT_CURRENTSCALE_getter");
   yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg",
                                  "SVGSVGELEMENT_CURRENTSCALE_setter");
 
+  // Check that even loads from the imglib cache update use counters.  The
+  // images should still be there, because we just loaded them in the last
+  // set of tests.  But we won't get updated counts for the document
+  // counters, because we won't be re-parsing the SVG documents.
+  yield check_use_counter_iframe("file_use_counter_svg_getElementById.svg",
+                                 "SVGSVGELEMENT_GETELEMENTBYID", false);
+  yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg",
+                                 "SVGSVGELEMENT_CURRENTSCALE_getter", false);
+  yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg",
+                                 "SVGSVGELEMENT_CURRENTSCALE_setter", false);
+
   // Check that use counters are incremented by SVGs loaded as images.
   // Note that SVG images are not permitted to execute script, so we can only
   // check for properties here.
   yield check_use_counter_img("file_use_counter_svg_getElementById.svg",
                               "PROPERTY_FILL");
   yield check_use_counter_img("file_use_counter_svg_currentScale.svg",
                               "PROPERTY_FILL");
 
@@ -59,29 +70,18 @@ add_task(function* () {
                                  "PROPERTY_FILLOPACITY");
   // data: URLs don't correctly propagate to their referring document yet.
   //yield check_use_counter_direct("file_use_counter_svg_fill_pattern_data.svg",
   //                               "PROPERTY_FILL_OPACITY");
 
   // Check that use counters are incremented by SVGs loaded as CSS images in
   // pages loaded in iframes.  Again, SVG images in CSS aren't permitted to
   // execute script, so we need to use properties here.
-  yield check_use_counter_iframe("file_use_counter_svg_background.html",
-                                 "PROPERTY_FILL");
   yield check_use_counter_iframe("file_use_counter_svg_list_style_image.html",
                                  "PROPERTY_FILL");
-
-  // Check that even loads from the imglib cache update use counters.  The
-  // background images should still be there, because we just loaded them
-  // in the last set of tests.  But we won't get updated counts for the
-  // document counters, because we won't be re-parsing the SVG documents.
-  yield check_use_counter_iframe("file_use_counter_svg_background.html",
-                                 "PROPERTY_FILL", false);
-  yield check_use_counter_iframe("file_use_counter_svg_list_style_image.html",
-                                 "PROPERTY_FILL", false);
 });
 
 add_task(function* () {
   let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
   Telemetry.canRecordExtended = gOldParentCanRecord;
 
   yield ContentTask.spawn(gBrowser.selectedBrowser, { oldCanRecord: gOldContentCanRecord }, function (arg) {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
--- a/dom/base/test/file_use_counter_svg_currentScale.svg
+++ b/dom/base/test/file_use_counter_svg_currentScale.svg
@@ -6,13 +6,12 @@
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink">
   <desc>Test graphic for hitting currentScale
   </desc>
   <script type="text/javascript"> <![CDATA[
     document.documentElement.currentScale = document.documentElement.currentScale;
     ]]>
   </script>
-  <image id="i1" x="200" y="200" width="100px" height="80px"
-         xlink:href="no-such-scheme:nothing">
+  <image id="i1" x="200" y="200" width="100px" height="80px">
   </image>
   <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>
 </svg>
--- a/dom/base/test/file_use_counter_svg_getElementById.svg
+++ b/dom/base/test/file_use_counter_svg_getElementById.svg
@@ -2,18 +2,17 @@
 
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
   "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg width="4in" height="3in" version="1.1"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink">
   <desc>Test graphic for hitting getElementById
   </desc>
-  <image id="i1" x="200" y="200" width="100px" height="80px"
-         xlink:href="no-such-scheme:nothing">
+  <image id="i1" x="200" y="200" width="100px" height="80px">
   </image>
   <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>
   <script type="text/javascript"> <![CDATA[
     var image = document.documentElement.getElementById("i1");
     image.addEventListener("load",
                            function() {
                             document.documentElement.removeAttribute("class");
                            },
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -87,17 +87,17 @@ GetErrorArgCount(const ErrNum aErrorNumb
   return GetErrorMessage(nullptr, aErrorNumber)->argCount;
 }
 
 void
 binding_detail::ThrowErrorMessage(JSContext* aCx, const unsigned aErrorNumber, ...)
 {
   va_list ap;
   va_start(ap, aErrorNumber);
-  JS_ReportErrorNumberVA(aCx, GetErrorMessage, nullptr, aErrorNumber, ap);
+  JS_ReportErrorNumberUTF8VA(aCx, GetErrorMessage, nullptr, aErrorNumber, ap);
   va_end(ap);
 }
 
 bool
 ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
                  bool aSecurityError, const char* aInterfaceName)
 {
   NS_ConvertASCIItoUTF16 ifaceName(aInterfaceName);
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -1223,38 +1223,35 @@ inline bool
 HandleNewBindingWrappingFailure(JSContext* cx, JS::Handle<JSObject*> scope,
                                 T& value, JS::MutableHandle<JS::Value> rval)
 {
   return HandleNewBindingWrappingFailureHelper<T>::Wrap(cx, scope, value, rval);
 }
 
 template<bool Fatal>
 inline bool
-EnumValueNotFound(JSContext* cx, JSString* str, const char* type,
-                  const char* sourceDescription)
-{
-  return false;
-}
+EnumValueNotFound(JSContext* cx, JS::HandleString str, const char* type,
+                  const char* sourceDescription);
 
 template<>
 inline bool
-EnumValueNotFound<false>(JSContext* cx, JSString* str, const char* type,
+EnumValueNotFound<false>(JSContext* cx, JS::HandleString str, const char* type,
                          const char* sourceDescription)
 {
   // TODO: Log a warning to the console.
   return true;
 }
 
 template<>
 inline bool
-EnumValueNotFound<true>(JSContext* cx, JSString* str, const char* type,
+EnumValueNotFound<true>(JSContext* cx, JS::HandleString str, const char* type,
                         const char* sourceDescription)
 {
-  JSAutoByteString deflated(cx, str);
-  if (!deflated) {
+  JSAutoByteString deflated;
+  if (!deflated.encodeUtf8(cx, str)) {
     return false;
   }
   return ThrowErrorMessage(cx, MSG_INVALID_ENUM_VALUE, sourceDescription,
                            deflated.ptr(), type);
 }
 
 template<typename CharT>
 inline int
@@ -1284,17 +1281,17 @@ FindEnumStringIndexImpl(const CharT* cha
 }
 
 template<bool InvalidValueFatal>
 inline int
 FindEnumStringIndex(JSContext* cx, JS::Handle<JS::Value> v, const EnumEntry* values,
                     const char* type, const char* sourceDescription, bool* ok)
 {
   // JS_StringEqualsAscii is slow as molasses, so don't use it here.
-  JSString* str = JS::ToString(cx, v);
+  JS::RootedString str(cx, JS::ToString(cx, v));
   if (!str) {
     *ok = false;
     return 0;
   }
 
   {
     int index;
     size_t length;
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -652,17 +652,19 @@ NS_IMETHODIMP JSStackFrame::GetFormatted
     mFormattedStackInitialized = true;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP JSStackFrame::GetNativeSavedFrame(JS::MutableHandle<JS::Value> aSavedFrame)
 {
-  JS::ExposeObjectToActiveJS(mStack);
+  if (mStack) {
+    JS::ExposeObjectToActiveJS(mStack);
+  }
   aSavedFrame.setObjectOrNull(mStack);
   return NS_OK;
 }
 
 NS_IMETHODIMP JSStackFrame::ToString(JSContext* aCx, nsACString& _retval)
 {
   _retval.Truncate();
 
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -2431,17 +2431,19 @@ CanvasRenderingContext2D::CreatePattern(
     // Special case for Canvas, which could be an Azure canvas!
     nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0);
     if (srcCanvas) {
       // This might not be an Azure canvas!
       RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot();
       if (!srcSurf) {
         JSContext* context = nsContentUtils::GetCurrentJSContext();
         if (context) {
-          JS_ReportWarning(context, "CanvasRenderingContext2D.createPattern() failed to snapshot source canvas.");
+          JS_ReportWarningASCII(context,
+                                "CanvasRenderingContext2D.createPattern()"
+                                " failed to snapshot source canvas.");
         }
         aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
         return nullptr;
       }
 
       RefPtr<CanvasPattern> pat =
         new CanvasPattern(this, srcSurf, repeatMode, htmlElement->NodePrincipal(), canvas->IsWriteOnly(), false);
 
@@ -2462,17 +2464,19 @@ CanvasRenderingContext2D::CreatePattern(
   } else {
     // Special case for ImageBitmap
     ImageBitmap& imgBitmap = aSource.GetAsImageBitmap();
     EnsureTarget();
     RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget);
     if (!srcSurf) {
       JSContext* context = nsContentUtils::GetCurrentJSContext();
       if (context) {
-        JS_ReportWarning(context, "CanvasRenderingContext2D.createPattern() failed to prepare source ImageBitmap.");
+        JS_ReportWarningASCII(context,
+                              "CanvasRenderingContext2D.createPattern()"
+                              " failed to prepare source ImageBitmap.");
       }
       aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
       return nullptr;
     }
 
     // An ImageBitmap never taints others so we set principalForSecurityCheck to
     // nullptr and set CORSUsed to true for passing the security check in
     // CanvasUtils::DoDrawImageSecurityCheck().
@@ -3975,18 +3979,19 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
 
     // Defer the tasks to gfxTextRun which will handle color/svg-in-ot fonts
     // appropriately.
     StrokeOptions strokeOpts;
     DrawOptions drawOpts;
     Style style = (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL)
                     ? Style::FILL
                     : Style::STROKE;
+    AdjustedTarget target(mCtx);
     RefPtr<gfxContext> thebes =
-      gfxContext::CreatePreservingTransformOrNull(mCtx->mTarget);
+      gfxContext::CreatePreservingTransformOrNull(target);
     gfxTextRun::DrawParams params(thebes);
 
     if (mState->StyleIsColor(style)) { // Color
       nscolor fontColor = mState->colorStyles[style];
       if (style == Style::FILL) {
         params.context->SetColor(Color::FromABGR(fontColor));
       } else {
         params.textStrokeColor = fontColor;
--- a/dom/canvas/WebGL2ContextVertices.cpp
+++ b/dom/canvas/WebGL2ContextVertices.cpp
@@ -75,27 +75,24 @@ WebGL2Context::VertexAttribIPointer(GLui
     return;
   }
 
   MOZ_ASSERT(mBoundVertexArray);
   mBoundVertexArray->EnsureAttrib(index);
 
   InvalidateBufferFetching();
 
-  WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[index];
-  vd.buf = mBoundArrayBuffer;
-  vd.stride = stride;
-  vd.size = size;
-  vd.byteOffset = offset;
-  vd.type = type;
-  vd.normalized = false;
-  vd.integer = true;
-
   MakeContextCurrent();
   gl->fVertexAttribIPointer(index, size, type, stride, reinterpret_cast<void*>(offset));
+
+  WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[index];
+  const bool integerFunc = true;
+  const bool normalized = false;
+  vd.VertexAttribPointer(integerFunc, mBoundArrayBuffer, size, type, normalized, stride,
+                         offset);
 }
 
 void
 WebGL2Context::VertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w)
 {
   if (IsContextLost())
     return;
 
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1518,16 +1518,18 @@ protected:
     GLfloat mDepthClearValue;
 
     GLint mViewportX;
     GLint mViewportY;
     GLsizei mViewportWidth;
     GLsizei mViewportHeight;
     bool mAlreadyWarnedAboutViewportLargerThanDest;
 
+    GLfloat mLineWidth;
+
     WebGLContextLossHandler mContextLossHandler;
     bool mAllowContextRestore;
     bool mLastLossWasSimulated;
     ContextStatus mContextStatus;
     bool mContextLostErrorSet;
 
     // Used for some hardware (particularly Tegra 2 and 4) that likes to
     // be Flushed while doing hundreds of draw calls.
--- a/dom/canvas/WebGLContextBuffers.cpp
+++ b/dom/canvas/WebGLContextBuffers.cpp
@@ -528,17 +528,17 @@ WebGLContext::DeleteBuffer(WebGLBuffer* 
 
         for (auto& binding : mIndexedUniformBufferBindings) {
             fnClearIfBuffer(binding.mBufferBinding);
         }
     }
 
     for (int32_t i = 0; i < mGLMaxVertexAttribs; i++) {
         if (mBoundVertexArray->HasAttrib(i)) {
-            fnClearIfBuffer(mBoundVertexArray->mAttribs[i].buf);
+            fnClearIfBuffer(mBoundVertexArray->mAttribs[i].mBuf);
         }
     }
 
     ////
 
     buffer->RequestDelete();
 }
 
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -813,20 +813,20 @@ WebGLContext::ValidateBufferFetching(con
     uint32_t maxVertices = UINT32_MAX;
     uint32_t maxInstances = UINT32_MAX;
     const uint32_t attribCount = mBoundVertexArray->mAttribs.Length();
 
     uint32_t i = 0;
     for (const auto& vd : mBoundVertexArray->mAttribs) {
         // If the attrib array isn't enabled, there's nothing to check;
         // it's a static value.
-        if (!vd.enabled)
+        if (!vd.mEnabled)
             continue;
 
-        if (vd.buf == nullptr) {
+        if (!vd.mBuf) {
             ErrorInvalidOperation("%s: no VBO bound to enabled vertex attrib index %du!",
                                   info, i);
             return false;
         }
 
         ++i;
     }
 
@@ -838,62 +838,49 @@ WebGLContext::ValidateBufferFetching(con
         if (attribLoc >= attribCount)
             continue;
 
         if (attribLoc == 0) {
             mBufferFetch_IsAttrib0Active = true;
         }
 
         const auto& vd = mBoundVertexArray->mAttribs[attribLoc];
-        if (!vd.enabled)
+        if (!vd.mEnabled)
             continue;
 
-        // the base offset
-        CheckedUint32 checked_byteLength = CheckedUint32(vd.buf->ByteLength()) - vd.byteOffset;
-        CheckedUint32 checked_sizeOfLastElement = CheckedUint32(vd.componentSize()) * vd.size;
-
-        if (!checked_byteLength.isValid() ||
-            !checked_sizeOfLastElement.isValid())
-        {
-            ErrorInvalidOperation("%s: Integer overflow occured while checking vertex"
-                                  " attrib %u.",
-                                  info, attribLoc);
-            return false;
-        }
-
-        if (checked_byteLength.value() < checked_sizeOfLastElement.value()) {
+        const auto& bufByteLen = vd.mBuf->ByteLength();
+        if (vd.ByteOffset() > bufByteLen) {
             maxVertices = 0;
             maxInstances = 0;
             break;
         }
 
-        CheckedUint32 checked_maxAllowedCount = ((checked_byteLength - checked_sizeOfLastElement) / vd.actualStride()) + 1;
+        size_t availBytes = bufByteLen - vd.ByteOffset();
+        if (vd.BytesPerVertex() > availBytes) {
+            maxVertices = 0;
+            maxInstances = 0;
+            break;
+        }
+        availBytes -= vd.BytesPerVertex();
+        const size_t vertCapacity = 1 + availBytes / vd.ExplicitStride();
 
-        if (!checked_maxAllowedCount.isValid()) {
-            ErrorInvalidOperation("%s: Integer overflow occured while checking vertex"
-                                  " attrib %u.",
-                                  info, attribLoc);
-            return false;
-        }
-
-        if (vd.divisor == 0) {
-            maxVertices = std::min(maxVertices, checked_maxAllowedCount.value());
+        if (vd.mDivisor == 0) {
+            if (vertCapacity < maxVertices) {
+                maxVertices = vertCapacity;
+            }
             hasPerVertex = true;
         } else {
-            CheckedUint32 checked_curMaxInstances = checked_maxAllowedCount * vd.divisor;
-
-            uint32_t curMaxInstances = UINT32_MAX;
-            // If this isn't valid, it's because we overflowed our
-            // uint32 above. Just leave this as UINT32_MAX, since
-            // sizeof(uint32) becomes our limiting factor.
-            if (checked_curMaxInstances.isValid()) {
-                curMaxInstances = checked_curMaxInstances.value();
+            const auto curMaxInstances = CheckedInt<size_t>(vertCapacity) * vd.mDivisor;
+            // If this isn't valid, it's because we overflowed, which means we can support
+            // *too much*. Don't update maxInstances in this case.
+            if (curMaxInstances.isValid() &&
+                curMaxInstances.value() < maxInstances)
+            {
+                maxInstances = curMaxInstances.value();
             }
-
-            maxInstances = std::min(maxInstances, curMaxInstances);
         }
     }
 
     mBufferFetchingIsVerified = true;
     mBufferFetchingHasPerVertex = hasPerVertex;
     mMaxFetchedVertices = maxVertices;
     mMaxFetchedInstances = maxInstances;
 
@@ -1020,33 +1007,20 @@ WebGLContext::DoFakeVertexAttrib0(GLuint
 void
 WebGLContext::UndoFakeVertexAttrib0()
 {
     WebGLVertexAttrib0Status whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
 
     if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
         return;
 
-    if (mBoundVertexArray->HasAttrib(0) && mBoundVertexArray->mAttribs[0].buf) {
+    if (mBoundVertexArray->HasAttrib(0) && mBoundVertexArray->mAttribs[0].mBuf) {
         const WebGLVertexAttribData& attrib0 = mBoundVertexArray->mAttribs[0];
-        gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0.buf->mGLName);
-        if (attrib0.integer) {
-            gl->fVertexAttribIPointer(0,
-                                      attrib0.size,
-                                      attrib0.type,
-                                      attrib0.stride,
-                                      reinterpret_cast<const GLvoid*>(attrib0.byteOffset));
-        } else {
-            gl->fVertexAttribPointer(0,
-                                     attrib0.size,
-                                     attrib0.type,
-                                     attrib0.normalized,
-                                     attrib0.stride,
-                                     reinterpret_cast<const GLvoid*>(attrib0.byteOffset));
-        }
+        gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0.mBuf->mGLName);
+        attrib0.DoVertexAttribPointer(gl, 0);
     } else {
         gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
     }
 
     gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundArrayBuffer ? mBoundArrayBuffer->mGLName : 0);
 }
 
 static GLuint
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -2403,16 +2403,22 @@ WebGLContext::LineWidth(GLfloat width)
 
     // Doing it this way instead of `if (width <= 0.0)` handles NaNs.
     const bool isValid = width > 0.0;
     if (!isValid) {
         ErrorInvalidValue("lineWidth: `width` must be positive and non-zero.");
         return;
     }
 
+    mLineWidth = width;
+
+    if (gl->IsCoreProfile() && width > 1.0) {
+        width = 1.0;
+    }
+
     MakeContextCurrent();
     gl->fLineWidth(width);
 }
 
 void
 WebGLContext::PolygonOffset(GLfloat factor, GLfloat units) {
     if (IsContextLost())
         return;
--- a/dom/canvas/WebGLContextState.cpp
+++ b/dom/canvas/WebGLContextState.cpp
@@ -504,18 +504,20 @@ WebGLContext::GetParameter(JSContext* cx
 
         case LOCAL_GL_STENCIL_VALUE_MASK:
             return JS::DoubleValue(mStencilValueMaskFront);
 
         case LOCAL_GL_STENCIL_WRITEMASK:
             return JS::DoubleValue(mStencilWriteMaskFront);
 
         // float
+        case LOCAL_GL_LINE_WIDTH:
+            return JS::DoubleValue(mLineWidth);
+
         case LOCAL_GL_DEPTH_CLEAR_VALUE:
-        case LOCAL_GL_LINE_WIDTH:
         case LOCAL_GL_POLYGON_OFFSET_FACTOR:
         case LOCAL_GL_POLYGON_OFFSET_UNITS:
         case LOCAL_GL_SAMPLE_COVERAGE_VALUE: {
             GLfloat f = 0.f;
             gl->fGetFloatv(pname, &f);
             return JS::DoubleValue(f);
         }
 
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -85,22 +85,23 @@ WebGLContext::GenerateWarning(const char
     }
 
     dom::AutoJSAPI api;
     if (!api.Init(mCanvasElement->OwnerDoc()->GetScopeObject())) {
         return;
     }
 
     JSContext* cx = api.cx();
-    JS_ReportWarning(cx, "WebGL: %s", buf);
+    JS_ReportWarningASCII(cx, "WebGL: %s", buf);
     if (!ShouldGenerateWarnings()) {
-        JS_ReportWarning(cx,
-                         "WebGL: No further warnings will be reported for this"
-                         " WebGL context. (already reported %d warnings)",
-                         mAlreadyGeneratedWarnings);
+        JS_ReportWarningASCII(cx,
+                              "WebGL: No further warnings will be reported for"
+                              " this WebGL context."
+                              " (already reported %d warnings)",
+                              mAlreadyGeneratedWarnings);
     }
 }
 
 bool
 WebGLContext::ShouldGenerateWarnings() const
 {
     if (mMaxWarnings == -1)
         return true;
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -667,16 +667,18 @@ WebGLContext::InitAndValidateGL(FailureR
     mColorClearValue[1] = 0.f;
     mColorClearValue[2] = 0.f;
     mColorClearValue[3] = 0.f;
     mDepthClearValue = 1.f;
     mStencilClearValue = 0;
     mStencilRefFront = 0;
     mStencilRefBack = 0;
 
+    mLineWidth = 1.0;
+
     /*
     // Technically, we should be setting mStencil[...] values to
     // `allOnes`, but either ANGLE breaks or the SGX540s on Try break.
     GLuint stencilBits = 0;
     gl->GetUIntegerv(LOCAL_GL_STENCIL_BITS, &stencilBits);
     GLuint allOnes = ~(UINT32_MAX << stencilBits);
     mStencilValueMaskFront = allOnes;
     mStencilValueMaskBack  = allOnes;
--- a/dom/canvas/WebGLContextVertices.cpp
+++ b/dom/canvas/WebGLContextVertices.cpp
@@ -295,37 +295,38 @@ WebGLContext::EnableVertexAttribArray(GL
 
     MakeContextCurrent();
     InvalidateBufferFetching();
 
     gl->fEnableVertexAttribArray(index);
 
     MOZ_ASSERT(mBoundVertexArray);
     mBoundVertexArray->EnsureAttrib(index);
-    mBoundVertexArray->mAttribs[index].enabled = true;
+    mBoundVertexArray->mAttribs[index].mEnabled = true;
 }
 
 void
 WebGLContext::DisableVertexAttribArray(GLuint index)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateAttribIndex(index, "disableVertexAttribArray"))
         return;
 
     MakeContextCurrent();
     InvalidateBufferFetching();
 
-    if (index || gl->IsGLES())
+    if (index || gl->IsGLES()) {
         gl->fDisableVertexAttribArray(index);
+    }
 
     MOZ_ASSERT(mBoundVertexArray);
     mBoundVertexArray->EnsureAttrib(index);
-    mBoundVertexArray->mAttribs[index].enabled = false;
+    mBoundVertexArray->mAttribs[index].mEnabled = false;
 }
 
 JS::Value
 WebGLContext::GetVertexAttrib(JSContext* cx, GLuint index, GLenum pname,
                               ErrorResult& rv)
 {
     if (IsContextLost())
         return JS::NullValue();
@@ -335,38 +336,38 @@ WebGLContext::GetVertexAttrib(JSContext*
 
     MOZ_ASSERT(mBoundVertexArray);
     mBoundVertexArray->EnsureAttrib(index);
 
     MakeContextCurrent();
 
     switch (pname) {
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
-        return WebGLObjectAsJSValue(cx, mBoundVertexArray->mAttribs[index].buf.get(), rv);
+        return WebGLObjectAsJSValue(cx, mBoundVertexArray->mAttribs[index].mBuf.get(), rv);
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_STRIDE:
-        return JS::Int32Value(mBoundVertexArray->mAttribs[index].stride);
+        return JS::Int32Value(mBoundVertexArray->mAttribs[index].Stride());
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_SIZE:
-        return JS::Int32Value(mBoundVertexArray->mAttribs[index].size);
+        return JS::Int32Value(mBoundVertexArray->mAttribs[index].Size());
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_TYPE:
-        return JS::Int32Value(mBoundVertexArray->mAttribs[index].type);
+        return JS::Int32Value(mBoundVertexArray->mAttribs[index].Type());
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_INTEGER:
         if (IsWebGL2())
-            return JS::BooleanValue(mBoundVertexArray->mAttribs[index].integer);
+            return JS::BooleanValue(mBoundVertexArray->mAttribs[index].IntegerFunc());
 
         break;
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
         if (IsWebGL2() ||
             IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays))
         {
-            return JS::Int32Value(mBoundVertexArray->mAttribs[index].divisor);
+            return JS::Int32Value(mBoundVertexArray->mAttribs[index].mDivisor);
         }
         break;
 
     case LOCAL_GL_CURRENT_VERTEX_ATTRIB:
         {
             JS::RootedObject obj(cx);
             switch (mVertexAttribType[index]) {
             case LOCAL_GL_FLOAT:
@@ -383,20 +384,20 @@ WebGLContext::GetVertexAttrib(JSContext*
             }
 
             if (!obj)
                 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
             return JS::ObjectOrNullValue(obj);
         }
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED:
-        return JS::BooleanValue(mBoundVertexArray->mAttribs[index].enabled);
+        return JS::BooleanValue(mBoundVertexArray->mAttribs[index].mEnabled);
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
-        return JS::BooleanValue(mBoundVertexArray->mAttribs[index].normalized);
+        return JS::BooleanValue(mBoundVertexArray->mAttribs[index].Normalized());
 
     default:
         break;
     }
 
     ErrorInvalidEnumInfo("getVertexAttrib: parameter", pname);
     return JS::NullValue();
 }
@@ -412,17 +413,17 @@ WebGLContext::GetVertexAttribOffset(GLui
 
     if (pname != LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER) {
         ErrorInvalidEnum("getVertexAttribOffset: bad parameter");
         return 0;
     }
 
     MOZ_ASSERT(mBoundVertexArray);
     mBoundVertexArray->EnsureAttrib(index);
-    return mBoundVertexArray->mAttribs[index].byteOffset;
+    return mBoundVertexArray->mAttribs[index].ByteOffset();
 }
 
 void
 WebGLContext::VertexAttribPointer(GLuint index, GLint size, GLenum type,
                                   WebGLboolean normalized, GLsizei stride,
                                   WebGLintptr byteOffset)
 {
     if (IsContextLost())
@@ -439,45 +440,40 @@ WebGLContext::VertexAttribPointer(GLuint
 
     InvalidateBufferFetching();
 
     /* XXX make work with bufferSubData & heterogeneous types
      if (type != mBoundArrayBuffer->GLType())
      return ErrorInvalidOperation("vertexAttribPointer: type must match bound VBO type: %d != %d", type, mBoundArrayBuffer->GLType());
      */
 
-    WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[index];
-
-    vd.buf = mBoundArrayBuffer;
-    vd.stride = stride;
-    vd.size = size;
-    vd.byteOffset = byteOffset;
-    vd.type = type;
-    vd.normalized = normalized;
-    vd.integer = false;
-
     MakeContextCurrent();
     gl->fVertexAttribPointer(index, size, type, normalized, stride,
                              reinterpret_cast<void*>(byteOffset));
+
+    WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[index];
+    const bool integerFunc = false;
+    vd.VertexAttribPointer(integerFunc, mBoundArrayBuffer, size, type, normalized, stride,
+                           byteOffset);
 }
 
 void
 WebGLContext::VertexAttribDivisor(GLuint index, GLuint divisor)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateAttribIndex(index, "vertexAttribDivisor"))
         return;
 
     MOZ_ASSERT(mBoundVertexArray);
     mBoundVertexArray->EnsureAttrib(index);
 
     WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[index];
-    vd.divisor = divisor;
+    vd.mDivisor = divisor;
 
     InvalidateBufferFetching();
 
     MakeContextCurrent();
 
     gl->fVertexAttribDivisor(index, divisor);
 }
 
--- a/dom/canvas/WebGLVertexArray.h
+++ b/dom/canvas/WebGLVertexArray.h
@@ -34,17 +34,17 @@ public:
         BindVertexArrayImpl();
     };
 
     void EnsureAttrib(GLuint index);
     bool HasAttrib(GLuint index) const {
         return index < mAttribs.Length();
     }
     bool IsAttribArrayEnabled(GLuint index) const {
-        return HasAttrib(index) && mAttribs[index].enabled;
+        return HasAttrib(index) && mAttribs[index].mEnabled;
     }
 
     // Implement parent classes:
     void Delete();
     bool IsVertexArray();
 
     WebGLContext* GetParentObject() const {
         return mContext;
--- a/dom/canvas/WebGLVertexArrayFake.cpp
+++ b/dom/canvas/WebGLVertexArrayFake.cpp
@@ -24,41 +24,36 @@ WebGLVertexArrayFake::BindVertexArrayImp
 
     WebGLRefPtr<WebGLVertexArray> prevVertexArray = mContext->mBoundVertexArray;
 
     mContext->mBoundVertexArray = this;
 
     WebGLRefPtr<WebGLBuffer> prevBuffer = mContext->mBoundArrayBuffer;
     mContext->BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, mElementArrayBuffer);
 
-    for (size_t i = 0; i < mAttribs.Length(); ++i) {
-        const WebGLVertexAttribData& vd = mAttribs[i];
+    size_t i = 0;
+    for (const auto& vd : mAttribs) {
+        mContext->BindBuffer(LOCAL_GL_ARRAY_BUFFER, vd.mBuf);
+        vd.DoVertexAttribPointer(gl, i);
 
-        mContext->BindBuffer(LOCAL_GL_ARRAY_BUFFER, vd.buf);
-
-        if (vd.integer) {
-            gl->fVertexAttribIPointer(i, vd.size, vd.type, vd.stride,
-                                      reinterpret_cast<const GLvoid*>(vd.byteOffset));
+        if (vd.mEnabled) {
+            gl->fEnableVertexAttribArray(i);
         } else {
-            gl->fVertexAttribPointer(i, vd.size, vd.type, vd.normalized, vd.stride,
-                                     reinterpret_cast<const GLvoid*>(vd.byteOffset));
+            gl->fDisableVertexAttribArray(i);
         }
-
-        if (vd.enabled)
-            gl->fEnableVertexAttribArray(i);
-        else
-            gl->fDisableVertexAttribArray(i);
+        ++i;
     }
 
     size_t len = prevVertexArray->mAttribs.Length();
-    for (size_t i = mAttribs.Length(); i < len; ++i) {
-        const WebGLVertexAttribData& vd = prevVertexArray->mAttribs[i];
+    for (; i < len; ++i) {
+        const auto& vd = prevVertexArray->mAttribs[i];
 
-        if (vd.enabled)
+        if (vd.mEnabled) {
             gl->fDisableVertexAttribArray(i);
+        }
     }
 
     mContext->BindBuffer(LOCAL_GL_ARRAY_BUFFER, prevBuffer);
     mIsVAO = true;
 }
 
 void
 WebGLVertexArrayFake::DeleteImpl()
new file mode 100644
--- /dev/null
+++ b/dom/canvas/WebGLVertexAttribData.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#include "WebGLVertexAttribData.h"
+
+#include "GLContext.h"
+
+namespace mozilla {
+
+static uint8_t
+CalcBytesPerVertex(GLenum type, uint8_t size)
+{
+    uint8_t bytesPerType;
+    switch (type) {
+    case LOCAL_GL_INT_2_10_10_10_REV:
+    case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
+        return 4;
+
+    case LOCAL_GL_BYTE:
+    case LOCAL_GL_UNSIGNED_BYTE:
+        bytesPerType = 1;
+        break;
+
+    case LOCAL_GL_HALF_FLOAT:
+    case LOCAL_GL_SHORT:
+    case LOCAL_GL_UNSIGNED_SHORT:
+        bytesPerType = 2;
+        break;
+
+    case LOCAL_GL_FIXED: // GLES 3.0.4 p9: 32-bit signed, with 16 fractional bits.
+    case LOCAL_GL_FLOAT:
+    case LOCAL_GL_INT:
+    case LOCAL_GL_UNSIGNED_INT:
+        bytesPerType = 4;
+        break;
+
+    default:
+        MOZ_CRASH("Bad `type`.");
+    }
+
+    return bytesPerType * size;
+}
+
+void
+WebGLVertexAttribData::VertexAttribPointer(bool integerFunc, WebGLBuffer* buf,
+                                           uint8_t size, GLenum type, bool normalized,
+                                           uint32_t stride, uint64_t byteOffset)
+{
+    mIntegerFunc = integerFunc;
+    mBuf = buf;
+    mType = type;
+    mSize = size;
+    mBytesPerVertex = CalcBytesPerVertex(mType, mSize);
+    mNormalized = normalized;
+    mStride = stride;
+    mExplicitStride = (mStride ? mStride : mBytesPerVertex);
+    mByteOffset = byteOffset;
+}
+
+void
+WebGLVertexAttribData::DoVertexAttribPointer(gl::GLContext* gl, GLuint index) const
+{
+    if (mIntegerFunc) {
+        gl->fVertexAttribIPointer(index, mSize, mType, mStride,
+                                  (const void*)mByteOffset);
+    } else {
+        gl->fVertexAttribPointer(index, mSize, mType, mNormalized, mStride,
+                                 (const void*)mByteOffset);
+    }
+}
+
+} // namespace mozilla
--- a/dom/canvas/WebGLVertexAttribData.h
+++ b/dom/canvas/WebGLVertexAttribData.h
@@ -8,82 +8,69 @@
 
 #include "GLDefs.h"
 #include "WebGLObjectModel.h"
 
 namespace mozilla {
 
 class WebGLBuffer;
 
-struct WebGLVertexAttribData
+class WebGLVertexAttribData final
 {
+public:
+    uint32_t mDivisor;
+    bool mEnabled;
+
+private:
+    bool mIntegerFunc;
+public:
+    WebGLRefPtr<WebGLBuffer> mBuf;
+private:
+    GLenum mType;
+    uint8_t mSize; // num of mType vals per vert
+    uint8_t mBytesPerVertex;
+    bool mNormalized;
+    uint32_t mStride; // bytes
+    uint32_t mExplicitStride;
+    uint64_t mByteOffset;
+
+public:
+
+#define GETTER(X) const decltype(m##X)& X() const { return m##X; }
+
+    GETTER(IntegerFunc)
+    GETTER(Type)
+    GETTER(Size)
+    GETTER(BytesPerVertex)
+    GETTER(Normalized)
+    GETTER(Stride)
+    GETTER(ExplicitStride)
+    GETTER(ByteOffset)
+
+#undef GETTER
+
     // note that these initial values are what GL initializes vertex attribs to
     WebGLVertexAttribData()
-        : buf(0)
-        , stride(0)
-        , size(4)
-        , divisor(0) // OpenGL ES 3.0 specs paragraphe 6.2 p240
-        , byteOffset(0)
-        , type(LOCAL_GL_FLOAT)
-        , enabled(false)
-        , normalized(false)
-        , integer(false)
-    {}
-
-    WebGLRefPtr<WebGLBuffer> buf;
-    GLuint stride;
-    GLuint size;
-    GLuint divisor;
-    GLuint byteOffset;
-    GLenum type;
-    bool enabled;
-    bool normalized;
-    bool integer;
-
-    GLuint componentSize() const {
-        switch(type) {
-        case LOCAL_GL_BYTE:
-        case LOCAL_GL_UNSIGNED_BYTE:
-            return 1;
-
-        case LOCAL_GL_SHORT:
-        case LOCAL_GL_UNSIGNED_SHORT:
-        case LOCAL_GL_HALF_FLOAT:
-        case LOCAL_GL_HALF_FLOAT_OES:
-            return 2;
-
-        case LOCAL_GL_INT:
-        case LOCAL_GL_UNSIGNED_INT:
-        case LOCAL_GL_FLOAT:
-            return 4;
-
-        default:
-            MOZ_ASSERT(false, "Should never get here!");
-            return 0;
-        }
+        : mDivisor(0)
+        , mEnabled(false)
+    {
+        VertexAttribPointer(false, nullptr, 4, LOCAL_GL_FLOAT, false, 0, 0);
     }
 
-    GLuint actualStride() const {
-        if (stride)
-            return stride;
+    void VertexAttribPointer(bool integerFunc, WebGLBuffer* buf, uint8_t size,
+                             GLenum type, bool normalized, uint32_t stride,
+                             uint64_t byteOffset);
 
-        return size * componentSize();
-    }
+    void DoVertexAttribPointer(gl::GLContext* gl, GLuint index) const;
 };
 
 } // namespace mozilla
 
 inline void
-ImplCycleCollectionUnlink(mozilla::WebGLVertexAttribData& field)
-{
-    field.buf = nullptr;
-}
-
-inline void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
                             mozilla::WebGLVertexAttribData& field,
                             const char* name,
                             uint32_t flags = 0)
 {
-    CycleCollectionNoteChild(callback, field.buf.get(), name, flags);
+    CycleCollectionNoteChild(callback, field.mBuf.get(), name, flags);
 }
 
 #endif // WEBGL_VERTEX_ATTRIB_DATA_H_
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -152,16 +152,17 @@ UNIFIED_SOURCES += [
     'WebGLTimerQuery.cpp',
     'WebGLTransformFeedback.cpp',
     'WebGLUniformLocation.cpp',
     'WebGLValidateStrings.cpp',
     'WebGLVertexArray.cpp',
     'WebGLVertexArrayFake.cpp',
     'WebGLVertexArrayGL.cpp',
     'WebGLVertexArrayObject.cpp',
+    'WebGLVertexAttribData.cpp',
 ]
 
 SOURCES += [
     'MurmurHash3.cpp',
 ]
 
 # Suppress warnings from third-party code.
 if CONFIG['CLANG_CXX']:
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-with-filter-grayscale-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.fillStyle = "rgb(85,85,85)";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-with-filter-grayscale-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.fillStyle = "rgb(85,85,85)";
+ctx.filter = "grayscale(100%)";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-with-filter-grayscale-2-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.globalAlpha = 0.5;
+ctx.fillStyle = "rgb(85,85,85)";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-with-filter-grayscale-2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.globalAlpha = 0.5;
+ctx.fillStyle = "rgb(85,85,85)";
+ctx.filter = "grayscale(100%)";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-with-filter-opacity-1-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.globalAlpha = 0.5;
+ctx.fillStyle = "rgb(0,128,0)";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-with-filter-opacity-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.fillStyle = "rgb(0,128,0)";
+ctx.filter = "opacity(0.5)";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-with-filter-opacity-2-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.globalAlpha = 0.25;
+ctx.fillStyle = "rgb(0,255,0)";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-with-filter-opacity-2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.globalAlpha = 0.5;
+ctx.fillStyle = "rgb(0,255,0)";
+ctx.filter = "opacity(0.5)";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-with-shadow-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.shadowColor = "black";
+ctx.shadowBlur = 2;
+ctx.fillStyle = "green";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-with-shadow-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.globalAlpha = 0.5;
+ctx.shadowColor = "black";
+ctx.shadowBlur = 2;
+ctx.fillStyle = "green";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-without-shadow-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.fillStyle = "green";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/fillText-without-shadow-2-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.globalAlpha = 0.5;
+ctx.fillStyle = "green";
+ctx.font = "20px serif";
+ctx.fillText("Hello world", 0, 50);
+</script>
+</body></html>
--- a/dom/canvas/test/reftest/filters/reftest.list
+++ b/dom/canvas/test/reftest/filters/reftest.list
@@ -13,8 +13,18 @@ fuzzy-if(azureSkia,1,1500) == global-alp
 == svg-bbox.html svg-bbox-ref.html
 == svg-inline.html ref.html
 == svg-liveness.html ref.html
 == svg-off-screen.html ref.html
 == units.html ref.html
 == units-em.html ref.html
 == units-ex.html ref.html
 == units-off-screen.html ref.html
+fuzzy(1,700) == fillText-with-filter-opacity-1.html fillText-with-filter-opacity-1-ref.html
+fuzzy(1,300) == fillText-with-filter-opacity-2.html fillText-with-filter-opacity-2-ref.html
+fuzzy(1,400) == strokeText-with-filter-grayscale-1.html strokeText-with-filter-grayscale-1-ref.html
+fuzzy(1,400) == strokeText-with-filter-grayscale-2.html strokeText-with-filter-grayscale-2-ref.html
+!= fillText-with-shadow-1.html fillText-without-shadow-1-ref.html
+!= fillText-with-shadow-2.html fillText-without-shadow-2-ref.html
+fuzzy(1,400) == fillText-with-filter-grayscale-1.html fillText-with-filter-grayscale-1-ref.html
+fuzzy(1,400) == fillText-with-filter-grayscale-2.html fillText-with-filter-grayscale-2-ref.html
+!= strokeText-with-shadow-1.html strokeText-without-shadow-1-ref.html
+!= strokeText-with-shadow-2.html strokeText-without-shadow-2-ref.html
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/strokeText-with-filter-grayscale-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.strokeStyle = "rgb(85,85,85)";
+ctx.font = "20px serif";
+ctx.strokeText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/strokeText-with-filter-grayscale-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.strokeStyle = "rgb(85,85,85)";
+ctx.filter = "grayscale(100%)";
+ctx.font = "20px serif";
+ctx.strokeText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/strokeText-with-filter-grayscale-2-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.globalAlpha = 0.5;
+ctx.strokeStyle = "rgb(85,85,85)";
+ctx.font = "20px serif";
+ctx.strokeText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/strokeText-with-filter-grayscale-2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.globalAlpha = 0.5;
+ctx.strokeStyle = "rgb(85,85,85)";
+ctx.filter = "grayscale(100%)";
+ctx.font = "20px serif";
+ctx.strokeText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/strokeText-with-shadow-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.shadowColor = "black";
+ctx.shadowBlur = 2;
+ctx.strokeStyle = "green";
+ctx.font = "20px serif";
+ctx.strokeText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/strokeText-with-shadow-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.globalAlpha = 0.5;
+ctx.shadowColor = "black";
+ctx.shadowBlur = 2;
+ctx.strokeStyle = "green";
+ctx.font = "20px serif";
+ctx.strokeText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/strokeText-without-shadow-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.strokeStyle = "green";
+ctx.font = "20px serif";
+ctx.strokeText("Hello world", 0, 50);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/filters/strokeText-without-shadow-2-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+var canvas = document.getElementById("canvas");
+var ctx = canvas.getContext("2d");
+ctx.globalAlpha = 0.5;
+ctx.strokeStyle = "green";
+ctx.font = "20px serif";
+ctx.strokeText("Hello world", 0, 50);
+</script>
+</body></html>
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -467,26 +467,16 @@ DataTransfer::ClearData(const Optional<n
   if (aFormat.WasPassed()) {
     MozClearDataAtHelper(aFormat.Value(), 0, aSubjectPrincipal, aRv);
   } else {
     MozClearDataAtHelper(EmptyString(), 0, aSubjectPrincipal, aRv);
   }
 }
 
 NS_IMETHODIMP
-DataTransfer::ClearData(const nsAString& aFormat)
-{
-  Optional<nsAString> format;
-  format = &aFormat;
-  ErrorResult rv;
-  ClearData(format, Some(nsContentUtils::SubjectPrincipal()), rv);
-  return rv.StealNSResult();
-}
-
-NS_IMETHODIMP
 DataTransfer::GetMozItemCount(uint32_t* aCount)
 {
   *aCount = MozItemCount();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DataTransfer::GetMozCursor(nsAString& aCursorState)
@@ -797,24 +787,16 @@ DataTransfer::MozClearDataAtHelper(const
   MOZ_ASSERT(aSubjectPrincipal.isSome());
 
   nsAutoString format;
   GetRealFormat(aFormat, format);
 
   mItems->MozRemoveByTypeAt(format, aIndex, aSubjectPrincipal, aRv);
 }
 
-NS_IMETHODIMP
-DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex)
-{
-  ErrorResult rv;
-  MozClearDataAt(aFormat, aIndex, Some(nsContentUtils::SubjectPrincipal()), rv);
-  return rv.StealNSResult();
-}
-
 void
 DataTransfer::SetDragImage(Element& aImage, int32_t aX, int32_t aY,
                            ErrorResult& aRv)
 {
   if (mReadOnly) {
     aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
     return;
   }
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -23,17 +23,16 @@
 #include "nsCOMPtr.h"
 #include "nsDeviceContext.h"
 #include "nsError.h"
 #include "nsGlobalWindow.h"
 #include "nsIFrame.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsIPresShell.h"
-#include "nsIScreen.h"
 #include "nsIScrollableFrame.h"
 #include "nsJSEnvironment.h"
 #include "nsLayoutUtils.h"
 #include "nsPIWindowRoot.h"
 #include "WorkerPrivate.h"
 
 namespace mozilla {
 namespace dom {
@@ -923,56 +922,27 @@ Event::GetScreenCoords(nsPresContext* aP
 
   // Doing a straight conversion from LayoutDeviceIntPoint to CSSIntPoint
   // seem incorrect, but it is needed to maintain legacy functionality.
   WidgetGUIEvent* guiEvent = aEvent->AsGUIEvent();
   if (!aPresContext || !(guiEvent && guiEvent->mWidget)) {
     return CSSIntPoint(aPoint.x, aPoint.y);
   }
 
+  nsPoint pt =
+    LayoutDevicePixel::ToAppUnits(aPoint, aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
 
-  int32_t appPerDevFullZoom =
-    aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
-
-  LayoutDevicePoint devPt = aPoint;
   if (nsIPresShell* ps = aPresContext->GetPresShell()) {
-    // convert to appUnits in order to use RemoveResolution, then back to
-    // device pixels
-    nsPoint pt =
-      LayoutDevicePixel::ToAppUnits(aPoint, appPerDevFullZoom);
     pt = pt.RemoveResolution(nsLayoutUtils::GetCurrentAPZResolutionScale(ps));
-    devPt = LayoutDevicePixel::FromAppUnits(pt, appPerDevFullZoom);
   }
 
-  devPt += guiEvent->mWidget->WidgetToScreenOffset();
+  pt += LayoutDevicePixel::ToAppUnits(guiEvent->mWidget->WidgetToScreenOffset(),
+                                      aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
 
-  nsCOMPtr<nsIScreen> screen = guiEvent->mWidget->GetWidgetScreen();
-  if (screen) {
-    // subtract device-pixel origin of current screen
-    int32_t x, y, w, h;
-    screen->GetRect(&x, &y, &w, &h);
-    devPt.x -= x;
-    devPt.y -= y;
-    // then convert position *within the current screen* to unscaled CSS px
-    double cssToDevScale;
-    screen->GetDefaultCSSScaleFactor(&cssToDevScale);
-    CSSIntPoint cssPt(NSToIntRound(devPt.x / cssToDevScale),
-                      NSToIntRound(devPt.y / cssToDevScale));
-    // finally, add the desktop-pixel origin of the screen, to get a global
-    // CSS pixel value that is compatible with window.screenX/Y positions
-    screen->GetRectDisplayPix(&x, &y, &w, &h);
-    cssPt.x += x;
-    cssPt.y += y;
-    return cssPt;
-  }
-
-  // this shouldn't happen, but just in case...
-  NS_WARNING("failed to find screen, using default CSS px conversion");
-  return CSSPixel::FromAppUnitsRounded(
-      LayoutDevicePixel::ToAppUnits(aPoint, appPerDevFullZoom));
+  return CSSPixel::FromAppUnitsRounded(pt);
 }
 
 // static
 CSSIntPoint
 Event::GetPageCoords(nsPresContext* aPresContext,
                      WidgetEvent* aEvent,
                      LayoutDeviceIntPoint aPoint,
                      CSSIntPoint aDefaultPoint)
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -776,16 +776,20 @@ NON_IDL_EVENT(close,
 NON_IDL_EVENT(popupshowing,
               eXULPopupShowing,
               EventNameType_XUL,
               eBasicEventClass)
 NON_IDL_EVENT(popupshown,
               eXULPopupShown,
               EventNameType_XUL,
               eBasicEventClass)
+NON_IDL_EVENT(popuppositioned,
+              eXULPopupPositioned,
+              EventNameType_XUL,
+              eBasicEventClass)
 NON_IDL_EVENT(popuphiding,
               eXULPopupHiding,
               EventNameType_XUL,
               eBasicEventClass)
 NON_IDL_EVENT(popuphidden,
               eXULPopupHidden,
               EventNameType_XUL,
               eBasicEventClass)
--- a/dom/fetch/FetchUtil.cpp
+++ b/dom/fetch/FetchUtil.cpp
@@ -46,19 +46,20 @@ FindCRLF(nsACString::const_iterator& aSt
          nsACString::const_iterator& aEnd)
 {
   nsACString::const_iterator end(aEnd);
   return FindInReadable(NS_LITERAL_CSTRING("\r\n"), aStart, end);
 }
 
 // Reads over a CRLF and positions start after it.
 static bool
-PushOverLine(nsACString::const_iterator& aStart)
+PushOverLine(nsACString::const_iterator& aStart,
+	     const nsACString::const_iterator& aEnd)
 {
-  if (*aStart == nsCRT::CR && (aStart.size_forward() > 1) && *(++aStart) == nsCRT::LF) {
+  if (*aStart == nsCRT::CR && (aEnd - aStart > 1) && *(++aStart) == nsCRT::LF) {
     ++aStart; // advance to after CRLF
     return true;
   }
 
   return false;
 }
 
 // static
@@ -81,32 +82,33 @@ FetchUtil::ExtractHeader(nsACString::con
 
   if (aStart.get() == beginning) {
     *aWasEmptyHeader = true;
     return true;
   }
 
   nsAutoCString header(beginning, aStart.get() - beginning);
 
-  nsACString::const_iterator headerStart, headerEnd;
+  nsACString::const_iterator headerStart, iter, headerEnd;
   header.BeginReading(headerStart);
   header.EndReading(headerEnd);
-  if (!FindCharInReadable(':', headerStart, headerEnd)) {
+  iter = headerStart;
+  if (!FindCharInReadable(':', iter, headerEnd)) {
     return false;
   }
 
-  aHeaderName.Assign(StringHead(header, headerStart.size_backward()));
+  aHeaderName.Assign(StringHead(header, iter - headerStart));
   aHeaderName.CompressWhitespace();
   if (!NS_IsValidHTTPToken(aHeaderName)) {
     return false;
   }
 
-  aHeaderValue.Assign(Substring(++headerStart, headerEnd));
+  aHeaderValue.Assign(Substring(++iter, headerEnd));
   if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue)) {
     return false;
   }
   aHeaderValue.CompressWhitespace();
 
-  return PushOverLine(aStart);
+  return PushOverLine(aStart, aEnd);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/flyweb/HttpServer.cpp
+++ b/dom/flyweb/HttpServer.cpp
@@ -32,16 +32,18 @@ static LazyLogModule gHttpServerLog("Htt
 #define LOG_E(...) MOZ_LOG(gHttpServerLog, mozilla::LogLevel::Error, (__VA_ARGS__))
 
 
 NS_IMPL_ISUPPORTS(HttpServer,
                   nsIServerSocketListener,
                   nsILocalCertGetCallback)
 
 HttpServer::HttpServer()
+  : mPort()
+  , mHttps()
 {
 }
 
 HttpServer::~HttpServer()
 {
 }
 
 void
@@ -298,16 +300,18 @@ NS_IMPL_ISUPPORTS(HttpServer::Connection
                   nsIOutputStreamCallback)
 
 HttpServer::Connection::Connection(nsISocketTransport* aTransport,
                                    HttpServer* aServer,
                                    nsresult& rv)
   : mServer(aServer)
   , mTransport(aTransport)
   , mState(eRequestLine)
+  , mPendingReqVersion()
+  , mRemainingBodySize()
   , mCloseAfterRequest(false)
 {
   nsCOMPtr<nsIInputStream> input;
   rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(input));
   NS_ENSURE_SUCCESS_VOID(rv);
 
   mInput = do_QueryInterface(input);
 
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -511,16 +511,18 @@ nsGeolocationRequest::GetRequester(nsICo
   requester.forget(aRequester);
 
   return NS_OK;
 }
 
 void
 nsGeolocationRequest::SetTimeoutTimer()
 {
+  MOZ_ASSERT(!mShutdown, "set timeout after shutdown");
+
   StopTimeoutTimer();
 
   if (mOptions && mOptions->mTimeout != 0 && mOptions->mTimeout != 0x7fffffff) {
     mTimeoutTimer = do_CreateInstance("@mozilla.org/timer;1");
     RefPtr<TimerCallbackHolder> holder = new TimerCallbackHolder(this);
     mTimeoutTimer->InitWithCallback(holder, mOptions->mTimeout, nsITimer::TYPE_ONE_SHOT);
   }
 }
@@ -580,17 +582,20 @@ nsGeolocationRequest::SendLocation(nsIDO
 
     MOZ_ASSERT(callback);
     callback->Call(*wrapped);
   } else {
     nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback();
     MOZ_ASSERT(callback);
     callback->HandleEvent(aPosition);
   }
-  SetTimeoutTimer();
+
+  if (mIsWatchPositionRequest && !mShutdown) {
+    SetTimeoutTimer();
+  }
   MOZ_ASSERT(mShutdown || mIsWatchPositionRequest,
              "non-shutdown getCurrentPosition request after callback!");
 }
 
 nsIPrincipal*
 nsGeolocationRequest::GetPrincipal()
 {
   if (!mLocator) {
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -210,19 +210,23 @@ const Decimal HTMLInputElement::kStepSca
 const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
 const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
 const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1);
 const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
 const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
 const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
 const Decimal HTMLInputElement::kStepAny = Decimal(0);
 
+const double HTMLInputElement::kMinimumYear = 1;
 const double HTMLInputElement::kMaximumYear = 275760;
-const double HTMLInputElement::kMinimumYear = 1;
+const double HTMLInputElement::kMaximumWeekInMaximumYear = 37;
+const double HTMLInputElement::kMaximumDayInMaximumYear = 13;
+const double HTMLInputElement::kMaximumMonthInMaximumYear = 9;
 const double HTMLInputElement::kMaximumWeekInYear = 53;
+const double HTMLInputElement::kMsPerDay = 24 * 60 * 60 * 1000;
 
 #define NS_INPUT_ELEMENT_STATE_IID                 \
 { /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */       \
   0xdc3b3d14,                                      \
   0x23e2,                                          \
   0x4479,                                          \
   {0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
 }
@@ -1875,29 +1879,49 @@ HTMLInputElement::ConvertStringToNumber(
       return true;
     case NS_FORM_INPUT_MONTH:
       {
         uint32_t year, month;
         if (!ParseMonth(aValue, &year, &month)) {
           return false;
         }
 
-        // Maximum valid month is 275760-09.
         if (year < kMinimumYear || year > kMaximumYear) {
           return false;
         }
 
-        if (year == kMaximumYear && month > 9) {
+        // Maximum valid month is 275760-09.
+        if (year == kMaximumYear && month > kMaximumMonthInMaximumYear) {
           return false;
         }
 
         int32_t months = MonthsSinceJan1970(year, month);
         aResultValue = Decimal(int32_t(months));
         return true;
       }
+    case NS_FORM_INPUT_WEEK:
+      {
+        uint32_t year, week;
+        if (!ParseWeek(aValue, &year, &week)) {
+          return false;
+        }
+
+        if (year < kMinimumYear || year > kMaximumYear) {
+          return false;
+        }
+
+        // Maximum week is 275760-W37, the week of 275760-09-13.
+        if (year == kMaximumYear && week > kMaximumWeekInMaximumYear) {
+          return false;
+        }
+
+        double days = DaysSinceEpochFromWeek(year, week);
+        aResultValue = Decimal::fromDouble(days * kMsPerDay);
+        return true;
+      }
     default:
       MOZ_ASSERT(false, "Unrecognized input type");
       return false;
   }
 }
 
 Decimal
 HTMLInputElement::GetValueAsDecimal() const
@@ -2126,28 +2150,62 @@ HTMLInputElement::ConvertNumberToString(
 
         if (year == kMaximumYear && month > 8) {
           return false;
         }
 
         aResultString.AppendPrintf("%04.0f-%02.0f", year, month + 1);
         return true;
       }
+    case NS_FORM_INPUT_WEEK:
+      {
+        aValue = aValue.floor();
+
+        // Based on ISO 8601 date.
+        double year = JS::YearFromTime(aValue.toDouble());
+        double month = JS::MonthFromTime(aValue.toDouble());
+        double day = JS::DayFromTime(aValue.toDouble());
+        // Adding 1 since day starts from 0.
+        double dayInYear = JS::DayWithinYear(aValue.toDouble(), year) + 1;
+
+        // Adding 1 since month starts from 0.
+        uint32_t isoWeekday = DayOfWeek(year, month + 1, day, true);
+        // Target on Wednesday since ISO 8601 states that week 1 is the week
+        // with the first Thursday of that year.
+        uint32_t week = (dayInYear - isoWeekday + 10) / 7;
+
+        if (week < 1) {
+          year--;
+          if (year < 1) {
+            return false;
+          }
+          week = MaximumWeekInYear(year);
+        } else if (week > MaximumWeekInYear(year)) {
+          year++;
+          if (year > kMaximumYear ||
+              (year == kMaximumYear && week > kMaximumWeekInMaximumYear)) {
+            return false;
+          }
+          week = 1;
+        }
+
+        aResultString.AppendPrintf("%04.0f-W%02d", year, week);
+        return true;
+      }
     default:
       MOZ_ASSERT(false, "Unrecognized input type");
       return false;
   }
 }
 
 
 Nullable<Date>
 HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
 {
-  // TODO: this is temporary until bug 888316 is fixed.
-  if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_WEEK) {
+  if (!IsDateTimeInputType(mType)) {
     return Nullable<Date>();
   }
 
   switch (mType) {
     case NS_FORM_INPUT_DATE:
     {
       uint32_t year, month, day;
       nsAutoString value;
@@ -2181,28 +2239,41 @@ HTMLInputElement::GetValueAsDate(ErrorRe
       GetValueInternal(value);
       if (!ParseMonth(value, &year, &month)) {
         return Nullable<Date>();
       }
 
       JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, 1));
       return Nullable<Date>(Date(time));
     }
+    case NS_FORM_INPUT_WEEK:
+    {
+      uint32_t year, week;
+      nsAutoString value;
+      GetValueInternal(value);
+      if (!ParseWeek(value, &year, &week)) {
+        return Nullable<Date>();
+      }
+
+      double days = DaysSinceEpochFromWeek(year, week);
+      JS::ClippedTime time = JS::TimeClip(days * kMsPerDay);
+
+      return Nullable<Date>(Date(time));
+    }
   }
 
   MOZ_ASSERT(false, "Unrecognized input type");
   aRv.Throw(NS_ERROR_UNEXPECTED);
   return Nullable<Date>();
 }
 
 void
 HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv)
 {
-  // TODO: this is temporary until bug 888316 is fixed.
-  if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_WEEK) {
+  if (!IsDateTimeInputType(mType)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   if (aDate.IsNull() || aDate.Value().IsUndefined()) {
     aRv = SetValue(EmptyString());
     return;
   }
@@ -5091,34 +5162,41 @@ HTMLInputElement::IsLeapYear(uint32_t aY
 {
   if ((aYear % 4 == 0 && aYear % 100 != 0) || ( aYear % 400 == 0)) {
     return true;
   }
   return false;
 }
 
 uint32_t
-HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay) const
+HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay,
+                            bool isoWeek) const
 {
   // Tomohiko Sakamoto algorithm.
   int monthTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
   aYear -= aMonth < 3;
 
-  return (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
-          monthTable[aMonth - 1] + aDay) % 7;
+  uint32_t day = (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
+                  monthTable[aMonth - 1] + aDay) % 7;
+
+  if (isoWeek) {
+    return ((day + 6) % 7) + 1;
+  }
+
+  return day;
 }
 
 uint32_t
 HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const
 {
-  int day = DayOfWeek(aYear, 1, 1); // January 1.
+  int day = DayOfWeek(aYear, 1, 1, true); // January 1.
   // A year starting on Thursday or a leap year starting on Wednesday has 53
   // weeks. All other years have 52 weeks.
-  return day == 4 || (day == 3 && IsLeapYear(aYear)) ? kMaximumWeekInYear
-                                                     : kMaximumWeekInYear - 1;
+  return day == 4 || (day == 3 && IsLeapYear(aYear)) ?
+    kMaximumWeekInYear : kMaximumWeekInYear - 1;
 }
 
 bool
 HTMLInputElement::IsValidWeek(const nsAString& aValue) const
 {
   uint32_t year, week;
   return ParseWeek(aValue, &year, &week);
 }
@@ -5223,16 +5301,35 @@ bool HTMLInputElement::ParseDate(const n
   if (!ParseMonth(yearMonthStr, aYear, aMonth)) {
     return false;
   }
 
   return DigitSubStringToNumber(aValue, endOfMonthOffset + 1, 2, aDay) &&
          *aDay > 0 && *aDay <= NumberOfDaysInMonth(*aMonth, *aYear);
 }
 
+double
+HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear, uint32_t aWeek) const
+{
+  double days = JS::DayFromYear(aYear) + (aWeek - 1) * 7;
+  uint32_t dayOneIsoWeekday = DayOfWeek(aYear, 1, 1, true);
+
+  // If day one of that year is on/before Thursday, we should subtract the
+  // days that belong to last year in our first week, otherwise, our first
+  // days belong to last year's last week, and we should add those days
+  // back.
+  if (dayOneIsoWeekday <= 4) {
+    days -= (dayOneIsoWeekday - 1);
+  } else {
+    days += (7 - dayOneIsoWeekday + 1);
+  }
+
+  return days;
+}
+
 uint32_t
 HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const
 {
 /*
  * Returns the number of days in a month.
  * Months that are |longMonths| always have 31 days.
  * Months that are not |longMonths| have 30 days except February (month 2).
  * February has 29 days during leap years which are years that are divisible by 400.
@@ -5246,18 +5343,17 @@ HTMLInputElement::NumberOfDaysInMonth(ui
   if (longMonths[aMonth-1]) {
     return 31;
   }
 
   if (aMonth != 2) {
     return 30;
   }
 
-  return (aYear % 400 == 0 || (aYear % 100 != 0 && aYear % 4 == 0))
-          ? 29 : 28;
+  return IsLeapYear(aYear) ? 29 : 28;
 }
 
 /* static */ bool
 HTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
                                          uint32_t aStart, uint32_t aLen,
                                          uint32_t* aRetVal)
 {
   MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -1044,21 +1044,17 @@ protected:
   /**
    * Returns if stepDown and stepUp methods apply for the current type.
    */
   bool DoStepDownStepUpApply() const { return DoesStepApply(); }
 
   /**
    * Returns if valueAsNumber attribute applies for the current type.
    */
-  bool DoesValueAsNumberApply() const
-  {
-    // TODO: this is temporary until bug 888316 is fixed.
-    return DoesMinMaxApply() && mType != NS_FORM_INPUT_WEEK;
-  }
+  bool DoesValueAsNumberApply() const { return DoesMinMaxApply(); }
 
   /**
    * Returns if autocomplete attribute applies for the current type.
    */
   bool DoesAutocompleteApply() const;
 
   /**
    * Returns if the minlength or maxlength attributes apply for the current type.
@@ -1227,30 +1223,38 @@ protected:
    * @return whether the parsing was successful.
    */
   bool ParseDate(const nsAString& aValue,
                  uint32_t* aYear,
                  uint32_t* aMonth,
                  uint32_t* aDay) const;
 
   /**
+   * This methods returns the number of days since epoch for a given year and
+   * week.
+   */
+  double DaysSinceEpochFromWeek(uint32_t aYear, uint32_t aWeek) const;
+
+  /**
    * This methods returns the number of days in a given month, for a given year.
    */
   uint32_t NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const;
 
   /**
    * This methods returns the number of months between January 1970 and the
    * given year and month.
    */
   int32_t MonthsSinceJan1970(uint32_t aYear, uint32_t aMonth) const;
 
   /**
-   * This methods returns the day of the week given a date, note that 0 = Sunday.
+   * This methods returns the day of the week given a date. If @isoWeek is true,
+   * 7=Sunday, otherwise, 0=Sunday.
    */
-  uint32_t DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay) const;
+  uint32_t DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay,
+                     bool isoWeek) const;
 
   /**
    * This methods returns the maximum number of week in a given year, the
    * result is either 52 or 53.
    */
   uint32_t MaximumWeekInYear(uint32_t aYear) const;
 
   /**
@@ -1485,22 +1489,31 @@ protected:
 
   // Default step used when there is no specified step.
   static const Decimal kDefaultStep;
   static const Decimal kDefaultStepTime;
 
   // Float value returned by GetStep() when the step attribute is set to 'any'.
   static const Decimal kStepAny;
 
+  // Minimum year limited by HTML standard, year >= 1.
+  static const double kMinimumYear;
   // Maximum year limited by ECMAScript date object range, year <= 275760.
   static const double kMaximumYear;
-  // Minimum year limited by HTML standard, year >= 1.
-  static const double kMinimumYear;
+  // Maximum valid week is 275760-W37.
+  static const double kMaximumWeekInMaximumYear;
+  // Maximum valid day is 275760-09-13.
+  static const double kMaximumDayInMaximumYear;
+  // Maximum valid month is 275760-09.
+  static const double kMaximumMonthInMaximumYear;
   // Long years in a ISO calendar have 53 weeks in them.
   static const double kMaximumWeekInYear;
+  // Milliseconds in a day.
+  static const double kMsPerDay;
+
 
   /**
    * The type of this input (<input type=...>) as an integer.
    * @see nsIFormControl.h (specifically NS_FORM_INPUT_*)
    */
   uint8_t                  mType;
   nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
   bool                     mDisabledChanged     : 1;
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -96,16 +96,17 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(TextTra
 
 StaticRefPtr<nsIWebVTTParserWrapper> TextTrackManager::sParserWrapper;
 
 TextTrackManager::TextTrackManager(HTMLMediaElement *aMediaElement)
   : mMediaElement(aMediaElement)
   , mHasSeeked(false)
   , mLastTimeMarchesOnCalled(0.0)
   , mTimeMarchesOnDispatched(false)
+  , mUpdateCueDisplayDispatched(false)
   , performedTrackSelection(false)
   , mCueTelemetryReported(false)
   , mShutdown(false)
 {
   nsISupports* parentObject =
     mMediaElement->OwnerDoc()->GetParentObject();
 
   NS_ENSURE_TRUE_VOID(parentObject);
--- a/dom/html/test/forms/test_valueasdate_attribute.html
+++ b/dom/html/test/forms/test_valueasdate_attribute.html
@@ -41,30 +41,29 @@ var validTypes =
   ["reset", false],
   ["button", false],
   ["number", false],
   ["range", false],
   ["date", true],
   ["time", true],
   ["color", false],
   ["month", true],
-  // TODO: temporary set to false until bug 888316 is fixed.
-  ["week", false],
+  ["week", true],
 ];
 
 var todoTypes =
 [
   ["datetime", true],
   ["datetime-local", true],
 ];
 
 function checkAvailability()
 {
 
-  for (data of validTypes) {
+  for (let data of validTypes) {
     var exceptionCatched = false;
     element.type = data[0];
     try {
       element.valueAsDate;
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, false,
@@ -75,17 +74,17 @@ function checkAvailability()
       element.valueAsDate = new Date();
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, !data[1], "valueAsDate for " + data[0] +
                                    " availability is not correct");
   }
 
-  for (data of todoTypes) {
+  for (let data of todoTypes) {
     var exceptionCatched = false;
     element.type = data[0];
     try {
       element.valueAsDate;
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, false,
@@ -99,17 +98,17 @@ function checkAvailability()
     }
     todo_is(exceptionCatched, !data[1],
             "valueAsDate for " + data[0] + " availability is not correct");
   }
 }
 
 function checkGarbageValues()
 {
-  for (type of validTypes) {
+  for (let type of validTypes) {
     if (!type[1]) {
       continue;
     }
     type = type[0];
 
     var element = document.createElement('input');
     element.type = type;
 
@@ -124,17 +123,17 @@ function checkGarbageValues()
     element.value = "test";
     element.valueAsDate = new Date(NaN);
     is(element.value, "", "valueAsDate should set the value to the empty string");
 
     var illegalValues = [
       "foobar", 42, {}, function() { return 42; }, function() { return Date(); }
     ];
 
-    for (value of illegalValues) {
+    for (let value of illegalValues) {
       try {
         var caught = false;
         element.valueAsDate = value;
       } catch(e) {
         is(e.name, "TypeError", "Exception should be 'TypeError'.");
         caught = true;
       }
       ok(caught, "Assigning " + value + " to .valueAsDate should throw");
@@ -172,24 +171,24 @@ function checkDateGet()
     // This date is valid for the input element, but is out of
     // the date object range. In this case, on getting valueAsDate,
     // a Date object will be created, but it will have a NaN internal value,
     // and will return the string "Invalid Date".
     [ "275760-09-14", true ],
   ];
 
   element.type = "date";
-  for (data of validData) {
+  for (let data of validData) {
     element.value = data[0];
     is(element.valueAsDate.valueOf(), data[1],
        "valueAsDate should return the " +
        "valid date object representing this date");
   }
 
-  for (data of invalidData) {
+  for (let data of invalidData) {
     element.value = data[0];
     if (data[1]) {
       is(String(element.valueAsDate), "Invalid Date",
          "valueAsDate should return an invalid Date object "  +
          "when the element value is not a valid date");
     } else {
       is(element.valueAsDate, null,
          "valueAsDate should return null "  +
@@ -220,17 +219,17 @@ function checkDateSet()
     [ 86400000,          "1970-01-02" ],
     // Negative years, this is out of range for the input element,
     // the corresponding date string is the empty string
     [ -62135596800001,   "" ],
     // Invalid dates.
   ];
 
   element.type = "date";
-  for (data of testData) {
+  for (let data of testData) {
     element.valueAsDate = new Date(data[0]);
     is(element.value, data[1], "valueAsDate should set the value to "
                                 + data[1]);
     element.valueAsDate = new testFrame.Date(data[0]);
     is(element.value, data[1], "valueAsDate with other-global date should " +
                                "set the value to " + data[1]);
   }
 }
@@ -271,17 +270,17 @@ function checkTimeGet()
     { value: "00:00:00.14", result: { time: 140, hours: 0, minutes: 0, seconds: 0, ms: 140 } },
     { value: "13:37:42.7", result: { time: 49062700, hours: 13, minutes: 37, seconds: 42, ms: 700 } },
     { value: "23:31:12.23", result: { time: 84672230, hours: 23, minutes: 31, seconds: 12, ms: 230 } },
   ];
 
   var element = document.createElement('input');
   element.type = 'time';
 
-  for (test of tests) {
+  for (let test of tests) {
     element.value = test.value;  
     if (test.result === null) {
       is(element.valueAsDate, null, "element.valueAsDate should return null");
     } else {
       var date = element.valueAsDate;
       isnot(date, null, "element.valueAsDate should not be null");
 
       is(date.getTime(), test.result.time);
@@ -314,26 +313,26 @@ function checkTimeSet()
     { value: -86400001, result: "23:59:59.999" },
     { value: -56789, result: "23:59:03.211" },
     { value: 0.9, result: "00:00" },
   ];
 
   var element = document.createElement('input');
   element.type = 'time';
 
-  for (test of tests) {
+  for (let test of tests) {
     element.valueAsDate = new Date(test.value);
     is(element.value, test.result,
        "element.value should have been changed by setting valueAsDate");
   }
 }
 
 function checkWithBustedPrototype()
 {
-  for (type of validTypes) {
+  for (let type of validTypes) {
     if (!type[1]) {
       continue;
     }
 
     type = type[0];
 
     var element = document.createElement('input');
     element.type = type;
@@ -438,24 +437,24 @@ function checkMonthGet()
     // This month is valid for the input element, but is out of
     // the date object range. In this case, on getting valueAsDate,
     // a Date object will be created, but it will have a NaN internal value,
     // and will return the string "Invalid Date".
     [ "275760-10", true ],
   ];
 
   element.type = "month";
-  for (data of validData) {
+  for (let data of validData) {
     element.value = data[0];
     is(element.valueAsDate.valueOf(), data[1],
        "valueAsDate should return the " +
        "valid date object representing this month");
   }
 
-  for (data of invalidData) {
+  for (let data of invalidData) {
     element.value = data[0];
     if (data[1]) {
       is(String(element.valueAsDate), "Invalid Date",
          "valueAsDate should return an invalid Date object "  +
          "when the element value is not a valid month");
     } else {
       is(element.valueAsDate, null,
          "valueAsDate should return null "  +
@@ -485,17 +484,166 @@ function checkMonthSet()
     [ -86400000,          "1969-12" ],
     [ 86400000,           "1970-01" ],
     // Negative years, this is out of range for the input element,
     // the corresponding month string is the empty string
     [ -62135596800001,    "" ],
   ];
 
   element.type = "month";
-  for (data of testData) {
+  for (let data of testData) {
+    element.valueAsDate = new Date(data[0]);
+    is(element.value, data[1], "valueAsDate should set the value to "
+                                + data[1]);
+    element.valueAsDate = new testFrame.Date(data[0]);
+    is(element.value, data[1], "valueAsDate with other-global date should " +
+                               "set the value to " + data[1]);
+  }
+}
+
+function checkWeekGet()
+{
+  var validData =
+  [
+    // Common years starting on different days of week.
+    [ "2007-W01", Date.UTC(2007, 0, 1)   ], // Mon
+    [ "2013-W01", Date.UTC(2012, 11, 31) ], // Tue
+    [ "2014-W01", Date.UTC(2013, 11, 30) ], // Wed
+    [ "2015-W01", Date.UTC(2014, 11, 29) ], // Thu
+    [ "2010-W01", Date.UTC(2010, 0, 4)   ], // Fri
+    [ "2011-W01", Date.UTC(2011, 0, 3)   ], // Sat
+    [ "2017-W01", Date.UTC(2017, 0, 2)   ], // Sun
+    // Common years ending on different days of week.
+    [ "2007-W52", Date.UTC(2007, 11, 24) ], // Mon
+    [ "2013-W52", Date.UTC(2013, 11, 23) ], // Tue
+    [ "2014-W52", Date.UTC(2014, 11, 22) ], // Wed
+    [ "2015-W53", Date.UTC(2015, 11, 28) ], // Thu
+    [ "2010-W52", Date.UTC(2010, 11, 27) ], // Fri
+    [ "2011-W52", Date.UTC(2011, 11, 26) ], // Sat
+    [ "2017-W52", Date.UTC(2017, 11, 25) ], // Sun
+    // Leap years starting on different days of week.
+    [ "1996-W01", Date.UTC(1996, 0, 1)   ], // Mon
+    [ "2008-W01", Date.UTC(2007, 11, 31) ], // Tue
+    [ "2020-W01", Date.UTC(2019, 11, 30) ], // Wed
+    [ "2004-W01", Date.UTC(2003, 11, 29) ], // Thu
+    [ "2016-W01", Date.UTC(2016, 0, 4)   ], // Fri
+    [ "2000-W01", Date.UTC(2000, 0, 3)   ], // Sat
+    [ "2012-W01", Date.UTC(2012, 0, 2)   ], // Sun
+    // Leap years ending on different days of week.
+    [ "2012-W52", Date.UTC(2012, 11, 24) ], // Mon
+    [ "2024-W52", Date.UTC(2024, 11, 23) ], // Tue
+    [ "1980-W52", Date.UTC(1980, 11, 22) ], // Wed
+    [ "1992-W53", Date.UTC(1992, 11, 28) ], // Thu
+    [ "2004-W53", Date.UTC(2004, 11, 27) ], // Fri
+    [ "1988-W52", Date.UTC(1988, 11, 26) ], // Sat
+    [ "2000-W52", Date.UTC(2000, 11, 25) ], // Sun
+    // Other normal cases.
+    [ "2016-W36",   1473033600000    ],
+    [ "1969-W52",   -864000000       ],
+    [ "1970-W01",   -259200000       ],
+    [ "275760-W37", 8639999568000000 ],
+  ];
+
+  var invalidData =
+  [
+    [ "invalidweek" ],
+    [ "0000-W01"     ],
+    [ "2016-W00"     ],
+    [ "123-W01"      ],
+    [ "2016-W53"     ],
+    [ ""             ],
+    // This week is valid for the input element, but is out of
+    // the date object range. In this case, on getting valueAsDate,
+    // a Date object will be created, but it will have a NaN internal value,
+    // and will return the string "Invalid Date".
+    [ "275760-W38", true ],
+  ];
+
+  element.type = "week";
+  for (let data of validData) {
+    element.value = data[0];
+    is(element.valueAsDate.valueOf(), data[1],
+       "valueAsDate should return the " +
+       "valid date object representing this week");
+  }
+
+  for (let data of invalidData) {
+    element.value = data[0];
+    if (data[1]) {
+      is(String(element.valueAsDate), "Invalid Date",
+         "valueAsDate should return an invalid Date object "  +
+         "when the element value is not a valid week");
+    } else {
+      is(element.valueAsDate, null,
+         "valueAsDate should return null "  +
+         "when the element value is not a valid week");
+    }
+  }
+}
+
+function checkWeekSet()
+{
+  var testData =
+  [
+    // Common years starting on different days of week.
+    [ Date.UTC(2007, 0, 1), "2007-W01"   ], // Mon
+    [ Date.UTC(2013, 0, 1), "2013-W01"   ], // Tue
+    [ Date.UTC(2014, 0, 1), "2014-W01"   ], // Wed
+    [ Date.UTC(2015, 0, 1), "2015-W01"   ], // Thu
+    [ Date.UTC(2010, 0, 1), "2009-W53"   ], // Fri
+    [ Date.UTC(2011, 0, 1), "2010-W52"   ], // Sat
+    [ Date.UTC(2017, 0, 1), "2016-W52"   ], // Sun
+    // Common years ending on different days of week.
+    [ Date.UTC(2007, 11, 31), "2008-W01" ], // Mon
+    [ Date.UTC(2013, 11, 31), "2014-W01" ], // Tue
+    [ Date.UTC(2014, 11, 31), "2015-W01" ], // Wed
+    [ Date.UTC(2015, 11, 31), "2015-W53" ], // Thu
+    [ Date.UTC(2010, 11, 31), "2010-W52" ], // Fri
+    [ Date.UTC(2011, 11, 31), "2011-W52" ], // Sat
+    [ Date.UTC(2017, 11, 31), "2017-W52" ], // Sun
+    // Leap years starting on different days of week.
+    [ Date.UTC(1996, 0, 1), "1996-W01"   ], // Mon
+    [ Date.UTC(2008, 0, 1), "2008-W01"   ], // Tue
+    [ Date.UTC(2020, 0, 1), "2020-W01"   ], // Wed
+    [ Date.UTC(2004, 0, 1), "2004-W01"   ], // Thu
+    [ Date.UTC(2016, 0, 1), "2015-W53"   ], // Fri
+    [ Date.UTC(2000, 0, 1), "1999-W52"   ], // Sat
+    [ Date.UTC(2012, 0, 1), "2011-W52"   ], // Sun
+    // Leap years ending on different days of week.
+    [ Date.UTC(2012, 11, 31), "2013-W01" ], // Mon
+    [ Date.UTC(2024, 11, 31), "2025-W01" ], // Tue
+    [ Date.UTC(1980, 11, 31), "1981-W01" ], // Wed
+    [ Date.UTC(1992, 11, 31), "1992-W53" ], // Thu
+    [ Date.UTC(2004, 11, 31), "2004-W53" ], // Fri
+    [ Date.UTC(1988, 11, 31), "1988-W52" ], // Sat
+    [ Date.UTC(2000, 11, 31), "2000-W52" ], // Sun
+    // Other normal cases.
+    [ Date.UTC(2016, 8, 9),  "2016-W36" ],
+    [ Date.UTC(2010, 0, 3),  "2009-W53" ],
+    [ Date.UTC(2010, 0, 4),  "2010-W01" ],
+    [ Date.UTC(2010, 0, 10), "2010-W01" ],
+    [ Date.UTC(2010, 0, 11), "2010-W02" ],
+    [ 0,                     "1970-W01" ],
+    // Maximum valid month (limited by the ecma date object range).
+    [ 8640000000000000,      "275760-W37" ],
+    // Minimum valid month (limited by the input element minimum valid value).
+    [ -62135596800000 ,      "0001-W01" ],
+    // "Values must be truncated to valid week"
+    [ 42.1234,               "1970-W01" ],
+    [ 123.123456789123,      "1970-W01" ],
+    [ 1e-1,                  "1970-W01" ],
+    [ -1.1,                  "1970-W01" ],
+    [ -345600000,            "1969-W52" ],
+    // Negative years, this is out of range for the input element,
+    // the corresponding week string is the empty string
+    [ -62135596800001,       "" ],
+  ];
+
+  element.type = "week";
+  for (let data of testData) {
     element.valueAsDate = new Date(data[0]);
     is(element.value, data[1], "valueAsDate should set the value to "
                                 + data[1]);
     element.valueAsDate = new testFrame.Date(data[0]);
     is(element.value, data[1], "valueAsDate with other-global date should " +
                                "set the value to " + data[1]);
   }
 }
@@ -511,12 +659,17 @@ checkDateSet();
 // Test <input type='time'>.
 checkTimeGet();
 checkTimeSet();
 
 // Test <input type='month'>.
 checkMonthGet();
 checkMonthSet();
 
+// Test <input type='week'>.
+checkWeekGet();
+checkWeekSet();
+
 </script>
 </pre>
 </body>
 </html>
+
--- a/dom/html/test/forms/test_valueasnumber_attribute.html
+++ b/dom/html/test/forms/test_valueasnumber_attribute.html
@@ -41,28 +41,28 @@ function checkAvailability()
     ["button", false],
     ["number", true],
     ["range", true],
     ["date", true],
     ["time", true],
     ["color", false],
     ["month", true],
     // TODO: temporary set to false until bug 888316 is fixed.
-    ["week", false],
+    ["week", true],
   ];
 
   var todoList =
   [
     ["datetime", true],
     ["datetime-local", true],
   ];
 
   var element = document.createElement('input');
 
-  for (data of testData) {
+  for (let data of testData) {
     var exceptionCatched = false;
     element.type = data[0];
     try {
       element.valueAsNumber;
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, false,
@@ -73,17 +73,17 @@ function checkAvailability()
       element.valueAsNumber = 42;
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, !data[1], "valueAsNumber for " + data[0] +
                                    " availability is not correct");
   }
 
-  for (data of todoList) {
+  for (let data of todoList) {
     var exceptionCatched = false;
     element.type = data[0];
     try {
       element.valueAsNumber;
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, false,
@@ -117,17 +117,17 @@ function checkNumberGet()
     ["1e0.1", null],
     ["", null], // the empty string is not a number
     ["foo", null],
     ["42,13", null], // comma can't be used as a decimal separator
   ];
 
   var element = document.createElement('input');
   element.type = "number";
-  for (data of testData) {
+  for (let data of testData) {
     element.value = data[0];
 
     // Given that NaN != NaN, we have to use null when the expected value is NaN.
     if (data[1] != null) {
       is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
          "floating point representation of the value");
     } else {
       ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN "  +
@@ -157,17 +157,17 @@ function checkNumberSet()
     [Infinity, "42", true],
     [-Infinity, "42", true],
     // Setting NaN should change the value to the empty string.
     [NaN, ""],
   ];
 
   var element = document.createElement('input');
   element.type = "number";
-  for (data of testData) {
+  for (let data of testData) {
     var caught = false;
     try {
       element.valueAsNumber = data[0];
       is(element.value, data[1],
          "valueAsNumber should be able to set the value");
     } catch (e) {
       caught = true;
     }
@@ -208,17 +208,17 @@ function checkRangeGet()
     ["42,13", defaultValue],
   ];
 
   var element = document.createElement('input');
   element.type = "range";
   element.setAttribute("min", min); // avoids out of range sanitization
   element.setAttribute("max", max);
   element.setAttribute("step", "any"); // avoids step mismatch sanitization
-  for (data of testData) {
+  for (let data of testData) {
     element.value = data[0];
 
     // Given that NaN != NaN, we have to use null when the expected value is NaN.
     is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
        "floating point representation of the value");
   }
 }
 
@@ -248,17 +248,17 @@ function checkRangeSet()
     [NaN, defaultValue],
   ];
 
   var element = document.createElement('input');
   element.type = "range";
   element.setAttribute("min", min); // avoids out of range sanitization
   element.setAttribute("max", max);
   element.setAttribute("step", "any"); // avoids step mismatch sanitization
-  for (data of testData) {
+  for (let data of testData) {
     var caught = false;
     try {
       element.valueAsNumber = data[0];
       is(element.value, data[1],
          "valueAsNumber should be able to set the value");
     } catch (e) {
       caught = true;
     }
@@ -300,23 +300,23 @@ function checkDateGet()
     "1901-12-32",
     "1901-00-12",
     "1901-01-00",
     "1900-02-29",
   ];
 
   var element = document.createElement('input');
   element.type = "date";
-  for (data of validData) {
+  for (let data of validData) {
     element.value = data[0];
     is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
        "timestamp representing this date");
   }
 
-  for (data of invalidData) {
+  for (let data of invalidData) {
     element.value = data;
     ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN "  +
        "when the element value is not a valid date");
   }
 }
 
 function checkDateSet()
 {
@@ -356,28 +356,28 @@ function checkDateSet()
     // Infinity will keep the current value and throw (so we need to set a current value).
     [ 1298851200010, "2011-02-28" ],
     [ Infinity, "2011-02-28", true ],
     [ -Infinity, "2011-02-28", true ],
   ];
 
   var element = document.createElement('input');
   element.type = "date";
-  for (data of testData) {
+  for (let data of testData) {
     var caught = false;
 
     try {
       element.valueAsNumber = data[0];
       is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
     } catch(e) {
       caught = true;
     }
 
     if (data[2]) {
-      ok(caught, "valueAsNumber should have trhown"); 
+      ok(caught, "valueAsNumber should have thrown");
       is(element.value, data[1], "the value should not have changed");
     } else {
       ok(!caught, "valueAsNumber should not have thrown");
     }
   }
 
 }
 
@@ -417,17 +417,17 @@ function checkTimeGet()
     { value: "00:00:00.14", result: 140 },
     { value: "13:37:42.7", result: 49062700 },
     { value: "23:31:12.23", result: 84672230 },
   ];
 
   var element = document.createElement('input');
   element.type = 'time';
 
-  for (test of tests) {
+  for (let test of tests) {
     element.value = test.value;
     if (isNaN(test.result)) {
       ok(isNaN(element.valueAsNumber),
          "invalid value should have .valueAsNumber return NaN");
     } else {
       is(element.valueAsNumber, test.result,
          ".valueAsNumber should return " + test.result);
     }
@@ -464,17 +464,17 @@ function checkTimeSet()
     { value: -86400001, result: "23:59:59.999" },
     { value: -56789, result: "23:59:03.211" },
     { value: 0.9, result: "00:00" },
   ];
 
   var element = document.createElement('input');
   element.type = 'time';
 
-  for (test of tests) {
+  for (let test of tests) {
     try {
       var caught = false;
       element.valueAsNumber = test.value;
       is(element.value, test.result, "value should return " + test.result);
     } catch(e) {
       caught = true;
     }
 
@@ -483,17 +483,16 @@ function checkTimeSet()
     }
 
     is(caught, test.throw, "the test throwing status should be " + test.throw);
   }
 }
 
 function checkMonthGet()
 {
-  dump("checkMonthGet\n");
   var validData =
   [
     [ "2016-07", 558       ],
     [ "1970-01", 0         ],
     [ "1969-12", -1        ],
     [ "0001-01", -23628    ],
     [ "10000-12", 96371    ],
     [ "275760-09", 3285488 ],
@@ -506,23 +505,23 @@ function checkMonthGet()
     "2000-00",
     "2012-13",
     // Out of range.
     "275760-10",
   ];
 
   var element = document.createElement('input');
   element.type = "month";
-  for (data of validData) {
+  for (let data of validData) {
     element.value = data[0];
     is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
        "integer value representing this month");
   }
 
-  for (data of invalidData) {
+  for (let data of invalidData) {
     element.value = data;
     ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN "  +
        "when the element value is not a valid month");
   }
 }
 
 function checkMonthSet()
 {
@@ -559,28 +558,184 @@ function checkMonthSet()
     // Infinity will keep the current value and throw (so we need to set a current value)
     [ 558,               "2016-07"   ],
     [ Infinity,          "2016-07", true ],
     [ -Infinity,         "2016-07", true ],
   ];
 
   var element = document.createElement('input');
   element.type = "month";
-  for (data of testData) {
+  for (let data of testData) {
     var caught = false;
 
     try {
       element.valueAsNumber = data[0];
       is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
     } catch(e) {
       caught = true;
     }
 
     if (data[2]) {
-      ok(caught, "valueAsNumber should have trhown");
+      ok(caught, "valueAsNumber should have thrown");
+      is(element.value, data[1], "the value should not have changed");
+    } else {
+      ok(!caught, "valueAsNumber should not have thrown");
+    }
+  }
+}
+
+function checkWeekGet()
+{
+  var validData =
+  [
+    // Common years starting on different days of week.
+    [ "2007-W01", Date.UTC(2007, 0, 1)   ], // Mon
+    [ "2013-W01", Date.UTC(2012, 11, 31) ], // Tue
+    [ "2014-W01", Date.UTC(2013, 11, 30) ], // Wed
+    [ "2015-W01", Date.UTC(2014, 11, 29) ], // Thu
+    [ "2010-W01", Date.UTC(2010, 0, 4)   ], // Fri
+    [ "2011-W01", Date.UTC(2011, 0, 3)   ], // Sat
+    [ "2017-W01", Date.UTC(2017, 0, 2)   ], // Sun
+    // Common years ending on different days of week.
+    [ "2007-W52", Date.UTC(2007, 11, 24) ], // Mon
+    [ "2013-W52", Date.UTC(2013, 11, 23) ], // Tue
+    [ "2014-W52", Date.UTC(2014, 11, 22) ], // Wed
+    [ "2015-W53", Date.UTC(2015, 11, 28) ], // Thu
+    [ "2010-W52", Date.UTC(2010, 11, 27) ], // Fri
+    [ "2011-W52", Date.UTC(2011, 11, 26) ], // Sat
+    [ "2017-W52", Date.UTC(2017, 11, 25) ], // Sun
+    // Leap years starting on different days of week.
+    [ "1996-W01", Date.UTC(1996, 0, 1)   ], // Mon
+    [ "2008-W01", Date.UTC(2007, 11, 31) ], // Tue
+    [ "2020-W01", Date.UTC(2019, 11, 30) ], // Wed
+    [ "2004-W01", Date.UTC(2003, 11, 29) ], // Thu
+    [ "2016-W01", Date.UTC(2016, 0, 4)   ], // Fri
+    [ "2000-W01", Date.UTC(2000, 0, 3)   ], // Sat
+    [ "2012-W01", Date.UTC(2012, 0, 2)   ], // Sun
+    // Leap years ending on different days of week.
+    [ "2012-W52", Date.UTC(2012, 11, 24) ], // Mon
+    [ "2024-W52", Date.UTC(2024, 11, 23) ], // Tue
+    [ "1980-W52", Date.UTC(1980, 11, 22) ], // Wed
+    [ "1992-W53", Date.UTC(1992, 11, 28) ], // Thu
+    [ "2004-W53", Date.UTC(2004, 11, 27) ], // Fri
+    [ "1988-W52", Date.UTC(1988, 11, 26) ], // Sat
+    [ "2000-W52", Date.UTC(2000, 11, 25) ], // Sun
+    // Other normal cases.
+    [ "2015-W53", Date.UTC(2015, 11, 28)   ],
+    [ "2016-W36", Date.UTC(2016, 8, 5)     ],
+    [ "1970-W01", Date.UTC(1969, 11, 29)   ],
+    [ "275760-W37", Date.UTC(275760, 8, 8) ],
+  ];
+
+  var invalidData =
+  [
+    "invalidweek",
+    "0000-W01",
+    "2016-W00",
+    "2016-W53",
+    // Out of range.
+    "275760-W38",
+  ];
+
+  var element = document.createElement('input');
+  element.type = "week";
+  for (let data of validData) {
+    dump("Test: " + data[0]);
+    element.value = data[0];
+    is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
+       "integer value representing this week");
+  }
+
+  for (let data of invalidData) {
+    element.value = data;
+    ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN "  +
+       "when the element value is not a valid week");
+  }
+}
+
+function checkWeekSet()
+{
+  var testData =
+  [
+    // Common years starting on different days of week.
+    [ Date.UTC(2007, 0, 1), "2007-W01"   ], // Mon
+    [ Date.UTC(2013, 0, 1), "2013-W01"   ], // Tue
+    [ Date.UTC(2014, 0, 1), "2014-W01"   ], // Wed
+    [ Date.UTC(2015, 0, 1), "2015-W01"   ], // Thu
+    [ Date.UTC(2010, 0, 1), "2009-W53"   ], // Fri
+    [ Date.UTC(2011, 0, 1), "2010-W52"   ], // Sat
+    [ Date.UTC(2017, 0, 1), "2016-W52"   ], // Sun
+    // Common years ending on different days of week.
+    [ Date.UTC(2007, 11, 31), "2008-W01" ], // Mon
+    [ Date.UTC(2013, 11, 31), "2014-W01" ], // Tue
+    [ Date.UTC(2014, 11, 31), "2015-W01" ], // Wed
+    [ Date.UTC(2015, 11, 31), "2015-W53" ], // Thu
+    [ Date.UTC(2010, 11, 31), "2010-W52" ], // Fri
+    [ Date.UTC(2011, 11, 31), "2011-W52" ], // Sat
+    [ Date.UTC(2017, 11, 31), "2017-W52" ], // Sun
+    // Leap years starting on different days of week.
+    [ Date.UTC(1996, 0, 1), "1996-W01"   ], // Mon
+    [ Date.UTC(2008, 0, 1), "2008-W01"   ], // Tue
+    [ Date.UTC(2020, 0, 1), "2020-W01"   ], // Wed
+    [ Date.UTC(2004, 0, 1), "2004-W01"   ], // Thu
+    [ Date.UTC(2016, 0, 1), "2015-W53"   ], // Fri
+    [ Date.UTC(2000, 0, 1), "1999-W52"   ], // Sat
+    [ Date.UTC(2012, 0, 1), "2011-W52"   ], // Sun
+    // Leap years ending on different days of week.
+    [ Date.UTC(2012, 11, 31), "2013-W01" ], // Mon
+    [ Date.UTC(2024, 11, 31), "2025-W01" ], // Tue
+    [ Date.UTC(1980, 11, 31), "1981-W01" ], // Wed
+    [ Date.UTC(1992, 11, 31), "1992-W53" ], // Thu
+    [ Date.UTC(2004, 11, 31), "2004-W53" ], // Fri
+    [ Date.UTC(1988, 11, 31), "1988-W52" ], // Sat
+    [ Date.UTC(2000, 11, 31), "2000-W52" ], // Sun
+    // Other normal cases.
+    [ Date.UTC(2008, 8, 26),  "2008-W39" ],
+    [ Date.UTC(2016, 0, 4),   "2016-W01" ],
+    [ Date.UTC(2016, 0, 10),  "2016-W01" ],
+    [ Date.UTC(2016, 0, 11),  "2016-W02" ],
+    // Maximum valid week (limited by the ecma date object range).
+    [ 8640000000000000,  "275760-W37" ],
+    // Minimum valid week (limited by the input element minimum valid value)
+    [ -62135596800000,   "0001-W01" ],
+    // "Values must be truncated to valid weeks"
+    [ 0.3,               "1970-W01"  ],
+    [ 1e-1,              "1970-W01"  ],
+    [ -1.1,              "1970-W01"  ],
+    [ -345600000,        "1969-W52"  ],
+    // Invalid numbers.
+    // Those are implicitly converted to numbers
+    [ "",                "1970-W01" ],
+    [ true,              "1970-W01" ],
+    [ false,             "1970-W01" ],
+    [ null,              "1970-W01" ],
+    // Those are converted to NaN, the corresponding week string is the empty string
+    [ "invalidweek",     "" ],
+    [ NaN,               "" ],
+    [ undefined,         "" ],
+    // Infinity will keep the current value and throw (so we need to set a current value).
+    [ Date.UTC(2016, 8, 8), "2016-W36" ],
+    [ Infinity,             "2016-W36", true ],
+    [ -Infinity,            "2016-W36", true ],
+  ];
+
+  var element = document.createElement('input');
+  element.type = "week";
+  for (let data of testData) {
+    var caught = false;
+
+    try {
+      element.valueAsNumber = data[0];
+      is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
+    } catch(e) {
+      caught = true;
+    }
+
+    if (data[2]) {
+      ok(caught, "valueAsNumber should have thrown");
       is(element.value, data[1], "the value should not have changed");
     } else {
       ok(!caught, "valueAsNumber should not have thrown");
     }
   }
 }
 
 checkAvailability();
@@ -600,12 +755,16 @@ checkDateSet();
 // <input type='time'> test
 checkTimeGet();
 checkTimeSet();
 
 // <input type='month'> test
 checkMonthGet();
 checkMonthSet();
 
+// <input type='week'> test
+checkWeekGet();
+checkWeekSet();
+
 </script>
 </pre>
 </body>
 </html>
--- a/dom/indexedDB/FileSnapshot.cpp
+++ b/dom/indexedDB/FileSnapshot.cpp
@@ -277,16 +277,28 @@ StreamWrapper::Deserialize(const InputSt
 
   if (stream) {
     return stream->Deserialize(aParams, aFileDescriptors);
   }
 
   return false;
 }
 
+Maybe<uint64_t>
+StreamWrapper::ExpectedSerializedLength()
+{
+  nsCOMPtr<nsIIPCSerializableInputStream> stream =
+    do_QueryInterface(mInputStream);
+
+  if (stream) {
+    return stream->ExpectedSerializedLength();
+  }
+  return Nothing();
+}
+
 NS_IMPL_ISUPPORTS_INHERITED0(StreamWrapper::CloseRunnable,
                              Runnable)
 
 NS_IMETHODIMP
 StreamWrapper::
 CloseRunnable::Run()
 {
   mStreamWrapper->Finish();
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1755,16 +1755,23 @@ interface nsIDOMWindowUtils : nsISupport
   /**
    * Returns true if a user input is being handled.
    *
    * This calls EventStateManager::IsHandlingUserInput().
    */
   readonly attribute boolean isHandlingUserInput;
 
   /**
+   * Returns milliseconds elapsed since last user input was started
+   *
+   * This relies on EventStateManager::LatestUserInputStart()
+   */
+  readonly attribute double millisSinceLastUserInput;
+
+  /**
    * After calling the method, the window for which this DOMWindowUtils
    * was created can be closed using scripts.
    */
   void allowScriptsToClose();
 
   /**
    * Is the parent window's main widget visible?  If it isn't, we probably
    * don't want to display any dialogs etc it may request.  This corresponds
--- a/dom/interfaces/events/nsIDOMDataTransfer.idl
+++ b/dom/interfaces/events/nsIDOMDataTransfer.idl
@@ -69,24 +69,16 @@ interface nsIDOMDataTransfer : nsISuppor
   /**
    * Holds a list of the format types of the data that is stored for the first
    * item, in the same order the data was added. An empty list will be
    * returned if no data was added.
    */
   readonly attribute nsISupports types;
 
   /**
-   * Remove the data associated with a given format. If format is empty or not
-   * specified, the data associated with all formats is removed. If data for
-   * the specified format does not exist, or the data transfer contains no
-   * data, this method will have no effect.
-   */
-  void clearData([optional] in DOMString format);
-
-  /**
    * Retrieves the data for a given format, or an empty string if data for
    * that format does not exist or the data transfer contains no data.
    */
   DOMString getData(in DOMString format);
 
   /**
    * Set the image to be used for dragging if a custom one is desired. Most of
    * the time, this would not be set, as a default image is created from the
@@ -140,32 +132,16 @@ interface nsIDOMDataTransfer : nsISuppor
   /**
    * Holds a list of the format types of the data that is stored for an item
    * at the specified index. If the index is not in the range from 0 to
    * itemCount - 1, an empty string list is returned.
    */
   nsISupports mozTypesAt(in unsigned long index);
 
   /**
-   * Remove the data associated with the given format for an item at the
-   * specified index. The index is in the range from zero to itemCount - 1.
-   *
-   * If the last format for the item is removed, the entire item is removed,
-   * reducing the itemCount by one.
-   *
-   * If format is empty, then the data associated with all formats is removed.
-   * If the format is not found, then this method has no effect.
-   *
-   * @param format the format to remove
-   * @throws NS_ERROR_DOM_INDEX_SIZE_ERR if index is greater or equal than itemCount
-   * @throws NO_MODIFICATION_ALLOWED_ERR if the item cannot be modified
-   */
-  void mozClearDataAt(in DOMString format, in unsigned long index);
-
-  /**
    * Will be true when the user has cancelled the drag (typically by pressing
    * Escape) and when the drag has been cancelled unexpectedly.  This will be
    * false otherwise, including when the drop has been rejected by its target.
    * This property is only relevant for the dragend event.
    */
   readonly attribute boolean mozUserCancelled;
 
   /**
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -1302,16 +1302,22 @@ bool
 RemoteInputStream::Deserialize(const InputStreamParams& /* aParams */,
                                const FileDescriptorArray& /* aFDs */)
 {
   // See InputStreamUtils.cpp to see how deserialization of a
   // RemoteInputStream is special-cased.
   MOZ_CRASH("RemoteInputStream should never be deserialized");
 }
 
+Maybe<uint64_t>
+RemoteInputStream::ExpectedSerializedLength()
+{
+  return Nothing();
+}
+
 nsIInputStream*
 RemoteInputStream::BlockAndGetInternalStream()
 {
   MOZ_ASSERT(!IsOnOwningThread());
 
   nsresult rv = BlockAndWaitForStream();
   NS_ENSURE_SUCCESS(rv, nullptr);
 
--- a/dom/ipc/PScreenManager.ipdl
+++ b/dom/ipc/PScreenManager.ipdl
@@ -18,17 +18,16 @@ struct ScreenDetails {
   uint32_t id;
   nsIntRect rect;
   nsIntRect rectDisplayPix;
   nsIntRect availRect;
   nsIntRect availRectDisplayPix;
   int32_t pixelDepth;
   int32_t colorDepth;
   double contentsScaleFactor;
-  double defaultCSSScaleFactor;
 };
 
 prio(normal upto high) sync protocol PScreenManager
 {
   manager PContent;
 
 parent:
     prio(high) sync Refresh()
--- a/dom/ipc/ScreenManagerParent.cpp
+++ b/dom/ipc/ScreenManagerParent.cpp
@@ -211,21 +211,16 @@ ScreenManagerParent::ExtractScreenDetail
   NS_ENSURE_SUCCESS(rv, false);
   aDetails.colorDepth() = colorDepth;
 
   double contentsScaleFactor = 1.0;
   rv = aScreen->GetContentsScaleFactor(&contentsScaleFactor);
   NS_ENSURE_SUCCESS(rv, false);
   aDetails.contentsScaleFactor() = contentsScaleFactor;
 
-  double defaultCSSScaleFactor = 1.0;
-  rv = aScreen->GetDefaultCSSScaleFactor(&defaultCSSScaleFactor);
-  NS_ENSURE_SUCCESS(rv, false);
-  aDetails.defaultCSSScaleFactor() = defaultCSSScaleFactor;
-
   return true;
 }
 
 void
 ScreenManagerParent::ActorDestroy(ActorDestroyReason why)
 {
 }
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1777,22 +1777,30 @@ TabChild::RecvHandleTap(const GeckoConte
   CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint / scale, aGuid);
 
   switch (aType) {
   case GeckoContentController::TapType::eSingleTap:
     if (mRemoteFrame) {
       mRemoteFrame->SendTakeFocusForClickFromTap();
     }
     if (mGlobal && mTabChildGlobal) {
-      mAPZEventState->ProcessSingleTap(point, scale, aModifiers, aGuid);
+      mAPZEventState->ProcessSingleTap(point, scale, aModifiers, aGuid, 1);
     }
     break;
   case GeckoContentController::TapType::eDoubleTap:
     HandleDoubleTap(point, aModifiers, aGuid);
     break;
+  case GeckoContentController::TapType::eSecondTap:
+    if (mRemoteFrame) {
+      mRemoteFrame->SendTakeFocusForClickFromTap();
+    }
+    if (mGlobal && mTabChildGlobal) {
+      mAPZEventState->ProcessSingleTap(point, scale, aModifiers, aGuid, 2);
+    }
+    break;
   case GeckoContentController::TapType::eLongTap:
     if (mGlobal && mTabChildGlobal) {
       mAPZEventState->ProcessLongTap(presShell, point, scale, aModifiers, aGuid,
           aInputBlockId);
     }
     break;
   case GeckoContentController::TapType::eLongTapUp:
     if (mGlobal && mTabChildGlobal) {
--- a/dom/locales/en-US/chrome/security/csp.properties
+++ b/dom/locales/en-US/chrome/security/csp.properties
@@ -72,16 +72,19 @@ notSupportingDirective = Not supporting directive ‘%1$S’. Directive and values will be ignored.
 # %1$S is the URL of the blocked resource load.
 blockAllMixedContent = Blocking insecure request ‘%1$S’.
 # LOCALIZATION NOTE (ignoringDirectiveWithNoValues):
 # %1$S is the name of a CSP directive that requires additional values (e.g., 'require-sri-for')
 ignoringDirectiveWithNoValues = Ignoring ‘%1$S’ since it does not contain any parameters.
 # LOCALIZATION NOTE (ignoringReportOnlyDirective):
 # %1$S is the directive that is ignored in report-only mode.
 ignoringReportOnlyDirective = Ignoring sandbox directive when delivered in a report-only policy ‘%1$S’
+# LOCALIZATION NOTE (deprecatedReferrerDirective):
+# %1$S is the value of the deprecated Referrer Directive.
+deprecatedReferrerDirective = Referrer Directive ‘%1$S’ has been deprecated. Please use the Referrer-Policy header instead.
 
 # CSP Errors:
 # LOCALIZATION NOTE (couldntParseInvalidSource):
 # %1$S is the source that could not be parsed
 couldntParseInvalidSource = Couldn’t parse invalid source %1$S
 # LOCALIZATION NOTE (couldntParseInvalidHost):
 # %1$S is the host that's invalid
 couldntParseInvalidHost = Couldn’t parse invalid host %1$S
--- a/dom/media/ipc/VideoDecoderChild.h
+++ b/dom/media/ipc/VideoDecoderChild.h
@@ -35,16 +35,17 @@ public:
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   RefPtr<MediaDataDecoder::InitPromise> Init();
   void Input(MediaRawData* aSample);
   void Flush();
   void Drain();
   void Shutdown();
 
+  MOZ_IS_CLASS_INIT
   void InitIPDL(MediaDataDecoderCallback* aCallback,
                 const VideoInfo& aVideoInfo,
                 layers::LayersBackend aLayersBackend);
   void DestroyIPDL();
 
   // Called from IPDL when our actor has been destroyed
   void IPDLActorDestroyed();
 
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -1790,19 +1790,19 @@ NPObjWrapper_toPrimitive(JSContext *cx, 
     return false;
   if (v.isObject() && JS::IsCallable(&v.toObject())) {
     if (!JS_CallFunctionValue(cx, obj, v, JS::HandleValueArray::empty(), args.rval()))
       return false;
     if (args.rval().isPrimitive())
       return true;
   }
 
-  JS_ReportErrorNumber(cx, js::GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
-                       JS_GetClass(obj)->name,
-                       "primitive type");
+  JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
+                            JSMSG_CANT_CONVERT_TO,
+                            JS_GetClass(obj)->name, "primitive type");
   return false;
 }
 
 bool
 nsNPObjWrapper::IsWrapper(JSObject *obj)
 {
   return js::GetObjectClass(obj) == &sNPObjectJSWrapperClass;
 }
--- a/dom/presentation/PresentationCallbacks.cpp
+++ b/dom/presentation/PresentationCallbacks.cpp
@@ -105,16 +105,20 @@ PresentationReconnectCallback::NotifySuc
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsresult rv = NS_OK;
   // We found a matched connection with the same window ID, URL, and
   // the session ID. Resolve the promise with this connection and dispatch
   // the event.
   if (mConnection) {
+    mConnection->NotifyStateChange(
+      mSessionId,
+      nsIPresentationSessionListener::STATE_CONNECTING,
+      NS_OK);
     mPromise->MaybeResolve(mConnection);
     rv = mRequest->DispatchConnectionAvailableEvent(mConnection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   } else {
     // Use |PresentationRequesterCallback::NotifySuccess| to create a new
     // connection since we don't find one that can be reused.
@@ -137,16 +141,22 @@ PresentationReconnectCallback::NotifySuc
              service->BuildTransport(sessionId,
                                      nsIPresentationService::ROLE_CONTROLLER);
            }));
 }
 
 NS_IMETHODIMP
 PresentationReconnectCallback::NotifyError(nsresult aError)
 {
+  if (mConnection) {
+    mConnection->NotifyStateChange(
+      mSessionId,
+      nsIPresentationSessionListener::STATE_CLOSED,
+      aError);
+  }
   return PresentationRequesterCallback::NotifyError(aError);
 }
 
 NS_IMPL_ISUPPORTS(PresentationResponderLoadingCallback,
                   nsIWebProgressListener,
                   nsISupportsWeakReference)
 
 PresentationResponderLoadingCallback::PresentationResponderLoadingCallback(const nsAString& aSessionId)
--- a/dom/presentation/PresentationConnection.cpp
+++ b/dom/presentation/PresentationConnection.cpp
@@ -357,16 +357,21 @@ PresentationConnection::NotifyStateChang
   PRES_DEBUG("connection state change:id[%s], state[%x], reason[%x], role[%d]\n",
              NS_ConvertUTF16toUTF8(aSessionId).get(), aState,
              aReason, mRole);
 
   if (!aSessionId.Equals(mId)) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  // A terminated connection should always remain in terminated.
+  if (mState == PresentationConnectionState::Terminated) {
+    return NS_OK;
+  }
+
   PresentationConnectionState state;
   switch (aState) {
     case nsIPresentationSessionListener::STATE_CONNECTING:
       state = PresentationConnectionState::Connecting;
       break;
     case nsIPresentationSessionListener::STATE_CONNECTED:
       state = PresentationConnectionState::Connected;
       break;
@@ -519,27 +524,16 @@ PresentationConnection::DoReceiveMessage
     if(NS_WARN_IF(!ToJSValue(cx, utf16Data, &jsData))) {
       return NS_ERROR_FAILURE;
     }
   }
 
   return DispatchMessageEvent(jsData);
 }
 
-NS_IMETHODIMP
-PresentationConnection::NotifyReplaced()
-{
-  PRES_DEBUG("connection %s:id[%s], role[%d]\n", __func__,
-             NS_ConvertUTF16toUTF8(mId).get(), mRole);
-
-  return NotifyStateChange(mId,
-                           nsIPresentationSessionListener::STATE_CLOSED,
-                           NS_OK);
-}
-
 nsresult
 PresentationConnection::DispatchConnectionClosedEvent(
   PresentationConnectionClosedReason aReason,
   const nsAString& aMessage,
   bool aDispatchNow)
 {
   if (mState != PresentationConnectionState::Closed) {
     MOZ_ASSERT(false, "The connection state should be closed.");
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -256,20 +256,19 @@ PresentationDeviceRequest::Cancel(nsresu
 {
   return mCallback->NotifyError(aReason);
 }
 
 /*
  * Implementation of PresentationService
  */
 
-NS_IMPL_ISUPPORTS_INHERITED(PresentationService,
-                            PresentationServiceBase,
-                            nsIPresentationService,
-                            nsIObserver)
+NS_IMPL_ISUPPORTS(PresentationService,
+                  nsIPresentationService,
+                  nsIObserver)
 
 PresentationService::PresentationService()
   : mIsAvailable(false)
 {
 }
 
 PresentationService::~PresentationService()
 {
--- a/dom/presentation/PresentationService.h
+++ b/dom/presentation/PresentationService.h
@@ -19,44 +19,29 @@ class nsIURI;
 class nsIPresentationSessionTransportBuilder;
 
 namespace mozilla {
 namespace dom {
 
 class PresentationDeviceRequest;
 class PresentationRespondingInfo;
 
-class PresentationService final : public nsIPresentationService
-                                , public nsIObserver
-                                , public PresentationServiceBase
+class PresentationService final
+                      : public nsIPresentationService
+                      , public nsIObserver
+                      , public PresentationServiceBase<PresentationSessionInfo>
 {
 public:
-  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIPRESENTATIONSERVICE
 
   PresentationService();
   bool Init();
 
-  already_AddRefed<PresentationSessionInfo>
-  GetSessionInfo(const nsAString& aSessionId, const uint8_t aRole)
-  {
-    MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
-               aRole == nsIPresentationService::ROLE_RECEIVER);
-
-    RefPtr<PresentationSessionInfo> info;
-    if (aRole == nsIPresentationService::ROLE_CONTROLLER) {
-      return mSessionInfoAtController.Get(aSessionId, getter_AddRefs(info)) ?
-             info.forget() : nullptr;
-    } else {
-      return mSessionInfoAtReceiver.Get(aSessionId, getter_AddRefs(info)) ?
-             info.forget() : nullptr;
-    }
-  }
-
   bool IsSessionAccessible(const nsAString& aSessionId,
                            const uint8_t aRole,
                            base::ProcessId aProcessId);
 
 private:
   friend class PresentationDeviceRequest;
 
   virtual ~PresentationService();
@@ -71,16 +56,14 @@ private:
   // This is meant to be called by PresentationDeviceRequest.
   already_AddRefed<PresentationSessionInfo>
   CreateControllingSessionInfo(const nsAString& aUrl,
                                const nsAString& aSessionId,
                                uint64_t aWindowId);
 
   bool mIsAvailable;
   nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener>> mAvailabilityListeners;
-  nsRefPtrHashtable<nsStringHashKey, PresentationSessionInfo> mSessionInfoAtController;
-  nsRefPtrHashtable<nsStringHashKey, PresentationSessionInfo> mSessionInfoAtReceiver;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationService_h
deleted file mode 100644
--- a/dom/presentation/PresentationServiceBase.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set sw=2 ts=8 et ft=cpp : */
-/* 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/. */
-
-#include "PresentationServiceBase.h"
-
-#include "nsString.h"
-
-namespace mozilla {
-namespace dom {
-
-NS_IMPL_ISUPPORTS0(PresentationServiceBase)
-
-nsresult
-PresentationServiceBase::SessionIdManager::GetWindowId(
-                                                   const nsAString& aSessionId,
-                                                   uint64_t* aWindowId)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mRespondingWindowIds.Get(aSessionId, aWindowId)) {
-    return NS_OK;
-  }
-  return NS_ERROR_NOT_AVAILABLE;
-}
-
-nsresult
-PresentationServiceBase::SessionIdManager::GetSessionIds(
-                                               uint64_t aWindowId,
-                                               nsTArray<nsString>& aSessionIds)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsTArray<nsString>* sessionIdArray;
-  if (!mRespondingSessionIds.Get(aWindowId, &sessionIdArray)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  aSessionIds.Assign(*sessionIdArray);
-  return NS_OK;
-}
-
-void
-PresentationServiceBase::SessionIdManager::AddSessionId(
-                                                   uint64_t aWindowId,
-                                                   const nsAString& aSessionId)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (NS_WARN_IF(aWindowId == 0)) {
-    return;
-  }
-
-  nsTArray<nsString>* sessionIdArray;
-  if (!mRespondingSessionIds.Get(aWindowId, &sessionIdArray)) {
-    sessionIdArray = new nsTArray<nsString>();
-    mRespondingSessionIds.Put(aWindowId, sessionIdArray);
-  }
-
-  sessionIdArray->AppendElement(nsString(aSessionId));
-  mRespondingWindowIds.Put(aSessionId, aWindowId);
-}
-
-void
-PresentationServiceBase::SessionIdManager::RemoveSessionId(
-                                                   const nsAString& aSessionId)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  uint64_t windowId = 0;
-  if (mRespondingWindowIds.Get(aSessionId, &windowId)) {
-    mRespondingWindowIds.Remove(aSessionId);
-    nsTArray<nsString>* sessionIdArray;
-    if (mRespondingSessionIds.Get(windowId, &sessionIdArray)) {
-      sessionIdArray->RemoveElement(nsString(aSessionId));
-      if (sessionIdArray->IsEmpty()) {
-        mRespondingSessionIds.Remove(windowId);
-      }
-    }
-  }
-}
-
-nsresult
-PresentationServiceBase::SessionIdManager::UpdateWindowId(
-                                                   const nsAString& aSessionId,
-                                                   const uint64_t aWindowId)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  RemoveSessionId(aSessionId);
-  AddSessionId(aWindowId, aSessionId);
-  return NS_OK;
-}
-
-nsresult
-PresentationServiceBase::GetWindowIdBySessionIdInternal(
-                                                   const nsAString& aSessionId,
-                                                   uint8_t aRole,
-                                                   uint64_t* aWindowId)
-{
-  MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
-             aRole == nsIPresentationService::ROLE_RECEIVER);
-
-  if (NS_WARN_IF(!aWindowId)) {
-    return NS_ERROR_INVALID_POINTER;
-  }
-
-  if (aRole == nsIPresentationService::ROLE_CONTROLLER) {
-    return mControllerSessionIdManager.GetWindowId(aSessionId, aWindowId);
-  }
-
-  return mReceiverSessionIdManager.GetWindowId(aSessionId, aWindowId);
-}
-
-void
-PresentationServiceBase::AddRespondingSessionId(uint64_t aWindowId,
-                                                const nsAString& aSessionId,
-                                                uint8_t aRole)
-{
-  MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
-             aRole == nsIPresentationService::ROLE_RECEIVER);
-
-  if (aRole == nsIPresentationService::ROLE_CONTROLLER) {
-    mControllerSessionIdManager.AddSessionId(aWindowId, aSessionId);
-  } else {
-    mReceiverSessionIdManager.AddSessionId(aWindowId, aSessionId);
-  }
-}
-
-void
-PresentationServiceBase::RemoveRespondingSessionId(const nsAString& aSessionId,
-                                                   uint8_t aRole)
-{
-  MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
-             aRole == nsIPresentationService::ROLE_RECEIVER);
-
-  if (aRole == nsIPresentationService::ROLE_CONTROLLER) {
-    mControllerSessionIdManager.RemoveSessionId(aSessionId);
-  } else {
-    mReceiverSessionIdManager.RemoveSessionId(aSessionId);
-  }
-}
-
-nsresult
-PresentationServiceBase::UpdateWindowIdBySessionIdInternal(
-                                                   const nsAString& aSessionId,
-                                                   uint8_t aRole,
-                                                   const uint64_t aWindowId)
-{
-  MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
-             aRole == nsIPresentationService::ROLE_RECEIVER);
-
-  if (aRole == nsIPresentationService::ROLE_CONTROLLER) {
-    return mControllerSessionIdManager.UpdateWindowId(aSessionId, aWindowId);
-  }
-
-  return mReceiverSessionIdManager.UpdateWindowId(aSessionId, aWindowId);
-}
-
-} // namespace dom
-} // namespace mozilla
--- a/dom/presentation/PresentationServiceBase.h
+++ b/dom/presentation/PresentationServiceBase.h
@@ -3,55 +3,128 @@
 /* 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/. */
 
 #ifndef mozilla_dom_PresentationServiceBase_h
 #define mozilla_dom_PresentationServiceBase_h
 
 #include "nsClassHashtable.h"
+#include "nsIPresentationService.h"
 #include "nsRefPtrHashtable.h"
+#include "nsString.h"
 #include "nsTArray.h"
 
 class nsIPresentationRespondingListener;
-class nsString;
 
 namespace mozilla {
 namespace dom {
 
-class PresentationServiceBase : public nsISupports
+template<class T>
+class PresentationServiceBase
 {
 public:
-  NS_DECL_ISUPPORTS
+  PresentationServiceBase() = default;
+
+  already_AddRefed<T>
+  GetSessionInfo(const nsAString& aSessionId, const uint8_t aRole)
+  {
+    MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
+               aRole == nsIPresentationService::ROLE_RECEIVER);
 
-  PresentationServiceBase() = default;
+    RefPtr<T> info;
+    if (aRole == nsIPresentationService::ROLE_CONTROLLER) {
+      return mSessionInfoAtController.Get(aSessionId, getter_AddRefs(info)) ?
+             info.forget() : nullptr;
+    } else {
+      return mSessionInfoAtReceiver.Get(aSessionId, getter_AddRefs(info)) ?
+             info.forget() : nullptr;
+    }
+  }
 
 protected:
   class SessionIdManager final
   {
   public:
     explicit SessionIdManager()
     {
       MOZ_COUNT_CTOR(SessionIdManager);
     }
 
     ~SessionIdManager()
     {
       MOZ_COUNT_DTOR(SessionIdManager);
     }
 
-    nsresult GetWindowId(const nsAString& aSessionId, uint64_t* aWindowId);
+    nsresult GetWindowId(const nsAString& aSessionId, uint64_t* aWindowId)
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+
+      if (mRespondingWindowIds.Get(aSessionId, aWindowId)) {
+        return NS_OK;
+      }
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    nsresult GetSessionIds(uint64_t aWindowId, nsTArray<nsString>& aSessionIds)
+    {
+      MOZ_ASSERT(NS_IsMainThread());
 
-    nsresult GetSessionIds(uint64_t aWindowId, nsTArray<nsString>& aSessionIds);
+      nsTArray<nsString>* sessionIdArray;
+      if (!mRespondingSessionIds.Get(aWindowId, &sessionIdArray)) {
+        return NS_ERROR_INVALID_ARG;
+      }
+
+      aSessionIds.Assign(*sessionIdArray);
+      return NS_OK;
+    }
+
+    void AddSessionId(uint64_t aWindowId, const nsAString& aSessionId)
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+
+      if (NS_WARN_IF(aWindowId == 0)) {
+        return;
+      }
 
-    void AddSessionId(uint64_t aWindowId, const nsAString& aSessionId);
+      nsTArray<nsString>* sessionIdArray;
+      if (!mRespondingSessionIds.Get(aWindowId, &sessionIdArray)) {
+        sessionIdArray = new nsTArray<nsString>();
+        mRespondingSessionIds.Put(aWindowId, sessionIdArray);
+      }
+
+      sessionIdArray->AppendElement(nsString(aSessionId));
+      mRespondingWindowIds.Put(aSessionId, aWindowId);
+    }
+
+    void RemoveSessionId(const nsAString& aSessionId)
+    {
+      MOZ_ASSERT(NS_IsMainThread());
 
-    void RemoveSessionId(const nsAString& aSessionId);
+      uint64_t windowId = 0;
+      if (mRespondingWindowIds.Get(aSessionId, &windowId)) {
+        mRespondingWindowIds.Remove(aSessionId);
+        nsTArray<nsString>* sessionIdArray;
+        if (mRespondingSessionIds.Get(windowId, &sessionIdArray)) {
+          sessionIdArray->RemoveElement(nsString(aSessionId));
+          if (sessionIdArray->IsEmpty()) {
+            mRespondingSessionIds.Remove(windowId);
+          }
+        }
+      }
+    }
 
-    nsresult UpdateWindowId(const nsAString& aSessionId, const uint64_t aWindowId);
+    nsresult UpdateWindowId(const nsAString& aSessionId, const uint64_t aWindowId)
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+
+      RemoveSessionId(aSessionId);
+      AddSessionId(aWindowId, aSessionId);
+      return NS_OK;
+    }
 
     void Clear()
     {
       mRespondingSessionIds.Clear();
       mRespondingWindowIds.Clear();
     }
 
   private:
@@ -65,36 +138,86 @@ protected:
   {
     mRespondingListeners.Clear();
     mControllerSessionIdManager.Clear();
     mReceiverSessionIdManager.Clear();
   }
 
   nsresult GetWindowIdBySessionIdInternal(const nsAString& aSessionId,
                                           uint8_t aRole,
-                                          uint64_t* aWindowId);
+                                          uint64_t* aWindowId)
+  {
+    MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
+               aRole == nsIPresentationService::ROLE_RECEIVER);
+
+    if (NS_WARN_IF(!aWindowId)) {
+      return NS_ERROR_INVALID_POINTER;
+    }
+
+    if (aRole == nsIPresentationService::ROLE_CONTROLLER) {
+      return mControllerSessionIdManager.GetWindowId(aSessionId, aWindowId);
+    }
+
+    return mReceiverSessionIdManager.GetWindowId(aSessionId, aWindowId);
+  }
+
   void AddRespondingSessionId(uint64_t aWindowId,
                               const nsAString& aSessionId,
-                              uint8_t aRole);
+                              uint8_t aRole)
+  {
+    MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
+               aRole == nsIPresentationService::ROLE_RECEIVER);
+
+    if (aRole == nsIPresentationService::ROLE_CONTROLLER) {
+      mControllerSessionIdManager.AddSessionId(aWindowId, aSessionId);
+    } else {
+      mReceiverSessionIdManager.AddSessionId(aWindowId, aSessionId);
+    }
+  }
+
   void RemoveRespondingSessionId(const nsAString& aSessionId,
-                                 uint8_t aRole);
+                                 uint8_t aRole)
+  {
+    MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
+               aRole == nsIPresentationService::ROLE_RECEIVER);
+
+    if (aRole == nsIPresentationService::ROLE_CONTROLLER) {
+      mControllerSessionIdManager.RemoveSessionId(aSessionId);
+    } else {
+      mReceiverSessionIdManager.RemoveSessionId(aSessionId);
+    }
+  }
+
   nsresult UpdateWindowIdBySessionIdInternal(const nsAString& aSessionId,
                                              uint8_t aRole,
-                                             const uint64_t aWindowId);
+                                             const uint64_t aWindowId)
+  {
+    MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
+               aRole == nsIPresentationService::ROLE_RECEIVER);
+
+    if (aRole == nsIPresentationService::ROLE_CONTROLLER) {
+      return mControllerSessionIdManager.UpdateWindowId(aSessionId, aWindowId);
+    }
+
+    return mReceiverSessionIdManager.UpdateWindowId(aSessionId, aWindowId);
+  }
 
   // Store the responding listener based on the window ID of the (in-process or
   // OOP) receiver page.
   nsRefPtrHashtable<nsUint64HashKey, nsIPresentationRespondingListener>
   mRespondingListeners;
 
   // Store the mapping between the window ID of the in-process and OOP page and the ID
   // of the responding session. It's used for both controller and receiver page
   // to retrieve the correspondent session ID. Besides, also keep the mapping
   // between the responding session ID and the window ID to help look up the
   // window ID.
   SessionIdManager mControllerSessionIdManager;
   SessionIdManager mReceiverSessionIdManager;
+
+  nsRefPtrHashtable<nsStringHashKey, T> mSessionInfoAtController;
+  nsRefPtrHashtable<nsStringHashKey, T> mSessionInfoAtReceiver;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationServiceBase_h
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -242,20 +242,16 @@ PresentationSessionInfo::Shutdown(nsresu
   mIsOnTerminating = false;
 
   ResetBuilder();
 }
 
 nsresult
 PresentationSessionInfo::SetListener(nsIPresentationSessionListener* aListener)
 {
-  if (mListener && aListener) {
-    Unused << NS_WARN_IF(NS_FAILED(mListener->NotifyReplaced()));
-  }
-
   mListener = aListener;
 
   if (mListener) {
     // Enable data notification for the transport channel if it's available.
     if (mTransport) {
       nsresult rv = mTransport->EnableDataNotification();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
@@ -311,20 +307,16 @@ PresentationSessionInfo::SendBlob(nsIDOM
 
   return mTransport->SendBlob(aBlob);
 }
 
 nsresult
 PresentationSessionInfo::Close(nsresult aReason,
                                uint32_t aState)
 {
-  if (NS_WARN_IF(!IsSessionReady())) {
-    return NS_ERROR_DOM_INVALID_STATE_ERR;
-  }
-
   // Do nothing if session is already terminated.
   if (nsIPresentationSessionListener::STATE_TERMINATED == mState) {
     return NS_OK;
   }
 
   SetStateWithReason(aState, aReason);
 
   switch (aState) {
@@ -515,33 +507,32 @@ PresentationSessionInfo::NotifyData(cons
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return mListener->NotifyMessage(mSessionId, aData, aIsBinary);
 }
 
 // nsIPresentationSessionTransportBuilderListener
 NS_IMETHODIMP
-PresentationSessionInfo::OnSessionTransport(nsIPresentationSessionTransport* transport)
+PresentationSessionInfo::OnSessionTransport(nsIPresentationSessionTransport* aTransport)
 {
   PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
              NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
 
   ResetBuilder();
 
   if (mState != nsIPresentationSessionListener::STATE_CONNECTING) {
     return NS_ERROR_FAILURE;
   }
 
-  // The session transport is managed by content process
-  if (!transport) {
-    return NS_OK;
+  if (NS_WARN_IF(!aTransport)) {
+    return NS_ERROR_INVALID_ARG;
   }
 
-  mTransport = transport;
+  mTransport = aTransport;
 
   nsresult rv = mTransport->SetCallback(this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (mListener) {
     mTransport->EnableDataNotification();
@@ -646,18 +637,16 @@ PresentationControllingInfo::Shutdown(ns
 {
   PresentationSessionInfo::Shutdown(aReason);
 
   // Close the server socket if any.
   if (mServerSocket) {
     Unused << NS_WARN_IF(NS_FAILED(mServerSocket->Close()));
     mServerSocket = nullptr;
   }
-
-  mIsReconnecting = false;
 }
 
 nsresult
 PresentationControllingInfo::GetAddress()
 {
 #if defined(MOZ_WIDGET_GONK)
   nsCOMPtr<nsINetworkManager> networkManager =
     do_GetService("@mozilla.org/network/manager;1");
@@ -837,23 +826,22 @@ PresentationControllingInfo::NotifyConne
 
 NS_IMETHODIMP
 PresentationControllingInfo::NotifyReconnected()
 {
   PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
              NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
 
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mReconnectCallback);
 
   if (NS_WARN_IF(mState != nsIPresentationSessionListener::STATE_CONNECTING)) {
     return NS_ERROR_FAILURE;
   }
 
-  return mReconnectCallback->NotifySuccess(GetUrl());
+  return NotifyReconnectResult(NS_OK);
 }
 
 nsresult
 PresentationControllingInfo::BuildTransport()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mState != nsIPresentationSessionListener::STATE_CONNECTING) {
@@ -931,18 +919,33 @@ PresentationControllingInfo::NotifyDisco
 
   if (NS_WARN_IF(NS_FAILED(aReason) || !mIsResponderReady)) {
     // The presentation session instance may already exist.
     // Change the state to CLOSED if it is not terminated.
     if (nsIPresentationSessionListener::STATE_TERMINATED != mState) {
       SetStateWithReason(nsIPresentationSessionListener::STATE_CLOSED, aReason);
     }
 
-    // Reply error for an abnormal close.
-    return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+    // If |aReason| is NS_OK, it implies that the user closes the connection
+    // before becomming connected. No need to call |ReplyError| in this case.
+    if (NS_FAILED(aReason)) {
+      if (mIsReconnecting) {
+        NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
+      }
+      // Reply error for an abnormal close.
+      return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+    }
+    Shutdown(aReason);
+  }
+
+  // This is the case for reconnecting a connection which is in
+  // connecting state and |mTransport| is not ready.
+  if (mDoReconnectAfterClose && !mTransport) {
+    mDoReconnectAfterClose = false;
+    return Reconnect(mReconnectCallback);
   }
 
   return NS_OK;
 }
 
 // nsIServerSocketListener
 NS_IMETHODIMP
 PresentationControllingInfo::OnSocketAccepted(nsIServerSocket* aServerSocket,
@@ -1017,60 +1020,72 @@ PresentationControllingInfo::OnStopListe
  * 5. Once both step 3 and 4 are done, the rest is to build a new data
  *    transport channel by following the same steps as starting a
  *    new session.
  */
 
 nsresult
 PresentationControllingInfo::Reconnect(nsIPresentationServiceCallback* aCallback)
 {
+  PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
+             NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
+
   if (!aCallback) {
     return NS_ERROR_INVALID_ARG;
   }
 
   mReconnectCallback = aCallback;
 
   if (NS_WARN_IF(mState == nsIPresentationSessionListener::STATE_TERMINATED)) {
-    return mReconnectCallback->NotifyError(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return NotifyReconnectResult(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 
-  SetStateWithReason(nsIPresentationSessionListener::STATE_CONNECTING, NS_OK);
+  // If |mState| is not CLOSED, we have to close the connection before
+  // reconnecting. The process to reconnect will be continued after
+  // |NotifyDisconnected| or |NotifyTransportClosed| is invoked.
+  if (mState == nsIPresentationSessionListener::STATE_CONNECTING ||
+      mState == nsIPresentationSessionListener::STATE_CONNECTED) {
+    mDoReconnectAfterClose = true;
+    return Close(NS_OK, nsIPresentationSessionListener::STATE_CLOSED);
+  }
+
+  // Make sure |mState| is closed at this point.
+  MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
+
+  mState = nsIPresentationSessionListener::STATE_CONNECTING;
+  mIsReconnecting = true;
 
   nsresult rv = NS_OK;
   if (!mControlChannel) {
     nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
     rv = mDevice->EstablishControlChannel(getter_AddRefs(ctrlChannel));
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      return mReconnectCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
+      return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
     }
 
     rv = Init(ctrlChannel);
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      return mReconnectCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
+      return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
     }
-
-    mIsReconnecting = true;
   } else {
     return ContinueReconnect();
   }
 
   return NS_OK;
 }
 
 nsresult
 PresentationControllingInfo::ContinueReconnect()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mControlChannel);
-  MOZ_ASSERT(mReconnectCallback);
 
   mIsReconnecting = false;
-  if (NS_WARN_IF(NS_FAILED(mControlChannel->Reconnect(mSessionId, GetUrl()))) &&
-      mReconnectCallback) {
-    return mReconnectCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
+  if (NS_WARN_IF(NS_FAILED(mControlChannel->Reconnect(mSessionId, GetUrl())))) {
+    return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   return NS_OK;
 }
 
 // nsIListNetworkAddressesListener
 NS_IMETHODIMP
 PresentationControllingInfo::OnListedNetworkAddresses(const char** aAddressArray,
@@ -1110,16 +1125,62 @@ PresentationControllingInfo::OnListNetwo
     NewRunnableMethod<nsCString>(
       this,
       &PresentationControllingInfo::OnGetAddress,
       "127.0.0.1"));
 
   return NS_OK;
 }
 
+nsresult
+PresentationControllingInfo::NotifyReconnectResult(nsresult aStatus)
+{
+  if (!mReconnectCallback) {
+    MOZ_ASSERT(false, "mReconnectCallback can not be null here.");
+    return NS_ERROR_FAILURE;
+  }
+
+  mIsReconnecting = false;
+  nsCOMPtr<nsIPresentationServiceCallback> callback =
+    mReconnectCallback.forget();
+  if (NS_FAILED(aStatus)) {
+    return callback->NotifyError(aStatus);
+  }
+
+  return callback->NotifySuccess(GetUrl());
+}
+
+// nsIPresentationSessionTransportCallback
+NS_IMETHODIMP
+PresentationControllingInfo::NotifyTransportReady()
+{
+  return PresentationSessionInfo::NotifyTransportReady();
+}
+
+NS_IMETHODIMP
+PresentationControllingInfo::NotifyTransportClosed(nsresult aReason)
+{
+  if (!mDoReconnectAfterClose) {
+    return PresentationSessionInfo::NotifyTransportClosed(aReason);;
+  }
+
+  MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
+
+  mTransport = nullptr;
+  mIsTransportReady = false;
+  mDoReconnectAfterClose = false;
+  return Reconnect(mReconnectCallback);
+}
+
+NS_IMETHODIMP
+PresentationControllingInfo::NotifyData(const nsACString& aData, bool aIsBinary)
+{
+  return PresentationSessionInfo::NotifyData(aData, aIsBinary);
+}
+
 /**
  * Implementation of PresentationPresentingInfo
  *
  * During presentation session establishment, the receiver expects the following
  * after trying to launch the app by notifying "presentation-launch-receiver":
  * (The order between step 2 and 3 is not guaranteed.)
  * 1. |Observe| of |nsIObserver| is called with "presentation-receiver-launched".
  *    Then start listen to document |STATE_TRANSFERRING| event.
@@ -1177,27 +1238,27 @@ PresentationPresentingInfo::Shutdown(nsr
   mRequesterDescription = nullptr;
   mPendingCandidates.Clear();
   mPromise = nullptr;
   mHasFlushPendingEvents = false;
 }
 
 // nsIPresentationSessionTransportBuilderListener
 NS_IMETHODIMP
-PresentationPresentingInfo::OnSessionTransport(nsIPresentationSessionTransport* transport)
+PresentationPresentingInfo::OnSessionTransport(nsIPresentationSessionTransport* aTransport)
 {
-  nsresult rv = PresentationSessionInfo::OnSessionTransport(transport);
+  nsresult rv = PresentationSessionInfo::OnSessionTransport(aTransport);
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // The session transport is managed by content process
-  if (!transport) {
-    return NS_OK;
+  if (NS_WARN_IF(!aTransport)) {
+    return NS_ERROR_INVALID_ARG;
   }
 
   // send answer for TCP session transport
   if (mTransportType == nsIPresentationChannelDescription::TYPE_TCP) {
     // Prepare and send the answer.
     // In the current implementation of |PresentationSessionTransport|,
     // |GetSelfAddress| cannot return the real info when it's initialized via
     // |buildTCPReceiverTransport|. Yet this deficiency only affects the channel
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -187,16 +187,17 @@ class PresentationControllingInfo final 
                                         , public nsIServerSocketListener
                                         , public nsIListNetworkAddressesListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
   NS_DECL_NSISERVERSOCKETLISTENER
   NS_DECL_NSILISTNETWORKADDRESSESLISTENER
+  NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTCALLBACK
 
   PresentationControllingInfo(const nsAString& aUrl,
                               const nsAString& aSessionId)
     : PresentationSessionInfo(aUrl,
                               aSessionId,
                               nsIPresentationService::ROLE_CONTROLLER)
   {}
 
@@ -215,19 +216,22 @@ private:
   void Shutdown(nsresult aReason) override;
 
   nsresult GetAddress();
 
   nsresult OnGetAddress(const nsACString& aAddress);
 
   nsresult ContinueReconnect();
 
+  nsresult NotifyReconnectResult(nsresult aStatus);
+
   nsCOMPtr<nsIServerSocket> mServerSocket;
   nsCOMPtr<nsIPresentationServiceCallback> mReconnectCallback;
   bool mIsReconnecting = false;
+  bool mDoReconnectAfterClose = false;
 };
 
 // Session info with presenting browsing context (receiver side) behaviors.
 class PresentationPresentingInfo final : public PresentationSessionInfo
                                        , public PromiseNativeHandler
                                        , public nsITimerCallback
 {
 public:
--- a/dom/presentation/interfaces/nsIPresentationListener.idl
+++ b/dom/presentation/interfaces/nsIPresentationListener.idl
@@ -29,21 +29,16 @@ interface nsIPresentationSessionListener
                          in nsresult reason);
 
   /*
    * Called when receive messages.
    */
   void notifyMessage(in DOMString sessionId,
                      in ACString data,
                      in boolean isBinary);
-
-  /*
-   * Called when this listener is replaced by another one.
-   */
-  void notifyReplaced();
 };
 
 [scriptable, uuid(27f101d7-9ed1-429e-b4f8-43b00e8e111c)]
 interface nsIPresentationRespondingListener : nsISupports
 {
   /*
    * Called when an incoming session connects.
    */
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -76,16 +76,19 @@ sync protocol PPresentation
 
 child:
   async NotifyAvailableChange(bool aAvailable);
   async NotifySessionStateChange(nsString aSessionId,
                                  uint16_t aState,
                                  nsresult aReason);
   async NotifyMessage(nsString aSessionId, nsCString aData, bool aIsBinary);
   async NotifySessionConnect(uint64_t aWindowId, nsString aSessionId);
+  async NotifyCloseSessionTransport(nsString aSessionId,
+                                    uint8_t aRole,
+                                    nsresult aReason);
 
   async PPresentationBuilder(nsString aSessionId, uint8_t aRole);
 
 parent:
   async __delete__();
 
   async RegisterAvailabilityHandler();
   async UnregisterAvailabilityHandler();
--- a/dom/presentation/ipc/PresentationBuilderParent.cpp
+++ b/dom/presentation/ipc/PresentationBuilderParent.cpp
@@ -6,16 +6,108 @@
 
 #include "DCPresentationChannelDescription.h"
 #include "PresentationBuilderParent.h"
 #include "PresentationSessionInfo.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace {
+
+class PresentationSessionTransportIPC final :
+  public nsIPresentationSessionTransport
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONSESSIONTRANSPORT
+
+  PresentationSessionTransportIPC(PresentationParent* aParent,
+                                  const nsAString& aSessionId,
+                                  uint8_t aRole)
+    : mParent(aParent)
+    , mSessionId(aSessionId)
+    , mRole(aRole)
+  {
+    MOZ_ASSERT(mParent);
+  }
+
+private:
+  virtual ~PresentationSessionTransportIPC() = default;
+
+  RefPtr<PresentationParent> mParent;
+  nsString mSessionId;
+  uint8_t mRole;
+};
+
+NS_IMPL_ISUPPORTS(PresentationSessionTransportIPC,
+                  nsIPresentationSessionTransport)
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::GetCallback(
+                           nsIPresentationSessionTransportCallback** aCallback)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::SetCallback(
+                            nsIPresentationSessionTransportCallback* aCallback)
+{
+  if (aCallback) {
+    aCallback->NotifyTransportReady();
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::GetSelfAddress(nsINetAddr** aSelfAddress)
+{
+  MOZ_ASSERT(false, "Not expected.");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::EnableDataNotification()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::Send(const nsAString& aData)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::SendBinaryMsg(const nsACString& aData)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::SendBlob(nsIDOMBlob* aBlob)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::Close(nsresult aReason)
+{
+  if (NS_WARN_IF(!mParent->SendNotifyCloseSessionTransport(mSessionId,
+                                                           mRole,
+                                                           aReason))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+} // anonymous namespace
+
 NS_IMPL_ISUPPORTS(PresentationBuilderParent,
                   nsIPresentationSessionTransportBuilder,
                   nsIPresentationDataChannelSessionTransportBuilder)
 
 PresentationBuilderParent::PresentationBuilderParent(PresentationParent* aParent)
   : mParent(aParent)
 {
   MOZ_COUNT_CTOR(PresentationBuilderParent);
@@ -34,21 +126,25 @@ NS_IMETHODIMP
 PresentationBuilderParent::BuildDataChannelTransport(
                       uint8_t aRole,
                       mozIDOMWindow* aWindow, /* unused */
                       nsIPresentationSessionTransportBuilderListener* aListener)
 {
   mBuilderListener = aListener;
 
   RefPtr<PresentationSessionInfo> info = static_cast<PresentationSessionInfo*>(aListener);
+  nsAutoString sessionId(info->GetSessionId());
   if (NS_WARN_IF(!mParent->SendPPresentationBuilderConstructor(this,
-                                                               nsString(info->GetSessionId()),
+                                                               sessionId,
                                                                aRole))) {
     return NS_ERROR_FAILURE;
   }
+  mIPCSessionTransport = new PresentationSessionTransportIPC(mParent,
+                                                             sessionId,
+                                                             aRole);
   mNeedDestroyActor = true;
   mParent = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationBuilderParent::OnIceCandidate(const nsAString& aCandidate)
 {
@@ -145,27 +241,20 @@ PresentationBuilderParent::RecvClose(con
   }
   return true;
 }
 
 // Delegate to nsIPresentationSessionTransportBuilderListener
 bool
 PresentationBuilderParent::RecvOnSessionTransport()
 {
-  // To avoid releasing |this| in this method
-  NS_DispatchToMainThread(NS_NewRunnableFunction([this]() -> void {
-    Unused <<
-      NS_WARN_IF(!mBuilderListener ||
-                 NS_FAILED(mBuilderListener->OnSessionTransport(nullptr)));
-  }));
-
-  nsCOMPtr<nsIPresentationSessionTransportCallback>
-    callback(do_QueryInterface(mBuilderListener));
-
-  callback->NotifyTransportReady();
+  RefPtr<PresentationBuilderParent> kungFuDeathGrip = this;
+  Unused <<
+    NS_WARN_IF(!mBuilderListener ||
+               NS_FAILED(mBuilderListener->OnSessionTransport(mIPCSessionTransport)));
   return true;
 }
 
 bool
 PresentationBuilderParent::RecvOnSessionTransportError(const nsresult& aReason)
 {
   if (NS_WARN_IF(!mBuilderListener ||
                  NS_FAILED(mBuilderListener->OnError(aReason)))) {
--- a/dom/presentation/ipc/PresentationBuilderParent.h
+++ b/dom/presentation/ipc/PresentationBuilderParent.h
@@ -38,14 +38,15 @@ public:
 
   virtual bool RecvOnSessionTransportError(const nsresult& aReason) override;
 
 private:
   virtual ~PresentationBuilderParent();
   bool mNeedDestroyActor = false;
   RefPtr<PresentationParent> mParent;
   nsCOMPtr<nsIPresentationSessionTransportBuilderListener> mBuilderListener;
+  nsCOMPtr<nsIPresentationSessionTransport> mIPCSessionTransport;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationBuilderParent_h__
--- a/dom/presentation/ipc/PresentationChild.cpp
+++ b/dom/presentation/ipc/PresentationChild.cpp
@@ -128,16 +128,28 @@ PresentationChild::RecvNotifySessionConn
                                             const nsString& aSessionId)
 {
   if (mService) {
     Unused << NS_WARN_IF(NS_FAILED(mService->NotifySessionConnect(aWindowId, aSessionId)));
   }
   return true;
 }
 
+bool
+PresentationChild::RecvNotifyCloseSessionTransport(const nsString& aSessionId,
+                                                   const uint8_t& aRole,
+                                                   const nsresult& aReason)
+{
+  if (mService) {
+    Unused << NS_WARN_IF(NS_FAILED(
+      mService->CloseContentSessionTransport(aSessionId, aRole, aReason)));
+  }
+  return true;
+}
+
 /*
  * Implementation of PresentationRequestChild
  */
 
 PresentationRequestChild::PresentationRequestChild(nsIPresentationServiceCallback* aCallback)
   : mActorDestroyed(false)
   , mCallback(aCallback)
 {
--- a/dom/presentation/ipc/PresentationChild.h
+++ b/dom/presentation/ipc/PresentationChild.h
@@ -54,16 +54,21 @@ public:
   RecvNotifyMessage(const nsString& aSessionId,
                     const nsCString& aData,
                     const bool& aIsBinary) override;
 
   virtual bool
   RecvNotifySessionConnect(const uint64_t& aWindowId,
                            const nsString& aSessionId) override;
 
+  virtual bool
+  RecvNotifyCloseSessionTransport(const nsString& aSessionId,
+                                  const uint8_t& aRole,
+                                  const nsresult& aReason) override;
+
 private:
   virtual ~PresentationChild();
 
   bool mActorDestroyed = false;
   RefPtr<PresentationIPCService> mService;
 };
 
 class PresentationRequestChild final : public PPresentationRequestChild
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -22,19 +22,17 @@ using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 namespace {
 
 PresentationChild* sPresentationChild;
 
 } // anonymous
 
-NS_IMPL_ISUPPORTS_INHERITED(PresentationIPCService,
-                            PresentationServiceBase,
-                            nsIPresentationService)
+NS_IMPL_ISUPPORTS(PresentationIPCService, nsIPresentationService)
 
 PresentationIPCService::PresentationIPCService()
 {
   ContentChild* contentChild = ContentChild::GetSingleton();
   if (NS_WARN_IF(!contentChild)) {
     return;
   }
   sPresentationChild = new PresentationChild(this);
@@ -44,17 +42,18 @@ PresentationIPCService::PresentationIPCS
 
 /* virtual */
 PresentationIPCService::~PresentationIPCService()
 {
   Shutdown();
 
   mAvailabilityListeners.Clear();
   mSessionListeners.Clear();
-  mSessionInfos.Clear();
+  mSessionInfoAtController.Clear();
+  mSessionInfoAtReceiver.Clear();
   sPresentationChild = nullptr;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::StartSession(
                const nsTArray<nsString>& aUrls,
                const nsAString& aSessionId,
                const nsAString& aOrigin,
@@ -85,19 +84,20 @@ PresentationIPCService::StartSession(
 NS_IMETHODIMP
 PresentationIPCService::SendSessionMessage(const nsAString& aSessionId,
                                            uint8_t aRole,
                                            const nsAString& aData)
 {
   MOZ_ASSERT(!aSessionId.IsEmpty());
   MOZ_ASSERT(!aData.IsEmpty());
 
-  RefPtr<PresentationContentSessionInfo> info;
+  RefPtr<PresentationContentSessionInfo> info =
+    GetSessionInfo(aSessionId, aRole);
   // data channel session transport is maintained by content process
-  if (mSessionInfos.Get(aSessionId, getter_AddRefs(info))) {
+  if (info) {
     return info->Send(aData);
   }
 
   return SendRequest(nullptr, SendSessionMessageRequest(nsString(aSessionId),
                                                         aRole,
                                                         nsString(aData)));
 }
 
@@ -107,19 +107,20 @@ PresentationIPCService::SendSessionBinar
                                              const nsACString &aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aData.IsEmpty());
   MOZ_ASSERT(!aSessionId.IsEmpty());
   MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
              aRole == nsIPresentationService::ROLE_RECEIVER);
 
-  RefPtr<PresentationContentSessionInfo> info;
+  RefPtr<PresentationContentSessionInfo> info =
+    GetSessionInfo(aSessionId, aRole);
   // data channel session transport is maintained by content process
-  if (mSessionInfos.Get(aSessionId, getter_AddRefs(info))) {
+  if (info) {
     return info->SendBinaryMsg(aData);
   }
 
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::SendSessionBlob(const nsAString& aSessionId,
@@ -127,19 +128,20 @@ PresentationIPCService::SendSessionBlob(
                                         nsIDOMBlob* aBlob)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aSessionId.IsEmpty());
   MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
              aRole == nsIPresentationService::ROLE_RECEIVER);
   MOZ_ASSERT(aBlob);
 
-  RefPtr<PresentationContentSessionInfo> info;
+  RefPtr<PresentationContentSessionInfo> info =
+    GetSessionInfo(aSessionId, aRole);
   // data channel session transport is maintained by content process
-  if (mSessionInfos.Get(aSessionId, getter_AddRefs(info))) {
+  if (info) {
     return info->SendBlob(aBlob);
   }
 
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::CloseSession(const nsAString& aSessionId,
@@ -150,19 +152,19 @@ PresentationIPCService::CloseSession(con
 
   nsresult rv = SendRequest(nullptr, CloseSessionRequest(nsString(aSessionId),
                                                          aRole,
                                                          aClosedReason));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  RefPtr<PresentationContentSessionInfo> info;
-  // data channel session transport is maintained by content process
-  if (mSessionInfos.Get(aSessionId, getter_AddRefs(info))) {
+  RefPtr<PresentationContentSessionInfo> info =
+    GetSessionInfo(aSessionId, aRole);
+  if (info) {
     return info->Close(NS_OK);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::TerminateSession(const nsAString& aSessionId,
@@ -170,19 +172,19 @@ PresentationIPCService::TerminateSession
 {
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
   nsresult rv = SendRequest(nullptr, TerminateSessionRequest(nsString(aSessionId), aRole));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  RefPtr<PresentationContentSessionInfo> info;
-  // data channel session transport is maintained by content process
-  if (mSessionInfos.Get(aSessionId, getter_AddRefs(info))) {
+  RefPtr<PresentationContentSessionInfo> info =
+    GetSessionInfo(aSessionId, aRole);
+  if (info) {
     return info->Close(NS_OK);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::ReconnectSession(const nsTArray<nsString>& aUrls,
@@ -261,17 +263,16 @@ PresentationIPCService::RegisterSessionL
                                                 uint8_t aRole,
                                                 nsIPresentationSessionListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aListener);
 
   nsCOMPtr<nsIPresentationSessionListener> listener;
   if (mSessionListeners.Get(aSessionId, getter_AddRefs(listener))) {
-    Unused << NS_WARN_IF(NS_FAILED(listener->NotifyReplaced()));
     mSessionListeners.Put(aSessionId, aListener);
     return NS_OK;
   }
 
   mSessionListeners.Put(aSessionId, aListener);
   if (sPresentationChild) {
     Unused <<
       NS_WARN_IF(!sPresentationChild->SendRegisterSessionHandler(
@@ -331,17 +332,22 @@ PresentationIPCService::NotifySessionTra
                                                nsIPresentationSessionTransport* aTransport)
 {
   RefPtr<PresentationContentSessionInfo> info =
     new PresentationContentSessionInfo(aSessionId, aRole, aTransport);
 
   if (NS_WARN_IF(NS_FAILED(info->Init()))) {
     return NS_ERROR_NOT_AVAILABLE;
   }
-  mSessionInfos.Put(aSessionId, info);
+
+  if (aRole == nsIPresentationService::ROLE_CONTROLLER) {
+    mSessionInfoAtController.Put(aSessionId, info);
+  } else {
+    mSessionInfoAtReceiver.Put(aSessionId, info);
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::GetWindowIdBySessionId(const nsAString& aSessionId,
                                                uint8_t aRole,
                                                uint64_t* aWindowId)
 {
@@ -384,17 +390,19 @@ PresentationIPCService::NotifyMessage(co
 }
 
 // Only used for OOP RTCDataChannel session transport case.
 nsresult
 PresentationIPCService::NotifyTransportClosed(const nsAString& aSessionId,
                                               uint8_t aRole,
                                               nsresult aReason)
 {
-  if (NS_WARN_IF(!mSessionInfos.Contains(aSessionId))) {
+  RefPtr<PresentationContentSessionInfo> info =
+    GetSessionInfo(aSessionId, aRole);
+  if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   Unused << NS_WARN_IF(!sPresentationChild->SendNotifyTransportClosed(nsString(aSessionId), aRole, aReason));
   return NS_OK;
 }
 
 nsresult
 PresentationIPCService::NotifySessionConnect(uint64_t aWindowId,
@@ -469,18 +477,21 @@ PresentationIPCService::UntrackSessionIn
           window->Close();
         }
       }));
     }
   }
 
   // Remove the OOP responding info (if it has never been used).
   RemoveRespondingSessionId(aSessionId, aRole);
-  if (mSessionInfos.Contains(aSessionId)) {
-    mSessionInfos.Remove(aSessionId);
+
+  if (nsIPresentationService::ROLE_CONTROLLER == aRole) {
+    mSessionInfoAtController.Remove(aSessionId);
+  } else {
+    mSessionInfoAtReceiver.Remove(aSessionId);
   }
 
   return NS_OK;
 }
 
 void
 PresentationIPCService::NotifyPresentationChildDestroyed()
 {
@@ -491,8 +502,22 @@ nsresult
 PresentationIPCService::MonitorResponderLoading(const nsAString& aSessionId,
                                                 nsIDocShell* aDocShell)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mCallback = new PresentationResponderLoadingCallback(aSessionId);
   return mCallback->Init(aDocShell);
 }
+
+nsresult
+PresentationIPCService::CloseContentSessionTransport(const nsString& aSessionId,
+                                                     uint8_t aRole,
+                                                     nsresult aReason)
+{
+  RefPtr<PresentationContentSessionInfo> info =
+    GetSessionInfo(aSessionId, aRole);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return info->Close(aReason);
+}
--- a/dom/presentation/ipc/PresentationIPCService.h
+++ b/dom/presentation/ipc/PresentationIPCService.h
@@ -16,21 +16,22 @@ class nsIDocShell;
 
 namespace mozilla {
 namespace dom {
 
 class PresentationIPCRequest;
 class PresentationContentSessionInfo;
 class PresentationResponderLoadingCallback;
 
-class PresentationIPCService final : public nsIPresentationService
-                                   , public PresentationServiceBase
+class PresentationIPCService final
+  : public nsIPresentationService
+  , public PresentationServiceBase<PresentationContentSessionInfo>
 {
 public:
-  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_ISUPPORTS
   NS_DECL_NSIPRESENTATIONSERVICE
 
   PresentationIPCService();
 
   nsresult NotifyAvailableChange(bool aAvailable);
 
   nsresult NotifySessionStateChange(const nsAString& aSessionId,
                                     uint16_t aState,
@@ -47,27 +48,29 @@ public:
 
   nsresult MonitorResponderLoading(const nsAString& aSessionId,
                                    nsIDocShell* aDocShell);
 
   nsresult NotifySessionTransport(const nsString& aSessionId,
                                   const uint8_t& aRole,
                                   nsIPresentationSessionTransport* transport);
 
+  nsresult CloseContentSessionTransport(const nsString& aSessionId,
+                                        uint8_t aRole,
+                                        nsresult aReason);
+
 private:
   virtual ~PresentationIPCService();
   nsresult SendRequest(nsIPresentationServiceCallback* aCallback,
                        const PresentationIPCRequest& aRequest);
 
   nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener> > mAvailabilityListeners;
   nsRefPtrHashtable<nsStringHashKey,
                     nsIPresentationSessionListener> mSessionListeners;
   nsRefPtrHashtable<nsUint64HashKey,
                     nsIPresentationRespondingListener> mRespondingListeners;
   RefPtr<PresentationResponderLoadingCallback> mCallback;
-  nsRefPtrHashtable<nsStringHashKey,
-                    PresentationContentSessionInfo> mSessionInfos;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationIPCService_h
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -301,24 +301,16 @@ PresentationParent::NotifyStateChange(co
                                                aState,
                                                aReason))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationParent::NotifyReplaced()
-{
-  // Do nothing here, since |PresentationIPCService::RegisterSessionListener|
-  // already dealt with this in content process.
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 PresentationParent::NotifyMessage(const nsAString& aSessionId,
                                   const nsACString& aData,
                                   bool aIsBinary)
 {
   if (NS_WARN_IF(mActorDestroyed ||
                  !SendNotifyMessage(nsString(aSessionId),
                                     nsCString(aData),
                                     aIsBinary))) {
--- a/dom/presentation/moz.build
+++ b/dom/presentation/moz.build
@@ -45,17 +45,16 @@ UNIFIED_SOURCES += [
     'PresentationAvailability.cpp',
     'PresentationCallbacks.cpp',
     'PresentationConnection.cpp',
     'PresentationConnectionList.cpp',
     'PresentationDeviceManager.cpp',
     'PresentationReceiver.cpp',
     'PresentationRequest.cpp',
     'PresentationService.cpp',
-    'PresentationServiceBase.cpp',
     'PresentationSessionInfo.cpp',
     'PresentationSessionRequest.cpp',
     'PresentationTCPSessionTransport.cpp',
     'PresentationTerminateRequest.cpp',
     'PresentationTransportBuilderConstructor.cpp'
 ]
 
 EXTRA_COMPONENTS += [
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -860,16 +860,21 @@ nsCSPParser::referrerDirectiveValue(nsCS
 
   if (!mozilla::net::IsValidReferrerPolicy(mCurDir[1])) {
     CSPPARSERLOG(("invalid value for referrer directive: %s",
                   NS_ConvertUTF16toUTF8(mCurDir[1]).get()));
     delete aDir;
     return;
   }
 
+  //referrer-directive deprecation warning
+  const char16_t* params[] = { mCurDir[1].get() };
+  logWarningErrorToConsole(nsIScriptError::warningFlag, "deprecatedReferrerDirective",
+                             params, ArrayLength(params));
+
   // the referrer policy is valid, so go ahead and use it.
   mPolicy->setReferrerPolicy(&mCurDir[1]);
   mPolicy->addDirective(aDir);
 }
 
 void
 nsCSPParser::requireSRIForDirectiveValue(nsRequireSRIForDirective* aDir)
 {
--- a/dom/smil/nsSMILParserUtils.cpp
+++ b/dom/smil/nsSMILParserUtils.cpp
@@ -704,27 +704,27 @@ nsSMILParserUtils::ParseClockValue(const
   return ::ParseClockValue(iter, end, aResult) && iter == end;
 }
 
 int32_t
 nsSMILParserUtils::CheckForNegativeNumber(const nsAString& aStr)
 {
   int32_t absValLocation = -1;
 
-  nsAString::const_iterator start, end;
-  aStr.BeginReading(start);
-  aStr.EndReading(end);
+  RangedPtr<const char16_t> start(SVGContentUtils::GetStartRangedPtr(aStr));
+  RangedPtr<const char16_t> iter = start;
+  RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aStr));
 
   // Skip initial whitespace
-  while (start != end && IsSVGWhitespace(*start)) {
-    ++start;
+  while (iter != end && IsSVGWhitespace(*iter)) {
+    ++iter;
   }
 
   // Check for dash
-  if (start != end && *start == '-') {
-    ++start;
+  if (iter != end && *iter == '-') {
+    ++iter;
     // Check for numeric character
-    if (start != end && SVGContentUtils::IsDigit(*start)) {
-      absValLocation = start.get() - start.start();
+    if (iter != end && SVGContentUtils::IsDigit(*iter)) {
+      absValLocation = iter - start;
     }
   }
   return absValLocation;
 }
--- a/dom/system/OSFileConstants.cpp
+++ b/dom/system/OSFileConstants.cpp
@@ -836,18 +836,19 @@ JSObject *GetOrCreateObjectProperty(JSCo
   if (!JS_GetProperty(cx, aObject, aProperty, &val)) {
     return nullptr;
   }
   if (!val.isUndefined()) {
     if (val.isObject()) {
       return &val.toObject();
     }
 
-    JS_ReportErrorNumber(cx, js::GetErrorMessage, nullptr,
-      JSMSG_UNEXPECTED_TYPE, aProperty, "not an object");
+    JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
+                              JSMSG_UNEXPECTED_TYPE,
+                              aProperty, "not an object");
     return nullptr;
   }
   return JS_DefineObject(cx, aObject, aProperty, nullptr, JSPROP_ENUMERATE);
 }
 
 /**
  * Set a property of an object from a nsString.
  *
@@ -875,18 +876,19 @@ bool DefineOSFileConstants(JSContext *cx
 {
   MOZ_ASSERT(gInitialized);
 
   if (gPaths == nullptr) {
     // If an initialization error was ignored, we may end up with
     // |gInitialized == true| but |gPaths == nullptr|. We cannot
     // |MOZ_ASSERT| this, as this would kill precompile_cache.js,
     // so we simply return an error.
-    JS_ReportErrorNumber(cx, js::GetErrorMessage, nullptr,
-      JSMSG_CANT_OPEN, "OSFileConstants", "initialization has failed");
+    JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
+                              JSMSG_CANT_OPEN,
+                              "OSFileConstants", "initialization has failed");
     return false;
   }
 
   JS::Rooted<JSObject*> objOS(cx);
   if (!(objOS = GetOrCreateObjectProperty(cx, global, "OS"))) {
     return false;
   }
   JS::Rooted<JSObject*> objConstants(cx);
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/geolocation/chrome.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  geolocation_common.js
+  network_geolocation.sjs
+
+[test_handlerSpinsEventLoop.html]
--- a/dom/tests/mochitest/geolocation/mochitest.ini
+++ b/dom/tests/mochitest/geolocation/mochitest.ini
@@ -10,17 +10,16 @@ support-files =
 [test_cachedPosition.html]
 [test_cancelCurrent.html]
 [test_cancelWatch.html]
 [test_clearWatch.html]
 [test_clearWatchBeforeAllowing.html]
 [test_clearWatch_invalid.html]
 [test_errorcheck.html]
 [test_geolocation_is_undefined_when_pref_is_off.html]
-[test_handlerSpinsEventLoop.html]
 [test_manyCurrentConcurrent.html]
 [test_manyCurrentSerial.html]
 [test_manyWatchConcurrent.html]
 [test_manyWatchSerial.html]
 [test_manyWindows.html]
 [test_optional_api_params.html]
 [test_shutdown.html]
 [test_timeoutCurrent.html]
--- a/dom/tests/mochitest/geolocation/test_handlerSpinsEventLoop.html
+++ b/dom/tests/mochitest/geolocation/test_handlerSpinsEventLoop.html
@@ -1,67 +1,65 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=911595
 -->
 <head>
   <title>Test for spinning the event loop inside position handlers</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="geolocation_common.js"></script>
 
-<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=911595 ">Mozilla Bug 911595</a>
 <p id="display"></p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /*
  * In bug 911595 , spinning the event loop from inside position
  * handlers could cause both success and error callbacks to be
  * fired for the same request if that request has a small timeout.
  */
 
+var { classes: Cc, interfaces: Ci, utils: Cu }  = Components;
+
 SimpleTest.waitForExplicitFinish();
-SimpleTest.requestFlakyTimeout("untriaged");
 
 resume_geolocationProvider(function() {
   force_prompt(true, test1);
 });
 
-function spinEventLoopAndSetTimeout() {
-  if (successCallbackCalled || errorCallbackCalled) {
-    // this should only be called once from either callback
-    return;
-  }
-
-  SpecialPowers.spinEventLoop(window);
-
-  setTimeout(function() {
-    ok(successCallbackCalled != errorCallbackCalled, "Ensure only one callback is called");
-    SimpleTest.finish();
-  }, 5);
-}
-
 var successCallbackCalled = false;
 function successCallback(position) {
-  spinEventLoopAndSetTimeout();
   successCallbackCalled = true;
+  check_geolocation(position);
+  while (!timeoutPassed) {
+    SpecialPowers.spinEventLoop(window);
+  }
+  ok(successCallbackCalled != errorCallbackCalled, "Ensure only one callback is called");
+  SimpleTest.finish();
 }
 
 var errorCallbackCalled = false;
 function errorCallback(error) {
-  spinEventLoopAndSetTimeout();
   errorCallbackCalled = true;
 }
 
+var timeoutPassed = false;
+var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 function test1() {
-  navigator.geolocation.getCurrentPosition(successCallback, errorCallback, {timeout: 1});
+  SpecialPowers.pushPrefEnv({"set": [["geo.wifi.timeToWaitBeforeSending", 10]]}, function() {
+    navigator.geolocation.getCurrentPosition(successCallback, errorCallback, {timeout: 500});
+    timer.initWithCallback(timer => {
+      timeoutPassed = true;
+    }, 600, Ci.nsITimer.TYPE_ONE_SHOT);
+  });
 }
 </script>
 </pre>
 </body>
 </html>
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -54,42 +54,43 @@ typedef any Transferable;
   // other browsing contexts
   [Replaceable, Throws, CrossOriginReadable] readonly attribute WindowProxy frames;
   [Replaceable, CrossOriginReadable] readonly attribute unsigned long length;
   //[Unforgeable, Throws, CrossOriginReadable] readonly attribute WindowProxy top;
   [Unforgeable, Throws, CrossOriginReadable] readonly attribute WindowProxy? top;
   [Throws, CrossOriginReadable] attribute any opener;
   //[Throws] readonly attribute WindowProxy parent;
   [Replaceable, Throws, CrossOriginReadable] readonly attribute WindowProxy? parent;
-  [Throws] readonly attribute Element? frameElement;
+  [Throws, NeedsSubjectPrincipal] readonly attribute Element? frameElement;
   //[Throws] WindowProxy open(optional DOMString url = "about:blank", optional DOMString target = "_blank", [TreatNullAs=EmptyString] optional DOMString features = "", optional boolean replace = false);
   [Throws, UnsafeInPrerendering] WindowProxy? open(optional DOMString url = "", optional DOMString target = "", [TreatNullAs=EmptyString] optional DOMString features = "");
   // We think the indexed getter is a bug in the spec, it actually needs to live
   // on the WindowProxy
   //getter WindowProxy (unsigned long index);
   getter object (DOMString name);
 
   // the user agent
   [Throws] readonly attribute Navigator navigator;
 #ifdef HAVE_SIDEBAR
   [Replaceable, Throws] readonly attribute External external;
 #endif
   [Throws, Pref="browser.cache.offline.enable"] readonly attribute ApplicationCache applicationCache;
 
   // user prompts
-  [Throws, UnsafeInPrerendering] void alert();
-  [Throws, UnsafeInPrerendering] void alert(DOMString message);
-  [Throws, UnsafeInPrerendering] boolean confirm(optional DOMString message = "");
-  [Throws, UnsafeInPrerendering] DOMString? prompt(optional DOMString message = "", optional DOMString default = "");
+  [Throws, UnsafeInPrerendering, NeedsSubjectPrincipal] void alert();
+  [Throws, UnsafeInPrerendering, NeedsSubjectPrincipal] void alert(DOMString message);
+  [Throws, UnsafeInPrerendering, NeedsSubjectPrincipal] boolean confirm(optional DOMString message = "");
+  [Throws, UnsafeInPrerendering, NeedsSubjectPrincipal] DOMString? prompt(optional DOMString message = "", optional DOMString default = "");
   [Throws, UnsafeInPrerendering] void print();
   //[Throws] any showModalDialog(DOMString url, optional any argument);
-  [Throws, Func="nsGlobalWindow::IsShowModalDialogEnabled", UnsafeInPrerendering]
+  [Throws, Func="nsGlobalWindow::IsShowModalDialogEnabled", UnsafeInPrerendering, NeedsSubjectPrincipal]
   any showModalDialog(DOMString url, optional any argument, optional DOMString options = "");
 
-  [Throws, CrossOriginCallable] void postMessage(any message, DOMString targetOrigin, optional sequence<Transferable> transfer);
+  [Throws, CrossOriginCallable, NeedsSubjectPrincipal]
+  void postMessage(any message, DOMString targetOrigin, optional sequence<Transferable> transfer);
 
   // also has obsolete members
 };
 Window implements GlobalEventHandlers;
 Window implements WindowEventHandlers;
 
 // https://w3c.github.io/manifest/#oninstall-attribute
 partial interface Window {
@@ -266,18 +267,21 @@ interface SpeechSynthesisGetter {
 };
 
 Window implements SpeechSynthesisGetter;
 #endif
 
 // http://www.whatwg.org/specs/web-apps/current-work/
 [NoInterfaceObject]
 interface WindowModal {
-  [Throws, Func="nsGlobalWindow::IsModalContentWindow"] readonly attribute any dialogArguments;
-  [Throws, Func="nsGlobalWindow::IsModalContentWindow"] attribute any returnValue;
+  [Throws, Func="nsGlobalWindow::IsModalContentWindow", NeedsSubjectPrincipal]
+  readonly attribute any dialogArguments;
+
+  [Throws, Func="nsGlobalWindow::IsModalContentWindow", NeedsSubjectPrincipal]
+  attribute any returnValue;
 };
 Window implements WindowModal;
 
 // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#self-caches
 partial interface Window {
 [Throws, Func="mozilla::dom::cache::CacheStorage::PrefEnabled", SameObject]
 readonly attribute CacheStorage caches;
 };
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -950,13 +950,13 @@ IsDebuggerSandbox(JSObject* object)
 {
   return SimpleGlobalObject::SimpleGlobalType(object) ==
     SimpleGlobalObject::GlobalType::WorkerDebuggerSandbox;
 }
 
 bool
 GetterOnlyJSNative(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
 {
-  JS_ReportErrorNumber(aCx, js::GetErrorMessage, nullptr, JSMSG_GETTER_ONLY);
+  JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, JSMSG_GETTER_ONLY);
   return false;
 }
 
 END_WORKERS_NAMESPACE
--- a/gfx/gl/GLReadTexImageHelper.cpp
+++ b/gfx/gl/GLReadTexImageHelper.cpp
@@ -412,16 +412,17 @@ ReadPixelsIntoDataSurface(GLContext* gl,
         gfx::Factory::CopyDataSourceSurface(readSurf, dest);
     }
 
     // Check if GL is giving back 1.0 alpha for
     // RGBA reads to RGBA images from no-alpha buffers.
 #ifdef XP_MACOSX
     if (gl->WorkAroundDriverBugs() &&
         gl->Vendor() == gl::GLVendor::NVIDIA &&
+        !gl->IsCoreProfile() &&
         hasAlpha &&
         width && height)
     {
         GLint alphaBits = 0;
         gl->fGetIntegerv(LOCAL_GL_ALPHA_BITS, &alphaBits);
         if (!alphaBits) {
             const uint32_t alphaMask = gfxPackedPixelNoPreMultiply(0xff,0,0,0);
 
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -38,20 +38,26 @@ public:
 
   /**
    * Different types of tap-related events that can be sent in
    * the HandleTap function. The names should be relatively self-explanatory.
    * Note that the eLongTapUp will always be preceded by an eLongTap, but not
    * all eLongTap notifications will be followed by an eLongTapUp (for instance,
    * if the user moves their finger after triggering the long-tap but before
    * lifting it).
+   * The difference between eDoubleTap and eSecondTap is subtle - the eDoubleTap
+   * is for an actual double-tap "gesture" while eSecondTap is for the same user
+   * input but where a double-tap gesture is not allowed. This is used to fire
+   * a click event with detail=2 to web content (similar to what a mouse double-
+   * click would do).
    */
   enum class TapType {
     eSingleTap,
     eDoubleTap,
+    eSecondTap,
     eLongTap,
     eLongTapUp,
 
     eSentinel,
   };
 
   /**
    * Requests handling of a tap event. |aPoint| is in LD pixels, relative to the
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1054,16 +1054,17 @@ nsEventStatus AsyncPanZoomController::Ha
   case TAPGESTURE_INPUT: {
     const TapGestureInput& tapGestureInput = aEvent.AsTapGestureInput();
     switch (tapGestureInput.mType) {
       case TapGestureInput::TAPGESTURE_LONG: rv = OnLongPress(tapGestureInput); break;
       case TapGestureInput::TAPGESTURE_LONG_UP: rv = OnLongPressUp(tapGestureInput); break;
       case TapGestureInput::TAPGESTURE_UP: rv = OnSingleTapUp(tapGestureInput); break;
       case TapGestureInput::TAPGESTURE_CONFIRMED: rv = OnSingleTapConfirmed(tapGestureInput); break;
       case TapGestureInput::TAPGESTURE_DOUBLE: rv = OnDoubleTap(tapGestureInput); break;
+      case TapGestureInput::TAPGESTURE_SECOND: rv = OnSecondTap(tapGestureInput); break;
       case TapGestureInput::TAPGESTURE_CANCEL: rv = OnCancelTap(tapGestureInput); break;
       default: NS_WARNING("Unhandled tap gesture"); break;
     }
     break;
   }
   default: NS_WARNING("Unhandled input event"); break;
   }
 
@@ -2114,16 +2115,22 @@ nsEventStatus AsyncPanZoomController::On
             aEvent.modifiers, GetGuid(), GetCurrentTouchBlock()->GetBlockId());
       }
     }
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
+nsEventStatus AsyncPanZoomController::OnSecondTap(const TapGestureInput& aEvent)
+{
+  APZC_LOG("%p got a second-tap in state %d\n", this, mState);
+  return GenerateSingleTap(TapType::eSecondTap, aEvent.mPoint, aEvent.modifiers);
+}
+
 nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a cancel-tap in state %d\n", this, mState);
   // XXX: Implement this.
   return nsEventStatus_eIgnore;
 }
 
 
 ScreenToParentLayerMatrix4x4 AsyncPanZoomController::GetTransformToThis() const {
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -474,16 +474,21 @@ protected:
   nsEventStatus OnSingleTapConfirmed(const TapGestureInput& aEvent);
 
   /**
    * Helper method for double taps.
    */
   nsEventStatus OnDoubleTap(const TapGestureInput& aEvent);
 
   /**
+   * Helper method for double taps where the double-tap gesture is disabled.
+   */
+  nsEventStatus OnSecondTap(const TapGestureInput& aEvent);
+
+  /**
    * Helper method to cancel any gesture currently going to Gecko. Used
    * primarily when a user taps the screen over some clickable content but then
    * pans down instead of letting go (i.e. to cancel a previous touch so that a
    * new one can properly take effect.
    */
   nsEventStatus OnCancelTap(const TapGestureInput& aEvent);
 
   /**
--- a/gfx/layers/apz/src/GestureEventListener.cpp
+++ b/gfx/layers/apz/src/GestureEventListener.cpp
@@ -191,28 +191,25 @@ nsEventStatus GestureEventListener::Hand
     break;
   case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN:
     CancelLongTapTimeoutTask();
     SetState(GESTURE_MULTI_TOUCH_DOWN);
     // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event
     rv = nsEventStatus_eConsumeNoDefault;
     break;
   case GESTURE_FIRST_SINGLE_TOUCH_UP:
+  case GESTURE_SECOND_SINGLE_TOUCH_DOWN:
     // Cancel wait for double tap
     CancelMaxTapTimeoutTask();
+    MOZ_ASSERT(mSingleTapSent.isSome());
+    if (!mSingleTapSent.value()) {
+      TriggerSingleTapConfirmedEvent();
+    }
+    mSingleTapSent = Nothing();
     SetState(GESTURE_MULTI_TOUCH_DOWN);
-    TriggerSingleTapConfirmedEvent();
-    // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event
-    rv = nsEventStatus_eConsumeNoDefault;
-    break;
-  case GESTURE_SECOND_SINGLE_TOUCH_DOWN:
-    // Cancel wait for single tap
-    CancelMaxTapTimeoutTask();
-    SetState(GESTURE_MULTI_TOUCH_DOWN);
-    TriggerSingleTapConfirmedEvent();
     // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event
     rv = nsEventStatus_eConsumeNoDefault;
     break;
   case GESTURE_LONG_TOUCH_DOWN:
     SetState(GESTURE_MULTI_TOUCH_DOWN);
     break;
   case GESTURE_MULTI_TOUCH_DOWN:
   case GESTURE_PINCH:
@@ -255,16 +252,17 @@ nsEventStatus GestureEventListener::Hand
 
   case GESTURE_FIRST_SINGLE_TOUCH_DOWN:
   case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN:
   case GESTURE_SECOND_SINGLE_TOUCH_DOWN: {
     // If we move too much, bail out of the tap.
     if (MoveDistanceIsLarge()) {
       CancelLongTapTimeoutTask();
       CancelMaxTapTimeoutTask();
+      mSingleTapSent = Nothing();
       SetState(GESTURE_NONE);
     }
     break;
   }
 
   case GESTURE_MULTI_TOUCH_DOWN: {
     if (mLastTouchInput.mTouches.Length() < 2) {
       NS_WARNING("Wrong input: less than 2 moving points in GESTURE_MULTI_TOUCH_DOWN state");
@@ -341,31 +339,31 @@ nsEventStatus GestureEventListener::Hand
     // GEL doesn't have a dedicated state for PANNING handled in APZC thus ignore.
     break;
 
   case GESTURE_FIRST_SINGLE_TOUCH_DOWN: {
     CancelLongTapTimeoutTask();
     CancelMaxTapTimeoutTask();
     nsEventStatus tapupStatus = mAsyncPanZoomController->HandleGestureEvent(
         CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_UP));
-    if (tapupStatus == nsEventStatus_eIgnore) {
-      SetState(GESTURE_FIRST_SINGLE_TOUCH_UP);
-      CreateMaxTapTimeoutTask();
-    } else {
-      // We sent the tapup into content without waiting for a double tap
-      SetState(GESTURE_NONE);
-    }
+    mSingleTapSent = Some(tapupStatus != nsEventStatus_eIgnore);
+    SetState(GESTURE_FIRST_SINGLE_TOUCH_UP);
+    CreateMaxTapTimeoutTask();
     break;
   }
 
   case GESTURE_SECOND_SINGLE_TOUCH_DOWN: {
     CancelMaxTapTimeoutTask();
-    SetState(GESTURE_NONE);
+    MOZ_ASSERT(mSingleTapSent.isSome());
     mAsyncPanZoomController->HandleGestureEvent(
-        CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_DOUBLE));
+        CreateTapEvent(mLastTouchInput,
+            mSingleTapSent.value() ? TapGestureInput::TAPGESTURE_SECOND
+                                   : TapGestureInput::TAPGESTURE_DOUBLE));
+    mSingleTapSent = Nothing();
+    SetState(GESTURE_NONE);
     break;