Merge m-c to fx-team, a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 07 Apr 2016 13:50:04 -0700
changeset 330117 dfd17abadcdfecd1251371a17dac9289c80667f8
parent 330116 0559e1ea4660abf46d6ad114bb6518e9d26c5b33 (current diff)
parent 330112 06678484909cc3712756f82d070548ac144d09c0 (diff)
child 330118 5815d4fc3043f1c007998b21305102be5e4ff1c0
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to fx-team, a=merge MozReview-Commit-ID: AOSIMTNjHLv
dom/html/HTMLExtAppElement.cpp
dom/html/HTMLExtAppElement.h
dom/html/test/test_extapp.html
dom/media/encoder/VorbisTrackEncoder.cpp
dom/media/encoder/VorbisTrackEncoder.h
dom/media/gtest/TestVorbisTrackEncoder.cpp
dom/media/test/test_mediarecorder_record_nosrc.html
dom/webidl/ExternalAppEvent.webidl
dom/webidl/HTMLExtAppElement.webidl
testing/web-platform/meta/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.html.ini
xpcom/system/nsIExternalApplication.idl
--- a/accessible/base/AccEvent.cpp
+++ b/accessible/base/AccEvent.cpp
@@ -72,38 +72,16 @@ AccTextChangeEvent::
   , mModifiedText(aModifiedText)
 {
   // XXX We should use IsFromUserInput here, but that isn't always correct
   // when the text change isn't related to content insertion or removal.
    mIsFromUserInput = mAccessible->State() &
     (states::FOCUSED | states::EDITABLE);
 }
 
-
-////////////////////////////////////////////////////////////////////////////////
-// AccReorderEvent
-////////////////////////////////////////////////////////////////////////////////
-
-uint32_t
-AccReorderEvent::IsShowHideEventTarget(const Accessible* aTarget) const
-{
-  uint32_t count = mDependentEvents.Length();
-  for (uint32_t index = count - 1; index < count; index--) {
-    if (mDependentEvents[index]->mAccessible == aTarget) {
-      uint32_t eventType = mDependentEvents[index]->mEventType;
-      if (eventType == nsIAccessibleEvent::EVENT_SHOW ||
-          eventType == nsIAccessibleEvent::EVENT_HIDE) {
-        return mDependentEvents[index]->mEventType;
-      }
-    }
-  }
-
-  return 0;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // AccHideEvent
 ////////////////////////////////////////////////////////////////////////////////
 
 AccHideEvent::
   AccHideEvent(Accessible* aTarget, bool aNeedsShutdown) :
   AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget),
   mNeedsShutdown(aNeedsShutdown)
--- a/accessible/base/AccEvent.h
+++ b/accessible/base/AccEvent.h
@@ -125,17 +125,17 @@ protected:
   virtual ~AccEvent() {}
 
   bool mIsFromUserInput;
   uint32_t mEventType;
   EEventRule mEventRule;
   RefPtr<Accessible> mAccessible;
 
   friend class EventQueue;
-  friend class AccReorderEvent;
+  friend class EventTree;
 };
 
 
 /**
  * Accessible state change event.
  */
 class AccStateChangeEvent: public AccEvent
 {
@@ -196,18 +196,17 @@ public:
     { aModifiedText = mModifiedText; }
   const nsString& ModifiedText() const { return mModifiedText; }
 
 private:
   int32_t mStart;
   bool mIsInserted;
   nsString mModifiedText;
 
-  friend class EventQueue;
-  friend class AccReorderEvent;
+  friend class EventTree;
 };
 
 
 /**
  * Base class for show and hide accessible events.
  */
 class AccMutationEvent: public AccEvent
 {
@@ -234,17 +233,17 @@ public:
 
   Accessible* Parent() const { return mParent; }
 
 protected:
   nsCOMPtr<nsINode> mNode;
   RefPtr<Accessible> mParent;
   RefPtr<AccTextChangeEvent> mTextChangeEvent;
 
-  friend class EventQueue;
+  friend class EventTree;
 };
 
 
 /**
  * Accessible hide event.
  */
 class AccHideEvent: public AccMutationEvent
 {
@@ -264,17 +263,17 @@ public:
   Accessible* TargetPrevSibling() const { return mPrevSibling; }
   bool NeedsShutdown() const { return mNeedsShutdown; }
 
 protected:
   bool mNeedsShutdown;
   RefPtr<Accessible> mNextSibling;
   RefPtr<Accessible> mPrevSibling;
 
-  friend class EventQueue;
+  friend class EventTree;
 };
 
 
 /**
  * Accessible show event.
  */
 class AccShowEvent: public AccMutationEvent
 {
@@ -307,47 +306,16 @@ public:
   virtual ~AccReorderEvent() { }
 
   // Event
   static const EventGroup kEventGroup = eReorderEvent;
   virtual unsigned int GetEventGroups() const override
   {
     return AccEvent::GetEventGroups() | (1U << eReorderEvent);
   }
-
-  /**
-   * Get connected with mutation event.
-   */
-  void AddSubMutationEvent(AccMutationEvent* aEvent)
-    { mDependentEvents.AppendElement(aEvent); }
-
-  /**
-   * Do not emit the reorder event and its connected mutation events.
-   */
-  void DoNotEmitAll()
-  {
-    mEventRule = AccEvent::eDoNotEmit;
-    uint32_t eventsCount = mDependentEvents.Length();
-    for (uint32_t idx = 0; idx < eventsCount; idx++)
-      mDependentEvents[idx]->mEventRule = AccEvent::eDoNotEmit;
-  }
-
-  /**
-   * Return true if the given accessible is a target of connected mutation
-   * event.
-   */
-  uint32_t IsShowHideEventTarget(const Accessible* aTarget) const;
-
-protected:
-  /**
-   * Show and hide events causing this reorder event.
-   */
-  nsTArray<AccMutationEvent*> mDependentEvents;
-
-  friend class EventQueue;
 };
 
 
 /**
  * Accessible caret move event.
  */
 class AccCaretMoveEvent: public AccEvent
 {
--- a/accessible/base/EventQueue.cpp
+++ b/accessible/base/EventQueue.cpp
@@ -34,109 +34,73 @@ EventQueue::PushEvent(AccEvent* aEvent)
                "Queued event belongs to another document!");
 
   if (!mEvents.AppendElement(aEvent))
     return false;
 
   // Filter events.
   CoalesceEvents();
 
+  if (aEvent->mEventRule != AccEvent::eDoNotEmit &&
+      (aEvent->mEventType == nsIAccessibleEvent::EVENT_NAME_CHANGE ||
+       aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED ||
+       aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED)) {
+    PushNameChange(aEvent->mAccessible);
+  }
+  return true;
+}
+
+bool
+EventQueue::PushNameChange(Accessible* aTarget)
+{
   // Fire name change event on parent given that this event hasn't been
   // coalesced, the parent's name was calculated from its subtree, and the
   // subtree was changed.
-  Accessible* target = aEvent->mAccessible;
-  if (aEvent->mEventRule != AccEvent::eDoNotEmit &&
-      target->HasNameDependentParent() &&
-      (aEvent->mEventType == nsIAccessibleEvent::EVENT_NAME_CHANGE ||
-       aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED ||
-       aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED ||
-       aEvent->mEventType == nsIAccessibleEvent::EVENT_SHOW ||
-       aEvent->mEventType == nsIAccessibleEvent::EVENT_HIDE)) {
+  if (aTarget->HasNameDependentParent()) {
     // Only continue traversing up the tree if it's possible that the parent
     // accessible's name can depend on this accessible's name.
-    Accessible* parent = target->Parent();
+    Accessible* parent = aTarget->Parent();
     while (parent &&
            nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule)) {
       // Test possible name dependent parent.
       if (nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) {
         nsAutoString name;
         ENameValueFlag nameFlag = parent->Name(name);
         // If name is obtained from subtree, fire name change event.
         if (nameFlag == eNameFromSubtree) {
           RefPtr<AccEvent> nameChangeEvent =
             new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, parent);
-          PushEvent(nameChangeEvent);
+          return PushEvent(nameChangeEvent);
         }
         break;
       }
       parent = parent->Parent();
     }
   }
-
-  // Associate text change with hide event if it wasn't stolen from hiding
-  // siblings during coalescence.
-  AccMutationEvent* showOrHideEvent = downcast_accEvent(aEvent);
-  if (showOrHideEvent && !showOrHideEvent->mTextChangeEvent)
-    CreateTextChangeEventFor(showOrHideEvent);
-
-  return true;
+  return false;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // EventQueue: private
 
 void
 EventQueue::CoalesceEvents()
 {
   NS_ASSERTION(mEvents.Length(), "There should be at least one pending event!");
   uint32_t tail = mEvents.Length() - 1;
   AccEvent* tailEvent = mEvents[tail];
 
   switch(tailEvent->mEventRule) {
     case AccEvent::eCoalesceReorder:
-      CoalesceReorderEvents(tailEvent);
+      MOZ_ASSERT(tailEvent->mAccessible->IsApplication() ||
+                 tailEvent->mAccessible->IsOuterDoc() ||
+                 tailEvent->mAccessible->IsXULTree(),
+                 "Only app or outerdoc accessible reorder events are in the queue");
       break; // case eCoalesceReorder
 
-    case AccEvent::eCoalesceMutationTextChange:
-    {
-      for (uint32_t index = tail - 1; index < tail; index--) {
-        AccEvent* thisEvent = mEvents[index];
-        if (thisEvent->mEventRule != tailEvent->mEventRule)
-          continue;
-
-        // We don't currently coalesce text change events from show/hide events.
-        if (thisEvent->mEventType != tailEvent->mEventType)
-          continue;
-
-        // Show events may be duped because of reinsertion (removal is ignored
-        // because initial insertion is not processed). Ignore initial
-        // insertion.
-        if (thisEvent->mAccessible == tailEvent->mAccessible)
-          thisEvent->mEventRule = AccEvent::eDoNotEmit;
-
-        AccMutationEvent* tailMutationEvent = downcast_accEvent(tailEvent);
-        AccMutationEvent* thisMutationEvent = downcast_accEvent(thisEvent);
-        if (tailMutationEvent->mParent != thisMutationEvent->mParent)
-          continue;
-
-        // Coalesce text change events for hide and show events.
-        if (thisMutationEvent->IsHide()) {
-          AccHideEvent* tailHideEvent = downcast_accEvent(tailEvent);
-          AccHideEvent* thisHideEvent = downcast_accEvent(thisEvent);
-          CoalesceTextChangeEventsFor(tailHideEvent, thisHideEvent);
-          break;
-        }
-
-        AccShowEvent* tailShowEvent = downcast_accEvent(tailEvent);
-        AccShowEvent* thisShowEvent = downcast_accEvent(thisEvent);
-        CoalesceTextChangeEventsFor(tailShowEvent, thisShowEvent);
-        break;
-      }
-    } break; // case eCoalesceMutationTextChange
-
     case AccEvent::eCoalesceOfSameType:
     {
       // Coalesce old events by newer event.
       for (uint32_t index = tail - 1; index < tail; index--) {
         AccEvent* accEvent = mEvents[index];
         if (accEvent->mEventType == tailEvent->mEventType &&
           accEvent->mEventRule == tailEvent->mEventRule) {
           accEvent->mEventRule = AccEvent::eDoNotEmit;
@@ -222,103 +186,16 @@ EventQueue::CoalesceEvents()
     } break; // case eRemoveDupes
 
     default:
       break; // case eAllowDupes, eDoNotEmit
   } // switch
 }
 
 void
-EventQueue::CoalesceReorderEvents(AccEvent* aTailEvent)
-{
-  uint32_t count = mEvents.Length();
-  for (uint32_t index = count - 2; index < count; index--) {
-    AccEvent* thisEvent = mEvents[index];
-
-    // Skip events of different types and targeted to application accessible.
-    if (thisEvent->mEventType != aTailEvent->mEventType ||
-        thisEvent->mAccessible->IsApplication())
-      continue;
-
-    // If thisEvent target is not in document longer, i.e. if it was
-    // removed from the tree then do not emit the event.
-    if (!thisEvent->mAccessible->IsDoc() &&
-        !thisEvent->mAccessible->IsInDocument()) {
-      thisEvent->mEventRule = AccEvent::eDoNotEmit;
-      continue;
-    }
-
-    // Coalesce earlier event of the same target.
-    if (thisEvent->mAccessible == aTailEvent->mAccessible) {
-      thisEvent->mEventRule = AccEvent::eDoNotEmit;
-
-      return;
-    }
-
-    // If tailEvent contains thisEvent
-    // then
-    //   if show or hide of tailEvent contains a grand parent of thisEvent
-    //   then ignore thisEvent and its show and hide events
-    //   otherwise ignore thisEvent but not its show and hide events
-    Accessible* thisParent = thisEvent->mAccessible;
-    while (thisParent && thisParent != mDocument) {
-      if (thisParent->Parent() == aTailEvent->mAccessible) {
-        AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent);
-        uint32_t eventType = tailReorder->IsShowHideEventTarget(thisParent);
-
-        // It can be either hide or show events which may occur because of
-        // accessible reparenting.
-        if (eventType == nsIAccessibleEvent::EVENT_SHOW ||
-            eventType == nsIAccessibleEvent::EVENT_HIDE) {
-          AccReorderEvent* thisReorder = downcast_accEvent(thisEvent);
-          thisReorder->DoNotEmitAll();
-        } else {
-          thisEvent->mEventRule = AccEvent::eDoNotEmit;
-        }
-
-        return;
-      }
-
-      thisParent = thisParent->Parent();
-    }
-
-    // If tailEvent is contained by thisEvent
-    // then
-    //   if show of thisEvent contains the tailEvent
-    //   then ignore tailEvent
-    //   if hide of thisEvent contains the tailEvent
-    //   then assert
-    //   otherwise ignore tailEvent but not its show and hide events
-    Accessible* tailParent = aTailEvent->mAccessible;
-    while (tailParent && tailParent != mDocument) {
-      if (tailParent->Parent() == thisEvent->mAccessible) {
-        AccReorderEvent* thisReorder = downcast_accEvent(thisEvent);
-        AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent);
-        uint32_t eventType = thisReorder->IsShowHideEventTarget(tailParent);
-        if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
-          tailReorder->DoNotEmitAll();
-        }
-        else if (eventType == nsIAccessibleEvent::EVENT_HIDE) {
-          NS_ERROR("Accessible tree was modified after it was removed! Huh?");
-        }
-        else {
-          aTailEvent->mEventRule = AccEvent::eDoNotEmit;
-          mEvents[index].swap(mEvents[count - 1]);
-        }
-
-        return;
-      }
-
-      tailParent = tailParent->Parent();
-    }
-
-  } // for (index)
-}
-
-void
 EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
                                     AccSelChangeEvent* aThisEvent,
                                     uint32_t aThisIndex)
 {
   aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
 
   // Pack all preceding events into single selection within event
   // when we receive too much selection add/remove events.
@@ -388,100 +265,16 @@ EventQueue::CoalesceSelChangeEvents(AccS
   }
 
   // Convert into selection add since control has single selection but other
   // selection events for this control are queued.
   if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION)
     aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
 }
 
-void
-EventQueue::CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent,
-                                        AccHideEvent* aThisEvent)
-{
-  // XXX: we need a way to ignore SplitNode and JoinNode() when they do not
-  // affect the text within the hypertext.
-
-  AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent;
-  if (!textEvent)
-    return;
-
-  if (aThisEvent->mNextSibling == aTailEvent->mAccessible) {
-    aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText);
-
-  } else if (aThisEvent->mPrevSibling == aTailEvent->mAccessible) {
-    uint32_t oldLen = textEvent->GetLength();
-    aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText);
-    textEvent->mStart -= textEvent->GetLength() - oldLen;
-  }
-
-  aTailEvent->mTextChangeEvent.swap(aThisEvent->mTextChangeEvent);
-}
-
-void
-EventQueue::CoalesceTextChangeEventsFor(AccShowEvent* aTailEvent,
-                                        AccShowEvent* aThisEvent)
-{
-  AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent;
-  if (!textEvent)
-    return;
-
-  if (aTailEvent->mAccessible->IndexInParent() ==
-      aThisEvent->mAccessible->IndexInParent() + 1) {
-    // If tail target was inserted after this target, i.e. tail target is next
-    // sibling of this target.
-    aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText);
-
-  } else if (aTailEvent->mAccessible->IndexInParent() ==
-             aThisEvent->mAccessible->IndexInParent() -1) {
-    // If tail target was inserted before this target, i.e. tail target is
-    // previous sibling of this target.
-    nsAutoString startText;
-    aTailEvent->mAccessible->AppendTextTo(startText);
-    textEvent->mModifiedText = startText + textEvent->mModifiedText;
-    textEvent->mStart -= startText.Length();
-  }
-
-  aTailEvent->mTextChangeEvent.swap(aThisEvent->mTextChangeEvent);
-}
-
-void
-EventQueue::CreateTextChangeEventFor(AccMutationEvent* aEvent)
-{
-  Accessible* container = aEvent->mAccessible->Parent();
-  if (!container)
-    return;
-
-  HyperTextAccessible* textAccessible = container->AsHyperText();
-  if (!textAccessible)
-    return;
-
-  // Don't fire event for the first html:br in an editor.
-  if (aEvent->mAccessible->Role() == roles::WHITESPACE) {
-    nsCOMPtr<nsIEditor> editor = textAccessible->GetEditor();
-    if (editor) {
-      bool isEmpty = false;
-      editor->GetDocumentIsEmpty(&isEmpty);
-      if (isEmpty)
-        return;
-    }
-  }
-
-  int32_t offset = textAccessible->GetChildOffset(aEvent->mAccessible);
-
-  nsAutoString text;
-  aEvent->mAccessible->AppendTextTo(text);
-  if (text.IsEmpty())
-    return;
-
-  aEvent->mTextChangeEvent =
-    new AccTextChangeEvent(textAccessible, offset, text, aEvent->IsShow(),
-                           aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // EventQueue: event queue
 
 void
 EventQueue::ProcessEventQueue()
 {
   // Process only currently queued events.
   nsTArray<RefPtr<AccEvent> > events;
@@ -534,26 +327,14 @@ EventQueue::ProcessEventQueue()
           nsEventShell::FireEvent(selChangeEvent->mPackedEvent->mAccessible,
                                   states::SELECTED,
                                   (selChangeEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd),
                                   selChangeEvent->mPackedEvent->mIsFromUserInput);
         }
       }
 
       nsEventShell::FireEvent(event);
-
-      // Fire text change events.
-      AccMutationEvent* mutationEvent = downcast_accEvent(event);
-      if (mutationEvent) {
-        if (mutationEvent->mTextChangeEvent)
-          nsEventShell::FireEvent(mutationEvent->mTextChangeEvent);
-      }
-    }
-
-    AccHideEvent* hideEvent = downcast_accEvent(event);
-    if (hideEvent && hideEvent->NeedsShutdown()) {
-      mDocument->ShutdownChildrenInSubtree(event->mAccessible);
     }
 
     if (!mDocument)
       return;
   }
 }
--- a/accessible/base/EventQueue.h
+++ b/accessible/base/EventQueue.h
@@ -22,16 +22,21 @@ protected:
   explicit EventQueue(DocAccessible* aDocument) : mDocument(aDocument) { }
 
   /**
    * Put an accessible event into the queue to process it later.
    */
   bool PushEvent(AccEvent* aEvent);
 
   /**
+   * Puts a name change event into the queue, if needed.
+   */
+  bool PushNameChange(Accessible* aTarget);
+
+  /**
    * Process events from the queue and fires events.
    */
   void ProcessEventQueue();
 
 private:
   EventQueue(const EventQueue&) = delete;
   EventQueue& operator = (const EventQueue&) = delete;
 
@@ -48,41 +53,25 @@ private:
 
   /**
    * Coalesce two selection change events within the same select control.
    */
   void CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
                                AccSelChangeEvent* aThisEvent,
                                uint32_t aThisIndex);
 
-  /**
-   * Coalesce text change events caused by sibling hide events.
-   */
-  void CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent,
-                                   AccHideEvent* aThisEvent);
-  void CoalesceTextChangeEventsFor(AccShowEvent* aTailEvent,
-                                   AccShowEvent* aThisEvent);
-
-  /**
-    * Create text change event caused by hide or show event. When a node is
-    * hidden/removed or shown/appended, the text in an ancestor hyper text will
-    * lose or get new characters.
-    */
-   void CreateTextChangeEventFor(AccMutationEvent* aEvent);
-
 protected:
-
   /**
    * The document accessible reference owning this queue.
    */
   DocAccessible* mDocument;
 
   /**
    * Pending events array. Don't make this an AutoTArray; we use
    * SwapElements() on it.
    */
-  nsTArray<RefPtr<AccEvent> > mEvents;
+  nsTArray<RefPtr<AccEvent>> mEvents;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_EventQueue_h_
new file mode 100644
--- /dev/null
+++ b/accessible/base/EventTree.cpp
@@ -0,0 +1,536 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EventTree.h"
+
+#include "Accessible-inl.h"
+#include "nsEventShell.h"
+#include "DocAccessible.h"
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// TreeMutation class
+
+EventTree* const TreeMutation::kNoEventTree = reinterpret_cast<EventTree*>(-1);
+
+TreeMutation::TreeMutation(Accessible* aParent, bool aNoEvents) :
+  mParent(aParent), mStartIdx(UINT32_MAX),
+  mStateFlagsCopy(mParent->mStateFlags),
+  mEventTree(aNoEvents ? kNoEventTree : nullptr)
+{
+#ifdef DEBUG
+  mIsDone = false;
+#endif
+
+#ifdef A11Y_LOG
+  if (mEventTree != kNoEventTree && logging::IsEnabled(logging::eEventTree)) {
+    logging::MsgBegin("EVENTS_TREE", "reordering tree before");
+    logging::AccessibleInfo("reordering for", mParent);
+    Controller()->RootEventTree().Log();
+    logging::MsgEnd();
+
+    logging::MsgBegin("EVENTS_TREE", "Container tree");
+    if (logging::IsEnabled(logging::eVerbose)) {
+      nsAutoString level;
+      Accessible* root = mParent->Document();
+      do {
+        const char* prefix = "";
+        if (mParent == root) {
+          prefix = "_X_";
+        }
+        else {
+          const EventTree& ret = Controller()->RootEventTree();
+          if (ret.Find(root)) {
+            prefix = "_с_";
+          }
+        }
+
+        printf("%s", NS_ConvertUTF16toUTF8(level).get());
+        logging::AccessibleInfo(prefix, root);
+        if (root->FirstChild() && !root->FirstChild()->IsDoc()) {
+          level.Append(NS_LITERAL_STRING("  "));
+          root = root->FirstChild();
+          continue;
+        }
+        int32_t idxInParent = root->mParent ?
+          root->mParent->mChildren.IndexOf(root) : -1;
+        if (idxInParent != -1 &&
+            idxInParent < static_cast<int32_t>(root->mParent->mChildren.Length() - 1)) {
+          root = root->mParent->mChildren.ElementAt(idxInParent + 1);
+          continue;
+        }
+
+        while ((root = root->Parent()) && !root->IsDoc()) {
+          level.Cut(0, 2);
+
+          int32_t idxInParent = root->mParent ?
+          root->mParent->mChildren.IndexOf(root) : -1;
+          if (idxInParent != -1 &&
+              idxInParent < static_cast<int32_t>(root->mParent->mChildren.Length() - 1)) {
+            root = root->mParent->mChildren.ElementAt(idxInParent + 1);
+            break;
+          }
+        }
+      }
+      while (root && !root->IsDoc());
+    }
+    logging::MsgEnd();
+  }
+#endif
+
+  mParent->mStateFlags |= Accessible::eKidsMutating;
+}
+
+TreeMutation::~TreeMutation()
+{
+  MOZ_ASSERT(mIsDone, "Done() must be called explicitly");
+}
+
+void
+TreeMutation::AfterInsertion(Accessible* aChild)
+{
+  MOZ_ASSERT(aChild->Parent() == mParent);
+
+  if (static_cast<uint32_t>(aChild->mIndexInParent) < mStartIdx) {
+    mStartIdx = aChild->mIndexInParent + 1;
+  }
+
+  if (!mEventTree) {
+    mEventTree = Controller()->QueueMutation(mParent);
+    if (!mEventTree) {
+      mEventTree = kNoEventTree;
+    }
+  }
+
+  if (mEventTree != kNoEventTree) {
+    mEventTree->Shown(aChild);
+    Controller()->QueueNameChange(aChild);
+  }
+}
+
+void
+TreeMutation::BeforeRemoval(Accessible* aChild, bool aNoShutdown)
+{
+  MOZ_ASSERT(aChild->Parent() == mParent);
+
+  if (static_cast<uint32_t>(aChild->mIndexInParent) < mStartIdx) {
+    mStartIdx = aChild->mIndexInParent;
+  }
+
+  if (!mEventTree) {
+    mEventTree = Controller()->QueueMutation(mParent);
+    if (!mEventTree) {
+      mEventTree = kNoEventTree;
+    }
+  }
+
+  if (mEventTree != kNoEventTree) {
+    mEventTree->Hidden(aChild, !aNoShutdown);
+    Controller()->QueueNameChange(aChild);
+  }
+}
+
+void
+TreeMutation::Done()
+{
+  MOZ_ASSERT(mParent->mStateFlags & Accessible::eKidsMutating);
+  mParent->mStateFlags &= ~Accessible::eKidsMutating;
+
+  uint32_t length = mParent->mChildren.Length();
+#ifdef DEBUG
+  for (uint32_t idx = 0; idx < mStartIdx && idx < length; idx++) {
+    MOZ_ASSERT(mParent->mChildren[idx]->mIndexInParent == static_cast<int32_t>(idx),
+               "Wrong index detected");
+  }
+#endif
+
+  for (uint32_t idx = mStartIdx; idx < length; idx++) {
+    mParent->mChildren[idx]->mIndexInParent = idx;
+    mParent->mChildren[idx]->mStateFlags |= Accessible::eGroupInfoDirty;
+  }
+
+  if (mStartIdx < mParent->mChildren.Length() - 1) {
+    mParent->mEmbeddedObjCollector = nullptr;
+  }
+
+  mParent->mStateFlags |= mStateFlagsCopy & Accessible::eKidsMutating;
+
+#ifdef DEBUG
+  mIsDone = true;
+#endif
+
+#ifdef A11Y_LOG
+  if (mEventTree != kNoEventTree && logging::IsEnabled(logging::eEventTree)) {
+    logging::MsgBegin("EVENTS_TREE", "reordering tree after");
+    logging::AccessibleInfo("reordering for", mParent);
+    Controller()->RootEventTree().Log();
+    logging::MsgEnd();
+  }
+#endif
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// EventTree
+
+void
+EventTree::Process()
+{
+  EventTree* node = mFirst;
+  while (node) {
+    node->Process();
+    node = node->mNext;
+  }
+
+  // Fire mutation events.
+  if (mContainer) {
+    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");
+
+      nsEventShell::FireEvent(mtEvent);
+      if (mtEvent->mTextChangeEvent) {
+        nsEventShell::FireEvent(mtEvent->mTextChangeEvent);
+      }
+
+      if (mtEvent->IsHide()) {
+        // Fire menupopup end event before a hide event if a menu goes away.
+
+        // XXX: We don't look into children of hidden subtree to find hiding
+        // menupopup (as we did prior bug 570275) because we don't do that when
+        // menu is showing (and that's impossible until bug 606924 is fixed).
+        // Nevertheless we should do this at least because layout coalesces
+        // the changes before our processing and we may miss some menupopup
+        // events. Now we just want to be consistent in content insertion/removal
+        // handling.
+        if (mtEvent->mAccessible->ARIARole() == roles::MENUPOPUP) {
+          nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
+                                  mtEvent->mAccessible);
+        }
+
+        AccHideEvent* hideEvent = downcast_accEvent(mtEvent);
+        if (hideEvent->NeedsShutdown()) {
+          mContainer->Document()->ShutdownChildrenInSubtree(hideEvent->mAccessible);
+        }
+      }
+    }
+
+    // Fire reorder event at last.
+    if (mFireReorder) {
+      nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_REORDER, mContainer);
+    }
+  }
+}
+
+EventTree*
+EventTree::FindOrInsert(Accessible* aContainer)
+{
+  if (!mFirst) {
+    return mFirst = new EventTree(aContainer);
+  }
+
+  EventTree* prevNode = nullptr;
+  EventTree* node = mFirst;
+  do {
+    MOZ_ASSERT(!node->mContainer->IsApplication(),
+               "No event for application accessible is expected here");
+    MOZ_ASSERT(!node->mContainer->IsDefunct(), "An event target has to be alive");
+
+    // Case of same target.
+    if (node->mContainer == aContainer) {
+      return node;
+    }
+
+    // Check if the given container is contained by a current node
+    Accessible* tailRoot = aContainer->Document();
+    Accessible* tailParent = aContainer;
+
+    EventTree* matchNode = nullptr;
+    Accessible* matchParent = nullptr;
+    while (true) {
+      // Reached a top, no match for a current event.
+      if (tailParent == tailRoot) {
+        // If we have a match in parents then continue to look in siblings.
+        if (matchNode && node->mNext) {
+          node = node->mNext;
+          if (node->mContainer == aContainer) {
+            return node; // case of same target
+          }
+          tailParent = aContainer;
+          continue;
+        }
+        break;
+      }
+
+      // We got a match.
+      if (tailParent->Parent() == node->mContainer) {
+        matchNode = node;
+        matchParent = tailParent;
+
+        // Search the subtree for a better match.
+        if (node->mFirst) {
+          tailRoot = node->mContainer;
+          node = node->mFirst;
+          if (node->mContainer == aContainer) {
+            return node; // case of same target
+          }
+          tailParent = aContainer;
+          continue;
+        }
+        break;
+      }
+
+      tailParent = tailParent->Parent();
+      MOZ_ASSERT(tailParent, "Wrong tree");
+      if (!tailParent) {
+        break;
+      }
+    }
+
+    // The given node is contained by a current node
+    //   if hide of a current node contains the given node
+    //   then assert
+    //   if show of a current node contains the given node
+    //   then ignore the given node
+    //   otherwise ignore the given node, but not its show and hide events
+    if (matchNode) {
+      uint32_t eventType = 0;
+      uint32_t count = matchNode->mDependentEvents.Length();
+      for (uint32_t idx = count - 1; idx < count; idx--) {
+        if (matchNode->mDependentEvents[idx]->mAccessible == matchParent) {
+          eventType = matchNode->mDependentEvents[idx]->mEventType;
+        }
+      }
+      MOZ_ASSERT(eventType != nsIAccessibleEvent::EVENT_HIDE,
+                 "Accessible tree was modified after it was removed");
+
+      // If contained by show event target then no events are required.
+      if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
+        return nullptr;
+      }
+
+      node->mFirst = new EventTree(aContainer);
+      node->mFirst->mFireReorder = false;
+      return node->mFirst;
+    }
+
+    // If the given node contains a current node
+    // then
+    //   if show or hide of the given node contains a grand parent of the current node
+    //   then ignore the current node and its show and hide events
+    //   otherwise ignore the current node, but not its show and hide events
+    Accessible* curParent = node->mContainer;
+    while (curParent && !curParent->IsDoc()) {
+      if (curParent->Parent() != aContainer) {
+        curParent = curParent->Parent();
+        continue;
+      }
+
+      // Insert the tail node into the hierarchy between the current node and
+      // its parent.
+      node->mFireReorder = false;
+      nsAutoPtr<EventTree>& nodeOwnerRef = prevNode ? prevNode->mNext : mFirst;
+      nsAutoPtr<EventTree> newNode(new EventTree(aContainer));
+      newNode->mFirst = Move(nodeOwnerRef);
+      nodeOwnerRef = Move(newNode);
+      nodeOwnerRef->mNext = Move(node->mNext);
+
+      // Check if a next node is contained by the given node too, and move them
+      // under the given node if so.
+      prevNode = nodeOwnerRef;
+      node = nodeOwnerRef->mNext;
+      nsAutoPtr<EventTree>* nodeRef = &nodeOwnerRef->mNext;
+      EventTree* insNode = nodeOwnerRef->mFirst;
+      while (node) {
+        Accessible* curParent = node->mContainer;
+        while (curParent && !curParent->IsDoc()) {
+          if (curParent->Parent() != aContainer) {
+            curParent = curParent->Parent();
+            continue;
+          }
+
+          MOZ_ASSERT(!insNode->mNext);
+
+          node->mFireReorder = false;
+          insNode->mNext = Move(*nodeRef);
+          insNode = insNode->mNext;
+
+          prevNode->mNext = Move(node->mNext);
+          node = prevNode;
+          break;
+        }
+
+        prevNode = node;
+        nodeRef = &node->mNext;
+        node = node->mNext;
+      }
+
+      return nodeOwnerRef;
+    }
+
+    prevNode = node;
+  } while ((node = node->mNext));
+
+  MOZ_ASSERT(prevNode, "Nowhere to insert");
+  return prevNode->mNext = new EventTree(aContainer);
+}
+
+const EventTree*
+EventTree::Find(const Accessible* aContainer) const
+{
+  const EventTree* et = this;
+  while (et) {
+    if (et->mContainer == aContainer) {
+      return et;
+    }
+
+    if (et->mFirst) {
+      et = et->mFirst;
+      const EventTree* cet = et->Find(aContainer);
+      if (cet) {
+        return cet;
+      }
+    }
+
+    et = et->mNext;
+    const EventTree* cet = et->Find(aContainer);
+    if (cet) {
+      return cet;
+    }
+  }
+
+  return nullptr;
+}
+
+#ifdef A11Y_LOG
+void
+EventTree::Log(uint32_t aLevel) const
+{
+  if (aLevel == UINT32_MAX) {
+    if (mFirst) {
+      mFirst->Log(0);
+    }
+    return;
+  }
+
+  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++) {
+        printf("  ");
+      }
+      logging::AccessibleInfo("shown", ev->mAccessible);
+    }
+    else {
+      for (uint32_t i = 0; i < aLevel; i++) {
+        printf("  ");
+      }
+      logging::AccessibleInfo("hidden", ev->mAccessible);
+    }
+  }
+
+  if (mFirst) {
+    mFirst->Log(aLevel + 1);
+  }
+
+  if (mNext) {
+    mNext->Log(aLevel);
+  }
+}
+#endif
+
+void
+EventTree::Mutated(AccMutationEvent* aEv)
+{
+  // If shown or hidden node is a root of previously mutated subtree, then
+  // discard those subtree mutations as we are no longer interested in them.
+  EventTree* node = mFirst;
+  while (node) {
+    if (node->mContainer == aEv->mAccessible) {
+      node->Clear();
+      break;
+    }
+    node = node->mNext;
+  }
+
+  AccMutationEvent* prevEvent = mDependentEvents.SafeLastElement(nullptr);
+  mDependentEvents.AppendElement(aEv);
+
+  // Coalesce text change events from this hide/show event and the previous one.
+  if (prevEvent && aEv->mEventType == prevEvent->mEventType) {
+    if (aEv->IsHide()) {
+      // XXX: we need a way to ignore SplitNode and JoinNode() when they do not
+      // affect the text within the hypertext.
+      AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent;
+      if (prevTextEvent) {
+        AccHideEvent* hideEvent = downcast_accEvent(aEv);
+        AccHideEvent* prevHideEvent = downcast_accEvent(prevEvent);
+
+        if (prevHideEvent->mNextSibling == hideEvent->mAccessible) {
+          hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
+        }
+        else if (prevHideEvent->mPrevSibling == hideEvent->mAccessible) {
+          uint32_t oldLen = prevTextEvent->GetLength();
+          hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
+          prevTextEvent->mStart -= prevTextEvent->GetLength() - oldLen;
+        }
+
+        hideEvent->mTextChangeEvent.swap(prevEvent->mTextChangeEvent);
+      }
+    }
+    else {
+      AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent;
+      if (prevTextEvent) {
+        if (aEv->mAccessible->IndexInParent() ==
+            prevEvent->mAccessible->IndexInParent() + 1) {
+          // If tail target was inserted after this target, i.e. tail target is next
+          // sibling of this target.
+          aEv->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
+        }
+        else if (aEv->mAccessible->IndexInParent() ==
+                 prevEvent->mAccessible->IndexInParent() - 1) {
+          // If tail target was inserted before this target, i.e. tail target is
+          // previous sibling of this target.
+          nsAutoString startText;
+          aEv->mAccessible->AppendTextTo(startText);
+          prevTextEvent->mModifiedText = startText + prevTextEvent->mModifiedText;
+          prevTextEvent->mStart -= startText.Length();
+        }
+
+        aEv->mTextChangeEvent.swap(prevEvent->mTextChangeEvent);
+      }
+    }
+  }
+
+  // Create a text change event caused by this hide/show event. When a node is
+  // hidden/removed or shown/appended, the text in an ancestor hyper text will
+  // lose or get new characters.
+  if (aEv->mTextChangeEvent || !mContainer->IsHyperText()) {
+    return;
+  }
+
+  nsAutoString text;
+  aEv->mAccessible->AppendTextTo(text);
+  if (text.IsEmpty()) {
+    return;
+  }
+
+  int32_t offset = mContainer->AsHyperText()->GetChildOffset(aEv->mAccessible);
+  aEv->mTextChangeEvent =
+    new AccTextChangeEvent(mContainer, offset, text, aEv->IsShow(),
+                           aEv->mIsFromUserInput ? eFromUserInput : eNoUserInput);
+}
new file mode 100644
--- /dev/null
+++ b/accessible/base/EventTree.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_a11y_EventTree_h_
+#define mozilla_a11y_EventTree_h_
+
+#include "AccEvent.h"
+#include "Accessible.h"
+
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * This class makes sure required tasks are done before and after tree
+ * mutations. Currently this only includes group info invalidation. You must
+ * have an object of this class on the stack when calling methods that mutate
+ * the accessible tree.
+ */
+class TreeMutation final
+{
+public:
+  static const bool kNoEvents = true;
+  static const bool kNoShutdown = true;
+
+  explicit TreeMutation(Accessible* aParent, bool aNoEvents = false);
+  ~TreeMutation();
+
+  void AfterInsertion(Accessible* aChild);
+  void BeforeRemoval(Accessible* aChild, bool aNoShutdown = false);
+  void Done();
+
+private:
+  NotificationController* Controller() const
+    { return mParent->Document()->Controller(); }
+
+  static EventTree* const kNoEventTree;
+
+  Accessible* mParent;
+  uint32_t mStartIdx;
+  uint32_t mStateFlagsCopy;
+  EventTree* mEventTree;
+
+#ifdef DEBUG
+  bool mIsDone;
+#endif
+};
+
+
+/**
+ * A mutation events coalescence structure.
+ */
+class EventTree final {
+public:
+  EventTree() :
+    mFirst(nullptr), mNext(nullptr), mContainer(nullptr), mFireReorder(true) { }
+  explicit EventTree(Accessible* aContainer) :
+    mFirst(nullptr), mNext(nullptr), mContainer(aContainer), mFireReorder(true) { }
+  ~EventTree() { Clear(); }
+
+  void Shown(Accessible* aChild)
+  {
+    RefPtr<AccShowEvent> ev = new AccShowEvent(aChild);
+    Mutated(ev);
+  }
+
+  void Hidden(Accessible* aChild, bool aNeedsShutdown = true)
+  {
+    RefPtr<AccHideEvent> ev = new AccHideEvent(aChild, aNeedsShutdown);
+    Mutated(ev);
+  }
+
+  /**
+   * 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;
+#endif
+
+private:
+  /**
+   * Processes the event queue and fires events.
+   */
+  void Process();
+
+  /**
+   * Return an event subtree for the given accessible.
+   */
+  EventTree* FindOrInsert(Accessible* aContainer);
+
+  void Mutated(AccMutationEvent* aEv);
+  void Clear() { mFirst = nullptr; mNext = nullptr; mContainer = nullptr; }
+
+  nsAutoPtr<EventTree> mFirst;
+  nsAutoPtr<EventTree> mNext;
+
+  Accessible* mContainer;
+  nsTArray<RefPtr<AccMutationEvent>> mDependentEvents;
+  bool mFireReorder;
+
+  friend class NotificationController;
+};
+
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_EventQueue_h_
--- a/accessible/base/Logging.cpp
+++ b/accessible/base/Logging.cpp
@@ -39,16 +39,17 @@ struct ModuleRep {
 
 static ModuleRep sModuleMap[] = {
   { "docload", logging::eDocLoad },
   { "doccreate", logging::eDocCreate },
   { "docdestroy", logging::eDocDestroy },
   { "doclifecycle", logging::eDocLifeCycle },
 
   { "events", logging::eEvents },
+  { "eventTree", logging::eEventTree },
   { "platforms", logging::ePlatforms },
   { "text", logging::eText },
   { "tree", logging::eTree },
 
   { "DOMEvents", logging::eDOMEvents },
   { "focus", logging::eFocus },
   { "selection", logging::eSelection },
   { "notifications", logging::eNotifications },
--- a/accessible/base/Logging.h
+++ b/accessible/base/Logging.h
@@ -29,28 +29,29 @@ namespace logging {
 
 enum EModules {
   eDocLoad = 1 << 0,
   eDocCreate = 1 << 1,
   eDocDestroy = 1 << 2,
   eDocLifeCycle = eDocLoad | eDocCreate | eDocDestroy,
 
   eEvents = 1 << 3,
-  ePlatforms = 1 << 4,
-  eText = 1 << 5,
-  eTree = 1 << 6,
+  eEventTree = 1 << 4,
+  ePlatforms = 1 << 5,
+  eText = 1 << 6,
+  eTree = 1 << 7,
 
-  eDOMEvents = 1 << 7,
-  eFocus = 1 << 8,
-  eSelection = 1 << 9,
+  eDOMEvents = 1 << 8,
+  eFocus = 1 << 9,
+  eSelection = 1 << 10,
   eNotifications = eDOMEvents | eSelection | eFocus,
 
   // extras
-  eStack = 1 << 10,
-  eVerbose = 1 << 11
+  eStack = 1 << 11,
+  eVerbose = 1 << 12
 };
 
 /**
  * Return true if any of the given modules is logged.
  */
 bool IsEnabled(uint32_t aModules);
 
 /**
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -92,16 +92,27 @@ NotificationController::Shutdown()
   mDocument = nullptr;
   mPresShell = nullptr;
 
   mTextHash.Clear();
   mContentInsertions.Clear();
   mNotifications.Clear();
   mEvents.Clear();
   mRelocations.Clear();
+  mEventTree.Clear();
+}
+
+EventTree*
+NotificationController::QueueMutation(Accessible* aContainer)
+{
+  EventTree* tree = mEventTree.FindOrInsert(aContainer);
+  if (tree) {
+    ScheduleProcessing();
+  }
+  return tree;
 }
 
 void
 NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument)
 {
   // Schedule child document binding to the tree.
   mHangingChildDocuments.AppendElement(aDocument);
   ScheduleProcessing();
@@ -383,16 +394,19 @@ NotificationController::WillRefresh(mozi
   }
   mRelocations.Clear();
 
   // If a generic notification occurs after this point then we may be allowed to
   // process it synchronously.  However we do not want to reenter if fireing
   // events causes script to run.
   mObservingState = eRefreshProcessing;
 
+  mEventTree.Process();
+  mEventTree.Clear();
+
   ProcessEventQueue();
 
   if (IPCAccessibilityActive()) {
     size_t newDocCount = newChildDocs.Length();
     for (size_t i = 0; i < newDocCount; i++) {
       DocAccessible* childDoc = newChildDocs[i];
       Accessible* parent = childDoc->Parent();
       DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc();
--- a/accessible/base/NotificationController.h
+++ b/accessible/base/NotificationController.h
@@ -2,16 +2,17 @@
 /* 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_a11y_NotificationController_h_
 #define mozilla_a11y_NotificationController_h_
 
 #include "EventQueue.h"
+#include "EventTree.h"
 
 #include "mozilla/IndexSequence.h"
 #include "mozilla/Tuple.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsRefreshDriver.h"
 
 #ifdef A11Y_LOG
 #include "Logging.h"
@@ -99,25 +100,48 @@ public:
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(NotificationController)
 
   /**
    * Shutdown the notification controller.
    */
   void Shutdown();
 
   /**
-   * Put an accessible event into the queue to process it later.
+   * Add an accessible event into the queue to process it later.
    */
   void QueueEvent(AccEvent* aEvent)
   {
-    if (PushEvent(aEvent))
+    if (PushEvent(aEvent)) {
       ScheduleProcessing();
+    }
   }
 
   /**
+   * Creates and adds a name change event into the queue for a container of
+   * the given accessible, if the accessible is a part of name computation of
+   * the container.
+   */
+  void QueueNameChange(Accessible* aChangeTarget)
+  {
+    if (PushNameChange(aChangeTarget)) {
+      ScheduleProcessing();
+    }
+  }
+
+  /**
+   * Returns existing event tree for the given the accessible or creates one if
+   * it doesn't exists yet.
+   */
+  EventTree* QueueMutation(Accessible* aContainer);
+
+#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);
 
   /**
    * Schedule the accessible tree update because of rendered text changes.
    */
   inline void ScheduleTextUpdate(nsIContent* aTextNode)
@@ -286,14 +310,19 @@ private:
    * use SwapElements() on it.
    */
   nsTArray<RefPtr<Notification> > mNotifications;
 
   /**
    * Holds all scheduled relocations.
    */
   nsTArray<RefPtr<Accessible> > mRelocations;
+
+  /**
+   * Holds all mutation events.
+   */
+  EventTree mEventTree;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_NotificationController_h_
--- a/accessible/base/moz.build
+++ b/accessible/base/moz.build
@@ -30,16 +30,17 @@ UNIFIED_SOURCES += [
     'AccEvent.cpp',
     'AccGroupInfo.cpp',
     'AccIterator.cpp',
     'ARIAMap.cpp',
     'ARIAStateMap.cpp',
     'Asserts.cpp',
     'DocManager.cpp',
     'EventQueue.cpp',
+    'EventTree.cpp',
     'Filters.cpp',
     'FocusManager.cpp',
     'NotificationController.cpp',
     'nsAccessibilityService.cpp',
     'nsAccessiblePivot.cpp',
     'nsAccUtils.cpp',
     'nsCoreUtils.cpp',
     'nsEventShell.cpp',
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -575,18 +575,19 @@ nsAccessibilityService::ContentRangeInse
 
 void
 nsAccessibilityService::ContentRemoved(nsIPresShell* aPresShell,
                                        nsIContent* aChildNode)
 {
 #ifdef A11Y_LOG
   if (logging::IsEnabled(logging::eTree)) {
     logging::MsgBegin("TREE", "content removed");
-    logging::Node("container", aChildNode->GetFlattenedTreeParent());
-    logging::Node("content", aChildNode);
+    logging::Node("container node", aChildNode->GetFlattenedTreeParent());
+    logging::Node("content node", aChildNode);
+    logging::MsgEnd();
   }
 #endif
 
   DocAccessible* document = GetDocAccessible(aPresShell);
   if (document) {
     // Flatten hierarchy may be broken at this point so we cannot get a true
     // container by traversing up the DOM tree. Find a parent of first accessible
     // from the subtree of the given DOM node, that'll be a container. If no
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -8,19 +8,21 @@
 #include "nsIXBLAccessible.h"
 
 #include "AccCollector.h"
 #include "AccGroupInfo.h"
 #include "AccIterator.h"
 #include "nsAccUtils.h"
 #include "nsAccessibilityService.h"
 #include "ApplicationAccessible.h"
+#include "NotificationController.h"
 #include "nsEventShell.h"
 #include "nsTextEquivUtils.h"
 #include "DocAccessibleChild.h"
+#include "EventTree.h"
 #include "Logging.h"
 #include "Relation.h"
 #include "Role.h"
 #include "RootAccessible.h"
 #include "States.h"
 #include "StyleInfo.h"
 #include "TableAccessible.h"
 #include "TableCellAccessible.h"
@@ -2114,25 +2116,29 @@ Accessible::MoveChild(uint32_t aNewIndex
 {
   MOZ_ASSERT(aChild, "No child was given");
   MOZ_ASSERT(aChild->mParent == this, "A child from different subtree was given");
   MOZ_ASSERT(aChild->mIndexInParent != -1, "Unbound child was given");
   MOZ_ASSERT(static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex,
              "No move, same index");
   MOZ_ASSERT(aNewIndex <= mChildren.Length(), "Wrong new index was given");
 
+  EventTree* eventTree = mDoc->Controller()->QueueMutation(this);
+  if (eventTree) {
+    eventTree->Hidden(aChild, false);
+  }
+
   mEmbeddedObjCollector = nullptr;
   mChildren.RemoveElementAt(aChild->mIndexInParent);
 
   uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent;
 
   // If the child is moved after its current position.
   if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) {
     startIdx = aChild->mIndexInParent;
-
     if (aNewIndex == mChildren.Length() + 1) {
       // The child is moved to the end.
       mChildren.AppendElement(aChild);
       endIdx = mChildren.Length() - 1;
     }
     else {
       mChildren.InsertElementAt(aNewIndex - 1, aChild);
       endIdx = aNewIndex;
@@ -2143,16 +2149,21 @@ Accessible::MoveChild(uint32_t aNewIndex
     mChildren.InsertElementAt(aNewIndex, aChild);
   }
 
   for (uint32_t idx = startIdx; idx <= endIdx; idx++) {
     mChildren[idx]->mIndexInParent = idx;
     mChildren[idx]->mStateFlags |= eGroupInfoDirty;
     mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1;
   }
+
+  if (eventTree) {
+    eventTree->Shown(aChild);
+    mDoc->Controller()->QueueNameChange(aChild);
+  }
 }
 
 Accessible*
 Accessible::GetChildAt(uint32_t aIndex) const
 {
   Accessible* child = mChildren.SafeElementAt(aIndex, nullptr);
   if (!child)
     return nullptr;
@@ -2786,39 +2797,8 @@ KeyBinding::ToAtkFormat(nsAString& aValu
   if (mModifierMask & kShift)
     aValue.AppendLiteral("<Shift>");
 
   if (mModifierMask & kMeta)
       aValue.AppendLiteral("<Meta>");
 
   aValue.Append(mKey);
 }
-
-
-////////////////////////////////////////////////////////////////////////////////
-// AutoTreeMutation class
-
-void
-AutoTreeMutation::Done()
-{
-  MOZ_ASSERT(mParent->mStateFlags & Accessible::eKidsMutating,
-             "The parent is not in mutating state.");
-  mParent->mStateFlags &= ~Accessible::eKidsMutating;
-
-  uint32_t length = mParent->mChildren.Length();
-#ifdef DEBUG
-  for (uint32_t idx = 0; idx < mStartIdx && idx < length; idx++) {
-    MOZ_ASSERT(mParent->mChildren[idx]->mIndexInParent == static_cast<int32_t>(idx),
-               "Wrong index detected");
-  }
-#endif
-
-  for (uint32_t idx = mStartIdx; idx < length; idx++) {
-    mParent->mChildren[idx]->mIndexInParent = idx;
-    mParent->mChildren[idx]->mStateFlags |= Accessible::eGroupInfoDirty;
-  }
-
-  if (mStartIdx < mParent->mChildren.Length() - 1) {
-    mParent->mEmbeddedObjCollector = nullptr;
-  }
-
-  mParent->mStateFlags |= mStateFlagsCopy & Accessible::eKidsMutating;
-}
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -28,16 +28,17 @@ namespace mozilla {
 namespace a11y {
 
 class Accessible;
 class AccEvent;
 class AccGroupInfo;
 class ApplicationAccessible;
 class DocAccessible;
 class EmbeddedObjCollector;
+class EventTree;
 class HTMLImageMapAccessible;
 class HTMLLIAccessible;
 class HyperTextAccessible;
 class ImageAccessible;
 class KeyBinding;
 class OuterDocAccessible;
 class ProxyAccessible;
 class Relation;
@@ -1107,17 +1108,17 @@ protected:
   uint32_t mContextFlags : kContextFlagsBits;
   uint32_t mType : kTypeBits;
   uint32_t mGenericTypes : kGenericTypesBits;
 
   void StaticAsserts() const;
 
   friend class DocAccessible;
   friend class xpcAccessible;
-  friend class AutoTreeMutation;
+  friend class TreeMutation;
 
   nsAutoPtr<mozilla::a11y::EmbeddedObjCollector> mEmbeddedObjCollector;
   union {
     int32_t mIndexOfEmbeddedChild;
     uint32_t mProxyInterfaces;
   } mInt;
 
   friend class EmbeddedObjCollector;
@@ -1199,49 +1200,12 @@ public:
 private:
   void ToPlatformFormat(nsAString& aValue) const;
   void ToAtkFormat(nsAString& aValue) const;
 
   uint32_t mKey;
   uint32_t mModifierMask;
 };
 
-
-/**
- * This class makes sure required tasks are done before and after tree
- * mutations. Currently this only includes group info invalidation. You must
- * have an object of this class on the stack when calling methods that mutate
- * the accessible tree.
- */
-class AutoTreeMutation
-{
-public:
-  explicit AutoTreeMutation(Accessible* aParent) :
-    mParent(aParent), mStartIdx(UINT32_MAX),
-    mStateFlagsCopy(mParent->mStateFlags)
-  {
-    mParent->mStateFlags |= Accessible::eKidsMutating;
-  }
-
-  void AfterInsertion(const Accessible* aChild) {
-    if (static_cast<uint32_t>(aChild->IndexInParent()) < mStartIdx) {
-      mStartIdx = aChild->IndexInParent() + 1;
-    }
-  }
-
-  void BeforeRemoval(const Accessible* aChild) {
-    if (static_cast<uint32_t>(aChild->IndexInParent()) < mStartIdx) {
-      mStartIdx = aChild->IndexInParent();
-    }
-  }
-
-  void Done();
-
-private:
-  Accessible* mParent;
-  uint32_t mStartIdx;
-  uint32_t mStateFlagsCopy;
-};
-
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1746,27 +1746,29 @@ DocAccessible::ProcessContentInserted(Ac
     return;
   }
 
   // If new root content has been inserted then update it.
   if (aContainer == this) {
     UpdateRootElIfNeeded();
   }
 
-  uint32_t updateFlags = 0;
-  AutoTreeMutation mt(aContainer);
-  RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
+  InsertIterator iter(aContainer, aNodes);
+  if (!iter.Next()) {
+    return;
+  }
 
 #ifdef A11Y_LOG
   logging::TreeInfo("children before insertion", logging::eVerbose,
                     aContainer);
 #endif
 
-  InsertIterator iter(aContainer, aNodes);
-  while (iter.Next()) {
+  uint32_t updateFlags = 0;
+  TreeMutation mt(aContainer);
+  do {
     Accessible* parent = iter.Child()->Parent();
     if (parent) {
       if (parent != aContainer) {
 #ifdef A11Y_LOG
         logging::TreeInfo("stealing accessible", 0,
                           "old parent", parent, "new parent",
                           aContainer, "child", iter.Child(), nullptr);
 #endif
@@ -1783,30 +1785,31 @@ DocAccessible::ProcessContentInserted(Ac
 
     if (aContainer->InsertAfter(iter.Child(), iter.ChildBefore())) {
 #ifdef A11Y_LOG
       logging::TreeInfo("accessible was inserted", 0,
                         "container", aContainer, "child", iter.Child(), nullptr);
 #endif
 
       mt.AfterInsertion(iter.Child());
-      updateFlags |= UpdateTreeInternal(iter.Child(), true, reorderEvent);
+      updateFlags |= UpdateTreeInternal(iter.Child(), true);
       continue;
     }
 
     MOZ_ASSERT_UNREACHABLE("accessible was rejected");
-  }
+  } while (iter.Next());
+
   mt.Done();
 
 #ifdef A11Y_LOG
   logging::TreeInfo("children after insertion", logging::eVerbose,
                     aContainer);
 #endif
 
-  FireEventsOnInsertion(aContainer, reorderEvent, updateFlags);
+  FireEventsOnInsertion(aContainer, updateFlags);
 }
 
 void
 DocAccessible::ProcessContentInserted(Accessible* aContainer, nsIContent* aNode)
 {
   if (!aContainer->IsInDocument()) {
     return;
   }
@@ -1814,32 +1817,29 @@ DocAccessible::ProcessContentInserted(Ac
   TreeWalker walker(aContainer);
   if (aContainer->IsAcceptableChild(aNode) && walker.Seek(aNode)) {
     Accessible* child = GetAccessible(aNode);
     if (!child) {
       child = GetAccService()->CreateAccessible(aNode, aContainer);
     }
 
     if (child) {
-      RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
-
-      AutoTreeMutation mt(aContainer);
+      TreeMutation mt(aContainer);
       aContainer->InsertAfter(child, walker.Prev());
       mt.AfterInsertion(child);
       mt.Done();
 
-      uint32_t flags = UpdateTreeInternal(child, true, reorderEvent);
-      FireEventsOnInsertion(aContainer, reorderEvent, flags);
+      uint32_t flags = UpdateTreeInternal(child, true);
+      FireEventsOnInsertion(aContainer, flags);
     }
   }
 }
 
 void
 DocAccessible::FireEventsOnInsertion(Accessible* aContainer,
-                                     AccReorderEvent* aReorderEvent,
                                      uint32_t aUpdateFlags)
 {
   // Content insertion did not cause an accessible tree change.
   if (aUpdateFlags == eNoAccessible) {
     return;
   }
 
   // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
@@ -1852,17 +1852,16 @@ DocAccessible::FireEventsOnInsertion(Acc
         FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
         break;
       }
     }
     while ((ancestor = ancestor->Parent()));
   }
 
   MaybeNotifyOfValueChange(aContainer);
-  FireDelayedEvent(aReorderEvent);
 }
 
 void
 DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode)
 {
   // If child node is not accessible then look for its accessible children.
   Accessible* child = GetAccessible(aChildNode);
 #ifdef A11Y_LOG
@@ -1875,79 +1874,57 @@ DocAccessible::UpdateTreeOnRemoval(Acces
     else
       logging::MsgEntry("child accessible: null");
 
     logging::MsgEnd();
   }
 #endif
 
   uint32_t updateFlags = eNoAccessible;
-  RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
-  AutoTreeMutation mt(aContainer);
+  TreeMutation mt(aContainer);
 
   if (child) {
     mt.BeforeRemoval(child);
-    updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
-  } else {
+    updateFlags |= UpdateTreeInternal(child, false);
+  }
+  else {
     TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
-    while (Accessible* child = walker.Next()) {
-      mt.BeforeRemoval(child);
-      updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
+    Accessible* child = walker.Next();
+    if (child) {
+      do {
+        mt.BeforeRemoval(child);
+        updateFlags |= UpdateTreeInternal(child, false);
+      }
+      while ((child = walker.Next()));
     }
   }
   mt.Done();
 
   // Content insertion/removal is not cause of accessible tree change.
-  if (updateFlags == eNoAccessible)
-    return;
-
-  MaybeNotifyOfValueChange(aContainer);
-  FireDelayedEvent(reorderEvent);
+  if (updateFlags != eNoAccessible) {
+    MaybeNotifyOfValueChange(aContainer);
+  }
 }
 
 uint32_t
-DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
-                                  AccReorderEvent* aReorderEvent)
+DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert)
 {
   uint32_t updateFlags = eAccessible;
 
   // If a focused node has been shown then it could mean its frame was recreated
   // while the node stays focused and we need to fire focus event on
   // the accessible we just created. If the queue contains a focus event for
   // this node already then it will be suppressed by this one.
   Accessible* focusedAcc = nullptr;
 
   if (aIsInsert) {
     // Create accessible tree for shown accessible.
     CacheChildrenInSubtree(aChild, &focusedAcc);
-
-  } else {
-    // Fire menupopup end event before hide event if a menu goes away.
-
-    // XXX: We don't look into children of hidden subtree to find hiding
-    // menupopup (as we did prior bug 570275) because we don't do that when
-    // menu is showing (and that's impossible until bug 606924 is fixed).
-    // Nevertheless we should do this at least because layout coalesces
-    // the changes before our processing and we may miss some menupopup
-    // events. Now we just want to be consistent in content insertion/removal
-    // handling.
-    if (aChild->ARIARole() == roles::MENUPOPUP)
-      FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, aChild);
   }
 
-  // Fire show/hide event.
-  RefPtr<AccMutationEvent> event;
-  if (aIsInsert)
-    event = new AccShowEvent(aChild);
-  else
-    event = new AccHideEvent(aChild);
-
-  FireDelayedEvent(event);
-  aReorderEvent->AddSubMutationEvent(event);
-
   if (aIsInsert) {
     roles::Role ariaRole = aChild->ARIARole();
     if (ariaRole == roles::MENUPOPUP) {
       // Fire EVENT_MENUPOPUP_START if ARIA menu appears.
       FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, aChild);
 
     } else if (ariaRole == roles::ALERT) {
       // Fire EVENT_ALERT if ARIA alert appears.
@@ -2060,30 +2037,29 @@ DocAccessible::DoARIAOwnsRelocation(Acce
   while (nsIContent* childEl = iter.NextElem()) {
     Accessible* child = GetAccessible(childEl);
 
     // Make an attempt to create an accessible if it wasn't created yet.
     if (!child) {
       if (aOwner->IsAcceptableChild(childEl)) {
         child = GetAccService()->CreateAccessible(childEl, aOwner);
         if (child) {
-          AutoTreeMutation imut(aOwner);
+          TreeMutation imut(aOwner);
           aOwner->InsertChildAt(insertIdx, child);
           imut.AfterInsertion(child);
           imut.Done();
 
           child->SetRelocated(true);
           children->InsertElementAt(arrayIdx, child);
 
           insertIdx = child->IndexInParent() + 1;
           arrayIdx++;
 
-          RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aOwner);
-          uint32_t flags = UpdateTreeInternal(child, true, reorderEvent);
-          FireEventsOnInsertion(aOwner, reorderEvent, flags);
+          uint32_t flags = UpdateTreeInternal(child, true);
+          FireEventsOnInsertion(aOwner, flags);
         }
       }
       continue;
     }
 
 #ifdef A11Y_LOG
   logging::TreeInfo("aria owns traversal", logging::eVerbose,
                     "candidate", child, nullptr);
@@ -2191,71 +2167,48 @@ DocAccessible::MoveChild(Accessible* aCh
   if (aChild->IsRelocated()) {
     nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(curParent);
     children->RemoveElement(aChild);
   }
 
   if (curParent == aNewParent) {
     MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
 
-    RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(curParent);
-    RefPtr<AccMutationEvent> hideEvent = new AccHideEvent(aChild, false);
-    reorderEvent->AddSubMutationEvent(hideEvent);
-    FireDelayedEvent(hideEvent);
-
     curParent->MoveChild(aIdxInParent, aChild);
-
-    RefPtr<AccMutationEvent> showEvent = new AccShowEvent(aChild);
-    reorderEvent->AddSubMutationEvent(showEvent);
-    FireDelayedEvent(showEvent);
-
     MaybeNotifyOfValueChange(curParent);
-    FireDelayedEvent(reorderEvent);
 
 #ifdef A11Y_LOG
     logging::TreeInfo("move child: parent tree after",
                       logging::eVerbose, curParent);
 #endif
     return true;
   }
 
   if (!aNewParent->IsAcceptableChild(aChild->GetContent())) {
     return false;
   }
 
-  RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(curParent);
-  RefPtr<AccMutationEvent> hideEvent = new AccHideEvent(aChild, false);
-  reorderEvent->AddSubMutationEvent(hideEvent);
-  FireDelayedEvent(hideEvent);
-
-  AutoTreeMutation rmut(curParent);
-  rmut.BeforeRemoval(aChild);
+  TreeMutation rmut(curParent);
+  rmut.BeforeRemoval(aChild, TreeMutation::kNoShutdown);
   curParent->RemoveChild(aChild);
   rmut.Done();
 
   MaybeNotifyOfValueChange(curParent);
-  FireDelayedEvent(reorderEvent);
 
   // No insertion point for the child.
   if (aIdxInParent == -1) {
     return true;
   }
 
-  AutoTreeMutation imut(aNewParent);
+  TreeMutation imut(aNewParent);
   aNewParent->InsertChildAt(aIdxInParent, aChild);
   imut.AfterInsertion(aChild);
   imut.Done();
 
-  reorderEvent = new AccReorderEvent(aNewParent);
-  RefPtr<AccMutationEvent> showEvent = new AccShowEvent(aChild);
-  reorderEvent->AddSubMutationEvent(showEvent);
-  FireDelayedEvent(showEvent);
-
   MaybeNotifyOfValueChange(aNewParent);
-  FireDelayedEvent(reorderEvent);
 
 #ifdef A11Y_LOG
   logging::TreeInfo("move child: old parent tree after",
                     logging::eVerbose, curParent);
   logging::TreeInfo("move child: new parent tree after",
                     logging::eVerbose, aNewParent);
 #endif
 
@@ -2270,36 +2223,35 @@ DocAccessible::CacheChildrenInSubtree(Ac
   // If the accessible is focused then report a focus event after all related
   // mutation events.
   if (aFocusedAcc && !*aFocusedAcc &&
       FocusMgr()->HasDOMFocus(aRoot->GetContent()))
     *aFocusedAcc = aRoot;
 
   Accessible* root = aRoot->IsHTMLCombobox() ? aRoot->FirstChild() : aRoot;
   if (root->KidsFromDOM()) {
-    AutoTreeMutation mt(root);
+#ifdef A11Y_LOG
+  logging::TreeInfo("caching children", logging::eVerbose, aRoot);
+#endif
+    TreeMutation mt(root, TreeMutation::kNoEvents);
     TreeWalker walker(root);
     while (Accessible* child = walker.Next()) {
       if (child->IsBoundToParent()) {
         MoveChild(child, root, root->ChildCount());
         continue;
       }
 
       root->AppendChild(child);
       mt.AfterInsertion(child);
 
       CacheChildrenInSubtree(child, aFocusedAcc);
     }
     mt.Done();
   }
 
-#ifdef A11Y_LOG
-  logging::TreeInfo("cached children", logging::eVerbose, aRoot);
-#endif
-
   // Fire document load complete on ARIA documents.
   // XXX: we should delay an event if the ARIA document has aria-busy.
   if (aRoot->HasARIARole() && !aRoot->IsDoc()) {
     a11y::role role = aRoot->ARIARole();
     if (role == roles::DIALOG || role == roles::DOCUMENT)
       FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
   }
 }
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -3,18 +3,18 @@
  * 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_a11y_DocAccessible_h__
 #define mozilla_a11y_DocAccessible_h__
 
 #include "nsIAccessiblePivot.h"
 
+#include "HyperTextAccessibleWrap.h"
 #include "AccEvent.h"
-#include "HyperTextAccessibleWrap.h"
 
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsIDocument.h"
 #include "nsIDocumentObserver.h"
 #include "nsIEditor.h"
 #include "nsIObserver.h"
 #include "nsIScrollPositionListener.h"
@@ -182,19 +182,17 @@ public:
   DocAccessible* GetChildDocumentAt(uint32_t aIndex) const
     { return mChildDocuments.SafeElementAt(aIndex, nullptr); }
 
   /**
    * Fire accessible event asynchronously.
    */
   void FireDelayedEvent(AccEvent* aEvent);
   void FireDelayedEvent(uint32_t aEventType, Accessible* aTarget);
-  void FireEventsOnInsertion(Accessible* aContainer,
-                             AccReorderEvent* aReorderEvent,
-                             uint32_t aUpdateFlags);
+  void FireEventsOnInsertion(Accessible* aContainer, uint32_t aUpdateFlags);
 
   /**
    * Fire value change event on the given accessible if applicable.
    */
   void MaybeNotifyOfValueChange(Accessible* aAccessible);
 
   /**
    * Get/set the anchor jump.
@@ -368,16 +366,21 @@ public:
 
   /**
    * Schedule ARIA owned element relocation if needed. Return true if relocation
    * was scheduled.
    */
   bool RelocateARIAOwnedIfNeeded(nsIContent* aEl);
 
   /**
+   * Return a notification controller associated with the document.
+   */
+  NotificationController* Controller() const { return mNotificationController; }
+
+  /**
    * If this document is in a content process return the object responsible for
    * communicating with the main process for it.
    */
   DocAccessibleChild* IPCDoc() const { return mIPCDoc; }
 
 protected:
   virtual ~DocAccessible();
 
@@ -517,19 +520,17 @@ protected:
    * Helper for UpdateTreeOn methods. Go down to DOM subtree and updates
    * accessible tree. Return one of these flags.
    */
   enum EUpdateTreeFlags {
     eNoAccessible = 0,
     eAccessible = 1,
     eAlertAccessible = 2
   };
-
-  uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
-                              AccReorderEvent* aReorderEvent);
+  uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert);
 
   /**
    * Validates all aria-owns connections and updates the tree accordingly.
    */
   void ValidateARIAOwned();
 
   /**
    * Steals or puts back accessible subtrees.
@@ -698,17 +699,17 @@ protected:
    */
   nsClassHashtable<nsPtrHashKey<Accessible>, nsTArray<RefPtr<Accessible> > >
     mARIAOwnsHash;
 
   /**
    * Used to process notification from core and accessible events.
    */
   RefPtr<NotificationController> mNotificationController;
-  friend class EventQueue;
+  friend class EventTree;
   friend class NotificationController;
 
 private:
 
   nsIPresShell* mPresShell;
 
   // Exclusively owned by IPDL so don't manually delete it!
   DocAccessibleChild* mIPCDoc;
--- a/accessible/html/HTMLImageMapAccessible.cpp
+++ b/accessible/html/HTMLImageMapAccessible.cpp
@@ -82,35 +82,26 @@ HTMLImageMapAccessible::UpdateChildAreas
 {
   nsImageFrame* imageFrame = do_QueryFrame(mContent->GetPrimaryFrame());
 
   // If image map is not initialized yet then we trigger one time more later.
   nsImageMap* imageMapObj = imageFrame->GetExistingImageMap();
   if (!imageMapObj)
     return;
 
-  bool treeChanged = false;
-  AutoTreeMutation mt(this);
-  RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this);
+  TreeMutation mt(this, TreeMutation::kNoEvents & !aDoFireEvents);
 
   // Remove areas that are not a valid part of the image map anymore.
   for (int32_t childIdx = mChildren.Length() - 1; childIdx >= 0; childIdx--) {
     Accessible* area = mChildren.ElementAt(childIdx);
     if (area->GetContent()->GetPrimaryFrame())
       continue;
 
-    if (aDoFireEvents) {
-      RefPtr<AccHideEvent> event = new AccHideEvent(area, area->GetContent());
-      mDoc->FireDelayedEvent(event);
-      reorderEvent->AddSubMutationEvent(event);
-    }
-
     mt.BeforeRemoval(area);
     RemoveChild(area);
-    treeChanged = true;
   }
 
   // Insert new areas into the tree.
   uint32_t areaElmCount = imageMapObj->AreaCount();
   for (uint32_t idx = 0; idx < areaElmCount; idx++) {
     nsIContent* areaContent = imageMapObj->GetAreaAt(idx);
     Accessible* area = mChildren.SafeElementAt(idx);
     if (!area || area->GetContent() != areaContent) {
@@ -118,32 +109,20 @@ HTMLImageMapAccessible::UpdateChildAreas
       mDoc->BindToDocument(area, aria::GetRoleMap(areaContent->AsElement()));
 
       if (!InsertChildAt(idx, area)) {
         mDoc->UnbindFromDocument(area);
         break;
       }
 
       mt.AfterInsertion(area);
-
-      if (aDoFireEvents) {
-        RefPtr<AccShowEvent> event = new AccShowEvent(area);
-        mDoc->FireDelayedEvent(event);
-        reorderEvent->AddSubMutationEvent(event);
-      }
-
-      treeChanged = true;
     }
   }
 
   mt.Done();
-
-  // Fire reorder event if needed.
-  if (treeChanged && aDoFireEvents)
-    mDoc->FireDelayedEvent(reorderEvent);
 }
 
 Accessible*
 HTMLImageMapAccessible::GetChildAccessibleFor(const nsINode* aNode) const
 {
   uint32_t length = mChildren.Length();
   for (uint32_t i = 0; i < length; i++) {
     Accessible* area = mChildren[i];
--- a/accessible/html/HTMLListAccessible.cpp
+++ b/accessible/html/HTMLListAccessible.cpp
@@ -98,41 +98,29 @@ HTMLLIAccessible::Bounds() const
 void
 HTMLLIAccessible::UpdateBullet(bool aHasBullet)
 {
   if (aHasBullet == !!mBullet) {
     NS_NOTREACHED("Bullet and accessible are in sync already!");
     return;
   }
 
-  RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this);
-  AutoTreeMutation mt(this);
-
-  DocAccessible* document = Document();
+  TreeMutation mt(this);
   if (aHasBullet) {
     mBullet = new HTMLListBulletAccessible(mContent, mDoc);
-    document->BindToDocument(mBullet, nullptr);
+    mDoc->BindToDocument(mBullet, nullptr);
     InsertChildAt(0, mBullet);
     mt.AfterInsertion(mBullet);
-
-    RefPtr<AccShowEvent> event = new AccShowEvent(mBullet);
-    mDoc->FireDelayedEvent(event);
-    reorderEvent->AddSubMutationEvent(event);
-  } else {
-    RefPtr<AccHideEvent> event = new AccHideEvent(mBullet, mBullet->GetContent());
-    mDoc->FireDelayedEvent(event);
-    reorderEvent->AddSubMutationEvent(event);
-
+  }
+  else {
     mt.BeforeRemoval(mBullet);
     RemoveChild(mBullet);
     mBullet = nullptr;
   }
   mt.Done();
-
-  mDoc->FireDelayedEvent(reorderEvent);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLListBulletAccessible
 ////////////////////////////////////////////////////////////////////////////////
 HTMLListBulletAccessible::
   HTMLListBulletAccessible(nsIContent* aContent, DocAccessible* aDoc) :
   LeafAccessible(aContent, aDoc)
--- a/accessible/tests/mochitest/actions/test_link.html
+++ b/accessible/tests/mochitest/actions/test_link.html
@@ -49,19 +49,18 @@
       }
 
       this.getID = function linkChecker_getID()
       {
         return "link '" + aID + "' states check ";
       }
     }
 
-    //gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true;
-    //enableLogging("tree");
+    //enableLogging("tree,eventTree,verbose");
 
     function doTest()
     {
       var actionsArray = [
         {
           ID: "link1",
           actionName: "jump",
           events: CLICK_EVENTS,
@@ -139,12 +138,10 @@
     <img src="../moz.png" id="img2">
   </a>
   <a id="link3" onclick="">
     <img src="../moz.png" id="img3">
   </a>
   <a id="link4" onmouseup="">
     <img src="../moz.png" id="img4">
   </a>
-
-  <div id="eventdump"></div>
 </body>
 </html>
--- a/accessible/tests/mochitest/events/test_aria_alert.html
+++ b/accessible/tests/mochitest/events/test_aria_alert.html
@@ -51,20 +51,20 @@
       {
         return "Change ARIA alert " + aID;
       }
     }
 
     ////////////////////////////////////////////////////////////////////////////
     // Do tests
 
-    var gQueue = null;
+    //gA11yEventDumpToConsole = true; // debuging
+    //enableLogging("tree,events,verbose");
 
-    //gA11yEventDumpID = "eventdump";
-
+    var gQueue = null;
     function doTests()
     {
       gQueue = new eventQueue(nsIAccessibleEvent.EVENT_ALERT);
 
       gQueue.push(new showAlert("alert"));
       gQueue.push(new changeAlert("alert"));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
@@ -82,12 +82,11 @@
      title="mochitest for bug 334386: fire alert event when ARIA alert is shown or new its children are inserted">
     Mozilla Bug 591199
   </a>
 
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
-  <div id="eventdump"></div>
 
 </body>
 </html>
--- a/accessible/tests/mochitest/events/test_aria_menu.html
+++ b/accessible/tests/mochitest/events/test_aria_menu.html
@@ -76,18 +76,18 @@
     }
 
     function closeMenu(aMenuID, aParentMenuID, aHow)
     {
       this.menuNode = getNode(aMenuID);
       this.menu = null;
 
       this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getMenu, this),
         new invokerChecker(EVENT_MENUPOPUP_END, getMenu, this),
-        new invokerChecker(EVENT_HIDE, getMenu, this),
         new invokerChecker(EVENT_REORDER, getNode(aParentMenuID))
       ];
 
       this.invoke = function closeMenu_invoke()
       {
         // Store menu accessible reference while menu is still open.
         this.menu = getAccessible(this.menuNode);
 
@@ -145,21 +145,20 @@
       {
         return "blur menu";
       }
     }
 
     ////////////////////////////////////////////////////////////////////////////
     // Do tests
 
-    var gQueue = null;
+    //gA11yEventDumpToConsole = true; // debuging
+    //enableLogging("tree,events,verbose");
 
-    //gA11yEventDumpToConsole = true; // debuging
-    //enableLogging("tree,verbose");
-
+    var gQueue = null;
     function doTests()
     {
       gQueue = new eventQueue();
 
       gQueue.push(new focusMenu("menubar2", "menu-help"));
       gQueue.push(new focusMenu("menubar", "menu-file", "menubar2"));
       gQueue.push(new showMenu("menupopup-file", "menu-file", kViaDisplayStyle));
       gQueue.push(new closeMenu("menupopup-file", "menu-file", kViaDisplayStyle));
@@ -252,17 +251,18 @@
   </div>
   <div tabindex="0" id="outsidemenu">outsidemenu</div>
 
   <!-- aria-owns relations -->
   <div id="menubar3" role="menubar" aria-owns="mb3-mi-outside"></div>
   <div id="mb3-mi-outside" role="menuitem" tabindex="0">Outside</div>
 
   <div id="menubar4" role="menubar">
-    <div role="menuitem" aria-haspopup="true" aria-owns="mb4-menu">Item</div>
+    <div id="mb4_topitem" role="menuitem" aria-haspopup="true"
+         aria-owns="mb4-menu">Item</div>
   </div>
   <div id="mb4-menu" role="menu" style="display:none;">
     <div role="menuitem" id="mb4-item1" tabindex="0">Item 1.1</div>
     <div role="menuitem" tabindex="0">Item 1.2</div>
   </div>
 
   <!-- focus top-level menu item having haspopup -->
   <div id="menubar5" role="menubar">
--- a/accessible/tests/mochitest/events/test_coalescence.html
+++ b/accessible/tests/mochitest/events/test_coalescence.html
@@ -318,23 +318,49 @@
 
       this.__proto__ = new coalescenceBase(kAddElm, kShowElm,
                                            aPerformActionOnChildInTheFirstPlace);
 
       this.init();
       this.initSequence();
     }
 
+    /**
+     * Remove children and parent
+     */
+    function removeGrandChildrenNHideParent(aChild1Id, aChild2Id, aParentId)
+    {
+      this.child1 = getNode(aChild1Id);
+      this.child2 = getNode(aChild2Id);
+      this.parent = getNode(aParentId);
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getAccessible(aParentId)),
+        new invokerChecker(EVENT_REORDER, getNode(aParentId).parentNode)
+      ];
+
+      this.invoke = function removeGrandChildrenNHideParent_invoke()
+      {
+        this.child1.parentNode.removeChild(this.child1);
+        this.child2.parentNode.removeChild(this.child2);
+        this.parent.hidden = true;
+      }
+
+      this.getID = function removeGrandChildrenNHideParent_getID() {
+        return "remove grand children of different parents and then hide their grand parent";
+      }
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Do tests.
 
+    //gA11yEventDumpToConsole = true; // debug stuff
+    //enableLogging("events,tree,eventTree,verbose");
+
     var gQueue = null;
-    //gA11yEventDumpToConsole = true; // debug stuff
-    //enableLogging("tree");
-
     function doTests()
     {
       gQueue = new eventQueue();
 
       gQueue.push(new removeChildNParent("option1", "select1"));
       gQueue.push(new removeParentNChild("option2", "select2"));
       gQueue.push(new hideChildNParent("option3", "select3"));
       gQueue.push(new hideParentNChild("option4", "select4"));
@@ -345,16 +371,18 @@
 
       gQueue.push(new addParentNChild("testContainer", false));
       gQueue.push(new addParentNChild("testContainer", true));
       gQueue.push(new showParentNChild("select9", "option9", false));
       gQueue.push(new showParentNChild("select10", "option10", true));
       gQueue.push(new showParentNAddChild("select11", false));
       gQueue.push(new showParentNAddChild("select12", true));
 
+      gQueue.push(new removeGrandChildrenNHideParent("t1_child1", "t1_child2", "t1_parent"));
+
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
 
@@ -407,10 +435,17 @@
       <option id="option9" style="display: none">testing</option>
     </select>
     <select id="select10" style="display: none">
       <option id="option10" style="display: none">testing</option>
     </select>
     <select id="select11" style="display: none"></select>
     <select id="select12" style="display: none"></select>
   </div>
+
+  <div id="testContainer2">
+    <div id="t1_parent">
+      <div id="t1_mid1"><div id="t1_child1"></div></div>
+      <div id="t1_mid2"><div id="t1_child2"></div></div>
+    </div>
+  </div>
 </body>
 </html>
--- a/accessible/tests/mochitest/events/test_mutation.html
+++ b/accessible/tests/mochitest/events/test_mutation.html
@@ -356,18 +356,18 @@
       {
         return "insert inaccessible element and then insert referring element to make it accessible";
       }
     }
 
     function showHiddenParentOfVisibleChild()
     {
       this.eventSeq = [
+        new invokerChecker(EVENT_SHOW, getNode("c4_middle")),
         new invokerChecker(EVENT_HIDE, getNode("c4_child")),
-        new invokerChecker(EVENT_SHOW, getNode("c4_middle")),
         new invokerChecker(EVENT_REORDER, getNode("c4"))
       ];
 
       this.invoke = function showHiddenParentOfVisibleChild_invoke()
       {
         getNode("c4_middle").style.visibility = 'visible';
       }
 
@@ -410,17 +410,17 @@
     }
 
     function getParent(aNode)
     {
       return aNode.parentNode;
     }
 
     //gA11yEventDumpToConsole = true; // debug stuff
-    //enableLogging("tree,verbose");
+    //enableLogging("events,verbose");
 
     /**
      * Do tests.
      */
     var gQueue = null;
 
     function doTests()
     {
--- a/accessible/tests/mochitest/events/test_namechange.html
+++ b/accessible/tests/mochitest/events/test_namechange.html
@@ -33,16 +33,37 @@
       }
 
       this.getID = function setAttr_getID()
       {
         return "set attr '" + aAttr + "', value '" + aValue + "'";
       }
     }
 
+    /**
+     * No name change on an accessible, because the accessible is recreated.
+     */
+    function setAttr_recreate(aID, aAttr, aValue)
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getAccessible(aID)),
+        new invokerChecker(EVENT_SHOW, aID)
+      ];
+      this.invoke = function setAttr_recreate_invoke()
+      {
+        todo(false, "No accessible recreation should happen, just name change event");
+        getNode(aID).setAttribute(aAttr, aValue);
+      }
+
+      this.getID = function setAttr_recreate_getID()
+      {
+        return "set attr '" + aAttr + "', value '" + aValue + "'";
+      }
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Do tests
 
     //gA11yEventDumpToConsole = true; // debuggin
 
     var gQueue = null;
     function doTests()
     {
@@ -59,18 +80,17 @@
 
       gQueue.push(new setAttr("tst2", "aria-labelledby", "display",
                               new invokerChecker(EVENT_NAME_CHANGE, "tst2")));
       gQueue.push(new setAttr("tst2", "alt", "alt",
                               new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
       gQueue.push(new setAttr("tst2", "title", "title",
                               new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
 
-      gQueue.push(new setAttr("tst3", "alt", "alt",
-                              new invokerChecker(EVENT_NAME_CHANGE, "tst3")));
+      gQueue.push(new setAttr_recreate("tst3", "alt", "alt"));
       gQueue.push(new setAttr("tst3", "title", "title",
                               new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst3")));
 
       gQueue.push(new setAttr("tst4", "title", "title",
                               new invokerChecker(EVENT_NAME_CHANGE, "tst4")));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
--- a/accessible/tests/mochitest/events/test_selection.html
+++ b/accessible/tests/mochitest/events/test_selection.html
@@ -109,12 +109,10 @@
   <p>Pizza</p>
   <select id="listbox2" multiple size=5>
     <option id="lb2_item1" value="mushrooms">mushrooms
     <option id="lb2_item2" value="greenpeppers">green peppers
     <option id="lb2_item3" value="onions" id="onions">onions
     <option id="lb2_item4" value="tomatoes">tomatoes
     <option id="lb2_item5" value="olives">olives
   </select>
-
-  <div id="eventdump"></div>
 </body>
 </html>
--- a/accessible/tests/mochitest/events/test_selection.xul
+++ b/accessible/tests/mochitest/events/test_selection.xul
@@ -20,22 +20,30 @@
   <script type="application/javascript"
           src="../states.js" />
   <script type="application/javascript"
           src="../events.js" />
 
   <script type="application/javascript">
     function advanceTab(aTabsID, aDirection, aNextTabID)
     {
-      this.eventSeq = [
+      var eventSeq1 = [
         new invokerChecker(EVENT_SELECTION, aNextTabID)
+      ]
+      defineScenario(this, eventSeq1);
+
+      var eventSeq2 = [
+        new invokerChecker(EVENT_HIDE, getAccessible(aNextTabID)),
+        new invokerChecker(EVENT_SHOW, aNextTabID)
       ];
+      defineScenario(this, eventSeq2);
 
       this.invoke = function advanceTab_invoke()
       {
+        todo(false, "No accessible recreation should happen, just selection event");
         getNode(aTabsID).advanceSelectedTab(aDirection, true);
       }
 
       this.getID = function synthFocus_getID()
       {
         return "advanceTab on " + prettyName(aTabsID) + " to " + prettyName(aNextTabID);
       }
     }
@@ -143,16 +151,17 @@
       }
     }
 
     /**
      * Do tests.
      */
     var gQueue = null;
 
+    //enableLogging("events");
     //gA11yEventDumpToConsole = true; // debuggin
 
     function doTests()
     {
       gQueue = new eventQueue();
 
       //////////////////////////////////////////////////////////////////////////
       // tabbox
--- a/accessible/tests/mochitest/events/test_text.html
+++ b/accessible/tests/mochitest/events/test_text.html
@@ -249,19 +249,19 @@
           prettyName(aID);
       }
 
       this.textNode = getNode(aTextNode);
     }
 
     ////////////////////////////////////////////////////////////////////////////
     // Do tests
+    gA11yEventDumpToConsole = true; // debugging
+
     var gQueue = null;
-    //gA11yEventDumpID = "eventdump"; // debug stuff
-
     function doTests()
     {
       gQueue = new eventQueue();
 
       // Text remove event on inaccessible child HTML span removal containing
       // accessible text nodes.
       gQueue.push(new removeChildSpan("p"));
       gQueue.push(new insertChildSpan("p"), true);
@@ -322,17 +322,16 @@
      title="Rework accessible tree update code">
     Mozilla Bug 570275
   </a>
 
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
-  <div id="eventdump"></div>
 
   <p id="p"><span><span>333</span><span>22</span></span>1111</p>
   <div id="div">hello<div>hello</div>hello</div>
   <div id="div2"><div>txt</div>txt<div>txt</div></div>
   <div id="div3"><div>txt</div>txt<div>txt</div></div>
   <input id="input" value="value">
   <div contentEditable="true" id="editable">value</div>
   <div contentEditable="true" id="editable2"><span>value</span></div>
--- a/accessible/tests/mochitest/treeupdate/test_ariaowns.html
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -23,18 +23,18 @@
     // Invokers
     ////////////////////////////////////////////////////////////////////////////
 
     function changeARIAOwns()
     {
       this.eventSeq = [
         new invokerChecker(EVENT_HIDE, getNode("t1_button")),
         new invokerChecker(EVENT_SHOW, getNode("t1_button")),
-        new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
         new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
+        // no hide for t1_subdiv because it is contained by hidden t1_checkbox
         new invokerChecker(EVENT_HIDE, getNode("t1_checkbox")),
         new invokerChecker(EVENT_SHOW, getNode("t1_checkbox")),
         new invokerChecker(EVENT_REORDER, getNode("t1_container"))
       ];
 
       this.invoke = function setARIAOwns_invoke()
       {
         // children are swapped by ARIA owns
@@ -68,20 +68,20 @@
       {
         return "Change @aria-owns attribute";
       }
     }
 
     function removeARIAOwns()
     {
       this.eventSeq = [
+        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_SHOW, getNode("t1_subdiv")),
         new invokerChecker(EVENT_REORDER, getNode("t1_container"))
       ];
 
       this.invoke = function removeARIAOwns_invoke()
       {
         getNode("t1_container").removeAttribute("aria-owns");
       }
 
@@ -102,19 +102,19 @@
       {
         return "Remove @aria-owns attribute";
       }
     }
 
     function setARIAOwns()
     {
       this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, 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_SHOW, getNode("t1_subdiv")),
         new invokerChecker(EVENT_REORDER, getNode("t1_container"))
       ];
 
       this.invoke = function setARIAOwns_invoke()
       {
         getNode("t1_container").
           setAttribute("aria-owns", "t1_button t1_subdiv");
@@ -137,18 +137,18 @@
       {
         return "Set @aria-owns attribute";
       }
     }
 
     function addIdToARIAOwns()
     {
       this.eventSeq = [
+        new invokerChecker(EVENT_SHOW, getNode("t1_group")),
         new invokerChecker(EVENT_HIDE, getNode("t1_group")),
-        new invokerChecker(EVENT_SHOW, 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_SHOW, getNode("t1_grouptmp")),
         new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")),
-        new invokerChecker(EVENT_SHOW, getNode("t1_grouptmp")),
         new invokerChecker(EVENT_REORDER, document)
       ];
 
       this.invoke = function setId_invoke()
       {
         getNode("t1_grouptmp").setAttribute("id", "t1_group");
       }
 
@@ -510,17 +510,17 @@
     }
 
 
     ////////////////////////////////////////////////////////////////////////////
     // Test
     ////////////////////////////////////////////////////////////////////////////
 
     //gA11yEventDumpToConsole = true;
-    //enableLogging("tree,verbose"); // debug stuff
+    //enableLogging("tree,eventTree,verbose"); // debug stuff
 
     var gQueue = null;
 
     function doTest()
     {
       gQueue = new eventQueue();
 
       // test1
--- a/accessible/tests/mochitest/treeupdate/test_bug1189277.html
+++ b/accessible/tests/mochitest/treeupdate/test_bug1189277.html
@@ -16,19 +16,19 @@
           src="../events.js"></script>
 
   <script type="application/javascript">
     function runTest()
     {
       this.containerNode = getNode("outerDiv");
 
       this.eventSeq = [
-        new invokerChecker(EVENT_HIDE, getNode("child")),
         new invokerChecker(EVENT_HIDE, getNode("childDoc")),
         new invokerChecker(EVENT_SHOW, "newChildDoc"),
+        new invokerChecker(EVENT_HIDE, getNode("child")),
         new invokerChecker(EVENT_REORDER, this.containerNode)
       ];
 
       this.invoke = function runTest_invoke()
       {
         this.containerNode.removeChild(getNode("child"));
 
         var docContainer = getNode("docContainer");
@@ -41,17 +41,18 @@
       }
 
       this.getID = function runTest_getID()
       {
         return "check show events are not incorrectly coalesced";
       }
     }
 
-    gA11yEventDumpToConsole = true;
+    //enableLogging("tree");
+    //gA11yEventDumpToConsole = true;
     var gQueue = null;
     function doTest()
     {
       gQueue = new eventQueue();
       gQueue.push(new runTest());
       gQueue.invoke(); // SimpleTest.finish();
     }
 
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -55,16 +55,15 @@ MAR_CHANNEL_ID=firefox-mozilla-central
 MOZ_PROFILE_MIGRATOR=1
 MOZ_APP_STATIC_INI=1
 MOZ_WEBGL_CONFORMANT=1
 # Enable navigator.mozPay
 MOZ_PAY=1
 # Enable activities. These are used for FxOS developers currently.
 MOZ_ACTIVITIES=1
 MOZ_JSDOWNLOADS=1
-MOZ_WEBM_ENCODER=1
 MOZ_RUST_MP4PARSE=1
 
 # Enable checking that add-ons are signed by the trusted root
 MOZ_ADDON_SIGNING=1
 
 # Include the DevTools client, not just the server (which is the default)
 MOZ_DEVTOOLS=all
--- a/build/unix/mozconfig.linux
+++ b/build/unix/mozconfig.linux
@@ -2,28 +2,30 @@ if [ "x$IS_NIGHTLY" = "xyes" ]; then
   # Some nightlies (eg: Mulet) don't want these set.
   MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
   MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-1}
   MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
 fi
 
 . "$topsrcdir/build/mozconfig.common"
 
+TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
+
 # some b2g desktop builds still happen on i686 machines, and the tooltool
 # toolchain is x86_64 only.
 # We also deal with valgrind builds here, they don't use tooltool manifests at
 # all yet.
 if [ -z "$no_tooltool" ]
 then
-  CC="$topsrcdir/gcc/bin/gcc"
-  CXX="$topsrcdir/gcc/bin/g++"
+  CC="$TOOLTOOL_DIR/gcc/bin/gcc"
+  CXX="$TOOLTOOL_DIR/gcc/bin/g++"
 
   # We want to make sure we use binutils and other binaries in the tooltool
   # package.
-  mk_add_options PATH="$topsrcdir/gcc/bin:$PATH"
+  mk_add_options PATH="$TOOLTOOL_DIR/gcc/bin:$PATH"
 else
   CC="/tools/gcc-4.7.3-0moz1/bin/gcc"
   CXX="/tools/gcc-4.7.3-0moz1/bin/g++"
 fi
 
 ac_add_options --enable-elf-hack
 
 # Avoid dependency on libstdc++ 4.7
--- a/caps/tests/unit/test_origin.js
+++ b/caps/tests/unit/test_origin.js
@@ -198,17 +198,17 @@ function run_test() {
   checkKind(ssm.getSystemPrincipal(), 'systemPrincipal');
 
   //
   // Test Origin Attribute Manipulation
   //
 
   // check that we can create an empty origin attributes dict with default
   // members and values.
-  emptyAttrs = ChromeUtils.createDefaultOriginAttributes();
+  emptyAttrs = ChromeUtils.fillNonDefaultOriginAttributes({});
   checkValues(emptyAttrs);
 
   var uri = "http://example.org";
   var tests = [
     [ "", {} ],
     [ "^appId=5", {appId: 5} ],
     [ "^userContextId=3", {userContextId: 3} ],
     [ "^addonId=fooBar", {addonId: "fooBar"} ],
@@ -221,17 +221,17 @@ function run_test() {
   tests.forEach(function(t) {
     let attrs = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]);
     checkValues(attrs, t[1]);
     do_check_eq(ChromeUtils.originAttributesToSuffix(attrs), t[0]);
   });
 
   // check that we can create an origin attributes from a dict properly
   tests.forEach(function(t) {
-    let attrs = ChromeUtils.createOriginAttributesFromDict(t[1]);
+    let attrs = ChromeUtils.fillNonDefaultOriginAttributes(t[1]);
     checkValues(attrs, t[1]);
     do_check_eq(ChromeUtils.originAttributesToSuffix(attrs), t[0]);
   });
 
   // each row in the set_tests array has these values:
   // [0] - the suffix used to create an origin attribute from
   // [1] - the expected result of creating an origin attribute from [0]
   // [2] - the pattern to set on the origin attributes
--- a/config/external/icu/data/icudata_gas.S
+++ b/config/external/icu/data/icudata_gas.S
@@ -1,12 +1,12 @@
 # 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/.
 
 #if defined(__linux__) && defined(__ELF__)
-.section .note.GNU-stack,"",@progbits
+.section .note.GNU-stack,"",%progbits
 #endif
 .global ICU_DATA_SYMBOL
 .data
 .balign 16
 ICU_DATA_SYMBOL:
         .incbin ICU_DATA_FILE
--- a/dom/apps/PermissionsTable.jsm
+++ b/dom/apps/PermissionsTable.jsm
@@ -482,21 +482,16 @@ this.PermissionsTable =  { geolocation: 
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "inputport": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
-                           "external-app": {
-                             app: DENY_ACTION,
-                             privileged: ALLOW_ACTION,
-                             certified: ALLOW_ACTION
-                           },
                            "system-update": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "presentation": {
                              app: DENY_ACTION,
                              privileged: ALLOW_ACTION,
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -66,39 +66,32 @@ ChromeUtils::OriginAttributesMatchPatter
                                           const dom::OriginAttributesPatternDictionary& aPattern)
 {
   GenericOriginAttributes attrs(aAttrs);
   OriginAttributesPattern pattern(aPattern);
   return pattern.Matches(attrs);
 }
 
 /* static */ void
-ChromeUtils::CreateDefaultOriginAttributes(dom::GlobalObject& aGlobal,
-                                      dom::OriginAttributesDictionary& aAttrs)
-{
-  aAttrs = GenericOriginAttributes();
-}
-
-/* static */ void
 ChromeUtils::CreateOriginAttributesFromOrigin(dom::GlobalObject& aGlobal,
                                        const nsAString& aOrigin,
                                        dom::OriginAttributesDictionary& aAttrs,
                                        ErrorResult& aRv)
 {
   GenericOriginAttributes attrs;
   nsAutoCString suffix;
   if (!attrs.PopulateFromOrigin(NS_ConvertUTF16toUTF8(aOrigin), suffix)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
   aAttrs = attrs;
 }
 
 /* static */ void
-ChromeUtils::CreateOriginAttributesFromDict(dom::GlobalObject& aGlobal,
+ChromeUtils::FillNonDefaultOriginAttributes(dom::GlobalObject& aGlobal,
                                  const dom::OriginAttributesDictionary& aAttrs,
                                  dom::OriginAttributesDictionary& aNewAttrs)
 {
   aNewAttrs = aAttrs;
 }
 
 
 /* static */ bool
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -54,27 +54,23 @@ public:
                            nsCString& aSuffix);
 
   static bool
   OriginAttributesMatchPattern(dom::GlobalObject& aGlobal,
                                const dom::OriginAttributesDictionary& aAttrs,
                                const dom::OriginAttributesPatternDictionary& aPattern);
 
   static void
-  CreateDefaultOriginAttributes(dom::GlobalObject& aGlobal,
-                                dom::OriginAttributesDictionary& aAttrs);
-
-  static void
   CreateOriginAttributesFromOrigin(dom::GlobalObject& aGlobal,
                                    const nsAString& aOrigin,
                                    dom::OriginAttributesDictionary& aAttrs,
                                    ErrorResult& aRv);
 
   static void
-  CreateOriginAttributesFromDict(dom::GlobalObject& aGlobal,
+  FillNonDefaultOriginAttributes(dom::GlobalObject& aGlobal,
                                  const dom::OriginAttributesDictionary& aAttrs,
                                  dom::OriginAttributesDictionary& aNewAttrs);
 
   static bool
   IsOriginAttributesEqual(dom::GlobalObject& aGlobal,
                           const dom::OriginAttributesDictionary& aA,
                           const dom::OriginAttributesDictionary& aB);
 };
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -1799,17 +1799,17 @@ nsFrameLoader::ShouldUseRemoteProcess()
   if (PR_GetEnv("MOZ_DISABLE_OOP_TABS") ||
       Preferences::GetBool("dom.ipc.tabs.disabled", false)) {
     return false;
   }
 
   // Don't try to launch nested children if we don't have OMTC.
   // They won't render!
   if (XRE_IsContentProcess() &&
-      !CompositorBridgeChild::ChildProcessHasCompositor()) {
+      !CompositorBridgeChild::ChildProcessHasCompositorBridge()) {
     return false;
   }
 
   if (XRE_IsContentProcess() &&
       !(PR_GetEnv("MOZ_NESTED_OOP_TABS") ||
         Preferences::GetBool("dom.ipc.tabs.nested.enabled", false))) {
     return false;
   }
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -36,17 +36,16 @@ GK_ATOM(moz, "_moz")
 GK_ATOM(mozframetype, "mozframetype")
 GK_ATOM(_moz_abspos, "_moz_activated")
 GK_ATOM(_moz_activated, "_moz_activated")
 GK_ATOM(_moz_resizing, "_moz_resizing")
 GK_ATOM(mozallowfullscreen, "mozallowfullscreen")
 GK_ATOM(moztype, "_moz-type")
 GK_ATOM(mozdirty, "_moz_dirty")
 GK_ATOM(mozdisallowselectionprint, "mozdisallowselectionprint")
-GK_ATOM(moznomarginboxes, "moznomarginboxes")
 GK_ATOM(mozdonotsend, "moz-do-not-send")
 GK_ATOM(mozeditorbogusnode, "_moz_editor_bogus_node")
 GK_ATOM(mozgeneratedcontentbefore, "_moz_generated_content_before")
 GK_ATOM(mozgeneratedcontentafter, "_moz_generated_content_after")
 GK_ATOM(mozgeneratedcontentimage, "_moz_generated_content_image")
 GK_ATOM(mozquote, "_moz_quote")
 GK_ATOM(mozsignature, "moz-signature")
 GK_ATOM(_moz_is_glyph, "-moz-is-glyph")
@@ -367,17 +366,16 @@ GK_ATOM(equalsize, "equalsize")
 GK_ATOM(error, "error")
 GK_ATOM(even, "even")
 GK_ATOM(event, "event")
 GK_ATOM(events, "events")
 GK_ATOM(excludeResultPrefixes, "exclude-result-prefixes")
 GK_ATOM(excludes, "excludes")
 GK_ATOM(expr, "expr")
 GK_ATOM(expectingSystemMessage, "expecting-system-message")
-GK_ATOM(extapp, "extapp")
 GK_ATOM(extends, "extends")
 GK_ATOM(extensionElementPrefixes, "extension-element-prefixes")
 GK_ATOM(face, "face")
 GK_ATOM(fallback, "fallback")
 GK_ATOM(_false, "false")
 GK_ATOM(farthest, "farthest")
 GK_ATOM(field, "field")
 GK_ATOM(fieldset, "fieldset")
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -2810,25 +2810,16 @@ nsDOMAttributeMap*
 nsINode::GetAttributes()
 {
   if (!IsElement()) {
     return nullptr;
   }
   return AsElement()->Attributes();
 }
 
-bool
-EventTarget::DispatchEvent(Event& aEvent,
-                           ErrorResult& aRv)
-{
-  bool result = false;
-  aRv = DispatchEvent(&aEvent, &result);
-  return result;
-}
-
 Element*
 nsINode::GetParentElementCrossingShadowRoot() const
 {
   if (!mParent) {
     return nullptr;
   }
 
   if (mParent->IsElement()) {
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -466,17 +466,18 @@ DOMInterfaces = {
 'EventTarget': {
     # When we get rid of hasXPConnectImpls, we can get rid of the
     # couldBeDOMBinding stuff in GetOrCreateDOMReflector.
     #
     # We can also get rid of the UnwrapArg bits in
     # the dom QueryInterface (in BindingUtils.cpp) at that point.
     'hasXPConnectImpls': True,
     'concrete': False,
-    'jsImplParent': 'mozilla::DOMEventTargetHelper'
+    'jsImplParent': 'mozilla::DOMEventTargetHelper',
+    'implicitJSContext': [ 'dispatchEvent' ]
 },
 
 'Exception': {
     'headerFile': 'mozilla/dom/DOMException.h',
     'binaryNames': {
         'message': 'messageMoz',
     },
     'implicitJSContext': [ '__stringifier', 'filename', 'lineNumber', 'stack' ],
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -65,99 +65,84 @@ CallbackObject::CallSetup::CallSetup(Cal
 
   // Compute the caller's subject principal (if necessary) early, before we
   // do anything that might perturb the relevant state.
   nsIPrincipal* webIDLCallerPrincipal = nullptr;
   if (aIsJSImplementedWebIDL) {
     webIDLCallerPrincipal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
   }
 
-  // We need to produce a useful JSContext here.  Ideally one that the callback
-  // is in some sense associated with, so that we can sort of treat it as a
-  // "script entry point".  Though once we actually have script entry points,
-  // we'll need to do the script entry point bits once we have an actual
-  // callable.
-
   // First, find the real underlying callback.
   JSObject* realCallback = js::UncheckedUnwrap(aCallback->CallbackPreserveColor());
-  JSContext* cx = nullptr;
   nsIGlobalObject* globalObject = nullptr;
 
+  JSContext* cx;
   {
     // Bug 955660: we cannot do "proper" rooting here because we need the
     // global to get a context. Everything here is simple getters that cannot
     // GC, so just paper over the necessary dataflow inversion.
     JS::AutoSuppressGCAnalysis nogc;
     if (mIsMainThread) {
-      // Now get the global and JSContext for this callback.  Note that for the
-      // case of JS-implemented WebIDL we never have a window here.
+      // Now get the global for this callback. Note that for the case of
+      // JS-implemented WebIDL we never have a window here.
       nsGlobalWindow* win =
         aIsJSImplementedWebIDL ? nullptr : xpc::WindowGlobalOrNull(realCallback);
       if (win) {
-        // Make sure that if this is a window it has an active document, since
-        // the nsIScriptContext and hence JSContext are associated with the
-        // outer window.  Which means that if someone holds on to a function
-        // from a now-unloaded document we'd have the new document as the
-        // script entry point...
         MOZ_ASSERT(win->IsInnerWindow());
+        // We don't want to run script in windows that have been navigated away
+        // from.
         if (!win->AsInner()->HasActiveDocument()) {
-          // Just bail out from here
           return;
         }
-        cx = win->GetContext() ? win->GetContext()->GetNativeContext()
-                               // This happens - Removing it causes
-                               // test_bug293235.xul to go orange.
-                               : nsContentUtils::GetSafeJSContext();
         globalObject = win;
       } else {
-        // No DOM Window. Store the global and use the SafeJSContext.
+        // No DOM Window. Store the global.
         JSObject* glob = js::GetGlobalForObjectCrossCompartment(realCallback);
         globalObject = xpc::NativeGlobal(glob);
         MOZ_ASSERT(globalObject);
-        cx = nsContentUtils::GetSafeJSContext();
       }
     } else {
-      cx = workers::GetCurrentThreadJSContext();
       JSObject *global = js::GetGlobalForObjectCrossCompartment(realCallback);
       globalObject = workers::GetGlobalObjectForGlobal(global);
       MOZ_ASSERT(globalObject);
     }
 
     // Bail out if there's no useful global. This seems to happen intermittently
     // on gaia-ui tests, probably because nsInProcessTabChildGlobal is returning
     // null in some kind of teardown state.
     if (!globalObject->GetGlobalJSObject()) {
       return;
     }
 
-    mAutoEntryScript.emplace(globalObject, aExecutionReason,
-                             mIsMainThread, cx);
+    // Off the main thread, AutoEntryScript expects us to pass a JSContext.
+    mAutoEntryScript.emplace(globalObject, aExecutionReason, mIsMainThread,
+                             mIsMainThread ? nullptr
+                                           : workers::GetCurrentThreadJSContext());
     mAutoEntryScript->SetWebIDLCallerPrincipal(webIDLCallerPrincipal);
     nsIGlobalObject* incumbent = aCallback->IncumbentGlobalOrNull();
     if (incumbent) {
       // The callback object traces its incumbent JS global, so in general it
       // should be alive here. However, it's possible that we could run afoul
       // of the same IPC global weirdness described above, wherein the
       // nsIGlobalObject has severed its reference to the JS global. Let's just
       // be safe here, so that nobody has to waste a day debugging gaia-ui tests.
       if (!incumbent->GetGlobalJSObject()) {
         return;
       }
       mAutoIncumbentScript.emplace(incumbent);
     }
 
+    cx = mAutoEntryScript->cx();
+
     // Unmark the callable (by invoking Callback() and not the CallbackPreserveColor()
     // variant), and stick it in a Rooted before it can go gray again.
     // Nothing before us in this function can trigger a CC, so it's safe to wait
-    // until here it do the unmark. This allows us to order the following two
-    // operations _after_ the Push() above, which lets us take advantage of the
-    // JSAutoRequest embedded in the pusher.
-    //
-    // We can do this even though we're not in the right compartment yet, because
-    // Rooted<> does not care about compartments.
+    // until here it do the unmark. This allows us to construct mRootedCallable
+    // with the cx from mAutoEntryScript, avoiding the cost of finding another
+    // JSContext. (Rooted<> does not care about requests or compartments.)
     mRootedCallable.emplace(cx, aCallback->Callback());
   }
 
   // JS-implemented WebIDL is always OK to run, since it runs with Chrome
   // privileges anyway.
   if (mIsMainThread && !aIsJSImplementedWebIDL) {
     // Check that it's ok to run this callback at all.
     // Make sure to use realCallback to get the global of the callback object,
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -188,16 +188,20 @@ protected:
     if (mCallback) {
       mCallback = nullptr;
       mCreationStack = nullptr;
       mIncumbentJSGlobal = nullptr;
       mozilla::DropJSObjects(this);
     }
   }
 
+  // mCallback is not unwrapped, so it can be a cross-compartment-wrapper.
+  // This is done to ensure that, if JS code can't call a callback f(), or get
+  // its members, directly itself, this code won't call f(), or get its members,
+  // on the code's behalf.
   JS::Heap<JSObject*> mCallback;
   JS::Heap<JSObject*> mCreationStack;
   // Ideally, we'd just hold a reference to the nsIGlobalObject, since that's
   // what we need to pass to AutoIncumbentScript. Unfortunately, that doesn't
   // hold the actual JS global alive. So we maintain an additional pointer to
   // the JS global itself so that we can trace it.
   //
   // At some point we should consider trying to make native globals hold their
@@ -246,18 +250,16 @@ protected:
     // Caller's compartment. This will only have a sensible value if
     // mExceptionHandling == eRethrowContentExceptions or eRethrowExceptions.
     JSCompartment* mCompartment;
 
     // And now members whose construction/destruction order we need to control.
     Maybe<AutoEntryScript> mAutoEntryScript;
     Maybe<AutoIncumbentScript> mAutoIncumbentScript;
 
-    // Constructed the rooter within the scope of mCxPusher above, so that it's
-    // always within a request during its lifetime.
     Maybe<JS::Rooted<JSObject*> > mRootedCallable;
 
     // Members which are used to set the async stack.
     Maybe<JS::Rooted<JSObject*>> mAsyncStack;
     Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter;
 
     // Can't construct a JSAutoCompartment without a JSContext either.  Also,
     // Put mAc after mAutoEntryScript so that we exit the compartment before
--- a/dom/bindings/test/test_oom_reporting.html
+++ b/dom/bindings/test/test_oom_reporting.html
@@ -8,20 +8,20 @@ https://bugzilla.mozilla.org/show_bug.cg
   <title>Test for Bug </title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   /** Test for Bug  **/
   SimpleTest.waitForExplicitFinish();
 
-  window.length = 0xffffffff;
-
   SimpleTest.expectUncaughtException();
-  setTimeout(Array.prototype.splice, 0, undefined);
+  setTimeout(function() {
+    SpecialPowers.Cu.getJSTestingFunctions().throwOutOfMemory();
+  }, 0);
 
   addEventListener("error", function(e) {
     is(e.type, "error", "Should have an error event");
     is(e.message, "uncaught exception: out of memory",
        "Should have the right error message");
     // Make sure we finish async, in case the expectUncaughtException assertion
     // about having seen the exception runs after our listener
     SimpleTest.executeSoon(SimpleTest.finish);
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -245,17 +245,17 @@ nsDOMCameraControl::DiscardCachedCameraI
   sCachedCameraControl = nullptr;
 }
 #endif
 
 nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
                                        const CameraConfiguration& aInitialConfig,
                                        Promise* aPromise,
                                        nsPIDOMWindowInner* aWindow)
-  : DOMMediaStream()
+  : DOMMediaStream(aWindow, nullptr)
   , mCameraControl(nullptr)
   , mAudioChannelAgent(nullptr)
   , mGetCameraPromise(aPromise)
   , mWindow(aWindow)
   , mPreviewState(CameraControlListener::kPreviewStopped)
   , mRecording(false)
   , mRecordingStoppedDeferred(false)
   , mSetInitialConfig(false)
@@ -326,21 +326,16 @@ nsDOMCameraControl::nsDOMCameraControl(u
 
   // Register the playback listener directly on the camera input stream.
   // We want as low latency as possible for the camera, thus avoiding
   // MediaStreamGraph altogether. Don't do the regular InitStreamCommon()
   // to avoid initializing the Owned and Playback streams. This is OK since
   // we are not user/DOM facing anyway.
   CreateAndAddPlaybackStreamListener(mInput);
 
-  MOZ_ASSERT(mWindow, "Shouldn't be created with a null window!");
-  if (mWindow->GetExtantDoc()) {
-    CombineWithPrincipal(mWindow->GetExtantDoc()->NodePrincipal());
-  }
-
   // Register a listener for camera events.
   mListener = new DOMCameraControlListener(this, mInput);
   mCameraControl->AddListener(mListener);
 
 #ifdef MOZ_WIDGET_GONK
   if (!gotCached || NS_FAILED(sCachedCameraControlStartResult)) {
 #endif
     // Start the camera...
@@ -521,20 +516,27 @@ nsDOMCameraControl::Get(uint32_t aKey, n
 MediaStream*
 nsDOMCameraControl::GetCameraStream() const
 {
   return mInput;
 }
 
 void
 nsDOMCameraControl::TrackCreated(TrackID aTrackID) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(mWindow, "Shouldn't have been created with a null window!");
+  nsIPrincipal* principal = mWindow->GetExtantDoc()
+                          ? mWindow->GetExtantDoc()->NodePrincipal()
+                          : nullptr;
+
   // This track is not connected through a port.
   MediaInputPort* inputPort = nullptr;
   dom::VideoStreamTrack* track =
-    new dom::VideoStreamTrack(this, aTrackID, nsString());
+    new dom::VideoStreamTrack(this, aTrackID, aTrackID,
+                              new BasicUnstoppableTrackSource(principal));
   RefPtr<TrackPort> port =
     new TrackPort(inputPort, track,
                   TrackPort::InputPortOwnership::OWNED);
   mTracks.AppendElement(port.forget());
   NotifyTrackAdded(track);
 }
 
 #define THROW_IF_NO_CAMERACONTROL(...)                                          \
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -4509,17 +4509,17 @@ CanvasRenderingContext2D::DrawImage(cons
     uint16_t readyState;
     if (NS_SUCCEEDED(video->GetReadyState(&readyState)) &&
         readyState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
       // still loading, just return
       return;
     }
 
     // If it doesn't have a principal, just bail
-    nsCOMPtr<nsIPrincipal> principal = video->GetCurrentPrincipal();
+    nsCOMPtr<nsIPrincipal> principal = video->GetCurrentVideoPrincipal();
     if (!principal) {
       aError.Throw(NS_ERROR_NOT_AVAILABLE);
       return;
     }
 
     mozilla::layers::ImageContainer* container = video->GetImageContainer();
     if (!container) {
       aError.Throw(NS_ERROR_NOT_AVAILABLE);
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -683,17 +683,17 @@ ImageBitmap::CreateInternal(nsIGlobalObj
   // Check ready state.
   // Cannot be HTMLMediaElement::HAVE_NOTHING or HTMLMediaElement::HAVE_METADATA.
   if (aVideoEl.ReadyState() <= HTMLMediaElement::HAVE_METADATA) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   // Check security.
-  nsCOMPtr<nsIPrincipal> principal = aVideoEl.GetCurrentPrincipal();
+  nsCOMPtr<nsIPrincipal> principal = aVideoEl.GetCurrentVideoPrincipal();
   bool CORSUsed = aVideoEl.GetCORSMode() != CORS_NONE;
   if (!CheckSecurityForHTMLElements(false, CORSUsed, principal)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
   // Create ImageBitmap.
   ImageContainer *container = aVideoEl.GetImageContainer();
--- a/dom/canvas/test/captureStream_common.js
+++ b/dom/canvas/test/captureStream_common.js
@@ -61,16 +61,20 @@ CaptureStreamTestHelper.prototype = {
    * optional scaling of the drawImage() call (so that a 1x1 black image
    * won't just draw 1 pixel in the corner)
    */
   getPixel: function (video, offsetX, offsetY, width, height) {
     offsetX = offsetX || 0; // Set to 0 if not passed in.
     offsetY = offsetY || 0; // Set to 0 if not passed in.
     width = width || 0; // Set to 0 if not passed in.
     height = height || 0; // Set to 0 if not passed in.
+
+    // Avoids old values in case of a transparent image.
+    CaptureStreamTestHelper2D.prototype.clear.call(this, this.cout);
+
     var ctxout = this.cout.getContext('2d');
     if (width != 0 || height != 0) {
       ctxout.drawImage(video, 0, 0, width, height);
     } else {
       ctxout.drawImage(video, 0, 0);
     }
     return ctxout.getImageData(offsetX, offsetY, 1, 1).data;
   },
@@ -100,17 +104,16 @@ CaptureStreamTestHelper.prototype = {
 
   /*
    * Returns a promise that resolves when the provided function |test|
    * returns true.
    */
   waitForPixel: function (video, offsetX, offsetY, test, timeout, width, height) {
     return new Promise(resolve => {
       const startTime = video.currentTime;
-      CaptureStreamTestHelper2D.prototype.clear.call(this, this.cout);
       var ontimeupdate = () => {
         var pixelMatch = false;
         try {
             pixelMatch = test(this.getPixel(video, offsetX, offsetY, width, height));
         } catch (NS_ERROR_NOT_AVAILABLE) {
           info("Waiting for pixel but no video available");
         }
         if (!pixelMatch &&
--- a/dom/events/EventTarget.cpp
+++ b/dom/events/EventTarget.cpp
@@ -58,10 +58,20 @@ EventTarget::SetEventHandler(nsIAtom* aT
 
 bool
 EventTarget::HasApzAwareListeners() const
 {
   EventListenerManager* elm = GetExistingListenerManager();
   return elm && elm->HasApzAwareListeners();
 }
 
+bool
+EventTarget::DispatchEvent(JSContext* aCx,
+                           Event& aEvent,
+                           ErrorResult& aRv)
+{
+  bool result = false;
+  aRv = DispatchEvent(&aEvent, &result);
+  return !aEvent.DefaultPrevented(aCx);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/events/EventTarget.h
+++ b/dom/events/EventTarget.h
@@ -45,17 +45,17 @@ public:
                                 EventListener* aCallback,
                                 bool aCapture,
                                 const Nullable<bool>& aWantsUntrusted,
                                 ErrorResult& aRv) = 0;
   virtual void RemoveEventListener(const nsAString& aType,
                                    EventListener* aCallback,
                                    bool aCapture,
                                    ErrorResult& aRv);
-  bool DispatchEvent(Event& aEvent, ErrorResult& aRv);
+  bool DispatchEvent(JSContext* aCx, Event& aEvent, ErrorResult& aRv);
 
   // Note, this takes the type in onfoo form!
   EventHandlerNonNull* GetEventHandler(const nsAString& aType)
   {
     nsCOMPtr<nsIAtom> type = NS_Atomize(aType);
     return GetEventHandler(type, EmptyString());
   }
 
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -115,16 +115,17 @@ skip-if = buildapp == 'b2g' # b2g(1 fail
 [test_bug659350.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_bug662678.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_bug667612.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_bug667919-1.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' #CRASH_DUMP, RANDOM # b2g(bug 900969, 5 tests) b2g-debug(bug 900969, 5 tests) b2g-desktop(bug 900969, 5 tests)
+[test_bug684208.html]
 [test_bug689564.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_bug698929.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_bug704423.html]
 [test_bug741666.html]
 [test_bug742376.html]
 [test_bug812744.html]
--- a/dom/events/test/test_bug517851.html
+++ b/dom/events/test/test_bug517851.html
@@ -58,19 +58,27 @@ e.initEvent("beforeunload", true, true);
 window.testReturnValue = true;
 is(target.dispatchEvent(e), true,
    "beforeunload event on random element should not be prevented!");
 is(handledCount, 4, "Wrong event count; handler should not have run!");
 is(target2.dispatchEvent(e), false,
    "beforeunload event should be prevented!");
 is(handledCount, 5, "Wrong event count!");
 window.testReturnValue = false;
+is(target.dispatchEvent(e), false,
+   "beforeunload event on random element should be prevented because the event was already cancelled!");
+is(handledCount, 5, "Wrong event count; handler should not have run! (2)");
+
+e = document.createEvent("BeforeUnloadEvent");
+e.initEvent("beforeunload", true, true);
+window.testReturnValue = false;
 is(target.dispatchEvent(e), true,
    "beforeunload event on random element should not be prevented (2)!");
 is(handledCount, 5, "Wrong event count; handler should not have run! (2)");
+
 is(target2.dispatchEvent(e), false,
    "beforeunload event should be prevented (2)!");
 is(handledCount, 6, "Wrong event count!");
 
 // Create normal event for beforeunload.
 e = document.createEvent("Event");
 e.initEvent("beforeunload", true, true);
 window.testReturnValue = true;
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_bug684208.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=684208
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 684208</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 684208 **/
+
+  function checkDispatchReturnValue(targetOrUndefined) {
+    var target = targetOrUndefined ? targetOrUndefined : self;
+    function createEvent() {
+      if ("MouseEvent" in this) {
+        return new MouseEvent("click", {cancelable: true});
+      }
+      return new Event("dummy", {cancelable: true});
+    }
+
+    function postSelfMessage(msg) {
+      try {
+        self.postMessage(msg);
+      } catch(ex) {
+        self.postMessage(msg, "*");
+      }
+    }
+
+    function passiveListener(e) {
+      e.target.removeEventListener(e.type, passiveListener);
+    }
+    var event = createEvent();
+    target.addEventListener(event.type, passiveListener);
+    postSelfMessage(target.dispatchEvent(event) == true);
+
+    function cancellingListener(e) {
+      e.target.removeEventListener(e.type, cancellingListener);
+      e.preventDefault();
+    }
+    event = createEvent();
+    target.addEventListener(event.type, cancellingListener);
+    postSelfMessage(target.dispatchEvent(event) == false);
+  }
+
+  function test() {
+    var expectedEvents = 6;
+    function messageHandler(e) {
+      ok(e.data, "All the dispatchEvent calls should pass.");
+      --expectedEvents;
+      if (!expectedEvents) {
+        window.onmessage = null;
+        window.worker.onmessage = null;
+        SimpleTest.finish();
+      }
+    }
+    window.onmessage = messageHandler;
+    checkDispatchReturnValue();
+    checkDispatchReturnValue(document.getElementById("link"));
+    window.worker =
+      new Worker(URL.createObjectURL(new Blob(["(" + checkDispatchReturnValue.toString() + ")();"])));
+    window.worker.onmessage = messageHandler;
+  }
+
+  SimpleTest.waitForExplicitFinish();
+
+  </script>
+</head>
+<body onload="test();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=684208">Mozilla Bug 684208</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<a id="link" href="#foo">foo</a>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -13,16 +13,17 @@
 #include "MediaSegment.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Base64.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/CanvasCaptureMediaStream.h"
 #include "mozilla/dom/CanvasRenderingContext2D.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/HTMLCanvasElementBinding.h"
+#include "mozilla/dom/MediaStreamTrack.h"
 #include "mozilla/dom/MouseEvent.h"
 #include "mozilla/dom/OffscreenCanvas.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/layers/AsyncCanvasRenderer.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
@@ -667,27 +668,27 @@ HTMLCanvasElement::CaptureStream(const O
 
   RefPtr<CanvasCaptureMediaStream> stream =
     CanvasCaptureMediaStream::CreateSourceStream(window, this);
   if (!stream) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  RefPtr<nsIPrincipal> principal = NodePrincipal();
-  stream->CombineWithPrincipal(principal);
-
   TrackID videoTrackId = 1;
-  nsresult rv = stream->Init(aFrameRate, videoTrackId);
+  nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
+  nsresult rv =
+    stream->Init(aFrameRate, videoTrackId, principal);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
-  stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString());
+  stream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO,
+                         new BasicUnstoppableTrackSource(principal));
 
   rv = RegisterFrameCaptureListener(stream->FrameCaptureListener());
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
   return stream.forget();
deleted file mode 100644
--- a/dom/html/HTMLExtAppElement.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* 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 "mozilla/dom/HTMLExtAppElement.h"
-#include "mozilla/dom/HTMLUnknownElement.h"
-#include "mozilla/dom/HTMLExtAppElementBinding.h"
-#include "mozilla/dom/ExternalAppEvent.h"
-
-#include "nsGkAtoms.h"
-#include "nsIAtom.h"
-#include "nsIPermissionManager.h"
-#include "nsStyleConsts.h"
-#include "nsRuleData.h"
-
-nsGenericHTMLElement*
-NS_NewHTMLExtAppElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
-                        mozilla::dom::FromParser aFromParser) {
-  // Return HTMLUnknownElement if the document doesn't have the 'external-app' permission.
-  nsCOMPtr<nsIPermissionManager> permissionManager =
-    mozilla::services::GetPermissionManager();
-  RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
-  nsIPrincipal* principal = ni->GetDocument()->NodePrincipal();
-
-  already_AddRefed<mozilla::dom::NodeInfo> aarni = ni.forget();
-
-  if (!permissionManager) {
-    return new mozilla::dom::HTMLUnknownElement(aarni);
-  }
-
-  uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION;
-  permissionManager->TestExactPermissionFromPrincipal(principal,
-                                                      "external-app",
-                                                      &perm);
-  if (perm != nsIPermissionManager::ALLOW_ACTION) {
-    return new mozilla::dom::HTMLUnknownElement(aarni);
-  }
-
-  return new mozilla::dom::HTMLExtAppElement(aarni);
-}
-
-namespace mozilla {
-namespace dom {
-
-HTMLExtAppElement::HTMLExtAppElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
-  : nsGenericHTMLElement(aNodeInfo)
-{
-  mCustomEventDispatch = new nsCustomEventDispatch(this);
-  mCustomPropertyBag = new nsCustomPropertyBag();
-
-  nsCOMPtr<nsIExternalApplication> app =
-    do_CreateInstance(NS_EXTERNALAPP_CONTRACTID);
-  if (app) {
-    nsresult rv = app->Init(OwnerDoc()->GetInnerWindow(), mCustomPropertyBag, mCustomEventDispatch);
-    if (NS_SUCCEEDED(rv)) {
-      mApp = app;
-    }
-  }
-}
-
-HTMLExtAppElement::~HTMLExtAppElement()
-{
-  mCustomEventDispatch->ClearEventTarget();
-}
-
-void
-HTMLExtAppElement::GetCustomProperty(const nsAString& aName, nsString& aReturn)
-{
-  mCustomPropertyBag->GetCustomProperty(aName, aReturn);
-}
-
-void
-HTMLExtAppElement::PostMessage(const nsAString& aMessage, ErrorResult& aRv)
-{
-  if (!mApp) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return;
-  }
-
-  aRv = mApp->PostMessage(aMessage);
-}
-
-NS_IMPL_ADDREF_INHERITED(HTMLExtAppElement, Element)
-NS_IMPL_RELEASE_INHERITED(HTMLExtAppElement, Element)
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLExtAppElement)
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLExtAppElement,
-                                                nsGenericHTMLElement)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLExtAppElement,
-                                                  nsGenericHTMLElement)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-// QueryInterface implementation for HTMLExtAppElement
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLExtAppElement)
-NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
-
-NS_IMPL_ELEMENT_CLONE(HTMLExtAppElement)
-
-JSObject*
-HTMLExtAppElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return HTMLExtAppElementBinding::Wrap(aCx, this, aGivenProto);
-}
-
-} // namespace dom
-} // namespace mozilla
-
-NS_IMPL_ISUPPORTS(nsCustomPropertyBag, nsICustomPropertyBag)
-
-nsCustomPropertyBag::nsCustomPropertyBag()
-{
-}
-
-nsCustomPropertyBag::~nsCustomPropertyBag()
-{
-}
-
-NS_IMETHODIMP
-nsCustomPropertyBag::SetProperty(const nsAString& aName, const nsAString& aValue)
-{
-  mBag.Put(nsString(aName), new nsString(aValue));
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsCustomPropertyBag::RemoveProperty(const nsAString& aName)
-{
-  mBag.Remove(nsString(aName));
-  return NS_OK;
-}
-
-void
-nsCustomPropertyBag::GetCustomProperty(const nsAString& aName, nsString& aReturn)
-{
-  nsString* value;
-  if (!mBag.Get(nsString(aName), &value)) {
-    aReturn.Truncate();
-    return;
-  }
-
-  MOZ_ASSERT(value);
-  aReturn.Assign(*value);
-}
-
-NS_IMPL_ISUPPORTS(nsCustomEventDispatch, nsICustomEventDispatch)
-
-nsCustomEventDispatch::nsCustomEventDispatch(mozilla::dom::EventTarget* aEventTarget)
-  : mEventTarget(aEventTarget)
-{
-  MOZ_ASSERT(mEventTarget);
-}
-
-void
-nsCustomEventDispatch::ClearEventTarget()
-{
-  mEventTarget = nullptr;
-}
-
-nsCustomEventDispatch::~nsCustomEventDispatch()
-{
-}
-
-NS_IMETHODIMP
-nsCustomEventDispatch::DispatchExternalEvent(const nsAString& value)
-{
-  if (!mEventTarget) {
-    return NS_OK;
-  }
-
-  mozilla::dom::ExternalAppEventInit init;
-  init.mData = value;
-
-  RefPtr<mozilla::dom::ExternalAppEvent> event =
-    mozilla::dom::ExternalAppEvent::Constructor(mEventTarget,
-                                                NS_LITERAL_STRING("externalappevent"),
-                                                init);
-
-  bool defaultActionEnabled;
-  return mEventTarget->DispatchEvent(event, &defaultActionEnabled);
-}
deleted file mode 100644
--- a/dom/html/HTMLExtAppElement.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* 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_HTMLExtAppElement_h
-#define mozilla_dom_HTMLExtAppElement_h
-
-#include "nsGenericHTMLElement.h"
-#include "nsIExternalApplication.h"
-
-class nsCustomEventDispatch;
-class nsCustomPropertyBag;
-
-namespace mozilla {
-namespace dom {
-
-class HTMLExtAppElement final : public nsGenericHTMLElement
-{
-public:
-  explicit HTMLExtAppElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
-
-  // nsISupports
-  NS_DECL_ISUPPORTS_INHERITED
-
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLExtAppElement,
-                                           nsGenericHTMLElement)
-
-  virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
-
-  void GetCustomProperty(const nsAString& aName, nsString& aReturn);
-  void PostMessage(const nsAString& aMessage, ErrorResult& aRv);
-
-protected:
-  virtual ~HTMLExtAppElement();
-
-  virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
-
-  RefPtr<nsCustomEventDispatch> mCustomEventDispatch;
-  RefPtr<nsCustomPropertyBag> mCustomPropertyBag;
-  nsCOMPtr<nsIExternalApplication> mApp;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-class nsCustomEventDispatch final : public nsICustomEventDispatch
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSICUSTOMEVENTDISPATCH
-
-  explicit nsCustomEventDispatch(mozilla::dom::EventTarget* aEventTarget);
-  void ClearEventTarget();
-
-private:
-  ~nsCustomEventDispatch();
-
-  // Weak pointer, this object is owned by the event target.
-  mozilla::dom::EventTarget* mEventTarget;
-};
-
-class nsCustomPropertyBag final : public nsICustomPropertyBag
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSICUSTOMPROPERTYBAG
-
-  nsCustomPropertyBag();
-  void GetCustomProperty(const nsAString& aName, nsString& aReturn);
-
-private:
-  ~nsCustomPropertyBag();
-
-  nsClassHashtable<nsStringHashKey, nsString> mBag;
-};
-
-#endif // mozilla_dom_HTMLExtAppElement_h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -64,16 +64,17 @@
 #include "nsURIHashKey.h"
 #include "nsJSUtils.h"
 #include "MediaStreamGraph.h"
 #include "nsIScriptError.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "mozilla/dom/MediaSource.h"
 #include "MediaMetadataManager.h"
 #include "MediaSourceDecoder.h"
+#include "DOMMediaStream.h"
 #include "AudioStreamTrack.h"
 #include "VideoStreamTrack.h"
 #include "MediaTrackList.h"
 
 #include "AudioChannelService.h"
 
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/WakeLock.h"
@@ -1859,16 +1860,130 @@ NS_IMETHODIMP HTMLMediaElement::SetMuted
   } else {
     SetMutedInternal(mMuted & ~MUTED_BY_CONTENT);
   }
 
   DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
   return NS_OK;
 }
 
+class HTMLMediaElement::CaptureStreamTrackSource :
+  public MediaStreamTrackSource,
+  public DecoderPrincipalChangeObserver
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CaptureStreamTrackSource,
+                                           MediaStreamTrackSource)
+
+  explicit CaptureStreamTrackSource(HTMLMediaElement* aElement)
+    : MediaStreamTrackSource(nsCOMPtr<nsIPrincipal>(aElement->GetCurrentPrincipal()),
+                             true,
+                             nsString())
+    , mElement(aElement)
+  {
+    MOZ_ASSERT(mElement);
+    mElement->AddDecoderPrincipalChangeObserver(this);
+  }
+
+  MediaSourceEnum GetMediaSource() const override
+  {
+    return MediaSourceEnum::Other;
+  }
+
+  CORSMode GetCORSMode() const override
+  {
+    return mElement->GetCORSMode();
+  }
+
+  already_AddRefed<Promise>
+  ApplyConstraints(nsPIDOMWindowInner* aWindow,
+                   const dom::MediaTrackConstraints& aConstraints,
+                   ErrorResult &aRv) override
+  {
+    NS_ERROR("ApplyConstraints not implemented for media element capture");
+    return nullptr;
+  }
+
+  void Stop() override
+  {
+    NS_ERROR("We're reporting remote=true to not be stoppable. "
+             "Stop() should not be called.");
+  }
+
+  void NotifyDecoderPrincipalChanged() override
+  {
+    nsCOMPtr<nsIPrincipal> newPrincipal = mElement->GetCurrentPrincipal();
+    if (nsContentUtils::CombineResourcePrincipals(&mPrincipal, newPrincipal)) {
+      PrincipalChanged();
+    }
+  }
+
+protected:
+  virtual ~CaptureStreamTrackSource()
+  {
+    if (mElement) {
+      DebugOnly<bool> res = mElement->RemoveDecoderPrincipalChangeObserver(this);
+      NS_ASSERTION(res, "Removing decoder principal changed observer failed. "
+                        "Had it already been removed?");
+    }
+  }
+
+  RefPtr<HTMLMediaElement> mElement;
+};
+
+NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::CaptureStreamTrackSource,
+                         MediaStreamTrackSource)
+NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::CaptureStreamTrackSource,
+                          MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::CaptureStreamTrackSource)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::CaptureStreamTrackSource,
+                                   MediaStreamTrackSource,
+                                   mElement)
+
+class HTMLMediaElement::CaptureStreamTrackSourceGetter :
+  public MediaStreamTrackSourceGetter
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CaptureStreamTrackSourceGetter,
+                                           MediaStreamTrackSourceGetter)
+
+  explicit CaptureStreamTrackSourceGetter(HTMLMediaElement* aElement)
+    : mElement(aElement) {}
+
+  already_AddRefed<dom::MediaStreamTrackSource>
+  GetMediaStreamTrackSource(TrackID aInputTrackID) override
+  {
+    // We can return a new source each time here, even for different streams,
+    // since the sources don't keep any internal state and all of them call
+    // through to the same HTMLMediaElement.
+    // If this changes (after implementing Stop()?) we'll have to ensure we
+    // return the same source for all requests to the same TrackID, and only
+    // have one getter.
+    return do_AddRef(new CaptureStreamTrackSource(mElement));
+  }
+
+protected:
+  virtual ~CaptureStreamTrackSourceGetter() {}
+
+  RefPtr<HTMLMediaElement> mElement;
+};
+
+NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
+                         MediaStreamTrackSourceGetter)
+NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
+                          MediaStreamTrackSourceGetter)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
+                                   MediaStreamTrackSourceGetter,
+                                   mElement)
+
 already_AddRefed<DOMMediaStream>
 HTMLMediaElement::CaptureStreamInternal(bool aFinishWhenEnded,
                                         MediaStreamGraph* aGraph)
 {
   nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
   if (!window) {
     return nullptr;
   }
@@ -1886,35 +2001,39 @@ HTMLMediaElement::CaptureStreamInternal(
   }
 
   if (!mOutputStreams.IsEmpty() &&
       aGraph != mOutputStreams[0].mStream->GetInputStream()->Graph()) {
     return nullptr;
   }
 
   OutputMediaStream* out = mOutputStreams.AppendElement();
-  out->mStream = DOMMediaStream::CreateTrackUnionStream(window, aGraph);
-  RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
-  out->mStream->CombineWithPrincipal(principal);
-  out->mStream->SetCORSMode(mCORSMode);
+  MediaStreamTrackSourceGetter* getter = new CaptureStreamTrackSourceGetter(this);
+  out->mStream = DOMMediaStream::CreateTrackUnionStream(window, aGraph, getter);
   out->mFinishWhenEnded = aFinishWhenEnded;
 
   mAudioCaptured = true;
   if (mDecoder) {
     mDecoder->AddOutputStream(out->mStream->GetInputStream()->AsProcessedStream(),
                               aFinishWhenEnded);
     if (mReadyState >= HAVE_METADATA) {
       // Expose the tracks to JS directly.
       if (HasAudio()) {
         TrackID audioTrackId = mMediaInfo.mAudio.mTrackId;
-        out->mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO, nsString());
+        RefPtr<MediaStreamTrackSource> trackSource =
+          getter->GetMediaStreamTrackSource(audioTrackId);
+        out->mStream->CreateDOMTrack(audioTrackId, MediaSegment::AUDIO,
+                                     trackSource);
       }
       if (HasVideo()) {
         TrackID videoTrackId = mMediaInfo.mVideo.mTrackId;
-        out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString());
+        RefPtr<MediaStreamTrackSource> trackSource =
+          getter->GetMediaStreamTrackSource(videoTrackId);
+        out->mStream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO,
+                                     trackSource);
       }
     }
   }
   RefPtr<DOMMediaStream> result = out->mStream;
   return result.forget();
 }
 
 already_AddRefed<DOMMediaStream>
@@ -2119,17 +2238,16 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mMediaSecurityVerified(false),
     mCORSMode(CORS_NONE),
     mIsEncrypted(false),
     mDownloadSuspendedByCache(false, "HTMLMediaElement::mDownloadSuspendedByCache"),
     mAudioChannelVolume(1.0),
     mPlayingThroughTheAudioChannel(false),
     mDisableVideo(false),
     mPlayBlockedBecauseHidden(false),
-    mMediaStreamTrackListener(nullptr),
     mElementInTreeState(ELEMENT_NOT_INTREE),
     mHasUserInteraction(false),
     mFirstFrameLoaded(false),
     mDefaultPlaybackStartPosition(0.0),
     mIsAudioTrackAudible(false)
 {
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
 
@@ -3119,22 +3237,21 @@ private:
   // These fields may only be accessed on the main thread
   HTMLMediaElement* mElement;
 
   // These fields may only be accessed on the MSG thread
   bool mInitialSizeFound;
 };
 
 class HTMLMediaElement::MediaStreamTracksAvailableCallback:
-    public DOMMediaStream::OnTracksAvailableCallback
+  public OnTracksAvailableCallback
 {
 public:
   explicit MediaStreamTracksAvailableCallback(HTMLMediaElement* aElement):
-      DOMMediaStream::OnTracksAvailableCallback(),
-      mElement(aElement)
+      OnTracksAvailableCallback(), mElement(aElement)
     {}
   virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
   {
     NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
     mElement->NotifyMediaStreamTracksAvailable(aStream);
   }
 
@@ -3155,18 +3272,16 @@ public:
   }
 
   void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override
   {
     mElement->NotifyMediaStreamTrackRemoved(aTrack);
   }
 
 protected:
-  ~MediaStreamTrackListener() {}
-
   HTMLMediaElement* const mElement;
 };
 
 void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags)
 {
   if (!mSrcStream) {
     return;
   }
@@ -3251,16 +3366,19 @@ void HTMLMediaElement::SetupSrcMediaStre
   // will continue to fire events at this element and alter its track list.
   // That's simpler than delaying the events, but probably confusing...
   ConstructMediaTracks();
 
   mSrcStream->OnTracksAvailable(new MediaStreamTracksAvailableCallback(this));
   mMediaStreamTrackListener = new MediaStreamTrackListener(this);
   mSrcStream->RegisterTrackListener(mMediaStreamTrackListener);
 
+  mSrcStream->AddPrincipalChangeObserver(this);
+  mSrcStreamVideoPrincipal = mSrcStream->GetVideoPrincipal();
+
   ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
   ChangeDelayLoadStatus(false);
   CheckAutoplayDataReady();
 
   // FirstFrameLoaded() will be called when the stream has current data.
 }
 
 void HTMLMediaElement::EndSrcMediaStreamPlayback()
@@ -3276,16 +3394,19 @@ void HTMLMediaElement::EndSrcMediaStream
     }
     mMediaStreamSizeListener->Forget();
     mMediaStreamSizeListener = nullptr;
   }
 
   mSrcStream->UnregisterTrackListener(mMediaStreamTrackListener);
   mMediaStreamTrackListener = nullptr;
 
+  mSrcStream->RemovePrincipalChangeObserver(this);
+  mSrcStreamVideoPrincipal = nullptr;
+
   mSrcStream = nullptr;
 }
 
 static already_AddRefed<AudioTrack>
 CreateAudioTrack(AudioStreamTrack* aStreamTrack)
 {
   nsAutoString id;
   nsAutoString label;
@@ -4059,16 +4180,76 @@ VideoFrameContainer* HTMLMediaElement::G
   }
 
   mVideoFrameContainer =
     new VideoFrameContainer(this, LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS));
 
   return mVideoFrameContainer;
 }
 
+void
+HTMLMediaElement::PrincipalChanged(DOMMediaStream* aStream)
+{
+  LOG(LogLevel::Info, ("HTMLMediaElement %p Stream principal changed.", this));
+  nsContentUtils::CombineResourcePrincipals(&mSrcStreamVideoPrincipal,
+                                            aStream->GetVideoPrincipal());
+
+  LOG(LogLevel::Debug, ("HTMLMediaElement %p Stream video principal changed to "
+                        "%p. Waiting for it to reach VideoFrameContainer before "
+                        "setting.", this, aStream->GetVideoPrincipal()));
+  if (mVideoFrameContainer) {
+    UpdateSrcStreamVideoPrincipal(mVideoFrameContainer->GetLastPrincipalHandle());
+  }
+}
+
+void
+HTMLMediaElement::UpdateSrcStreamVideoPrincipal(const PrincipalHandle& aPrincipalHandle)
+{
+  nsTArray<RefPtr<VideoStreamTrack>> videoTracks;
+  mSrcStream->GetVideoTracks(videoTracks);
+
+  PrincipalHandle handle(aPrincipalHandle);
+  bool matchesTrackPrincipal = false;
+  for (const RefPtr<VideoStreamTrack>& track : videoTracks) {
+    if (PrincipalHandleMatches(handle,
+                               track->GetPrincipal()) &&
+        !track->Ended()) {
+      // When the PrincipalHandle for the VideoFrameContainer changes to that of
+      // a track in mSrcStream we know that a removed track was displayed but
+      // is no longer so.
+      matchesTrackPrincipal = true;
+      LOG(LogLevel::Debug, ("HTMLMediaElement %p VideoFrameContainer's "
+                            "PrincipalHandle matches track %p. That's all we "
+                            "need.", this, track.get()));
+      break;
+    }
+  }
+
+  if (matchesTrackPrincipal) {
+    mSrcStreamVideoPrincipal = mSrcStream->GetVideoPrincipal();
+  }
+}
+
+void
+HTMLMediaElement::PrincipalHandleChangedForVideoFrameContainer(VideoFrameContainer* aContainer,
+                                                               const PrincipalHandle& aNewPrincipalHandle)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mSrcStream) {
+    return;
+  }
+
+  LOG(LogLevel::Debug, ("HTMLMediaElement %p PrincipalHandle changed in "
+                        "VideoFrameContainer.",
+                        this));
+
+  UpdateSrcStreamVideoPrincipal(aNewPrincipalHandle);
+}
+
 nsresult HTMLMediaElement::DispatchEvent(const nsAString& aName)
 {
   LOG_EVENT(LogLevel::Debug, ("%p Dispatching event %s", this,
                           NS_ConvertUTF16toUTF8(aName).get()));
 
   // Save events that occur while in the bfcache. These will be dispatched
   // if the page comes out of the bfcache.
   if (mEventDeliveryPaused) {
@@ -4152,33 +4333,54 @@ bool HTMLMediaElement::IsPlaybackEnded()
 }
 
 already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentPrincipal()
 {
   if (mDecoder) {
     return mDecoder->GetCurrentPrincipal();
   }
   if (mSrcStream) {
-    RefPtr<nsIPrincipal> principal = mSrcStream->GetPrincipal();
+    nsCOMPtr<nsIPrincipal> principal = mSrcStream->GetPrincipal();
+    return principal.forget();
+  }
+  return nullptr;
+}
+
+already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentVideoPrincipal()
+{
+  if (mDecoder) {
+    return mDecoder->GetCurrentPrincipal();
+  }
+  if (mSrcStream) {
+    nsCOMPtr<nsIPrincipal> principal = mSrcStreamVideoPrincipal;
     return principal.forget();
   }
   return nullptr;
 }
 
 void HTMLMediaElement::NotifyDecoderPrincipalChanged()
 {
   RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
 
   mDecoder->UpdateSameOriginStatus(!principal || IsCORSSameOrigin());
 
-  for (uint32_t i = 0; i < mOutputStreams.Length(); ++i) {
-    OutputMediaStream* ms = &mOutputStreams[i];
-    ms->mStream->SetCORSMode(mCORSMode);
-    ms->mStream->CombineWithPrincipal(principal);
-  }
+  for (DecoderPrincipalChangeObserver* observer :
+         mDecoderPrincipalChangeObservers) {
+    observer->NotifyDecoderPrincipalChanged();
+  }
+}
+
+void HTMLMediaElement::AddDecoderPrincipalChangeObserver(DecoderPrincipalChangeObserver* aObserver)
+{
+  mDecoderPrincipalChangeObservers.AppendElement(aObserver);
+}
+
+bool HTMLMediaElement::RemoveDecoderPrincipalChangeObserver(DecoderPrincipalChangeObserver* aObserver)
+{
+  return mDecoderPrincipalChangeObservers.RemoveElement(aObserver);
 }
 
 void HTMLMediaElement::UpdateMediaSize(const nsIntSize& aSize)
 {
   if (IsVideo() && mReadyState != HAVE_NOTHING &&
       mMediaInfo.mVideo.mDisplay != aSize) {
     DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
   }
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -7,43 +7,44 @@
 #define mozilla_dom_HTMLMediaElement_h
 
 #include "nsIDOMHTMLMediaElement.h"
 #include "nsGenericHTMLElement.h"
 #include "MediaDecoderOwner.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIObserver.h"
 #include "mozilla/CORSMode.h"
-#include "DOMMediaStream.h"
 #include "DecoderTraits.h"
 #include "nsIAudioChannelAgent.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/TextTrackManager.h"
 #include "MediaDecoder.h"
 #ifdef MOZ_EME
 #include "mozilla/dom/MediaKeys.h"
 #endif
 #include "mozilla/StateWatching.h"
 #include "nsGkAtoms.h"
+#include "PrincipalChangeObserver.h"
 
 // X.h on Linux #defines CurrentTime as 0L, so we have to #undef it here.
 #ifdef CurrentTime
 #undef CurrentTime
 #endif
 
 #include "mozilla/dom/HTMLMediaElementBinding.h"
 
 // Define to output information on decoding and painting framerate
 /* #define DEBUG_FRAME_RATE 1 */
 
 typedef uint16_t nsMediaNetworkState;
 typedef uint16_t nsMediaReadyState;
 
 namespace mozilla {
+class DOMMediaStream;
 class ErrorResult;
 class MediaResource;
 class MediaDecoder;
 class VideoFrameContainer;
 namespace dom {
 class MediaKeys;
 class TextTrack;
 class TimeRanges;
@@ -71,17 +72,18 @@ class MediaSource;
 class TextTrackList;
 class AudioTrackList;
 class VideoTrackList;
 
 class HTMLMediaElement : public nsGenericHTMLElement,
                          public nsIDOMHTMLMediaElement,
                          public nsIObserver,
                          public MediaDecoderOwner,
-                         public nsIAudioChannelAgentCallback
+                         public nsIAudioChannelAgentCallback,
+                         public PrincipalChangeObserver<DOMMediaStream>
 {
   friend AutoNotifyAudioChannelAgent;
 
 public:
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::layers::ImageContainer ImageContainer;
   typedef mozilla::VideoFrameContainer VideoFrameContainer;
   typedef mozilla::MediaStream MediaStream;
@@ -218,19 +220,29 @@ public:
   virtual void NotifySuspendedByCache(bool aIsSuspended) final override;
 
   virtual bool IsActive() const final override;
 
   virtual bool IsHidden() const final override;
 
   // Called by the media decoder and the video frame to get the
   // ImageContainer containing the video data.
-  B2G_ACL_EXPORT virtual VideoFrameContainer* GetVideoFrameContainer() final override;
+  virtual VideoFrameContainer* GetVideoFrameContainer() final override;
   layers::ImageContainer* GetImageContainer();
 
+  // From PrincipalChangeObserver<DOMMediaStream>.
+  void PrincipalChanged(DOMMediaStream* aStream) override;
+
+  void UpdateSrcStreamVideoPrincipal(const PrincipalHandle& aPrincipalHandle);
+
+  // Called after the MediaStream we're playing rendered a frame to aContainer
+  // with a different principalHandle than the previous frame.
+  void PrincipalHandleChangedForVideoFrameContainer(VideoFrameContainer* aContainer,
+                                                    const PrincipalHandle& aNewPrincipalHandle);
+
   // Dispatch events
   virtual nsresult DispatchAsyncEvent(const nsAString& aName) final override;
 
   // Triggers a recomputation of readyState.
   void UpdateReadyState() override { UpdateReadyStateInternal(); }
 
   // Dispatch events that were raised while in the bfcache
   nsresult DispatchPendingMediaEvents();
@@ -259,18 +271,51 @@ public:
   // http://www.whatwg.org/specs/web-apps/current-work/#ended
   bool IsPlaybackEnded() const;
 
   // principal of the currently playing resource. Anything accessing the contents
   // of this element must have a principal that subsumes this principal.
   // Returns null if nothing is playing.
   already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
 
+  // Principal of the currently playing video resource. Anything accessing the
+  // image container of this element must have a principal that subsumes this
+  // principal. If there are no live video tracks but content has been rendered
+  // to the image container, we return the last video principal we had. Should
+  // the image container be empty with no live video tracks, we return nullptr.
+  already_AddRefed<nsIPrincipal> GetCurrentVideoPrincipal();
+
   // called to notify that the principal of the decoder's media resource has changed.
-  virtual void NotifyDecoderPrincipalChanged() final override;
+  void NotifyDecoderPrincipalChanged() final override;
+
+  // An interface for observing principal changes on the media elements
+  // MediaDecoder. This will also be notified if the active CORSMode changes.
+  class DecoderPrincipalChangeObserver
+  {
+  public:
+    virtual void NotifyDecoderPrincipalChanged() = 0;
+  };
+
+  /**
+   * Add a DecoderPrincipalChangeObserver to this media element.
+   *
+   * Ownership of the DecoderPrincipalChangeObserver remains with the caller,
+   * and it's the caller's responsibility to remove the observer before it dies.
+   */
+  void AddDecoderPrincipalChangeObserver(DecoderPrincipalChangeObserver* aObserver);
+
+  /**
+   * Remove an added DecoderPrincipalChangeObserver from this media element.
+   *
+   * Returns true if it was successfully removed.
+   */
+  bool RemoveDecoderPrincipalChangeObserver(DecoderPrincipalChangeObserver* aObserver);
+
+  class CaptureStreamTrackSource;
+  class CaptureStreamTrackSourceGetter;
 
   // Update the visual size of the media. Called from the decoder on the
   // main thread when/if the size changes.
   void UpdateMediaSize(const nsIntSize& aSize);
   // Like UpdateMediaSize, but only updates the size if no size has yet
   // been set.
   void UpdateInitialMediaSize(const nsIntSize& aSize);
 
@@ -1106,16 +1151,20 @@ protected:
   using nsGenericHTMLElement::DispatchEvent;
   // For nsAsyncEventRunner.
   nsresult DispatchEvent(const nsAString& aName);
 
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   RefPtr<MediaDecoder> mDecoder;
 
+  // Observers listening to changes to the mDecoder principal.
+  // Used by streams captured from this element.
+  nsTArray<DecoderPrincipalChangeObserver*> mDecoderPrincipalChangeObservers;
+
   // State-watching manager.
   WatchManager<HTMLMediaElement> mWatchManager;
 
   // A reference to the VideoFrameContainer which contains the current frame
   // of video to display.
   RefPtr<VideoFrameContainer> mVideoFrameContainer;
 
   // Holds a reference to the DOM wrapper for the MediaStream that has been
@@ -1453,17 +1502,21 @@ protected:
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 
   RefPtr<TextTrackManager> mTextTrackManager;
 
   RefPtr<AudioTrackList> mAudioTrackList;
 
   RefPtr<VideoTrackList> mVideoTrackList;
 
-  RefPtr<MediaStreamTrackListener> mMediaStreamTrackListener;
+  nsAutoPtr<MediaStreamTrackListener> mMediaStreamTrackListener;
+
+  // The principal guarding mVideoFrameContainer access when playing a
+  // MediaStream.
+  nsCOMPtr<nsIPrincipal> mSrcStreamVideoPrincipal;
 
   enum ElementInTreeState {
     // The MediaElement is not in the DOM tree now.
     ELEMENT_NOT_INTREE,
     // The MediaElement is in the DOM tree now.
     ELEMENT_INTREE,
     // The MediaElement is not in the DOM tree now but had been binded to the
     // tree before.
--- a/dom/html/HTMLSummaryElement.cpp
+++ b/dom/html/HTMLSummaryElement.cpp
@@ -3,16 +3,17 @@
  * 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 "mozilla/dom/HTMLSummaryElement.h"
 
 #include "mozilla/dom/HTMLDetailsElement.h"
 #include "mozilla/dom/HTMLElementBinding.h"
 #include "mozilla/dom/HTMLUnknownElement.h"
+#include "mozilla/EventDispatcher.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEvents.h"
 #include "nsFocusManager.h"
 
 // Expand NS_IMPL_NS_NEW_HTML_ELEMENT(Summary) to add pref check.
 nsGenericHTMLElement*
 NS_NewHTMLSummaryElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
--- a/dom/html/VideoDocument.cpp
+++ b/dom/html/VideoDocument.cpp
@@ -28,17 +28,18 @@ public:
                                      nsIContentSink*     aSink = nullptr);
   virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject);
 
 protected:
 
   // Sets document <title> to reflect the file name and description.
   void UpdateTitle(nsIChannel* aChannel);
 
-  nsresult CreateSyntheticVideoDocument();
+  nsresult CreateSyntheticVideoDocument(nsIChannel* aChannel,
+                                        nsIStreamListener** aListener);
 
   RefPtr<MediaDocumentStreamListener> mStreamListener;
 };
 
 nsresult
 VideoDocument::StartDocumentLoad(const char*         aCommand,
                                  nsIChannel*         aChannel,
                                  nsILoadGroup*       aLoadGroup,
@@ -48,49 +49,47 @@ VideoDocument::StartDocumentLoad(const c
                                  nsIContentSink*     aSink)
 {
   nsresult rv =
     MediaDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer,
                                      aDocListener, aReset, aSink);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mStreamListener = new MediaDocumentStreamListener(this);
+
+  // Create synthetic document
+  rv = CreateSyntheticVideoDocument(aChannel,
+      getter_AddRefs(mStreamListener->mNextStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   NS_ADDREF(*aDocListener = mStreamListener);
   return rv;
 }
 
 void
 VideoDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject)
 {
   // Set the script global object on the superclass before doing
   // anything that might require it....
   MediaDocument::SetScriptGlobalObject(aScriptGlobalObject);
 
   if (aScriptGlobalObject) {
-    if (!GetRootElement()) {
-      // Create synthetic document
-#ifdef DEBUG
-      nsresult rv =
-#endif
-        CreateSyntheticVideoDocument();
-      NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create synthetic video document");
-    }
-
     if (!nsContentUtils::IsChildOfSameType(this) &&
         GetReadyStateEnum() != nsIDocument::READYSTATE_COMPLETE) {
       LinkStylesheet(NS_LITERAL_STRING("resource://gre/res/TopLevelVideoDocument.css"));
       LinkStylesheet(NS_LITERAL_STRING("chrome://global/skin/media/TopLevelVideoDocument.css"));
       LinkScript(NS_LITERAL_STRING("chrome://global/content/TopLevelVideoDocument.js"));
     }
     BecomeInteractive();
   }
 }
 
 nsresult
-VideoDocument::CreateSyntheticVideoDocument()
+VideoDocument::CreateSyntheticVideoDocument(nsIChannel* aChannel,
+                                            nsIStreamListener** aListener)
 {
   // make our generic document
   nsresult rv = MediaDocument::CreateSyntheticDocument();
   NS_ENSURE_SUCCESS(rv, rv);
 
   Element* body = GetBodyElement();
   if (!body) {
     NS_WARNING("no body on video document!");
@@ -105,19 +104,18 @@ VideoDocument::CreateSyntheticVideoDocum
 
   RefPtr<HTMLMediaElement> element =
     static_cast<HTMLMediaElement*>(NS_NewHTMLVideoElement(nodeInfo.forget(),
                                                           NOT_FROM_PARSER));
   if (!element)
     return NS_ERROR_OUT_OF_MEMORY;
   element->SetAutoplay(true);
   element->SetControls(true);
-  element->LoadWithChannel(mChannel,
-                           getter_AddRefs(mStreamListener->mNextStream));
-  UpdateTitle(mChannel);
+  element->LoadWithChannel(aChannel, aListener);
+  UpdateTitle(aChannel);
 
   if (nsContentUtils::IsChildOfSameType(this)) {
     // Video documents that aren't toplevel should fill their frames and
     // not have margins
     element->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
         NS_LITERAL_STRING("position:absolute; top:0; left:0; width:100%; height:100%"),
         true);
   }
--- a/dom/html/moz.build
+++ b/dom/html/moz.build
@@ -53,17 +53,16 @@ EXPORTS.mozilla.dom += [
     'HTMLBRElement.h',
     'HTMLButtonElement.h',
     'HTMLCanvasElement.h',
     'HTMLContentElement.h',
     'HTMLDataElement.h',
     'HTMLDataListElement.h',
     'HTMLDetailsElement.h',
     'HTMLDivElement.h',
-    'HTMLExtAppElement.h',
     'HTMLFieldSetElement.h',
     'HTMLFontElement.h',
     'HTMLFormControlsCollection.h',
     'HTMLFormElement.h',
     'HTMLFrameElement.h',
     'HTMLFrameSetElement.h',
     'HTMLHeadingElement.h',
     'HTMLHRElement.h',
@@ -133,17 +132,16 @@ UNIFIED_SOURCES += [
     'HTMLButtonElement.cpp',
     'HTMLCanvasElement.cpp',
     'HTMLContentElement.cpp',
     'HTMLDataElement.cpp',
     'HTMLDataListElement.cpp',
     'HTMLDetailsElement.cpp',
     'HTMLDivElement.cpp',
     'HTMLElement.cpp',
-    'HTMLExtAppElement.cpp',
     'HTMLFieldSetElement.cpp',
     'HTMLFontElement.cpp',
     'HTMLFormControlsCollection.cpp',
     'HTMLFormElement.cpp',
     'HTMLFrameElement.cpp',
     'HTMLFrameSetElement.cpp',
     'HTMLHeadingElement.cpp',
     'HTMLHRElement.cpp',
--- a/dom/html/nsFormSubmission.cpp
+++ b/dom/html/nsFormSubmission.cpp
@@ -35,17 +35,19 @@
 #include "nsCExternalHandlerService.h"
 #include "nsIFileStreams.h"
 #include "nsContentUtils.h"
 
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/dom/File.h"
 
 using namespace mozilla;
+using mozilla::dom::Blob;
 using mozilla::dom::EncodingUtils;
+using mozilla::dom::File;
 
 static void
 SendJSWarning(nsIDocument* aDocument,
               const char* aWarningName,
               const char16_t** aWarningArgs, uint32_t aWarningArgsLen)
 {
   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                   NS_LITERAL_CSTRING("HTML"), aDocument,
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -1767,17 +1767,16 @@ NS_DECLARE_NS_NEW_HTML_ELEMENT(Body)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Button)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Canvas)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Content)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Mod)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Data)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(DataList)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Details)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Div)
-NS_DECLARE_NS_NEW_HTML_ELEMENT(ExtApp)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(FieldSet)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Font)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Form)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Frame)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(FrameSet)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(HR)
 NS_DECLARE_NS_NEW_HTML_ELEMENT_AS_SHARED(Head)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Heading)
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -594,17 +594,16 @@ skip-if = (buildapp == 'b2g' && toolkit 
 support-files = file_bug871161-1.html file_bug871161-2.html
 [test_bug1013316.html]
 [test_hash_encoded.html]
 [test_bug1081037.html]
 [test_window_open_close.html]
 skip-if = buildapp == 'b2g' || (e10s && debug && os == 'win') # bug 1129014
 [test_img_complete.html]
 [test_viewport_resize.html]
-[test_extapp.html]
 [test_image_clone_load.html]
 [test_bug1203668.html]
 [test_bug1166138.html]
 [test_bug1230665.html]
 [test_filepicker_default_directory.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
 [test_bug1233598.html]
 [test_bug1250401.html]
deleted file mode 100644
--- a/dom/html/test/test_extapp.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Test for extapp element being HTMLUnknownElement when permission is not available</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id="log"></div>
-<script>
-test(function() {
-  assert_true(document.createElement("extapp") instanceof HTMLUnknownElement);
-}, "extapp should be HTMLUnknownElement when external-app permission is not enabled.");
-</script>
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -102,16 +102,18 @@ MediaLoadInvalidURI=Invalid URI. Load of
 # LOCALIZATION NOTE: %1$S is the media resource's format/codec type (basically equivalent to the file type, e.g. MP4,AVI,WMV,MOV etc), %2$S is the URL of the media resource which failed to load.
 MediaLoadUnsupportedTypeAttribute=Specified "type" attribute of "%1$S" is not supported. Load of media resource %2$S failed.
 # LOCALIZATION NOTE: %1$S is the "media" attribute value of the <source> element. It is a media query. %2$S is the URL of the media resource which failed to load.
 MediaLoadSourceMediaNotMatched=Specified "media" attribute of "%1$S" does not match the environment. Load of media resource %2$S failed.
 # LOCALIZATION NOTE: %1$S is the MIME type HTTP header being sent by the web server, %2$S is the URL of the media resource which failed to load.
 MediaLoadUnsupportedMimeType=HTTP "Content-Type" of "%1$S" is not supported. Load of media resource %2$S failed.
 # LOCALIZATION NOTE: %S is the URL of the media resource which failed to load because of error in decoding.
 MediaLoadDecodeError=Media resource %S could not be decoded.
+# LOCALIZATION NOTE: Do not translate "MediaRecorder".
+MediaRecorderMultiTracksNotSupported=MediaRecorder does not support recording multiple tracks of the same type at this time.
 # LOCALIZATION NOTE: %S is the ID of the MediaStreamTrack passed to MediaStream.addTrack(). Do not translate "MediaStreamTrack" and "AudioChannel".
 MediaStreamAddTrackDifferentAudioChannel=MediaStreamTrack %S could not be added since it belongs to a different AudioChannel.
 # LOCALIZATION NOTE: Do not translate "MediaStream", "stop()" and "MediaStreamTrack"
 MediaStreamStopDeprecatedWarning=MediaStream.stop() is deprecated and will soon be removed. Use MediaStreamTrack.stop() instead.
 # LOCALIZATION NOTE: Do not translate "DOMException", "code" and "name"
 DOMExceptionCodeWarning=Use of DOMException's code attribute is deprecated. Use name instead.
 # LOCALIZATION NOTE: Do not translate "__exposedProps__"
 NoExposedPropsWarning=Exposing chrome JS objects to content without __exposedProps__ is insecure and deprecated. See https://developer.mozilla.org/en/XPConnect_wrappers for more information.
--- a/dom/media/AudioCaptureStream.cpp
+++ b/dom/media/AudioCaptureStream.cpp
@@ -25,52 +25,79 @@ using namespace mozilla::gfx;
 
 namespace mozilla
 {
 
 // We are mixing to mono until PeerConnection can accept stereo
 static const uint32_t MONO = 1;
 
 AudioCaptureStream::AudioCaptureStream(DOMMediaStream* aWrapper, TrackID aTrackId)
-  : ProcessedMediaStream(aWrapper), mTrackId(aTrackId), mTrackCreated(false)
+  : ProcessedMediaStream(aWrapper), mTrackId(aTrackId), mStarted(false), mTrackCreated(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_CTOR(AudioCaptureStream);
   mMixer.AddCallback(this);
 }
 
 AudioCaptureStream::~AudioCaptureStream()
 {
   MOZ_COUNT_DTOR(AudioCaptureStream);
   mMixer.RemoveCallback(this);
 }
 
 void
+AudioCaptureStream::Start()
+{
+  class Message : public ControlMessage {
+  public:
+    explicit Message(AudioCaptureStream* aStream)
+      : ControlMessage(aStream), mStream(aStream) {}
+
+    virtual void Run()
+    {
+      mStream->mStarted = true;
+    }
+
+  protected:
+    AudioCaptureStream* mStream;
+  };
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this));
+}
+
+void
 AudioCaptureStream::ProcessInput(GraphTime aFrom, GraphTime aTo,
                                  uint32_t aFlags)
 {
+  if (!mStarted) {
+    return;
+  }
+
   uint32_t inputCount = mInputs.Length();
   StreamBuffer::Track* track = EnsureTrack(mTrackId);
   // Notify the DOM everything is in order.
   if (!mTrackCreated) {
     for (uint32_t i = 0; i < mListeners.Length(); i++) {
       MediaStreamListener* l = mListeners[i];
       AudioSegment tmp;
       l->NotifyQueuedTrackChanges(
         Graph(), mTrackId, 0, MediaStreamListener::TRACK_EVENT_CREATED, tmp);
       l->NotifyFinishedTrackCreation(Graph());
     }
     mTrackCreated = true;
   }
 
+  if (IsFinishedOnGraphThread()) {
+    return;
+  }
+
   // If the captured stream is connected back to a object on the page (be it an
   // HTMLMediaElement with a stream as source, or an AudioContext), a cycle
   // situation occur. This can work if it's an AudioContext with at least one
   // DelayNode, but the MSG will mute the whole cycle otherwise.
-  if (mFinished || InMutedCycle() || inputCount == 0) {
+  if (InMutedCycle() || inputCount == 0) {
     track->Get<AudioSegment>()->AppendNullData(aTo - aFrom);
   } else {
     // We mix down all the tracks of all inputs, to a stereo track. Everything
     // is {up,down}-mixed to stereo.
     mMixer.StartMixing();
     AudioSegment output;
     for (uint32_t i = 0; i < inputCount; i++) {
       MediaStream* s = mInputs[i]->GetSource();
--- a/dom/media/AudioCaptureStream.h
+++ b/dom/media/AudioCaptureStream.h
@@ -21,21 +21,24 @@ class DOMMediaStream;
  */
 class AudioCaptureStream : public ProcessedMediaStream,
                            public MixerCallbackReceiver
 {
 public:
   explicit AudioCaptureStream(DOMMediaStream* aWrapper, TrackID aTrackId);
   virtual ~AudioCaptureStream();
 
+  void Start();
+
   void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
 
 protected:
   void MixerCallback(AudioDataValue* aMixedBuffer, AudioSampleFormat aFormat,
                      uint32_t aChannels, uint32_t aFrames,
                      uint32_t aSampleRate) override;
   AudioMixer mMixer;
   TrackID mTrackId;
+  bool mStarted;
   bool mTrackCreated;
 };
 }
 
 #endif /* MOZILLA_AUDIOCAPTURESTREAM_H_ */
--- a/dom/media/AudioSegment.h
+++ b/dom/media/AudioSegment.h
@@ -140,16 +140,18 @@ DownmixAndInterleave(const nsTArray<cons
  * of the buffer. An AudioChunk maintains its own duration and channel data
  * pointers so it can represent a subinterval of a buffer without copying.
  * An AudioChunk can store its individual channels anywhere; it maintains
  * separate pointers to each channel's buffer.
  */
 struct AudioChunk {
   typedef mozilla::AudioSampleFormat SampleFormat;
 
+  AudioChunk() : mPrincipalHandle(PRINCIPAL_HANDLE_NONE) {}
+
   // Generic methods
   void SliceTo(StreamTime aStart, StreamTime aEnd)
   {
     MOZ_ASSERT(aStart >= 0 && aStart < aEnd && aEnd <= mDuration,
                "Slice out of bounds");
     if (mBuffer) {
       MOZ_ASSERT(aStart < INT32_MAX, "Can't slice beyond 32-bit sample lengths");
       for (uint32_t channel = 0; channel < mChannelData.Length(); ++channel) {
@@ -185,16 +187,17 @@ struct AudioChunk {
   bool IsNull() const { return mBuffer == nullptr; }
   void SetNull(StreamTime aDuration)
   {
     mBuffer = nullptr;
     mChannelData.Clear();
     mDuration = aDuration;
     mVolume = 1.0f;
     mBufferFormat = AUDIO_FORMAT_SILENCE;
+    mPrincipalHandle = PRINCIPAL_HANDLE_NONE;
   }
 
   size_t ChannelCount() const { return mChannelData.Length(); }
 
   bool IsMuted() const { return mVolume == 0.0f; }
 
   size_t SizeOfExcludingThisIfUnshared(MallocSizeOf aMallocSizeOf) const
   {
@@ -219,24 +222,29 @@ struct AudioChunk {
 
   template<typename T>
   const nsTArray<const T*>& ChannelData()
   {
     MOZ_ASSERT(AudioSampleTypeToFormat<T>::Format == mBufferFormat);
     return *reinterpret_cast<nsTArray<const T*>*>(&mChannelData);
   }
 
+  PrincipalHandle GetPrincipalHandle() const { return mPrincipalHandle; }
+
   StreamTime mDuration; // in frames within the buffer
   RefPtr<ThreadSharedObject> mBuffer; // the buffer object whose lifetime is managed; null means data is all zeroes
   nsTArray<const void*> mChannelData; // one pointer per channel; empty if and only if mBuffer is null
   float mVolume; // volume multiplier to apply (1.0f if mBuffer is nonnull)
   SampleFormat mBufferFormat; // format of frames in mBuffer (only meaningful if mBuffer is nonnull)
 #ifdef MOZILLA_INTERNAL_API
   mozilla::TimeStamp mTimeStamp;           // time at which this has been fetched from the MediaEngine
 #endif
+  // principalHandle for the data in this chunk.
+  // This can be compared to an nsIPrincipal* when back on main thread.
+  PrincipalHandle mPrincipalHandle;
 };
 
 /**
  * A list of audio samples consisting of a sequence of slices of SharedBuffers.
  * The audio rate is determined by the track, not stored in this class.
  */
 class AudioSegment : public MediaSegmentBase<AudioSegment, AudioChunk> {
 public:
@@ -296,56 +304,59 @@ public:
   }
 
   void ResampleChunks(SpeexResamplerState* aResampler,
                       uint32_t aInRate,
                       uint32_t aOutRate);
 
   void AppendFrames(already_AddRefed<ThreadSharedObject> aBuffer,
                     const nsTArray<const float*>& aChannelData,
-                    int32_t aDuration)
+                    int32_t aDuration, const PrincipalHandle& aPrincipalHandle)
   {
     AudioChunk* chunk = AppendChunk(aDuration);
     chunk->mBuffer = aBuffer;
     for (uint32_t channel = 0; channel < aChannelData.Length(); ++channel) {
       chunk->mChannelData.AppendElement(aChannelData[channel]);
     }
     chunk->mVolume = 1.0f;
     chunk->mBufferFormat = AUDIO_FORMAT_FLOAT32;
 #ifdef MOZILLA_INTERNAL_API
     chunk->mTimeStamp = TimeStamp::Now();
 #endif
+    chunk->mPrincipalHandle = aPrincipalHandle;
   }
   void AppendFrames(already_AddRefed<ThreadSharedObject> aBuffer,
                     const nsTArray<const int16_t*>& aChannelData,
-                    int32_t aDuration)
+                    int32_t aDuration, const PrincipalHandle& aPrincipalHandle)
   {
     AudioChunk* chunk = AppendChunk(aDuration);
     chunk->mBuffer = aBuffer;
     for (uint32_t channel = 0; channel < aChannelData.Length(); ++channel) {
       chunk->mChannelData.AppendElement(aChannelData[channel]);
     }
     chunk->mVolume = 1.0f;
     chunk->mBufferFormat = AUDIO_FORMAT_S16;
 #ifdef MOZILLA_INTERNAL_API
     chunk->mTimeStamp = TimeStamp::Now();
 #endif
+    chunk->mPrincipalHandle = aPrincipalHandle;
   }
   // Consumes aChunk, and returns a pointer to the persistent copy of aChunk
   // in the segment.
   AudioChunk* AppendAndConsumeChunk(AudioChunk* aChunk)
   {
     AudioChunk* chunk = AppendChunk(aChunk->mDuration);
     chunk->mBuffer = aChunk->mBuffer.forget();
     chunk->mChannelData.SwapElements(aChunk->mChannelData);
     chunk->mVolume = aChunk->mVolume;
     chunk->mBufferFormat = aChunk->mBufferFormat;
 #ifdef MOZILLA_INTERNAL_API
     chunk->mTimeStamp = TimeStamp::Now();
 #endif
+    chunk->mPrincipalHandle = aChunk->mPrincipalHandle;
     return chunk;
   }
   void ApplyVolume(float aVolume);
   // Mix the segment into a mixer, interleaved. This is useful to output a
   // segment to a system audio callback. It up or down mixes to aChannelCount
   // channels.
   void WriteTo(uint64_t aID, AudioMixer& aMixer, uint32_t aChannelCount,
                uint32_t aSampleRate);
--- a/dom/media/AudioStreamTrack.h
+++ b/dom/media/AudioStreamTrack.h
@@ -9,23 +9,37 @@
 #include "MediaStreamTrack.h"
 #include "DOMMediaStream.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioStreamTrack : public MediaStreamTrack {
 public:
-  AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
-    : MediaStreamTrack(aStream, aTrackID, aLabel) {}
+  AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
+                   TrackID aInputTrackID,
+                   MediaStreamTrackSource* aSource)
+    : MediaStreamTrack(aStream, aTrackID, aInputTrackID, aSource) {}
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   AudioStreamTrack* AsAudioStreamTrack() override { return this; }
 
+  const AudioStreamTrack* AsAudioStreamTrack() const override { return this; }
+
   // WebIDL
   void GetKind(nsAString& aKind) override { aKind.AssignLiteral("audio"); }
+
+protected:
+  already_AddRefed<MediaStreamTrack> CloneInternal(DOMMediaStream* aOwningStream,
+                                                   TrackID aTrackID) override
+  {
+    return do_AddRef(new AudioStreamTrack(aOwningStream,
+                                          aTrackID,
+                                          mInputTrackID,
+                                          mSource));
+  }
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* AUDIOSTREAMTRACK_H_ */
--- a/dom/media/CanvasCaptureMediaStream.cpp
+++ b/dom/media/CanvasCaptureMediaStream.cpp
@@ -19,20 +19,22 @@ using namespace mozilla::gfx;
 namespace mozilla {
 namespace dom {
 
 class OutputStreamDriver::StreamListener : public MediaStreamListener
 {
 public:
   explicit StreamListener(OutputStreamDriver* aDriver,
                           TrackID aTrackId,
+                          PrincipalHandle aPrincipalHandle,
                           SourceMediaStream* aSourceStream)
     : mEnded(false)
     , mSourceStream(aSourceStream)
     , mTrackId(aTrackId)
+    , mPrincipalHandle(aPrincipalHandle)
     , mMutex("CanvasCaptureMediaStream OutputStreamDriver::StreamListener")
     , mImage(nullptr)
   {
     MOZ_ASSERT(mSourceStream);
   }
 
   void EndStream() {
     mEnded = true;
@@ -50,44 +52,47 @@ public:
     StreamTime delta = aDesiredTime - mSourceStream->GetEndOfAppendedData(mTrackId);
     if (delta > 0) {
       MutexAutoLock lock(mMutex);
       MOZ_ASSERT(mSourceStream);
 
       RefPtr<Image> image = mImage;
       IntSize size = image ? image->GetSize() : IntSize(0, 0);
       VideoSegment segment;
-      segment.AppendFrame(image.forget(), delta, size);
+      segment.AppendFrame(image.forget(), delta, size, mPrincipalHandle);
 
       mSourceStream->AppendToTrack(mTrackId, &segment);
     }
 
     if (mEnded) {
       mSourceStream->EndAllTrackAndFinish();
     }
   }
 
 protected:
   ~StreamListener() { }
 
 private:
   Atomic<bool> mEnded;
   const RefPtr<SourceMediaStream> mSourceStream;
   const TrackID mTrackId;
+  const PrincipalHandle mPrincipalHandle;
 
   Mutex mMutex;
   // The below members are protected by mMutex.
   RefPtr<layers::Image> mImage;
 };
 
 OutputStreamDriver::OutputStreamDriver(SourceMediaStream* aSourceStream,
-                                       const TrackID& aTrackId)
+                                       const TrackID& aTrackId,
+                                       const PrincipalHandle& aPrincipalHandle)
   : FrameCaptureListener()
   , mSourceStream(aSourceStream)
-  , mStreamListener(new StreamListener(this, aTrackId, aSourceStream))
+  , mStreamListener(new StreamListener(this, aTrackId, aPrincipalHandle,
+                                       aSourceStream))
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mSourceStream);
   mSourceStream->AddListener(mStreamListener);
   mSourceStream->AddTrack(aTrackId, 0, new VideoSegment());
   mSourceStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
   mSourceStream->SetPullEnabled(true);
 
@@ -115,18 +120,19 @@ OutputStreamDriver::SetImage(const RefPt
 
 // ----------------------------------------------------------------------
 
 class TimerDriver : public OutputStreamDriver
 {
 public:
   explicit TimerDriver(SourceMediaStream* aSourceStream,
                        const double& aFPS,
-                       const TrackID& aTrackId)
-    : OutputStreamDriver(aSourceStream, aTrackId)
+                       const TrackID& aTrackId,
+                       const PrincipalHandle& aPrincipalHandle)
+    : OutputStreamDriver(aSourceStream, aTrackId, aPrincipalHandle)
     , mFPS(aFPS)
     , mTimer(nullptr)
   {
     if (mFPS == 0.0) {
       return;
     }
 
     mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
@@ -173,18 +179,19 @@ private:
 };
 
 // ----------------------------------------------------------------------
 
 class AutoDriver : public OutputStreamDriver
 {
 public:
   explicit AutoDriver(SourceMediaStream* aSourceStream,
-                      const TrackID& aTrackId)
-    : OutputStreamDriver(aSourceStream, aTrackId) {}
+                      const TrackID& aTrackId,
+                      const PrincipalHandle& aPrincipalHandle)
+    : OutputStreamDriver(aSourceStream, aTrackId, aPrincipalHandle) {}
 
   void NewFrame(already_AddRefed<Image> aImage) override
   {
     // Don't reset `mFrameCaptureRequested` since AutoDriver shall always have
     // `mFrameCaptureRequested` set to true.
     // This also means we should accept every frame as NewFrame is called only
     // after something changed.
 
@@ -202,18 +209,20 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(Canva
                                    mCanvas)
 
 NS_IMPL_ADDREF_INHERITED(CanvasCaptureMediaStream, DOMMediaStream)
 NS_IMPL_RELEASE_INHERITED(CanvasCaptureMediaStream, DOMMediaStream)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(CanvasCaptureMediaStream)
 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
 
-CanvasCaptureMediaStream::CanvasCaptureMediaStream(HTMLCanvasElement* aCanvas)
-  : mCanvas(aCanvas)
+CanvasCaptureMediaStream::CanvasCaptureMediaStream(nsPIDOMWindowInner* aWindow,
+                                                   HTMLCanvasElement* aCanvas)
+  : DOMMediaStream(aWindow, nullptr)
+  , mCanvas(aCanvas)
   , mOutputStreamDriver(nullptr)
 {
 }
 
 CanvasCaptureMediaStream::~CanvasCaptureMediaStream()
 {
   if (mOutputStreamDriver) {
     mOutputStreamDriver->Forget();
@@ -232,41 +241,44 @@ CanvasCaptureMediaStream::RequestFrame()
   MOZ_ASSERT(mOutputStreamDriver);
   if (mOutputStreamDriver) {
     mOutputStreamDriver->RequestFrameCapture();
   }
 }
 
 nsresult
 CanvasCaptureMediaStream::Init(const dom::Optional<double>& aFPS,
-                               const TrackID& aTrackId)
+                               const TrackID& aTrackId,
+                               nsIPrincipal* aPrincipal)
 {
+  PrincipalHandle principalHandle = MakePrincipalHandle(aPrincipal);
+
   if (!aFPS.WasPassed()) {
     mOutputStreamDriver =
-      new AutoDriver(GetInputStream()->AsSourceStream(), aTrackId);
+      new AutoDriver(GetInputStream()->AsSourceStream(), aTrackId, principalHandle);
   } else if (aFPS.Value() < 0) {
     return NS_ERROR_ILLEGAL_VALUE;
   } else {
     // Cap frame rate to 60 FPS for sanity
     double fps = std::min(60.0, aFPS.Value());
     mOutputStreamDriver =
-      new TimerDriver(GetInputStream()->AsSourceStream(), fps, aTrackId);
+      new TimerDriver(GetInputStream()->AsSourceStream(), fps, aTrackId, principalHandle);
   }
   return NS_OK;
 }
 
 already_AddRefed<CanvasCaptureMediaStream>
 CanvasCaptureMediaStream::CreateSourceStream(nsPIDOMWindowInner* aWindow,
                                              HTMLCanvasElement* aCanvas)
 {
-  RefPtr<CanvasCaptureMediaStream> stream = new CanvasCaptureMediaStream(aCanvas);
+  RefPtr<CanvasCaptureMediaStream> stream = new CanvasCaptureMediaStream(aWindow, aCanvas);
   MediaStreamGraph* graph =
     MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER,
                                   AudioChannel::Normal);
-  stream->InitSourceStream(aWindow, graph);
+  stream->InitSourceStream(graph);
   return stream.forget();
 }
 
 FrameCaptureListener*
 CanvasCaptureMediaStream::FrameCaptureListener()
 {
   return mOutputStreamDriver;
 }
--- a/dom/media/CanvasCaptureMediaStream.h
+++ b/dom/media/CanvasCaptureMediaStream.h
@@ -5,16 +5,18 @@
 
 #ifndef mozilla_dom_CanvasCaptureMediaStream_h_
 #define mozilla_dom_CanvasCaptureMediaStream_h_
 
 #include "DOMMediaStream.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "StreamBuffer.h"
 
+class nsIPrincipal;
+
 namespace mozilla {
 class DOMMediaStream;
 class MediaStreamListener;
 class SourceMediaStream;
 
 namespace layers {
 class Image;
 } // namespace layers
@@ -63,17 +65,18 @@ class OutputStreamFrameListener;
  * Base class for drivers of the output stream.
  * It is up to each sub class to implement the NewFrame() callback of
  * FrameCaptureListener.
  */
 class OutputStreamDriver : public FrameCaptureListener
 {
 public:
   OutputStreamDriver(SourceMediaStream* aSourceStream,
-                     const TrackID& aTrackId);
+                     const TrackID& aTrackId,
+                     const PrincipalHandle& aPrincipalHandle);
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OutputStreamDriver);
 
   /*
    * Sub classes can SetImage() to update the image being appended to the
    * output stream. It will be appended on the next NotifyPull from MSG.
    */
   void SetImage(const RefPtr<layers::Image>& aImage);
@@ -91,22 +94,23 @@ protected:
 private:
   RefPtr<SourceMediaStream> mSourceStream;
   RefPtr<StreamListener> mStreamListener;
 };
 
 class CanvasCaptureMediaStream : public DOMMediaStream
 {
 public:
-  explicit CanvasCaptureMediaStream(HTMLCanvasElement* aCanvas);
+  CanvasCaptureMediaStream(nsPIDOMWindowInner* aWindow, HTMLCanvasElement* aCanvas);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CanvasCaptureMediaStream, DOMMediaStream)
 
-  nsresult Init(const dom::Optional<double>& aFPS, const TrackID& aTrackId);
+  nsresult Init(const dom::Optional<double>& aFPS, const TrackID& aTrackId,
+                nsIPrincipal* aPrincipal);
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL
   HTMLCanvasElement* Canvas() const { return mCanvas; }
   void RequestFrame();
   dom::FrameCaptureListener* FrameCaptureListener();
 
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -13,37 +13,43 @@
 #include "mozilla/dom/LocalMediaStreamBinding.h"
 #include "mozilla/dom/AudioNode.h"
 #include "AudioChannelAgent.h"
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
-#include "mozilla/dom/MediaStreamError.h"
-#include "mozilla/dom/Promise.h"
+#include "mozilla/media/MediaUtils.h"
 #include "MediaStreamGraph.h"
 #include "AudioStreamTrack.h"
 #include "VideoStreamTrack.h"
 #include "Layers.h"
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with NS_DECL_NSIDOMMEDIASTREAM, containing
 // currentTime getter.
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 
 #ifdef LOG
 #undef LOG
 #endif
 
+// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
+// GetTickCount() and conflicts with MediaStream::GetCurrentTime.
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
+using namespace mozilla::media;
 
 static LazyLogModule gMediaStreamLog("MediaStream");
 #define LOG(type, msg) MOZ_LOG(gMediaStreamLog, type, msg)
 
 const TrackID TRACK_VIDEO_PRIMARY = 1;
 
 
 DOMMediaStream::TrackPort::TrackPort(MediaInputPort* aInputPort,
@@ -87,97 +93,119 @@ DOMMediaStream::TrackPort::GetSource() c
 }
 
 TrackID
 DOMMediaStream::TrackPort::GetSourceTrackId() const
 {
   return mInputPort ? mInputPort->GetSourceTrackId() : TRACK_INVALID;
 }
 
-void
+already_AddRefed<Pledge<bool>>
 DOMMediaStream::TrackPort::BlockTrackId(TrackID aTrackId)
 {
   if (mInputPort) {
-    mInputPort->BlockTrackId(aTrackId);
+    return mInputPort->BlockTrackId(aTrackId);
   }
+  RefPtr<Pledge<bool>> rejected = new Pledge<bool>();
+  rejected->Reject(NS_ERROR_FAILURE);
+  return rejected.forget();
 }
 
 NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackPort, mTrack)
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::TrackPort, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::TrackPort, Release)
 
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackSourceGetter)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackSourceGetter)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackSourceGetter)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTrackSourceGetter)
 
 /**
  * Listener registered on the Owned stream to detect added and ended owned
  * tracks for keeping the list of MediaStreamTracks in sync with the tracks
  * added and ended directly at the source.
  */
 class DOMMediaStream::OwnedStreamListener : public MediaStreamListener {
 public:
   explicit OwnedStreamListener(DOMMediaStream* aStream)
     : mStream(aStream)
   {}
 
   void Forget() { mStream = nullptr; }
 
-  void DoNotifyTrackCreated(TrackID aTrackId, MediaSegment::Type aType)
+  void DoNotifyTrackCreated(TrackID aTrackID, MediaSegment::Type aType,
+                            MediaStream* aInputStream, TrackID aInputTrackID)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mStream) {
       return;
     }
 
-    MediaStreamTrack* track = mStream->FindOwnedDOMTrack(
-      mStream->GetOwnedStream(), aTrackId);
-    if (track) {
-      // This track has already been manually created. Abort.
-      return;
+    MediaStreamTrack* track =
+      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID);
+    if (!track) {
+      // Track had not been created on main thread before, create it now.
+      NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(),
+                       "A new track was detected on the input stream; creating "
+                       "a corresponding MediaStreamTrack. Initial tracks "
+                       "should be added manually to immediately and "
+                       "synchronously be available to JS.");
+      RefPtr<MediaStreamTrackSource> source;
+      if (mStream->mTrackSourceGetter) {
+        source = mStream->mTrackSourceGetter->GetMediaStreamTrackSource(aTrackID);
+      }
+      if (!source) {
+        NS_ASSERTION(false, "Dynamic track created without an explicit TrackSource");
+        nsPIDOMWindowInner* window = mStream->GetParentObject();
+        nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
+        nsIPrincipal* principal = doc ? doc->NodePrincipal() : nullptr;
+        source = new BasicUnstoppableTrackSource(principal);
+      }
+      track = mStream->CreateDOMTrack(aTrackID, aType, source);
     }
-
-    NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(),
-                     "A new track was detected on the input stream; creating a corresponding MediaStreamTrack. "
-                     "Initial tracks should be added manually to immediately and synchronously be available to JS.");
-    mStream->CreateOwnDOMTrack(aTrackId, aType, nsString());
   }
 
-  void DoNotifyTrackEnded(TrackID aTrackId)
+  void DoNotifyTrackEnded(MediaStream* aInputStream, TrackID aInputTrackID)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mStream) {
       return;
     }
 
     RefPtr<MediaStreamTrack> track =
-      mStream->FindOwnedDOMTrack(mStream->GetOwnedStream(), aTrackId);
+      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID);
     NS_ASSERTION(track, "Owned MediaStreamTracks must be known by the DOMMediaStream");
     if (track) {
       LOG(LogLevel::Debug, ("DOMMediaStream %p MediaStreamTrack %p ended at the source. Marking it ended.",
                             mStream, track.get()));
       track->NotifyEnded();
     }
   }
 
   void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                 StreamTime aTrackOffset, uint32_t aTrackEvents,
                                 const MediaSegment& aQueuedMedia,
                                 MediaStream* aInputStream,
                                 TrackID aInputTrackID) override
   {
     if (aTrackEvents & TRACK_EVENT_CREATED) {
       nsCOMPtr<nsIRunnable> runnable =
-        NS_NewRunnableMethodWithArgs<TrackID, MediaSegment::Type>(
+        NS_NewRunnableMethodWithArgs<TrackID, MediaSegment::Type, MediaStream*, TrackID>(
           this, &OwnedStreamListener::DoNotifyTrackCreated,
-          aID, aQueuedMedia.GetType());
+          aID, aQueuedMedia.GetType(), aInputStream, aInputTrackID);
       aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
     } else if (aTrackEvents & TRACK_EVENT_ENDED) {
       nsCOMPtr<nsIRunnable> runnable =
-        NS_NewRunnableMethodWithArgs<TrackID>(
-          this, &OwnedStreamListener::DoNotifyTrackEnded, aID);
+        NS_NewRunnableMethodWithArgs<MediaStream*, TrackID>(
+          this, &OwnedStreamListener::DoNotifyTrackEnded,
+          aInputStream, aInputTrackID);
       aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
     }
   }
 
 private:
   // These fields may only be accessed on the main thread
   DOMMediaStream* mStream;
 };
@@ -219,20 +247,17 @@ public:
     }
 
     LOG(LogLevel::Debug, ("DOMMediaStream %p Playback track; notifying stream listeners.",
                            mStream));
     mStream->NotifyTrackRemoved(track);
 
     RefPtr<TrackPort> endedPort = mStream->FindPlaybackTrackPort(*track);
     NS_ASSERTION(endedPort, "Playback track should have a TrackPort");
-    if (endedPort &&
-        endedPort->GetSourceTrackId() != TRACK_ANY &&
-        endedPort->GetSourceTrackId() != TRACK_INVALID &&
-        endedPort->GetSourceTrackId() != TRACK_NONE) {
+    if (endedPort && IsTrackIDExplicit(endedPort->GetSourceTrackId())) {
       // If a track connected to a locked-track input port ends, we destroy the
       // port to allow our playback stream to finish.
       // XXX (bug 1208316) This should not be necessary when MediaStreams don't
       // finish but instead become inactive.
       endedPort->DestroyInputPort();
     }
   }
 
@@ -279,24 +304,30 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaS
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMMediaStream,
                                                 DOMEventTargetHelper)
   tmp->Destroy();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwnedTracks)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTrackSourceGetter)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoPrincipal)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMMediaStream,
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwnedTracks)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTrackSourceGetter)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoPrincipal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(DOMMediaStream, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(DOMMediaStream, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream)
   NS_INTERFACE_MAP_ENTRY(DOMMediaStream)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
@@ -312,21 +343,22 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMAu
                                    mStreamNode)
 
 NS_IMPL_ADDREF_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
 NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream)
 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
 
-DOMMediaStream::DOMMediaStream()
-  : mLogicalStreamStartTime(0), mInputStream(nullptr), mOwnedStream(nullptr),
-    mPlaybackStream(nullptr), mOwnedPort(nullptr), mPlaybackPort(nullptr),
-    mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false),
-    mCORSMode(CORS_NONE)
+DOMMediaStream::DOMMediaStream(nsPIDOMWindowInner* aWindow,
+                               MediaStreamTrackSourceGetter* aTrackSourceGetter)
+  : mLogicalStreamStartTime(0), mWindow(aWindow),
+    mInputStream(nullptr), mOwnedStream(nullptr), mPlaybackStream(nullptr),
+    mTracksPendingRemoval(0), mTrackSourceGetter(aTrackSourceGetter),
+    mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false)
 {
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
 
   if (NS_SUCCEEDED(rv) && uuidgen) {
     nsID uuid;
     memset(&uuid, 0, sizeof(uuid));
@@ -351,34 +383,41 @@ DOMMediaStream::Destroy()
   if (mOwnedListener) {
     mOwnedListener->Forget();
     mOwnedListener = nullptr;
   }
   if (mPlaybackListener) {
     mPlaybackListener->Forget();
     mPlaybackListener = nullptr;
   }
+  for (const RefPtr<TrackPort>& info : mTracks) {
+    // We must remove ourselves from each track's principal change observer list
+    // before we die. CC may have cleared info->mTrack so guard against it.
+    if (info->GetTrack()) {
+      info->GetTrack()->RemovePrincipalChangeObserver(this);
+    }
+  }
   if (mPlaybackPort) {
     mPlaybackPort->Destroy();
     mPlaybackPort = nullptr;
   }
   if (mOwnedPort) {
     mOwnedPort->Destroy();
     mOwnedPort = nullptr;
   }
   if (mPlaybackStream) {
-    mPlaybackStream->Destroy();
+    mPlaybackStream->UnregisterUser();
     mPlaybackStream = nullptr;
   }
   if (mOwnedStream) {
-    mOwnedStream->Destroy();
+    mOwnedStream->UnregisterUser();
     mOwnedStream = nullptr;
   }
   if (mInputStream) {
-    mInputStream->Destroy();
+    mInputStream->UnregisterUser();
     mInputStream = nullptr;
   }
 }
 
 JSObject*
 DOMMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return dom::MediaStreamBinding::Wrap(aCx, this, aGivenProto);
@@ -420,26 +459,24 @@ DOMMediaStream::Constructor(const Global
                             ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
   if (!ownerWindow) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  RefPtr<DOMMediaStream> newStream = new DOMMediaStream();
-  newStream->mWindow = ownerWindow;
+  // Streams created from JS cannot have dynamically created tracks.
+  MediaStreamTrackSourceGetter* getter = nullptr;
+  RefPtr<DOMMediaStream> newStream = new DOMMediaStream(ownerWindow, getter);
 
   for (MediaStreamTrack& track : aTracks) {
     if (!newStream->GetPlaybackStream()) {
-      MOZ_RELEASE_ASSERT(track.GetStream());
-      MOZ_RELEASE_ASSERT(track.GetStream()->GetPlaybackStream());
-      MOZ_RELEASE_ASSERT(track.GetStream()->GetPlaybackStream()->Graph());
-      MediaStreamGraph* graph = track.GetStream()->GetPlaybackStream()->Graph();
-      newStream->InitPlaybackStreamCommon(graph);
+      MOZ_RELEASE_ASSERT(track.Graph());
+      newStream->InitPlaybackStreamCommon(track.Graph());
     }
     newStream->AddTrack(track);
   }
 
   if (!newStream->GetPlaybackStream()) {
     MOZ_ASSERT(aTracks.IsEmpty());
     MediaStreamGraph* graph =
       MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER,
@@ -503,24 +540,22 @@ DOMMediaStream::AddTrack(MediaStreamTrac
 
   RefPtr<ProcessedMediaStream> dest = mPlaybackStream->AsProcessedStream();
   MOZ_ASSERT(dest);
   if (!dest) {
     return;
   }
 
   LOG(LogLevel::Info, ("DOMMediaStream %p Adding track %p (from stream %p with ID %d)",
-                       this, &aTrack, aTrack.GetStream(), aTrack.GetTrackID()));
+                       this, &aTrack, aTrack.mOwningStream.get(), aTrack.mTrackID));
 
-  if (mPlaybackStream->Graph() !=
-      aTrack.GetStream()->mPlaybackStream->Graph()) {
+  if (mPlaybackStream->Graph() != aTrack.Graph()) {
     NS_ASSERTION(false, "Cannot combine tracks from different MediaStreamGraphs");
     LOG(LogLevel::Error, ("DOMMediaStream %p Own MSG %p != aTrack's MSG %p",
-                         this, mPlaybackStream->Graph(),
-                         aTrack.GetStream()->mPlaybackStream->Graph()));
+                         this, mPlaybackStream->Graph(), aTrack.Graph()));
 
     nsAutoString trackId;
     aTrack.GetId(trackId);
     const char16_t* params[] = { trackId.get() };
     nsCOMPtr<nsPIDOMWindowInner> pWindow = GetParentObject();
     nsIDocument* document = pWindow ? pWindow->GetExtantDoc() : nullptr;
     nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
                                     NS_LITERAL_CSTRING("Media"),
@@ -531,352 +566,552 @@ DOMMediaStream::AddTrack(MediaStreamTrac
     return;
   }
 
   if (HasTrack(aTrack)) {
     LOG(LogLevel::Debug, ("DOMMediaStream %p already contains track %p", this, &aTrack));
     return;
   }
 
-  RefPtr<DOMMediaStream> addedDOMStream = aTrack.GetStream();
-  MOZ_RELEASE_ASSERT(addedDOMStream);
-
-  RefPtr<MediaStream> owningStream = addedDOMStream->GetOwnedStream();
-  MOZ_RELEASE_ASSERT(owningStream);
-
-  CombineWithPrincipal(addedDOMStream->mPrincipal);
-
   // Hook up the underlying track with our underlying playback stream.
   RefPtr<MediaInputPort> inputPort =
-    GetPlaybackStream()->AllocateInputPort(owningStream, aTrack.GetTrackID());
+    GetPlaybackStream()->AllocateInputPort(aTrack.GetOwnedStream(),
+                                           aTrack.mTrackID);
   RefPtr<TrackPort> trackPort =
     new TrackPort(inputPort, &aTrack, TrackPort::InputPortOwnership::OWNED);
   mTracks.AppendElement(trackPort.forget());
   NotifyTrackAdded(&aTrack);
 
   LOG(LogLevel::Debug, ("DOMMediaStream %p Added track %p", this, &aTrack));
 }
 
 void
 DOMMediaStream::RemoveTrack(MediaStreamTrack& aTrack)
 {
   LOG(LogLevel::Info, ("DOMMediaStream %p Removing track %p (from stream %p with ID %d)",
-                       this, &aTrack, aTrack.GetStream(), aTrack.GetTrackID()));
+                       this, &aTrack, aTrack.mOwningStream.get(), aTrack.mTrackID));
 
   RefPtr<TrackPort> toRemove = FindPlaybackTrackPort(aTrack);
   if (!toRemove) {
     LOG(LogLevel::Debug, ("DOMMediaStream %p does not contain track %p", this, &aTrack));
     return;
   }
 
   // If the track comes from a TRACK_ANY input port (i.e., mOwnedPort), we need
   // to block it in the port. Doing this for a locked track is still OK as it
   // will first block the track, then destroy the port. Both cause the track to
   // end.
-  toRemove->BlockTrackId(aTrack.GetTrackID());
+  BlockPlaybackTrack(toRemove);
 
   DebugOnly<bool> removed = mTracks.RemoveElement(toRemove);
   MOZ_ASSERT(removed);
+
+  NotifyTrackRemoved(&aTrack);
+
   LOG(LogLevel::Debug, ("DOMMediaStream %p Removed track %p", this, &aTrack));
 }
 
+class ClonedStreamSourceGetter :
+  public MediaStreamTrackSourceGetter
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ClonedStreamSourceGetter,
+                                           MediaStreamTrackSourceGetter)
+
+  explicit ClonedStreamSourceGetter(DOMMediaStream* aStream)
+    : mStream(aStream) {}
+
+  already_AddRefed<MediaStreamTrackSource>
+  GetMediaStreamTrackSource(TrackID aInputTrackID) override
+  {
+    MediaStreamTrack* sourceTrack =
+      mStream->FindOwnedDOMTrack(mStream->GetOwnedStream(), aInputTrackID);
+    MOZ_RELEASE_ASSERT(sourceTrack);
+
+    return do_AddRef(&sourceTrack->GetSource());
+  }
+
+protected:
+  virtual ~ClonedStreamSourceGetter() {}
+
+  RefPtr<DOMMediaStream> mStream;
+};
+
+NS_IMPL_ADDREF_INHERITED(ClonedStreamSourceGetter,
+                         MediaStreamTrackSourceGetter)
+NS_IMPL_RELEASE_INHERITED(ClonedStreamSourceGetter,
+                          MediaStreamTrackSourceGetter)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ClonedStreamSourceGetter)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ClonedStreamSourceGetter,
+                                   MediaStreamTrackSourceGetter,
+                                   mStream)
+
+already_AddRefed<DOMMediaStream>
+DOMMediaStream::Clone()
+{
+  return CloneInternal(TrackForwardingOption::CURRENT);
+}
+
+already_AddRefed<DOMMediaStream>
+DOMMediaStream::CloneInternal(TrackForwardingOption aForwarding)
+{
+  RefPtr<DOMMediaStream> newStream =
+    new DOMMediaStream(GetParentObject(), new ClonedStreamSourceGetter(this));
+
+  LOG(LogLevel::Info, ("DOMMediaStream %p created clone %p, forwarding %s tracks",
+                       this, newStream.get(),
+                       aForwarding == TrackForwardingOption::ALL
+                         ? "all" : "current"));
+
+  MOZ_RELEASE_ASSERT(mPlaybackStream);
+  MOZ_RELEASE_ASSERT(mPlaybackStream->Graph());
+  MediaStreamGraph* graph = mPlaybackStream->Graph();
+
+  // We initiate the owned and playback streams first, since we need to create
+  // all existing DOM tracks before we add the generic input port from
+  // mInputStream to mOwnedStream (see AllocateInputPort wrt. destination
+  // TrackID as to why).
+  newStream->InitOwnedStreamCommon(graph);
+  newStream->InitPlaybackStreamCommon(graph);
+
+  // Set up existing DOM tracks.
+  TrackID allocatedTrackID = 1;
+  for (const RefPtr<TrackPort>& info : mTracks) {
+    MediaStreamTrack& track = *info->GetTrack();
+
+    LOG(LogLevel::Debug, ("DOMMediaStream %p forwarding external track %p to clone %p",
+                          this, &track, newStream.get()));
+    RefPtr<MediaStreamTrack> trackClone =
+      newStream->CloneDOMTrack(track, allocatedTrackID++);
+  }
+
+  if (aForwarding == TrackForwardingOption::ALL) {
+    // Set up an input port from our input stream to the new DOM stream's owned
+    // stream, to allow for dynamically added tracks at the source to appear in
+    // the clone. The clone may treat mInputStream as its own mInputStream but
+    // ownership remains with us.
+    newStream->mInputStream = mInputStream;
+    if (mInputStream) {
+      // We have already set up track-locked input ports for all existing DOM
+      // tracks, so now we need to block those in the generic input port to
+      // avoid ending up with double instances of them.
+      nsTArray<TrackID> tracksToBlock;
+      for (const RefPtr<TrackPort>& info : mOwnedTracks) {
+        tracksToBlock.AppendElement(info->GetTrack()->mTrackID);
+      }
+
+      newStream->mInputStream->RegisterUser();
+      newStream->mOwnedPort =
+        newStream->mOwnedStream->AllocateInputPort(mInputStream,
+                                                   TRACK_ANY, TRACK_ANY, 0, 0,
+                                                   &tracksToBlock);
+    }
+  }
+
+  return newStream.forget();
+}
+
+MediaStreamTrack*
+DOMMediaStream::GetTrackById(const nsString& aId)
+{
+  for (const RefPtr<TrackPort>& info : mTracks) {
+    nsString id;
+    info->GetTrack()->GetId(id);
+    if (id == aId) {
+      return info->GetTrack();
+    }
+  }
+  return nullptr;
+}
+
+MediaStreamTrack*
+DOMMediaStream::GetOwnedTrackById(const nsString& aId)
+{
+  for (const RefPtr<TrackPort>& info : mOwnedTracks) {
+    nsString id;
+    info->GetTrack()->GetId(id);
+    if (id == aId) {
+      return info->GetTrack();
+    }
+  }
+  return nullptr;
+}
+
 bool
 DOMMediaStream::HasTrack(const MediaStreamTrack& aTrack) const
 {
   return !!FindPlaybackTrackPort(aTrack);
 }
 
 bool
 DOMMediaStream::OwnsTrack(const MediaStreamTrack& aTrack) const
 {
-  return (aTrack.GetStream() == this) && HasTrack(aTrack);
+  return !!FindOwnedTrackPort(aTrack);
+}
+
+bool
+DOMMediaStream::AddDirectListener(MediaStreamDirectListener* aListener)
+{
+  if (GetInputStream() && GetInputStream()->AsSourceStream()) {
+    GetInputStream()->AsSourceStream()->AddDirectListener(aListener);
+    return true; // application should ignore NotifyQueuedTrackData
+  }
+  return false;
+}
+
+void
+DOMMediaStream::RemoveDirectListener(MediaStreamDirectListener* aListener)
+{
+  if (GetInputStream() && GetInputStream()->AsSourceStream()) {
+    GetInputStream()->AsSourceStream()->RemoveDirectListener(aListener);
+  }
 }
 
 bool
 DOMMediaStream::IsFinished()
 {
   return !mPlaybackStream || mPlaybackStream->IsFinished();
 }
 
 void
-DOMMediaStream::InitSourceStream(nsPIDOMWindowInner* aWindow,
-                                 MediaStreamGraph* aGraph)
+DOMMediaStream::InitSourceStream(MediaStreamGraph* aGraph)
 {
-  mWindow = aWindow;
   InitInputStreamCommon(aGraph->CreateSourceStream(nullptr), aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
 }
 
 void
-DOMMediaStream::InitTrackUnionStream(nsPIDOMWindowInner* aWindow,
-                                     MediaStreamGraph* aGraph)
+DOMMediaStream::InitTrackUnionStream(MediaStreamGraph* aGraph)
 {
-  mWindow = aWindow;
   InitInputStreamCommon(aGraph->CreateTrackUnionStream(nullptr), aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
 }
 
 void
-DOMMediaStream::InitAudioCaptureStream(nsPIDOMWindowInner* aWindow,
-                                       MediaStreamGraph* aGraph)
+DOMMediaStream::InitAudioCaptureStream(nsIPrincipal* aPrincipal, MediaStreamGraph* aGraph)
 {
-  mWindow = aWindow;
-
   const TrackID AUDIO_TRACK = 1;
 
-  InitInputStreamCommon(aGraph->CreateAudioCaptureStream(this, AUDIO_TRACK), aGraph);
+  RefPtr<BasicUnstoppableTrackSource> audioCaptureSource =
+    new BasicUnstoppableTrackSource(aPrincipal, MediaSourceEnum::AudioCapture);
+
+  AudioCaptureStream* audioCaptureStream =
+    static_cast<AudioCaptureStream*>(aGraph->CreateAudioCaptureStream(this, AUDIO_TRACK));
+  InitInputStreamCommon(audioCaptureStream, aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
-  CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, nsString());
+  CreateDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, audioCaptureSource);
+  audioCaptureStream->Start();
 }
 
 void
 DOMMediaStream::InitInputStreamCommon(MediaStream* aStream,
                                       MediaStreamGraph* aGraph)
 {
   MOZ_ASSERT(!mOwnedStream, "Input stream must be initialized before owned stream");
 
   mInputStream = aStream;
+  mInputStream->RegisterUser();
 }
 
 void
 DOMMediaStream::InitOwnedStreamCommon(MediaStreamGraph* aGraph)
 {
   MOZ_ASSERT(!mPlaybackStream, "Owned stream must be initialized before playback stream");
 
   // We pass null as the wrapper since it is only used to signal finished
   // streams. This is only needed for the playback stream.
   mOwnedStream = aGraph->CreateTrackUnionStream(nullptr);
   mOwnedStream->SetAutofinish(true);
+  mOwnedStream->RegisterUser();
   if (mInputStream) {
     mOwnedPort = mOwnedStream->AllocateInputPort(mInputStream);
   }
 
   // Setup track listeners
   mOwnedListener = new OwnedStreamListener(this);
   mOwnedStream->AddListener(mOwnedListener);
 }
 
 void
 DOMMediaStream::InitPlaybackStreamCommon(MediaStreamGraph* aGraph)
 {
   mPlaybackStream = aGraph->CreateTrackUnionStream(this);
   mPlaybackStream->SetAutofinish(true);
+  mPlaybackStream->RegisterUser();
   if (mOwnedStream) {
     mPlaybackPort = mPlaybackStream->AllocateInputPort(mOwnedStream);
   }
 
   mPlaybackListener = new PlaybackStreamListener(this);
   mPlaybackStream->AddListener(mPlaybackListener);
 
   LOG(LogLevel::Debug, ("DOMMediaStream %p Initiated with mInputStream=%p, mOwnedStream=%p, mPlaybackStream=%p",
                         this, mInputStream, mOwnedStream, mPlaybackStream));
 }
 
 already_AddRefed<DOMMediaStream>
 DOMMediaStream::CreateSourceStream(nsPIDOMWindowInner* aWindow,
-                                   MediaStreamGraph* aGraph)
+                                   MediaStreamGraph* aGraph,
+                                   MediaStreamTrackSourceGetter* aTrackSourceGetter)
 {
-  RefPtr<DOMMediaStream> stream = new DOMMediaStream();
-  stream->InitSourceStream(aWindow, aGraph);
+  RefPtr<DOMMediaStream> stream = new DOMMediaStream(aWindow, aTrackSourceGetter);
+  stream->InitSourceStream(aGraph);
   return stream.forget();
 }
 
 already_AddRefed<DOMMediaStream>
 DOMMediaStream::CreateTrackUnionStream(nsPIDOMWindowInner* aWindow,
-                                       MediaStreamGraph* aGraph)
+                                       MediaStreamGraph* aGraph,
+                                       MediaStreamTrackSourceGetter* aTrackSourceGetter)
 {
-  RefPtr<DOMMediaStream> stream = new DOMMediaStream();
-  stream->InitTrackUnionStream(aWindow, aGraph);
+  RefPtr<DOMMediaStream> stream = new DOMMediaStream(aWindow, aTrackSourceGetter);
+  stream->InitTrackUnionStream(aGraph);
   return stream.forget();
 }
 
 already_AddRefed<DOMMediaStream>
 DOMMediaStream::CreateAudioCaptureStream(nsPIDOMWindowInner* aWindow,
+                                         nsIPrincipal* aPrincipal,
                                          MediaStreamGraph* aGraph)
 {
-  RefPtr<DOMMediaStream> stream = new DOMMediaStream();
-  stream->InitAudioCaptureStream(aWindow, aGraph);
+  // Audio capture doesn't create tracks dynamically
+  MediaStreamTrackSourceGetter* getter = nullptr;
+  RefPtr<DOMMediaStream> stream = new DOMMediaStream(aWindow, getter);
+  stream->InitAudioCaptureStream(aPrincipal, aGraph);
   return stream.forget();
 }
 
 void
-DOMMediaStream::SetTrackEnabled(TrackID aTrackID, bool aEnabled)
+DOMMediaStream::PrincipalChanged(MediaStreamTrack* aTrack)
 {
-  // XXX Bug 1208371 - This enables/disables the track across clones.
-  if (mInputStream) {
-    mInputStream->SetTrackEnabled(aTrackID, aEnabled);
-  }
+  MOZ_ASSERT(aTrack);
+  NS_ASSERTION(HasTrack(*aTrack), "Principal changed for an unknown track");
+  LOG(LogLevel::Info, ("DOMMediaStream %p Principal changed for track %p",
+                       this, aTrack));
+  RecomputePrincipal();
 }
 
 void
-DOMMediaStream::StopTrack(TrackID aTrackID)
-{
-  if (mInputStream && mInputStream->AsSourceStream()) {
-    mInputStream->AsSourceStream()->EndTrack(aTrackID);
-  }
-}
-
-already_AddRefed<Promise>
-DOMMediaStream::ApplyConstraintsToTrack(TrackID aTrackID,
-                                        const MediaTrackConstraints& aConstraints,
-                                        ErrorResult &aRv)
+DOMMediaStream::RecomputePrincipal()
 {
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
-  RefPtr<Promise> promise = Promise::Create(go, aRv);
-  MOZ_RELEASE_ASSERT(!aRv.Failed());
+  nsCOMPtr<nsIPrincipal> previousPrincipal = mPrincipal.forget();
+  nsCOMPtr<nsIPrincipal> previousVideoPrincipal = mVideoPrincipal.forget();
+
+  if (mTracksPendingRemoval > 0) {
+    LOG(LogLevel::Info, ("DOMMediaStream %p RecomputePrincipal() Cannot "
+                         "recompute stream principal with tracks pending "
+                         "removal.", this));
+    return;
+  }
+
+  LOG(LogLevel::Debug, ("DOMMediaStream %p Recomputing principal. "
+                        "Old principal was %p.", this, previousPrincipal.get()));
 
-  promise->MaybeReject(new MediaStreamError(
-      mWindow,
-      NS_LITERAL_STRING("OverconstrainedError"),
-      NS_LITERAL_STRING(""),
-      NS_LITERAL_STRING("")));
-  return promise.forget();
-}
+  // mPrincipal is recomputed based on all current tracks, and tracks that have
+  // not ended in our playback stream.
+  for (const RefPtr<TrackPort>& info : mTracks) {
+    if (info->GetTrack()->Ended()) {
+      continue;
+    }
+    LOG(LogLevel::Debug, ("DOMMediaStream %p Taking live track %p with "
+                          "principal %p into account.", this,
+                          info->GetTrack(), info->GetTrack()->GetPrincipal()));
+    nsContentUtils::CombineResourcePrincipals(&mPrincipal,
+                                              info->GetTrack()->GetPrincipal());
+    if (info->GetTrack()->AsVideoStreamTrack()) {
+      nsContentUtils::CombineResourcePrincipals(&mVideoPrincipal,
+                                                info->GetTrack()->GetPrincipal());
+    }
+  }
 
-bool
-DOMMediaStream::CombineWithPrincipal(nsIPrincipal* aPrincipal)
-{
-  bool changed =
-    nsContentUtils::CombineResourcePrincipals(&mPrincipal, aPrincipal);
-  if (changed) {
+  LOG(LogLevel::Debug, ("DOMMediaStream %p new principal is %p.",
+                        this, mPrincipal.get()));
+
+  if (previousPrincipal != mPrincipal ||
+      previousVideoPrincipal != mVideoPrincipal) {
     NotifyPrincipalChanged();
   }
-  return changed;
-}
-
-void
-DOMMediaStream::SetPrincipal(nsIPrincipal* aPrincipal)
-{
-  mPrincipal = aPrincipal;
-  NotifyPrincipalChanged();
-}
-
-void
-DOMMediaStream::SetCORSMode(CORSMode aCORSMode)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  mCORSMode = aCORSMode;
-}
-
-CORSMode
-DOMMediaStream::GetCORSMode()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return mCORSMode;
 }
 
 void
 DOMMediaStream::NotifyPrincipalChanged()
 {
+  if (!mPrincipal) {
+    // When all tracks are removed, mPrincipal will change to nullptr.
+    LOG(LogLevel::Info, ("DOMMediaStream %p Principal changed to nothing.",
+                         this));
+  } else {
+    LOG(LogLevel::Info, ("DOMMediaStream %p Principal changed. Now: "
+                         "null=%d, codebase=%d, expanded=%d, system=%d", this,
+                          mPrincipal->GetIsNullPrincipal(),
+                          mPrincipal->GetIsCodebasePrincipal(),
+                          mPrincipal->GetIsExpandedPrincipal(),
+                          mPrincipal->GetIsSystemPrincipal()));
+  }
+
   for (uint32_t i = 0; i < mPrincipalChangeObservers.Length(); ++i) {
     mPrincipalChangeObservers[i]->PrincipalChanged(this);
   }
 }
 
 
 bool
-DOMMediaStream::AddPrincipalChangeObserver(PrincipalChangeObserver* aObserver)
+DOMMediaStream::AddPrincipalChangeObserver(
+  PrincipalChangeObserver<DOMMediaStream>* aObserver)
 {
   return mPrincipalChangeObservers.AppendElement(aObserver) != nullptr;
 }
 
 bool
-DOMMediaStream::RemovePrincipalChangeObserver(PrincipalChangeObserver* aObserver)
+DOMMediaStream::RemovePrincipalChangeObserver(
+  PrincipalChangeObserver<DOMMediaStream>* aObserver)
 {
   return mPrincipalChangeObservers.RemoveElement(aObserver);
 }
 
 MediaStreamTrack*
-DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType, const nsString& aLabel)
+DOMMediaStream::CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType,
+                               MediaStreamTrackSource* aSource)
 {
   MOZ_RELEASE_ASSERT(mInputStream);
   MOZ_RELEASE_ASSERT(mOwnedStream);
 
-  MOZ_ASSERT(FindOwnedDOMTrack(GetOwnedStream(), aTrackID) == nullptr);
+  MOZ_ASSERT(FindOwnedDOMTrack(GetInputStream(), aTrackID) == nullptr);
 
   MediaStreamTrack* track;
   switch (aType) {
   case MediaSegment::AUDIO:
-    track = new AudioStreamTrack(this, aTrackID, aLabel);
+    track = new AudioStreamTrack(this, aTrackID, aTrackID, aSource);
     break;
   case MediaSegment::VIDEO:
-    track = new VideoStreamTrack(this, aTrackID, aLabel);
+    track = new VideoStreamTrack(this, aTrackID, aTrackID, aSource);
     break;
   default:
     MOZ_CRASH("Unhandled track type");
   }
 
   LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p with ID %u", this, track, aTrackID));
 
-  RefPtr<TrackPort> ownedTrackPort =
-    new TrackPort(mOwnedPort, track, TrackPort::InputPortOwnership::EXTERNAL);
-  mOwnedTracks.AppendElement(ownedTrackPort.forget());
+  mOwnedTracks.AppendElement(
+    new TrackPort(mOwnedPort, track, TrackPort::InputPortOwnership::EXTERNAL));
 
-  RefPtr<TrackPort> playbackTrackPort =
-    new TrackPort(mPlaybackPort, track, TrackPort::InputPortOwnership::EXTERNAL);
-  mTracks.AppendElement(playbackTrackPort.forget());
+  mTracks.AppendElement(
+    new TrackPort(mPlaybackPort, track, TrackPort::InputPortOwnership::EXTERNAL));
 
   NotifyTrackAdded(track);
   return track;
 }
 
+already_AddRefed<MediaStreamTrack>
+DOMMediaStream::CloneDOMTrack(MediaStreamTrack& aTrack,
+                              TrackID aCloneTrackID)
+{
+  MOZ_RELEASE_ASSERT(mOwnedStream);
+  MOZ_RELEASE_ASSERT(mPlaybackStream);
+  MOZ_RELEASE_ASSERT(IsTrackIDExplicit(aCloneTrackID));
+
+  TrackID inputTrackID = aTrack.mInputTrackID;
+  MediaStream* inputStream = aTrack.GetInputStream();
+
+  RefPtr<MediaStreamTrack> newTrack = aTrack.CloneInternal(this, aCloneTrackID);
+
+  newTrack->mOriginalTrack =
+    aTrack.mOriginalTrack ? aTrack.mOriginalTrack.get() : &aTrack;
+
+  LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p cloned from stream %p track %d",
+                        this, newTrack.get(), inputStream, inputTrackID));
+
+  RefPtr<MediaInputPort> inputPort =
+    mOwnedStream->AllocateInputPort(inputStream, inputTrackID, aCloneTrackID);
+
+  mOwnedTracks.AppendElement(
+    new TrackPort(inputPort, newTrack, TrackPort::InputPortOwnership::OWNED));
+
+  mTracks.AppendElement(
+    new TrackPort(mPlaybackPort, newTrack, TrackPort::InputPortOwnership::EXTERNAL));
+
+  NotifyTrackAdded(newTrack);
+
+  newTrack->SetEnabled(aTrack.Enabled());
+
+  return newTrack.forget();
+}
+
+static DOMMediaStream::TrackPort*
+FindTrackPortAmongTracks(const MediaStreamTrack& aTrack,
+                         const nsTArray<RefPtr<DOMMediaStream::TrackPort>>& aTracks)
+{
+  for (const RefPtr<DOMMediaStream::TrackPort>& info : aTracks) {
+    if (info->GetTrack() == &aTrack) {
+      return info;
+    }
+  }
+  return nullptr;
+}
+
 MediaStreamTrack*
-DOMMediaStream::FindOwnedDOMTrack(MediaStream* aOwningStream, TrackID aTrackID) const
+DOMMediaStream::FindOwnedDOMTrack(MediaStream* aInputStream,
+                                  TrackID aInputTrackID) const
 {
   MOZ_RELEASE_ASSERT(mOwnedStream);
 
-  if (aOwningStream != mOwnedStream) {
-    return nullptr;
-  }
-
   for (const RefPtr<TrackPort>& info : mOwnedTracks) {
-    if (info->GetTrack()->GetTrackID() == aTrackID) {
+    if (info->GetInputPort() &&
+        info->GetInputPort()->GetSource() == aInputStream &&
+        info->GetTrack()->mInputTrackID == aInputTrackID) {
+      // This track is owned externally but in our playback stream.
       return info->GetTrack();
     }
   }
   return nullptr;
 }
 
+DOMMediaStream::TrackPort*
+DOMMediaStream::FindOwnedTrackPort(const MediaStreamTrack& aTrack) const
+{
+  return FindTrackPortAmongTracks(aTrack, mOwnedTracks);
+}
+
+
 MediaStreamTrack*
 DOMMediaStream::FindPlaybackDOMTrack(MediaStream* aInputStream, TrackID aInputTrackID) const
 {
-  MOZ_RELEASE_ASSERT(mPlaybackStream);
+  if (!mPlaybackStream) {
+    // One would think we can assert mPlaybackStream here, but track clones have
+    // a dummy DOMMediaStream that doesn't have a playback stream, so we can't.
+    return nullptr;
+  }
 
   for (const RefPtr<TrackPort>& info : mTracks) {
     if (info->GetInputPort() == mPlaybackPort &&
         aInputStream == mOwnedStream &&
-        aInputTrackID == info->GetTrack()->GetTrackID()) {
+        info->GetTrack()->mInputTrackID == aInputTrackID) {
       // This track is in our owned and playback streams.
       return info->GetTrack();
     }
     if (info->GetInputPort() &&
         info->GetInputPort()->GetSource() == aInputStream &&
         info->GetSourceTrackId() == aInputTrackID) {
       // This track is owned externally but in our playback stream.
-      MOZ_ASSERT(aInputTrackID != TRACK_NONE);
-      MOZ_ASSERT(aInputTrackID != TRACK_INVALID);
-      MOZ_ASSERT(aInputTrackID != TRACK_ANY);
+      MOZ_ASSERT(IsTrackIDExplicit(aInputTrackID));
       return info->GetTrack();
     }
   }
   return nullptr;
 }
 
 DOMMediaStream::TrackPort*
 DOMMediaStream::FindPlaybackTrackPort(const MediaStreamTrack& aTrack) const
 {
-  for (const RefPtr<TrackPort>& info : mTracks) {
-    if (info->GetTrack() == &aTrack) {
-      return info;
-    }
-  }
-  return nullptr;
+  return FindTrackPortAmongTracks(aTrack, mTracks);
 }
 
 void
 DOMMediaStream::NotifyMediaStreamGraphShutdown()
 {
   // No more tracks will ever be added, so just clear these callbacks now
   // to prevent leaks.
   mNotifiedOfMediaStreamGraphShutdown = true;
@@ -940,47 +1175,99 @@ DOMMediaStream::RegisterTrackListener(Tr
 void
 DOMMediaStream::UnregisterTrackListener(TrackListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mTrackListeners.RemoveElement(aListener);
 }
 
 void
-DOMMediaStream::NotifyTrackAdded(
-    const RefPtr<MediaStreamTrack>& aTrack)
+DOMMediaStream::NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (mTracksPendingRemoval > 0) {
+    // If there are tracks pending removal we may not degrade the current
+    // principals until those tracks have been confirmed removed from the
+    // playback stream. Instead combine with the new track and the (potentially)
+    // degraded principal will be calculated when it's safe.
+    nsContentUtils::CombineResourcePrincipals(&mPrincipal,
+                                              aTrack->GetPrincipal());
+    LOG(LogLevel::Debug, ("DOMMediaStream %p saw a track get added. Combining "
+                          "its principal %p into our while waiting for pending "
+                          "tracks to be removed. New principal is %p.",
+                          this, aTrack->GetPrincipal(), mPrincipal.get()));
+    if (aTrack->AsVideoStreamTrack()) {
+      nsContentUtils::CombineResourcePrincipals(&mVideoPrincipal,
+                                                aTrack->GetPrincipal());
+    }
+  } else {
+    LOG(LogLevel::Debug, ("DOMMediaStream %p saw a track get added. "
+                          "Recomputing principal.", this));
+    RecomputePrincipal();
+  }
+
+  aTrack->AddPrincipalChangeObserver(this);
+
   for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
-    const RefPtr<TrackListener>& listener = mTrackListeners[i];
-    listener->NotifyTrackAdded(aTrack);
+    mTrackListeners[i]->NotifyTrackAdded(aTrack);
   }
 }
 
 void
-DOMMediaStream::NotifyTrackRemoved(
-    const RefPtr<MediaStreamTrack>& aTrack)
+DOMMediaStream::NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  aTrack->RemovePrincipalChangeObserver(this);
+
   for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
-    const RefPtr<TrackListener>& listener = mTrackListeners[i];
-    listener->NotifyTrackRemoved(aTrack);
+    mTrackListeners[i]->NotifyTrackRemoved(aTrack);
   }
+
+  // Don't call RecomputePrincipal here as the track may still exist in the
+  // playback stream in the MediaStreamGraph. It will instead be called when the
+  // track has been confirmed removed by the graph. See BlockPlaybackTrack().
 }
 
 void
 DOMMediaStream::CreateAndAddPlaybackStreamListener(MediaStream* aStream)
 {
   MOZ_ASSERT(GetCameraStream(), "I'm a hack. Only DOMCameraControl may use me.");
   mPlaybackListener = new PlaybackStreamListener(this);
   aStream->AddListener(mPlaybackListener);
 }
 
+void
+DOMMediaStream::BlockPlaybackTrack(TrackPort* aTrack)
+{
+  MOZ_ASSERT(aTrack);
+  ++mTracksPendingRemoval;
+  RefPtr<Pledge<bool>> p = aTrack->BlockTrackId(aTrack->GetTrack()->mTrackID);
+  RefPtr<DOMMediaStream> self = this;
+  p->Then([self] (const bool& aIgnore) { self->NotifyPlaybackTrackBlocked(); },
+          [] (const nsresult& aIgnore) { NS_ERROR("Could not remove track from MSG"); }
+  );
+}
+
+void
+DOMMediaStream::NotifyPlaybackTrackBlocked()
+{
+  MOZ_ASSERT(mTracksPendingRemoval > 0,
+             "A track reported finished blocking more times than we asked for");
+  if (--mTracksPendingRemoval == 0) {
+    // The MediaStreamGraph has reported a track was blocked and we are not
+    // waiting for any further tracks to get blocked. It is now safe to
+    // recompute the principal based on our main thread track set state.
+    LOG(LogLevel::Debug, ("DOMMediaStream %p saw all tracks pending removal "
+                          "finish. Recomputing principal.", this));
+    RecomputePrincipal();
+  }
+}
+
 DOMLocalMediaStream::~DOMLocalMediaStream()
 {
   if (mInputStream) {
     // Make sure Listeners of this stream know it's going away
     StopImpl();
   }
 }
 
@@ -1009,78 +1296,89 @@ DOMLocalMediaStream::StopImpl()
 {
   if (mInputStream && mInputStream->AsSourceStream()) {
     mInputStream->AsSourceStream()->EndAllTrackAndFinish();
   }
 }
 
 already_AddRefed<DOMLocalMediaStream>
 DOMLocalMediaStream::CreateSourceStream(nsPIDOMWindowInner* aWindow,
-                                        MediaStreamGraph* aGraph)
+                                        MediaStreamGraph* aGraph,
+                                        MediaStreamTrackSourceGetter* aTrackSourceGetter)
 {
-  RefPtr<DOMLocalMediaStream> stream = new DOMLocalMediaStream();
-  stream->InitSourceStream(aWindow, aGraph);
+  RefPtr<DOMLocalMediaStream> stream =
+    new DOMLocalMediaStream(aWindow, aTrackSourceGetter);
+  stream->InitSourceStream(aGraph);
   return stream.forget();
 }
 
 already_AddRefed<DOMLocalMediaStream>
 DOMLocalMediaStream::CreateTrackUnionStream(nsPIDOMWindowInner* aWindow,
-                                            MediaStreamGraph* aGraph)
+                                            MediaStreamGraph* aGraph,
+                                            MediaStreamTrackSourceGetter* aTrackSourceGetter)
 {
-  RefPtr<DOMLocalMediaStream> stream = new DOMLocalMediaStream();
-  stream->InitTrackUnionStream(aWindow, aGraph);
+  RefPtr<DOMLocalMediaStream> stream =
+    new DOMLocalMediaStream(aWindow, aTrackSourceGetter);
+  stream->InitTrackUnionStream(aGraph);
   return stream.forget();
 }
 
-already_AddRefed<DOMLocalMediaStream>
-DOMLocalMediaStream::CreateAudioCaptureStream(nsPIDOMWindowInner* aWindow,
-                                              MediaStreamGraph* aGraph)
-{
-  RefPtr<DOMLocalMediaStream> stream = new DOMLocalMediaStream();
-  stream->InitAudioCaptureStream(aWindow, aGraph);
-  return stream.forget();
-}
-
-DOMAudioNodeMediaStream::DOMAudioNodeMediaStream(AudioNode* aNode)
-: mStreamNode(aNode)
+DOMAudioNodeMediaStream::DOMAudioNodeMediaStream(nsPIDOMWindowInner* aWindow, AudioNode* aNode)
+  : DOMMediaStream(aWindow, nullptr),
+    mStreamNode(aNode)
 {
 }
 
 DOMAudioNodeMediaStream::~DOMAudioNodeMediaStream()
 {
 }
 
 already_AddRefed<DOMAudioNodeMediaStream>
 DOMAudioNodeMediaStream::CreateTrackUnionStream(nsPIDOMWindowInner* aWindow,
                                                 AudioNode* aNode,
                                                 MediaStreamGraph* aGraph)
 {
-  RefPtr<DOMAudioNodeMediaStream> stream = new DOMAudioNodeMediaStream(aNode);
-  stream->InitTrackUnionStream(aWindow, aGraph);
+  RefPtr<DOMAudioNodeMediaStream> stream = new DOMAudioNodeMediaStream(aWindow, aNode);
+  stream->InitTrackUnionStream(aGraph);
   return stream.forget();
 }
 
-DOMHwMediaStream::DOMHwMediaStream()
+DOMHwMediaStream::DOMHwMediaStream(nsPIDOMWindowInner* aWindow)
+  : DOMLocalMediaStream(aWindow, nullptr)
 {
+#ifdef MOZ_WIDGET_GONK
+  if (!mWindow) {
+    NS_ERROR("Expected window here.");
+    mPrincipalHandle = PRINCIPAL_ID_NONE;
+    return;
+  }
+  nsIDocument* doc = mWindow->GetDoc();
+  if (!doc) {
+    NS_ERROR("Expected document here.");
+    mPrincipalHandle = PRINCIPAL_ID_NONE;
+    return;
+  }
+  mPrincipalHandle = ConvertPrincipalToID(doc->GetPrincipal());
+#endif
 }
 
 DOMHwMediaStream::~DOMHwMediaStream()
 {
 }
 
 already_AddRefed<DOMHwMediaStream>
 DOMHwMediaStream::CreateHwStream(nsPIDOMWindowInner* aWindow,
                                  OverlayImage* aImage)
 {
-  RefPtr<DOMHwMediaStream> stream = new DOMHwMediaStream();
+  RefPtr<DOMHwMediaStream> stream = new DOMHwMediaStream(aWindow);
 
   MediaStreamGraph* graph =
     MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER,
                                   AudioChannel::Normal);
-  stream->InitSourceStream(aWindow, graph);
+  stream->InitSourceStream(graph);
   stream->Init(stream->GetInputStream(), aImage);
 
   return stream.forget();
 }
 
 void
 DOMHwMediaStream::Init(MediaStream* stream, OverlayImage* aImage)
 {
@@ -1104,17 +1402,17 @@ DOMHwMediaStream::Init(MediaStream* stre
     VideoSegment segment;
 #ifdef MOZ_WIDGET_GONK
     const StreamTime delta = STREAM_TIME_MAX; // Because MediaStreamGraph will run out frames in non-autoplay mode,
                                               // we must give it bigger frame length to cover this situation.
 
     RefPtr<Image> image = static_cast<Image*>(mOverlayImage.get());
     mozilla::gfx::IntSize size = image->GetSize();
 
-    segment.AppendFrame(image.forget(), delta, size);
+    segment.AppendFrame(image.forget(), delta, size, mPrincipalHandle);
 #endif
     srcStream->AddTrack(TRACK_VIDEO_PRIMARY, 0, new VideoSegment());
     srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment);
     srcStream->FinishAddTracks();
     srcStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
   }
 }
 
@@ -1161,17 +1459,17 @@ DOMHwMediaStream::SetImageSize(uint32_t 
   track->GetSegment()->Clear();
 
   // Change the image size.
   const StreamTime delta = STREAM_TIME_MAX;
   RefPtr<Image> image = static_cast<Image*>(mOverlayImage.get());
   mozilla::gfx::IntSize size = image->GetSize();
   VideoSegment segment;
 
-  segment.AppendFrame(image.forget(), delta, size);
+  segment.AppendFrame(image.forget(), delta, size, PRINCIPAL_ID_NONE);
   srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment);
 #endif
 }
 
 void
 DOMHwMediaStream::SetOverlayImage(OverlayImage* aImage)
 {
   if (!aImage) {
@@ -1197,17 +1495,17 @@ DOMHwMediaStream::SetOverlayImage(Overla
   track->GetSegment()->Clear();
 
   // Change the image size.
   const StreamTime delta = STREAM_TIME_MAX;
   RefPtr<Image> image = static_cast<Image*>(mOverlayImage.get());
   mozilla::gfx::IntSize size = image->GetSize();
   VideoSegment segment;
 
-  segment.AppendFrame(image.forget(), delta, size);
+  segment.AppendFrame(image.forget(), delta, size, PRINCIPAL_ID_NONE);
   srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment);
 #endif
 }
 
 void
 DOMHwMediaStream::SetOverlayId(int32_t aOverlayId)
 {
 #ifdef MOZ_WIDGET_GONK
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -8,67 +8,97 @@
 
 #include "ImageContainer.h"
 
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "StreamBuffer.h"
 #include "nsIDOMWindow.h"
 #include "nsIPrincipal.h"
-#include "mozilla/PeerIdentity.h"
 #include "mozilla/DOMEventTargetHelper.h"
-#include "mozilla/CORSMode.h"
+#include "PrincipalChangeObserver.h"
 
-// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
-// GetTickCount() and conflicts with NS_DECL_NSIDOMMEDIASTREAM, containing
-// currentTime getter.
-#ifdef GetCurrentTime
-#undef GetCurrentTime
-#endif
 // X11 has a #define for CurrentTime. Unbelievable :-(.
 // See dom/media/webaudio/AudioContext.h for more fun!
 #ifdef CurrentTime
 #undef CurrentTime
 #endif
 
 namespace mozilla {
 
 class DOMHwMediaStream;
 class DOMLocalMediaStream;
+class DOMMediaStream;
 class MediaStream;
-class MediaEngineSource;
 class MediaInputPort;
+class MediaStreamDirectListener;
 class MediaStreamGraph;
 class ProcessedMediaStream;
 
 namespace dom {
 class AudioNode;
 class HTMLCanvasElement;
 class MediaStreamTrack;
+class MediaStreamTrackSource;
 class AudioStreamTrack;
 class VideoStreamTrack;
 class AudioTrack;
 class VideoTrack;
 class AudioTrackList;
 class VideoTrackList;
 class MediaTrackListListener;
 struct MediaTrackConstraints;
 } // namespace dom
 
 namespace layers {
 class ImageContainer;
 class OverlayImage;
 } // namespace layers
 
-class MediaStreamDirectListener;
+namespace media {
+template<typename V, typename E> class Pledge;
+} // namespace media
 
 #define NS_DOMMEDIASTREAM_IID \
 { 0x8cb65468, 0x66c0, 0x444e, \
   { 0x89, 0x9f, 0x89, 0x1d, 0x9e, 0xd2, 0xbe, 0x7c } }
 
+class OnTracksAvailableCallback {
+public:
+  virtual ~OnTracksAvailableCallback() {}
+  virtual void NotifyTracksAvailable(DOMMediaStream* aStream) = 0;
+};
+
+/**
+ * Interface through which a DOMMediaStream can query its producer for a
+ * MediaStreamTrackSource. This will be used whenever a track occurs in the
+ * DOMMediaStream's owned stream that has not yet been created on the main
+ * thread (see DOMMediaStream::CreateOwnDOMTrack).
+ */
+class MediaStreamTrackSourceGetter : public nsISupports
+{
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(MediaStreamTrackSourceGetter)
+
+public:
+  MediaStreamTrackSourceGetter()
+  {
+    MOZ_COUNT_CTOR(MediaStreamTrackSourceGetter);
+  }
+
+  virtual already_AddRefed<dom::MediaStreamTrackSource>
+  GetMediaStreamTrackSource(TrackID aInputTrackID) = 0;
+
+protected:
+  virtual ~MediaStreamTrackSourceGetter()
+  {
+    MOZ_COUNT_DTOR(MediaStreamTrackSourceGetter);
+  }
+};
+
 /**
  * DOM wrapper for MediaStreams.
  *
  * To account for track operations such as clone(), addTrack() and
  * removeTrack(), a DOMMediaStream wraps three internal (and chained)
  * MediaStreams:
  *   1. mInputStream
  *      - Controlled by the owner/source of the DOMMediaStream.
@@ -166,50 +196,50 @@ class MediaStreamDirectListener;
  *               \    \
  * DOMStream A'   \    \
  *           Input \    \ Owned          Playback
  *                  \    -> t1 ------------> t1     <- MediaStreamTrack Y'
  *                   \                                 (pointing to t1 in A')
  *                    ----> t2 ------------> t2     <- MediaStreamTrack Z'
  *                                                     (pointing to t2 in A')
  */
-class DOMMediaStream : public DOMEventTargetHelper
+class DOMMediaStream : public DOMEventTargetHelper,
+                       public dom::PrincipalChangeObserver<dom::MediaStreamTrack>
 {
   friend class DOMLocalMediaStream;
+  friend class dom::MediaStreamTrack;
   typedef dom::MediaStreamTrack MediaStreamTrack;
   typedef dom::AudioStreamTrack AudioStreamTrack;
   typedef dom::VideoStreamTrack VideoStreamTrack;
+  typedef dom::MediaStreamTrackSource MediaStreamTrackSource;
   typedef dom::AudioTrack AudioTrack;
   typedef dom::VideoTrack VideoTrack;
   typedef dom::AudioTrackList AudioTrackList;
   typedef dom::VideoTrackList VideoTrackList;
 
 public:
   typedef dom::MediaTrackConstraints MediaTrackConstraints;
 
   class TrackListener {
-    NS_INLINE_DECL_REFCOUNTING(TrackListener)
+  public:
+    virtual ~TrackListener() {}
 
-  public:
     /**
      * Called when the DOMMediaStream has a new track added, either by
      * JS (addTrack()) or the source creating one.
      */
     virtual void
     NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) {};
 
     /**
      * Called when the DOMMediaStream removes a track, either by
      * JS (removeTrack()) or the source ending it.
      */
     virtual void
     NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) {};
-
-  protected:
-    virtual ~TrackListener() {}
   };
 
   /**
    * TrackPort is a representation of a MediaStreamTrack-MediaInputPort pair
    * that make up a link between the Owned stream and the Playback stream.
    *
    * Semantically, the track is the identifier/key and the port the value of this
    * connection.
@@ -265,30 +295,32 @@ public:
      */
     TrackID GetSourceTrackId() const;
 
     MediaInputPort* GetInputPort() const { return mInputPort; }
     MediaStreamTrack* GetTrack() const { return mTrack; }
 
     /**
      * Blocks aTrackId from going into mInputPort unless the port has been
-     * destroyed.
+     * destroyed. Returns a pledge that gets resolved when the MediaStreamGraph
+     * has applied the block in the playback stream.
      */
-    void BlockTrackId(TrackID aTrackId);
+    already_AddRefed<media::Pledge<bool, nsresult>> BlockTrackId(TrackID aTrackId);
 
   private:
     RefPtr<MediaInputPort> mInputPort;
     RefPtr<MediaStreamTrack> mTrack;
 
     // Defines if we've been given ownership of the input port or if it's owned
     // externally. The owner is responsible for destroying the port.
     const InputPortOwnership mOwnership;
   };
 
- DOMMediaStream();
+  DOMMediaStream(nsPIDOMWindowInner* aWindow,
+                 MediaStreamTrackSourceGetter* aTrackSourceGetter);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMMediaStream,
                                            DOMEventTargetHelper)
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOMMEDIASTREAM_IID)
 
   nsPIDOMWindowInner* GetParentObject() const
@@ -318,37 +350,68 @@ public:
   void GetId(nsAString& aID) const;
 
   void GetAudioTracks(nsTArray<RefPtr<AudioStreamTrack> >& aTracks) const;
   void GetVideoTracks(nsTArray<RefPtr<VideoStreamTrack> >& aTracks) const;
   void GetTracks(nsTArray<RefPtr<MediaStreamTrack> >& aTracks) const;
   void AddTrack(MediaStreamTrack& aTrack);
   void RemoveTrack(MediaStreamTrack& aTrack);
 
+  /** Identical to CloneInternal(TrackForwardingOption::EXPLICIT) */
+  already_AddRefed<DOMMediaStream> Clone();
+
   // NON-WebIDL
 
   /**
+   * Option to provide to CloneInternal() of which tracks should be forwarded
+   * from the source stream (`this`) to the returned stream clone.
+   *
+   * CURRENT forwards the tracks currently in the source stream's track set.
+   * ALL     forwards like EXPLICIT plus any and all future tracks originating
+   *         from the same input stream as the source DOMMediaStream (`this`).
+   */
+  enum class TrackForwardingOption {
+    CURRENT,
+    ALL
+  };
+  already_AddRefed<DOMMediaStream> CloneInternal(TrackForwardingOption aForwarding);
+
+  MediaStreamTrack* GetTrackById(const nsString& aId);
+
+  MediaStreamTrack* GetOwnedTrackById(const nsString& aId);
+
+  /**
    * Returns true if this DOMMediaStream has aTrack in its mPlaybackStream.
    */
   bool HasTrack(const MediaStreamTrack& aTrack) const;
 
   /**
    * Returns true if this DOMMediaStream owns aTrack.
    */
   bool OwnsTrack(const MediaStreamTrack& aTrack) const;
 
   /**
    * Returns the corresponding MediaStreamTrack if it's in our mOwnedStream.
+   * aInputTrackID should match the track's TrackID in its input stream.
    */
-  MediaStreamTrack* FindOwnedDOMTrack(MediaStream* aOwningStream, TrackID aTrackID) const;
+  MediaStreamTrack* FindOwnedDOMTrack(MediaStream* aInputStream,
+                                      TrackID aInputTrackID) const;
+
+  /**
+   * Returns the TrackPort connecting aTrack's input stream to mOwnedStream,
+   * or nullptr if aTrack is not owned by this DOMMediaStream.
+   */
+  TrackPort* FindOwnedTrackPort(const MediaStreamTrack& aTrack) const;
 
   /**
    * Returns the corresponding MediaStreamTrack if it's in our mPlaybackStream.
+   * aInputTrackID should match the track's TrackID in its owned stream.
    */
-  MediaStreamTrack* FindPlaybackDOMTrack(MediaStream* aOwningStream, TrackID aTrackID) const;
+  MediaStreamTrack* FindPlaybackDOMTrack(MediaStream* aInputStream,
+                                         TrackID aInputTrackID) const;
 
   /**
    * Returns the TrackPort connecting mOwnedStream to mPlaybackStream for aTrack.
    */
   TrackPort* FindPlaybackTrackPort(const MediaStreamTrack& aTrack) const;
 
   MediaStream* GetInputStream() const { return mInputStream; }
   ProcessedMediaStream* GetOwnedStream() const { return mOwnedStream; }
@@ -356,85 +419,57 @@ public:
 
   /**
    * Allows a video element to identify this stream as a camera stream, which
    * needs special treatment.
    */
   virtual MediaStream* GetCameraStream() const { return nullptr; }
 
   /**
-   * Overridden in DOMLocalMediaStreams to allow getUserMedia to pass
-   * data directly to RTCPeerConnection without going through graph queuing.
-   * Returns a bool to let us know if direct data will be delivered.
+   * Allows users to get access to media data without going through graph
+   * queuing. Returns a bool to let us know if direct data will be delivered.
    */
-  virtual bool AddDirectListener(MediaStreamDirectListener *aListener) { return false; }
-  virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) {}
-
-  /**
-   * Overridden in DOMLocalMediaStreams to allow getUserMedia to disable
-   * media at the SourceMediaStream.
-   */
-  virtual void SetTrackEnabled(TrackID aTrackID, bool aEnabled);
-
-  virtual void StopTrack(TrackID aTrackID);
-
-  virtual already_AddRefed<dom::Promise>
-  ApplyConstraintsToTrack(TrackID aTrackID,
-                          const MediaTrackConstraints& aConstraints,
-                          ErrorResult &aRv);
+  bool AddDirectListener(MediaStreamDirectListener *aListener);
+  void RemoveDirectListener(MediaStreamDirectListener *aListener);
 
   virtual DOMLocalMediaStream* AsDOMLocalMediaStream() { return nullptr; }
   virtual DOMHwMediaStream* AsDOMHwMediaStream() { return nullptr; }
 
   bool IsFinished();
   /**
    * Returns a principal indicating who may access this stream. The stream contents
    * can only be accessed by principals subsuming this principal.
    */
   nsIPrincipal* GetPrincipal() { return mPrincipal; }
-  mozilla::CORSMode GetCORSMode();
-  void SetCORSMode(mozilla::CORSMode aCORSMode);
 
   /**
-   * These are used in WebRTC.  A peerIdentity constrained MediaStream cannot be sent
-   * across the network to anything other than a peer with the provided identity.
-   * If this is set, then mPrincipal should be an instance of nsNullPrincipal.
+   * Returns a principal indicating who may access video data of this stream.
+   * The video principal will be a combination of all live video tracks.
    */
-  PeerIdentity* GetPeerIdentity() const { return mPeerIdentity; }
-  void SetPeerIdentity(PeerIdentity* aPeerIdentity)
-  {
-    mPeerIdentity = aPeerIdentity;
-  }
+  nsIPrincipal* GetVideoPrincipal() { return mVideoPrincipal; }
+
+  // From PrincipalChangeObserver<MediaStreamTrack>.
+  void PrincipalChanged(MediaStreamTrack* aTrack) override;
 
   /**
-   * Indicate that data will be contributed to this stream from origin aPrincipal.
-   * If aPrincipal is null, this is ignored. Otherwise, from now on the contents
-   * of this stream can only be accessed by principals that subsume aPrincipal.
-   * Returns true if the stream's principal changed.
+   * Add a PrincipalChangeObserver to this stream.
+   *
+   * Returns true if it was successfully added.
+   *
+   * Ownership of the PrincipalChangeObserver remains with the caller, and it's
+   * the caller's responsibility to remove the observer before it dies.
    */
-  bool CombineWithPrincipal(nsIPrincipal* aPrincipal);
+  bool AddPrincipalChangeObserver(dom::PrincipalChangeObserver<DOMMediaStream>* aObserver);
 
   /**
-   * This is used in WebRTC to move from a protected state (nsNullPrincipal) to
-   * one where the stream is accessible to script.  Don't call this.
-   * CombineWithPrincipal is almost certainly more appropriate.
+   * Remove an added PrincipalChangeObserver from this stream.
+   *
+   * Returns true if it was successfully removed.
    */
-  void SetPrincipal(nsIPrincipal* aPrincipal);
-
-  /**
-   * Used to learn about dynamic changes in principal occur.
-   * Operations relating to these observers must be confined to the main thread.
-   */
-  class PrincipalChangeObserver
-  {
-  public:
-    virtual void PrincipalChanged(DOMMediaStream* aMediaStream) = 0;
-  };
-  bool AddPrincipalChangeObserver(PrincipalChangeObserver* aObserver);
-  bool RemovePrincipalChangeObserver(PrincipalChangeObserver* aObserver);
+  bool RemovePrincipalChangeObserver(dom::PrincipalChangeObserver<DOMMediaStream>* aObserver);
 
   /**
    * Called when this stream's MediaStreamGraph has been shut down. Normally
    * MSGs are only shut down when all streams have been removed, so this
    * will only be called during a forced shutdown due to application exit.
    */
   void NotifyMediaStreamGraphShutdown();
   /**
@@ -444,50 +479,58 @@ public:
 
   // Webrtc allows the remote side to name a stream whatever it wants, and we
   // need to surface this to content.
   void AssignId(const nsAString& aID) { mID = aID; }
 
   /**
    * Create a DOMMediaStream whose underlying input stream is a SourceMediaStream.
    */
-  static already_AddRefed<DOMMediaStream>
-  CreateSourceStream(nsPIDOMWindowInner* aWindow, MediaStreamGraph* aGraph);
+  static already_AddRefed<DOMMediaStream> CreateSourceStream(nsPIDOMWindowInner* aWindow,
+                                                             MediaStreamGraph* aGraph,
+                                                             MediaStreamTrackSourceGetter* aTrackSourceGetter = nullptr);
 
   /**
    * Create a DOMMediaStream whose underlying input stream is a TrackUnionStream.
    */
-  static already_AddRefed<DOMMediaStream>
-  CreateTrackUnionStream(nsPIDOMWindowInner* aWindow, MediaStreamGraph* aGraph);
+  static already_AddRefed<DOMMediaStream> CreateTrackUnionStream(nsPIDOMWindowInner* aWindow,
+                                                                 MediaStreamGraph* aGraph,
+                                                                 MediaStreamTrackSourceGetter* aTrackSourceGetter = nullptr);
 
   /**
    * Create an DOMMediaStream whose underlying input stream is an
    * AudioCaptureStream.
    */
   static already_AddRefed<DOMMediaStream>
-  CreateAudioCaptureStream(nsPIDOMWindowInner* aWindow, MediaStreamGraph* aGraph);
+  CreateAudioCaptureStream(nsPIDOMWindowInner* aWindow,
+                           nsIPrincipal* aPrincipal,
+                           MediaStreamGraph* aGraph);
 
   void SetLogicalStreamStartTime(StreamTime aTime)
   {
     mLogicalStreamStartTime = aTime;
   }
 
   /**
    * Called for each track in our owned stream to indicate to JS that we
    * are carrying that track.
    *
    * Creates a MediaStreamTrack, adds it to mTracks and returns it.
    */
-  MediaStreamTrack* CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType, const nsString& aLabel);
+  MediaStreamTrack* CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType,
+                                   MediaStreamTrackSource* aSource);
 
-  class OnTracksAvailableCallback {
-  public:
-    virtual ~OnTracksAvailableCallback() {}
-    virtual void NotifyTracksAvailable(DOMMediaStream* aStream) = 0;
-  };
+  /**
+   * Creates a MediaStreamTrack cloned from aTrack, adds it to mTracks and
+   * returns it.
+   * aCloneTrackID is the TrackID the new track will get in mOwnedStream.
+   */
+  already_AddRefed<MediaStreamTrack> CloneDOMTrack(MediaStreamTrack& aTrack,
+                                                   TrackID aCloneTrackID);
+
   // When the initial set of tracks has been added, run
   // aCallback->NotifyTracksAvailable.
   // It is allowed to do anything, including run script.
   // aCallback may run immediately during this call if tracks are already
   // available!
   // We only care about track additions, we'll fire the notification even if
   // some of the tracks have been removed.
   // Takes ownership of aCallback.
@@ -499,26 +542,33 @@ public:
    */
   void AddConsumerToKeepAlive(nsISupports* aConsumer)
   {
     if (!IsFinished() && !mNotifiedOfMediaStreamGraphShutdown) {
       mConsumersToKeepAlive.AppendElement(aConsumer);
     }
   }
 
+  // Registers a track listener to this MediaStream, for listening to changes
+  // to our track set. The caller must call UnregisterTrackListener before
+  // being destroyed, so we don't hold on to a dead pointer. Main thread only.
   void RegisterTrackListener(TrackListener* aListener);
+
+  // Unregisters a track listener from this MediaStream. The caller must call
+  // UnregisterTrackListener before being destroyed, so we don't hold on to
+  // a dead pointer. Main thread only.
   void UnregisterTrackListener(TrackListener* aListener);
 
 protected:
   virtual ~DOMMediaStream();
 
   void Destroy();
-  void InitSourceStream(nsPIDOMWindowInner* aWindow, MediaStreamGraph* aGraph);
-  void InitTrackUnionStream(nsPIDOMWindowInner* aWindow, MediaStreamGraph* aGraph);
-  void InitAudioCaptureStream(nsPIDOMWindowInner* aWindow, MediaStreamGraph* aGraph);
+  void InitSourceStream(MediaStreamGraph* aGraph);
+  void InitTrackUnionStream(MediaStreamGraph* aGraph);
+  void InitAudioCaptureStream(nsIPrincipal* aPrincipal, MediaStreamGraph* aGraph);
 
   // Sets up aStream as mInputStream. A producer may append data to a
   // SourceMediaStream input stream, or connect another stream to a
   // TrackUnionStream input stream.
   void InitInputStreamCommon(MediaStream* aStream, MediaStreamGraph* aGraph);
 
   // Sets up a new TrackUnionStream as mOwnedStream and connects it to
   // mInputStream with a TRACK_ANY MediaInputPort if available.
@@ -548,16 +598,34 @@ protected:
   friend class OwnedStreamListener;
 
   class PlaybackStreamListener;
   friend class PlaybackStreamListener;
 
   // XXX Bug 1124630. Remove with CameraPreviewMediaStream.
   void CreateAndAddPlaybackStreamListener(MediaStream*);
 
+  /**
+   * Block a track in our playback stream. Calls NotifyPlaybackTrackBlocked()
+   * after the MediaStreamGraph has applied the block and the track is no longer
+   * live.
+   */
+  void BlockPlaybackTrack(TrackPort* aTrack);
+
+  /**
+   * Called on main thread after MediaStreamGraph has applied a track block in
+   * our playback stream.
+   */
+  void NotifyPlaybackTrackBlocked();
+
+  // Recomputes the current principal of this stream based on the set of tracks
+  // it currently contains. PrincipalChangeObservers will be notified only if
+  // the principal changes.
+  void RecomputePrincipal();
+
   // StreamTime at which the currentTime attribute would return 0.
   StreamTime mLogicalStreamStartTime;
 
   // We need this to track our parent object.
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
 
   // MediaStreams are owned by the graph, but we tell them when to die,
   // and they won't die until we let them.
@@ -583,103 +651,105 @@ protected:
   RefPtr<MediaInputPort> mPlaybackPort;
 
   // MediaStreamTracks corresponding to tracks in our mOwnedStream.
   AutoTArray<RefPtr<TrackPort>, 2> mOwnedTracks;
 
   // MediaStreamTracks corresponding to tracks in our mPlaybackStream.
   AutoTArray<RefPtr<TrackPort>, 2> mTracks;
 
+  // Number of MediaStreamTracks that have been removed on main thread but are
+  // waiting to be removed on MediaStreamGraph thread.
+  size_t mTracksPendingRemoval;
+
+  // The interface through which we can query the stream producer for
+  // track sources.
+  RefPtr<MediaStreamTrackSourceGetter> mTrackSourceGetter;
+
   RefPtr<OwnedStreamListener> mOwnedListener;
   RefPtr<PlaybackStreamListener> mPlaybackListener;
 
   nsTArray<nsAutoPtr<OnTracksAvailableCallback> > mRunOnTracksAvailable;
 
   // Set to true after MediaStreamGraph has created tracks for mPlaybackStream.
   bool mTracksCreated;
 
   nsString mID;
 
   // Keep these alive until the stream finishes
   nsTArray<nsCOMPtr<nsISupports> > mConsumersToKeepAlive;
 
   bool mNotifiedOfMediaStreamGraphShutdown;
 
   // The track listeners subscribe to changes in this stream's track set.
-  nsTArray<RefPtr<TrackListener>> mTrackListeners;
+  nsTArray<TrackListener*> mTrackListeners;
 
 private:
   void NotifyPrincipalChanged();
-
-  // Principal identifying who may access the contents of this stream.
+  // Principal identifying who may access the collected contents of this stream.
   // If null, this stream can be used by anyone because it has no content yet.
   nsCOMPtr<nsIPrincipal> mPrincipal;
-  nsTArray<PrincipalChangeObserver*> mPrincipalChangeObservers;
-  // this is used in gUM and WebRTC to identify peers that this stream
-  // is allowed to be sent to
-  nsAutoPtr<PeerIdentity> mPeerIdentity;
+  // Video principal is used by video element as access is requested to its
+  // image data.
+  nsCOMPtr<nsIPrincipal> mVideoPrincipal;
+  nsTArray<dom::PrincipalChangeObserver<DOMMediaStream>*> mPrincipalChangeObservers;
   CORSMode mCORSMode;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(DOMMediaStream,
                               NS_DOMMEDIASTREAM_IID)
 
 #define NS_DOMLOCALMEDIASTREAM_IID \
 { 0xb1437260, 0xec61, 0x4dfa, \
   { 0x92, 0x54, 0x04, 0x44, 0xe2, 0xb5, 0x94, 0x9c } }
 
 class DOMLocalMediaStream : public DOMMediaStream
 {
 public:
-  DOMLocalMediaStream() {}
+  explicit DOMLocalMediaStream(nsPIDOMWindowInner* aWindow,
+                               MediaStreamTrackSourceGetter* aTrackSourceGetter)
+    : DOMMediaStream(aWindow, aTrackSourceGetter) {}
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOMLOCALMEDIASTREAM_IID)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void Stop();
 
-  virtual MediaEngineSource* GetMediaEngine(TrackID aTrackID) { return nullptr; }
-
   /**
    * Create an nsDOMLocalMediaStream whose underlying stream is a SourceMediaStream.
    */
   static already_AddRefed<DOMLocalMediaStream>
   CreateSourceStream(nsPIDOMWindowInner* aWindow,
-                     MediaStreamGraph* aGraph);
+                     MediaStreamGraph* aGraph,
+                     MediaStreamTrackSourceGetter* aTrackSourceGetter = nullptr);
 
   /**
    * Create an nsDOMLocalMediaStream whose underlying stream is a TrackUnionStream.
    */
   static already_AddRefed<DOMLocalMediaStream>
   CreateTrackUnionStream(nsPIDOMWindowInner* aWindow,
-                         MediaStreamGraph* aGraph);
-
-  /**
-   * Create an nsDOMLocalMediaStream whose underlying stream is an
-   * AudioCaptureStream. */
-  static already_AddRefed<DOMLocalMediaStream>
-  CreateAudioCaptureStream(nsPIDOMWindowInner* aWindow,
-                           MediaStreamGraph* aGraph);
+                         MediaStreamGraph* aGraph,
+                         MediaStreamTrackSourceGetter* aTrackSourceGetter = nullptr);
 
 protected:
   virtual ~DOMLocalMediaStream();
 
   void StopImpl();
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(DOMLocalMediaStream,
                               NS_DOMLOCALMEDIASTREAM_IID)
 
 class DOMAudioNodeMediaStream : public DOMMediaStream
 {
   typedef dom::AudioNode AudioNode;
 public:
-  explicit DOMAudioNodeMediaStream(AudioNode* aNode);
+  DOMAudioNodeMediaStream(nsPIDOMWindowInner* aWindow, AudioNode* aNode);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
 
   /**
    * Create a DOMAudioNodeMediaStream whose underlying stream is a TrackUnionStream.
    */
   static already_AddRefed<DOMAudioNodeMediaStream>
@@ -700,17 +770,17 @@ class DOMHwMediaStream : public DOMLocal
 {
   typedef mozilla::gfx::IntSize IntSize;
   typedef layers::OverlayImage OverlayImage;
 #ifdef MOZ_WIDGET_GONK
   typedef layers::OverlayImage::Data Data;
 #endif
 
 public:
-  DOMHwMediaStream();
+  explicit DOMHwMediaStream(nsPIDOMWindowInner* aWindow);
 
   static already_AddRefed<DOMHwMediaStream> CreateHwStream(nsPIDOMWindowInner* aWindow,
                                                            OverlayImage* aImage = nullptr);
   virtual DOMHwMediaStream* AsDOMHwMediaStream() override { return this; }
   int32_t RequestOverlayId();
   void SetOverlayId(int32_t aOverlayId);
   void SetImageSize(uint32_t width, uint32_t height);
   void SetOverlayImage(OverlayImage* aImage);
@@ -721,14 +791,15 @@ protected:
 private:
   void Init(MediaStream* aStream, OverlayImage* aImage);
 
 #ifdef MOZ_WIDGET_GONK
   const int DEFAULT_IMAGE_ID = 0x01;
   const int DEFAULT_IMAGE_WIDTH = 400;
   const int DEFAULT_IMAGE_HEIGHT = 300;
   RefPtr<OverlayImage> mOverlayImage;
+  PrincipalID mPrincipalHandle;
 #endif
 };
 
 } // namespace mozilla
 
 #endif /* NSDOMMEDIASTREAM_H_ */
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -321,17 +321,17 @@ ThreadedDriver::RunThread()
       // A previous driver may have been processing further ahead of
       // iterationEnd.
       STREAM_LOG(LogLevel::Warning,
                  ("Prevent state from going backwards. interval[%ld; %ld] state[%ld; %ld]",
                   (long)mIterationStart, (long)mIterationEnd,
                   (long)stateComputedTime, (long)nextStateComputedTime));
       nextStateComputedTime = stateComputedTime;
     }
-    STREAM_LOG(LogLevel::Debug,
+    STREAM_LOG(LogLevel::Verbose,
                ("interval[%ld; %ld] state[%ld; %ld]",
                (long)mIterationStart, (long)mIterationEnd,
                (long)stateComputedTime, (long)nextStateComputedTime));
 
     stillProcessing = mGraphImpl->OneIteration(nextStateComputedTime);
 
     MonitorAutoLock lock(GraphImpl()->GetMonitor());
     if (NextDriver() && stillProcessing) {
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -551,16 +551,18 @@ MediaDecoder::MediaDecoder(MediaDecoderO
   , mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING,
                "MediaDecoder::mPlayState (Canonical)")
   , mNextState(AbstractThread::MainThread(), PLAY_STATE_PAUSED,
                "MediaDecoder::mNextState (Canonical)")
   , mLogicallySeeking(AbstractThread::MainThread(), false,
                       "MediaDecoder::mLogicallySeeking (Canonical)")
   , mSameOriginMedia(AbstractThread::MainThread(), false,
                      "MediaDecoder::mSameOriginMedia (Canonical)")
+  , mMediaPrincipalHandle(AbstractThread::MainThread(), PRINCIPAL_HANDLE_NONE,
+                          "MediaDecoder::mMediaPrincipalHandle (Canonical)")
   , mPlaybackBytesPerSecond(AbstractThread::MainThread(), 0.0,
                             "MediaDecoder::mPlaybackBytesPerSecond (Canonical)")
   , mPlaybackRateReliable(AbstractThread::MainThread(), true,
                           "MediaDecoder::mPlaybackRateReliable (Canonical)")
   , mDecoderPosition(AbstractThread::MainThread(), 0,
                      "MediaDecoder::mDecoderPosition (Canonical)")
   , mMediaSeekable(AbstractThread::MainThread(), true,
                    "MediaDecoder::mMediaSeekable (Canonical)")
@@ -1190,16 +1192,18 @@ MediaDecoder::NotifyDownloadEnded(nsresu
   }
 }
 
 void
 MediaDecoder::NotifyPrincipalChanged()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mShuttingDown);
+  nsCOMPtr<nsIPrincipal> newPrincipal = GetCurrentPrincipal();
+  mMediaPrincipalHandle = MakePrincipalHandle(newPrincipal);
   mOwner->NotifyDecoderPrincipalChanged();
 }
 
 void
 MediaDecoder::NotifyBytesConsumed(int64_t aBytes, int64_t aOffset)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mShuttingDown);
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -777,16 +777,20 @@ protected:
 
   // True if the decoder is seeking.
   Canonical<bool> mLogicallySeeking;
 
   // True if the media is same-origin with the element. Data can only be
   // passed to MediaStreams when this is true.
   Canonical<bool> mSameOriginMedia;
 
+  // An identifier for the principal of the media. Used to track when
+  // main-thread induced principal changes get reflected on MSG thread.
+  Canonical<PrincipalHandle> mMediaPrincipalHandle;
+
   // Estimate of the current playback rate (bytes/second).
   Canonical<double> mPlaybackBytesPerSecond;
 
   // True if mPlaybackBytesPerSecond is a reliable estimate.
   Canonical<bool> mPlaybackRateReliable;
 
   // Current decoding position in the stream. This is where the decoder
   // is up to consuming the stream. This is not adjusted during decoder
@@ -824,16 +828,19 @@ public:
     return &mNextState;
   }
   AbstractCanonical<bool>* CanonicalLogicallySeeking() {
     return &mLogicallySeeking;
   }
   AbstractCanonical<bool>* CanonicalSameOriginMedia() {
     return &mSameOriginMedia;
   }
+  AbstractCanonical<PrincipalHandle>* CanonicalMediaPrincipalHandle() {
+    return &mMediaPrincipalHandle;
+  }
   AbstractCanonical<double>* CanonicalPlaybackBytesPerSecond() {
     return &mPlaybackBytesPerSecond;
   }
   AbstractCanonical<bool>* CanonicalPlaybackRateReliable() {
     return &mPlaybackRateReliable;
   }
   AbstractCanonical<int64_t>* CanonicalDecoderPosition() {
     return &mDecoderPosition;
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -262,16 +262,18 @@ MediaDecoderStateMachine::MediaDecoderSt
                     "MediaDecoderStateMachine::mLogicallySeeking (Mirror)"),
   mVolume(mTaskQueue, 1.0, "MediaDecoderStateMachine::mVolume (Mirror)"),
   mLogicalPlaybackRate(mTaskQueue, 1.0,
                        "MediaDecoderStateMachine::mLogicalPlaybackRate (Mirror)"),
   mPreservesPitch(mTaskQueue, true,
                   "MediaDecoderStateMachine::mPreservesPitch (Mirror)"),
   mSameOriginMedia(mTaskQueue, false,
                    "MediaDecoderStateMachine::mSameOriginMedia (Mirror)"),
+  mMediaPrincipalHandle(mTaskQueue, PRINCIPAL_HANDLE_NONE,
+                        "MediaDecoderStateMachine::mMediaPrincipalHandle (Mirror)"),
   mPlaybackBytesPerSecond(mTaskQueue, 0.0,
                           "MediaDecoderStateMachine::mPlaybackBytesPerSecond (Mirror)"),
   mPlaybackRateReliable(mTaskQueue, true,
                         "MediaDecoderStateMachine::mPlaybackRateReliable (Mirror)"),
   mDecoderPosition(mTaskQueue, 0,
                    "MediaDecoderStateMachine::mDecoderPosition (Mirror)"),
   mMediaSeekable(mTaskQueue, true,
                  "MediaDecoderStateMachine::mMediaSeekable (Mirror)"),
@@ -350,16 +352,17 @@ MediaDecoderStateMachine::Initialization
   mExplicitDuration.Connect(aDecoder->CanonicalExplicitDuration());
   mPlayState.Connect(aDecoder->CanonicalPlayState());
   mNextPlayState.Connect(aDecoder->CanonicalNextPlayState());
   mLogicallySeeking.Connect(aDecoder->CanonicalLogicallySeeking());
   mVolume.Connect(aDecoder->CanonicalVolume());
   mLogicalPlaybackRate.Connect(aDecoder->CanonicalPlaybackRate());
   mPreservesPitch.Connect(aDecoder->CanonicalPreservesPitch());
   mSameOriginMedia.Connect(aDecoder->CanonicalSameOriginMedia());
+  mMediaPrincipalHandle.Connect(aDecoder->CanonicalMediaPrincipalHandle());
   mPlaybackBytesPerSecond.Connect(aDecoder->CanonicalPlaybackBytesPerSecond());
   mPlaybackRateReliable.Connect(aDecoder->CanonicalPlaybackRateReliable());
   mDecoderPosition.Connect(aDecoder->CanonicalDecoderPosition());
   mMediaSeekable.Connect(aDecoder->CanonicalMediaSeekable());
   mMediaSeekableOnlyInBufferedRanges.Connect(aDecoder->CanonicalMediaSeekableOnlyInBufferedRanges());
 
   // Initialize watchers.
   mWatchManager.Watch(mBuffered, &MediaDecoderStateMachine::BufferedRangeUpdated);
@@ -389,17 +392,18 @@ MediaDecoderStateMachine::CreateAudioSin
   return new AudioSinkWrapper(mTaskQueue, audioSinkCreator);
 }
 
 already_AddRefed<media::MediaSink>
 MediaDecoderStateMachine::CreateMediaSink(bool aAudioCaptured)
 {
   RefPtr<media::MediaSink> audioSink = aAudioCaptured
     ? new DecodedStream(mTaskQueue, mAudioQueue, mVideoQueue,
-                        mOutputStreamManager, mSameOriginMedia.Ref())
+                        mOutputStreamManager, mSameOriginMedia.Ref(),
+                        mMediaPrincipalHandle.Ref())
     : CreateAudioSink();
 
   RefPtr<media::MediaSink> mediaSink =
     new VideoSink(mTaskQueue, audioSink, mVideoQueue,
                   mVideoFrameContainer, *mFrameStats,
                   sVideoQueueSendToCompositorSize);
   return mediaSink.forget();
 }
@@ -2149,16 +2153,17 @@ MediaDecoderStateMachine::FinishShutdown
   mExplicitDuration.DisconnectIfConnected();
   mPlayState.DisconnectIfConnected();
   mNextPlayState.DisconnectIfConnected();
   mLogicallySeeking.DisconnectIfConnected();
   mVolume.DisconnectIfConnected();
   mLogicalPlaybackRate.DisconnectIfConnected();
   mPreservesPitch.DisconnectIfConnected();
   mSameOriginMedia.DisconnectIfConnected();
+  mMediaPrincipalHandle.DisconnectIfConnected();
   mPlaybackBytesPerSecond.DisconnectIfConnected();
   mPlaybackRateReliable.DisconnectIfConnected();
   mDecoderPosition.DisconnectIfConnected();
   mMediaSeekable.DisconnectIfConnected();
   mMediaSeekableOnlyInBufferedRanges.DisconnectIfConnected();
 
   mDuration.DisconnectAll();
   mIsShutdown.DisconnectAll();
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -1082,16 +1082,20 @@ private:
 
   // Pitch preservation for the playback rate.
   Mirror<bool> mPreservesPitch;
 
   // True if the media is same-origin with the element. Data can only be
   // passed to MediaStreams when this is true.
   Mirror<bool> mSameOriginMedia;
 
+  // An identifier for the principal of the media. Used to track when
+  // main-thread induced principal changes get reflected on MSG thread.
+  Mirror<PrincipalHandle> mMediaPrincipalHandle;
+
   // Estimate of the current playback rate (bytes/second).
   Mirror<double> mPlaybackBytesPerSecond;
 
   // True if mPlaybackBytesPerSecond is a reliable estimate.
   Mirror<bool> mPlaybackRateReliable;
 
   // Current decoding position in the stream.
   Mirror<int64_t> mDecoderPosition;
--- a/dom/media/MediaDevices.cpp
+++ b/dom/media/MediaDevices.cpp
@@ -23,17 +23,17 @@ class MediaDevices::GumResolver : public
 public:
   NS_DECL_ISUPPORTS
 
   explicit GumResolver(Promise* aPromise) : mPromise(aPromise) {}
 
   NS_IMETHOD
   OnSuccess(nsISupports* aStream) override
   {
-    RefPtr<DOMLocalMediaStream> stream = do_QueryObject(aStream);
+    RefPtr<DOMMediaStream> stream = do_QueryObject(aStream);
     if (!stream) {
       return NS_ERROR_FAILURE;
     }
     mPromise->MaybeResolve(stream);
     return NS_OK;
   }
 
 private:
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -129,23 +129,25 @@ namespace mozilla {
 LogModule*
 GetMediaManagerLog()
 {
   static LazyLogModule sLog("MediaManager");
   return sLog;
 }
 #define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
 
+using dom::BasicUnstoppableTrackSource;
 using dom::ConstrainDOMStringParameters;
 using dom::File;
 using dom::GetUserMediaRequest;
 using dom::MediaSourceEnum;
 using dom::MediaStreamConstraints;
 using dom::MediaStreamError;
 using dom::MediaStreamTrack;
+using dom::MediaStreamTrackSource;
 using dom::MediaTrackConstraints;
 using dom::MediaTrackConstraintSet;
 using dom::OwningBooleanOrMediaTrackConstraints;
 using dom::OwningStringOrStringSequence;
 using dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters;
 using dom::Promise;
 using dom::Sequence;
 using media::NewRunnableFrom;
@@ -246,17 +248,17 @@ HostHasPermission(nsIURI &docURI)
 // ProxyReleases mStream since it's cycle collected.
 class MediaOperationTask : public Task
 {
 public:
   // so we can send Stop without AddRef()ing from the MSG thread
   MediaOperationTask(MediaOperation aType,
     GetUserMediaCallbackMediaStreamListener* aListener,
     DOMMediaStream* aStream,
-    DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
+    OnTracksAvailableCallback* aOnTracksAvailableCallback,
     AudioDevice* aAudioDevice,
     VideoDevice* aVideoDevice,
     bool aBool,
     uint64_t aWindowID,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
     const dom::MediaTrackConstraints& aConstraints = dom::MediaTrackConstraints())
     : mType(aType)
     , mStream(aStream)
@@ -289,24 +291,26 @@ public:
 
     switch (mType) {
       case MEDIA_START:
         {
           NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
           nsresult rv;
 
           if (mAudioDevice) {
-            rv = mAudioDevice->GetSource()->Start(source, kAudioTrack);
+            rv = mAudioDevice->GetSource()->Start(source, kAudioTrack,
+                                                  mListener->GetPrincipalHandle());
             if (NS_FAILED(rv)) {
               ReturnCallbackError(rv, "Starting audio failed");
               return;
             }
           }
           if (mVideoDevice) {
-            rv = mVideoDevice->GetSource()->Start(source, kVideoTrack);
+            rv = mVideoDevice->GetSource()->Start(source, kVideoTrack,
+                                                  mListener->GetPrincipalHandle());
             if (NS_FAILED(rv)) {
               ReturnCallbackError(rv, "Starting video failed");
               return;
             }
           }
           // Start() queued the tracks to be added synchronously to avoid races
           source->FinishAddTracks();
 
@@ -373,17 +377,17 @@ public:
         MOZ_ASSERT(false,"invalid MediaManager operation");
         break;
     }
   }
 
 private:
   MediaOperation mType;
   RefPtr<DOMMediaStream> mStream;
-  nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
+  nsAutoPtr<OnTracksAvailableCallback> mOnTracksAvailableCallback;
   RefPtr<AudioDevice> mAudioDevice; // threadsafe
   RefPtr<VideoDevice> mVideoDevice; // threadsafe
   RefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
   bool mBool;
   uint64_t mWindowID;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   dom::MediaTrackConstraints mConstraints;
 };
@@ -651,184 +655,16 @@ nsresult VideoDevice::Restart(const dom:
   return GetSource()->Restart(aConstraints, aPrefs, mID);
 }
 
 nsresult AudioDevice::Restart(const dom::MediaTrackConstraints &aConstraints,
                               const MediaEnginePrefs &aPrefs) {
   return GetSource()->Restart(aConstraints, aPrefs, mID);
 }
 
-/**
- * A subclass that we only use to stash internal pointers to MediaStreamGraph objects
- * that need to be cleaned up.
- */
-class nsDOMUserMediaStream : public DOMLocalMediaStream
-{
-public:
-  static already_AddRefed<nsDOMUserMediaStream>
-  CreateSourceStream(nsPIDOMWindowInner* aWindow,
-                     GetUserMediaCallbackMediaStreamListener* aListener,
-                     AudioDevice* aAudioDevice,
-                     VideoDevice* aVideoDevice,
-                     MediaStreamGraph* aMSG)
-  {
-    RefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aListener,
-                                                                   aAudioDevice,
-                                                                     aVideoDevice);
-    stream->InitSourceStream(aWindow, aMSG);
-    return stream.forget();
-  }
-
-  nsDOMUserMediaStream(GetUserMediaCallbackMediaStreamListener* aListener,
-                       AudioDevice *aAudioDevice,
-                       VideoDevice *aVideoDevice) :
-    mListener(aListener),
-    mAudioDevice(aAudioDevice),
-    mVideoDevice(aVideoDevice)
-  {}
-
-  virtual ~nsDOMUserMediaStream()
-  {
-    StopImpl();
-
-    if (GetSourceStream()) {
-      GetSourceStream()->Destroy();
-    }
-  }
-
-  // For gUM streams, we have a trackunion which assigns TrackIDs.  However, for a
-  // single-source trackunion like we have here, the TrackUnion will assign trackids
-  // that match the source's trackids, so we can avoid needing a mapping function.
-  // XXX This will not handle more complex cases well.
-  void StopTrack(TrackID aTrackID) override
-  {
-    if (GetSourceStream()) {
-      GetSourceStream()->EndTrack(aTrackID);
-      // We could override NotifyMediaStreamTrackEnded(), and maybe should, but it's
-      // risky to do late in a release since that will affect all track ends, and not
-      // just StopTrack()s.
-      RefPtr<dom::MediaStreamTrack> ownedTrack = FindOwnedDOMTrack(mOwnedStream, aTrackID);
-      if (ownedTrack) {
-        mListener->StopTrack(aTrackID);
-      } else {
-        LOG(("StopTrack(%d) on non-existent track", aTrackID));
-      }
-    }
-  }
-
-  already_AddRefed<Promise>
-  ApplyConstraintsToTrack(TrackID aTrackID,
-                          const MediaTrackConstraints& aConstraints,
-                          ErrorResult &aRv) override
-  {
-    nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
-    RefPtr<Promise> promise = Promise::Create(go, aRv);
-
-    if (sInShutdown) {
-      RefPtr<MediaStreamError> error = new MediaStreamError(mWindow,
-          NS_LITERAL_STRING("AbortError"),
-          NS_LITERAL_STRING("In shutdown"));
-      promise->MaybeReject(error);
-      return promise.forget();
-    }
-    if (!GetSourceStream()) {
-      RefPtr<MediaStreamError> error = new MediaStreamError(mWindow,
-          NS_LITERAL_STRING("InternalError"),
-          NS_LITERAL_STRING("No stream."));
-      promise->MaybeReject(error);
-      return promise.forget();
-    }
-
-    RefPtr<dom::MediaStreamTrack> track = FindOwnedDOMTrack(mOwnedStream, aTrackID);
-    if (!track) {
-      LOG(("ApplyConstraintsToTrack(%d) on non-existent track", aTrackID));
-      RefPtr<MediaStreamError> error = new MediaStreamError(mWindow,
-          NS_LITERAL_STRING("InternalError"),
-          NS_LITERAL_STRING("No track."));
-      promise->MaybeReject(error);
-      return promise.forget();
-    }
-
-    typedef media::Pledge<bool, MediaStreamError*> PledgeVoid;
-
-    RefPtr<PledgeVoid> p = mListener->ApplyConstraintsToTrack(mWindow,
-        aTrackID, !!track->AsAudioStreamTrack(), aConstraints);
-    p->Then([promise](bool& aDummy) mutable {
-      promise->MaybeResolve(false);
-    }, [promise](MediaStreamError*& reason) mutable {
-      promise->MaybeReject(reason);
-    });
-    return promise.forget();
-  }
-
-#if 0
-  virtual void NotifyMediaStreamTrackEnded(dom::MediaStreamTrack* aTrack)
-  {
-    TrackID trackID = aTrack->GetTrackID();
-    // We override this so we can also tell the backend to stop capturing if the track ends
-    LOG(("track %d ending, type = %s",
-         trackID, aTrack->AsAudioStreamTrack() ? "audio" : "video"));
-    MOZ_ASSERT(aTrack->AsVideoStreamTrack() || aTrack->AsAudioStreamTrack());
-    mListener->StopTrack(trackID, !!aTrack->AsAudioStreamTrack());
-
-    // forward to superclass
-    DOMLocalMediaStream::NotifyMediaStreamTrackEnded(aTrack);
-  }
-#endif
-
-  // Allow getUserMedia to pass input data directly to PeerConnection/MediaPipeline
-  bool AddDirectListener(MediaStreamDirectListener *aListener) override
-  {
-    if (GetSourceStream()) {
-      GetSourceStream()->AddDirectListener(aListener);
-      return true; // application should ignore NotifyQueuedTrackData
-    }
-    return false;
-  }
-
-  void RemoveDirectListener(MediaStreamDirectListener *aListener) override
-  {
-    if (GetSourceStream()) {
-      GetSourceStream()->RemoveDirectListener(aListener);
-    }
-  }
-
-  DOMLocalMediaStream* AsDOMLocalMediaStream() override
-  {
-    return this;
-  }
-
-  MediaEngineSource* GetMediaEngine(TrackID aTrackID) override
-  {
-    // MediaEngine supports only one video and on video track now and TrackID is
-    // fixed in MediaEngine.
-    if (aTrackID == kVideoTrack) {
-      return mVideoDevice ? mVideoDevice->GetSource() : nullptr;
-    }
-    else if (aTrackID == kAudioTrack) {
-      return mAudioDevice ? mAudioDevice->GetSource() : nullptr;
-    }
-
-    return nullptr;
-  }
-
-  SourceMediaStream* GetSourceStream()
-  {
-    if (GetInputStream()) {
-      return GetInputStream()->AsSourceStream();
-    }
-    return nullptr;
-  }
-
-  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
-  RefPtr<AudioDevice> mAudioDevice; // so we can turn on AEC
-  RefPtr<VideoDevice> mVideoDevice;
-};
-
-
 void
 MediaOperationTask::ReturnCallbackError(nsresult rv, const char* errorLog)
 {
   MM_LOG(("%s , rv=%d", errorLog, rv));
   NS_DispatchToMainThread(do_AddRef(new ReleaseMediaOperationResource(mStream.forget(),
     mOnTracksAvailableCallback.forget())));
   nsString log;
 
@@ -839,16 +675,57 @@ MediaOperationTask::ReturnCallbackError(
   NS_DispatchToMainThread(do_AddRef(
     new ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>(onSuccess,
                                                                  mOnFailure,
                                                                  *error,
                                                                  mWindowID)));
 }
 
 /**
+ * This class is only needed since fake tracks are added dynamically.
+ * Instead of refactoring to add them explicitly we let the DOMMediaStream
+ * query us for the source as they become available.
+ * Since they are used only for testing the API surface, we make them very
+ * simple.
+ */
+class FakeTrackSourceGetter : public MediaStreamTrackSourceGetter
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FakeTrackSourceGetter,
+                                           MediaStreamTrackSourceGetter)
+
+  explicit FakeTrackSourceGetter(nsIPrincipal* aPrincipal)
+    : mPrincipal(aPrincipal) {}
+
+  already_AddRefed<dom::MediaStreamTrackSource>
+  GetMediaStreamTrackSource(TrackID aInputTrackID) override
+  {
+    NS_ASSERTION(kAudioTrack != aInputTrackID,
+                 "Only fake tracks should appear dynamically");
+    NS_ASSERTION(kVideoTrack != aInputTrackID,
+                 "Only fake tracks should appear dynamically");
+    return do_AddRef(new BasicUnstoppableTrackSource(mPrincipal));
+  }
+
+protected:
+  virtual ~FakeTrackSourceGetter() {}
+
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+};
+
+NS_IMPL_ADDREF_INHERITED(FakeTrackSourceGetter, MediaStreamTrackSourceGetter)
+NS_IMPL_RELEASE_INHERITED(FakeTrackSourceGetter, MediaStreamTrackSourceGetter)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FakeTrackSourceGetter)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(FakeTrackSourceGetter,
+                                   MediaStreamTrackSourceGetter,
+                                   mPrincipal)
+
+/**
  * Creates a MediaStream, attaches a listener and fires off a success callback
  * to the DOM with the stream. We also pass in the error callback so it can
  * be released correctly.
  *
  * All of this must be done on the main thread!
  *
  * Note that the various GetUserMedia Runnable classes currently allow for
  * two streams.  If we ever need to support getting more than two streams
@@ -878,17 +755,17 @@ public:
     , mManager(MediaManager::GetInstance())
   {
     mOnSuccess.swap(aOnSuccess);
     mOnFailure.swap(aOnFailure);
   }
 
   ~GetUserMediaStreamRunnable() {}
 
-  class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback
+  class TracksAvailableCallback : public OnTracksAvailableCallback
   {
   public:
     TracksAvailableCallback(MediaManager* aManager,
                             nsIDOMGetUserMediaSuccessCallback* aSuccess,
                             uint64_t aWindowID,
                             DOMMediaStream* aStream)
       : mWindowID(aWindowID), mOnSuccess(aSuccess), mManager(aManager),
         mStream(aStream) {}
@@ -939,58 +816,137 @@ public:
 
     MediaStreamGraph::GraphDriverType graphDriverType =
       mAudioDevice ? MediaStreamGraph::AUDIO_THREAD_DRIVER
                    : MediaStreamGraph::SYSTEM_THREAD_DRIVER;
     MediaStreamGraph* msg =
       MediaStreamGraph::GetInstance(graphDriverType,
                                     dom::AudioChannel::Normal);
 
-    RefPtr<DOMLocalMediaStream> domStream;
+    RefPtr<DOMMediaStream> domStream;
     RefPtr<SourceMediaStream> stream;
     // AudioCapture is a special case, here, in the sense that we're not really
     // using the audio source and the SourceMediaStream, which acts as
     // placeholders. We re-route a number of stream internaly in the MSG and mix
     // them down instead.
     if (mAudioDevice &&
         mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
-      domStream = DOMLocalMediaStream::CreateAudioCaptureStream(window, msg);
       // It should be possible to pipe the capture stream to anything. CORS is
       // not a problem here, we got explicit user content.
-      domStream->SetPrincipal(window->GetExtantDoc()->NodePrincipal());
+      nsCOMPtr<nsIPrincipal> principal = window->GetExtantDoc()->NodePrincipal();
+      domStream =
+        DOMMediaStream::CreateAudioCaptureStream(window, principal, msg);
+
       stream = msg->CreateSourceStream(nullptr); // Placeholder
       msg->RegisterCaptureStreamForWindow(
             mWindowID, domStream->GetInputStream()->AsProcessedStream());
       window->SetAudioCapture(true);
     } else {
+      class LocalTrackSource : public MediaStreamTrackSource
+      {
+      public:
+        LocalTrackSource(nsIPrincipal* aPrincipal,
+                         const nsString& aLabel,
+                         GetUserMediaCallbackMediaStreamListener* aListener,
+                         const MediaSourceEnum aSource,
+                         const TrackID aTrackID,
+                         const PeerIdentity* aPeerIdentity)
+          : MediaStreamTrackSource(aPrincipal, false, aLabel), mListener(aListener),
+            mSource(aSource), mTrackID(aTrackID), mPeerIdentity(aPeerIdentity) {}
+
+        MediaSourceEnum GetMediaSource() const override
+        {
+          return mSource;
+        }
+
+        const PeerIdentity* GetPeerIdentity() const override
+        {
+          return mPeerIdentity;
+        }
+
+        already_AddRefed<Promise>
+        ApplyConstraints(nsPIDOMWindowInner* aWindow,
+                         const MediaTrackConstraints& aConstraints,
+                         ErrorResult &aRv) override
+        {
+          nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(aWindow);
+          RefPtr<Promise> promise = Promise::Create(go, aRv);
+
+          if (sInShutdown) {
+            RefPtr<MediaStreamError> error = new MediaStreamError(aWindow,
+                NS_LITERAL_STRING("AbortError"),
+                NS_LITERAL_STRING("In shutdown"));
+            promise->MaybeReject(error);
+            return promise.forget();
+          }
+
+          typedef media::Pledge<bool, MediaStreamError*> PledgeVoid;
+
+          RefPtr<PledgeVoid> p =
+            mListener->ApplyConstraintsToTrack(aWindow, mTrackID, aConstraints);
+          p->Then([promise](bool& aDummy) mutable {
+            promise->MaybeResolve(false);
+          }, [promise](MediaStreamError*& reason) mutable {
+            promise->MaybeReject(reason);
+          });
+          return promise.forget();
+        }
+
+
+        void Stop() override
+        {
+          if (mListener) {
+            mListener->StopTrack(mTrackID);
+            mListener = nullptr;
+          }
+        }
+
+      protected:
+        ~LocalTrackSource() {}
+
+        RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
+        const MediaSourceEnum mSource;
+        const TrackID mTrackID;
+        const RefPtr<const PeerIdentity> mPeerIdentity;
+      };
+
+      nsCOMPtr<nsIPrincipal> principal;
+      if (mPeerIdentity) {
+        principal = nsNullPrincipal::Create();
+      } else {
+        principal = window->GetExtantDoc()->NodePrincipal();
+      }
+
       // Normal case, connect the source stream to the track union stream to
-      // avoid us blocking
-      domStream = nsDOMUserMediaStream::CreateSourceStream(window, mListener,
-                                                           mAudioDevice, mVideoDevice,
-                                                           msg);
+      // avoid us blocking. Pass a simple TrackSourceGetter for potential
+      // fake tracks. Apart from them gUM never adds tracks dynamically.
+      domStream =
+        DOMLocalMediaStream::CreateSourceStream(window, msg,
+                                                new FakeTrackSourceGetter(principal));
 
       if (mAudioDevice) {
         nsString audioDeviceName;
         mAudioDevice->GetName(audioDeviceName);
-        domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioDeviceName);
+        const MediaSourceEnum source =
+          mAudioDevice->GetSource()->GetMediaSource();
+        RefPtr<MediaStreamTrackSource> audioSource =
+          new LocalTrackSource(principal, audioDeviceName, mListener, source,
+                               kAudioTrack, mPeerIdentity);
+        domStream->CreateDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioSource);
       }
       if (mVideoDevice) {
         nsString videoDeviceName;
         mVideoDevice->GetName(videoDeviceName);
-        domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoDeviceName);
+        const MediaSourceEnum source =
+          mVideoDevice->GetSource()->GetMediaSource();
+        RefPtr<MediaStreamTrackSource> videoSource =
+          new LocalTrackSource(principal, videoDeviceName, mListener, source,
+                               kVideoTrack, mPeerIdentity);
+        domStream->CreateDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoSource);
       }
-
-      nsCOMPtr<nsIPrincipal> principal;
-      if (mPeerIdentity) {
-        principal = nsNullPrincipal::Create();
-        domStream->SetPeerIdentity(mPeerIdentity.forget());
-      } else {
-        principal = window->GetExtantDoc()->NodePrincipal();
-      }
-      domStream->CombineWithPrincipal(principal);
       stream = domStream->GetInputStream()->AsSourceStream();
     }
 
     if (!domStream || sInShutdown) {
       nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure = mOnFailure.forget();
       LOG(("Returning error for getUserMedia() - no stream"));
 
       if (auto* window = nsGlobalWindow::GetInnerWindowWithId(mWindowID)) {
@@ -1037,17 +993,17 @@ public:
 private:
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   RefPtr<AudioDevice> mAudioDevice;
   RefPtr<VideoDevice> mVideoDevice;
   uint64_t mWindowID;
   RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   nsCString mOrigin;
-  nsAutoPtr<PeerIdentity> mPeerIdentity;
+  RefPtr<PeerIdentity> mPeerIdentity;
   RefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
 static bool
 IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) {
   return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
 }
 
@@ -2035,39 +1991,41 @@ MediaManager::GetUserMedia(nsPIDOMWindow
     }
   } else if (IsOn(c.mAudio)) {
    audioType = MediaSourceEnum::Microphone;
   }
 
   StreamListeners* listeners = AddWindowID(windowID);
 
   // Create a disabled listener to act as a placeholder
+  nsIPrincipal* principal = aWindow->GetExtantDoc()->NodePrincipal();
   RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-    new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowID);
+    new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowID,
+                                                MakePrincipalHandle(principal));
 
   // No need for locking because we always do this in the main thread.
   listeners->AppendElement(listener);
 
   if (!privileged) {
     // Check if this site has had persistent permissions denied.
     nsCOMPtr<nsIPermissionManager> permManager =
       do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
     if (IsOn(c.mAudio)) {
       rv = permManager->TestExactPermissionFromPrincipal(
-        aWindow->GetExtantDoc()->NodePrincipal(), "microphone", &audioPerm);
+        principal, "microphone", &audioPerm);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     uint32_t videoPerm = nsIPermissionManager::UNKNOWN_ACTION;
     if (IsOn(c.mVideo)) {
       rv = permManager->TestExactPermissionFromPrincipal(
-        aWindow->GetExtantDoc()->NodePrincipal(), "camera", &videoPerm);
+        principal, "camera", &videoPerm);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     if ((!IsOn(c.mAudio) || audioPerm == nsIPermissionManager::DENY_ACTION) &&
         (!IsOn(c.mVideo) || videoPerm == nsIPermissionManager::DENY_ACTION)) {
       RefPtr<MediaStreamError> error =
           new MediaStreamError(aWindow, NS_LITERAL_STRING("SecurityError"));
       onFailure->OnError(error);
@@ -2285,17 +2243,17 @@ MediaManager::ToJSArray(SourceSet& aDevi
 
 already_AddRefed<MediaManager::PledgeSourceSet>
 MediaManager::EnumerateDevicesImpl(uint64_t aWindowId,
                                    MediaSourceEnum aVideoType,
                                    MediaSourceEnum aAudioType,
                                    bool aFake, bool aFakeTracks)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  nsPIDOMWindowInner* window = 
+  nsPIDOMWindowInner* window =
     nsGlobalWindow::GetInnerWindowWithId(aWindowId)->AsInner();
 
   // This function returns a pledge, a promise-like object with the future result
   RefPtr<PledgeSourceSet> pledge = new PledgeSourceSet();
   uint32_t id = mOutstandingPledges.Append(*pledge);
 
   // To get a device list anonymized for a particular origin, we must:
   // 1. Get an origin-key (for either regular or private browsing)
@@ -2351,19 +2309,22 @@ MediaManager::EnumerateDevices(nsPIDOMWi
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE(!sInShutdown, NS_ERROR_FAILURE);
   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
   uint64_t windowId = aWindow->WindowID();
 
   StreamListeners* listeners = AddWindowID(windowId);
 
+  nsIPrincipal* principal = aWindow->GetExtantDoc()->NodePrincipal();
+
   // Create a disabled listener to act as a placeholder
   RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-    new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowId);
+    new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowId,
+                                                MakePrincipalHandle(principal));
 
   // No need for locking because we always do this in the main thread.
   listeners->AppendElement(listener);
 
   bool fake = Preferences::GetBool("media.navigator.streams.fake");
 
   RefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowId,
                                                      MediaSourceEnum::Camera,
@@ -3150,36 +3111,36 @@ GetUserMediaCallbackMediaStreamListener:
 }
 
 // ApplyConstraints for track
 
 already_AddRefed<GetUserMediaCallbackMediaStreamListener::PledgeVoid>
 GetUserMediaCallbackMediaStreamListener::ApplyConstraintsToTrack(
     nsPIDOMWindowInner* aWindow,
     TrackID aTrackID,
-    bool aIsAudio,
     const MediaTrackConstraints& aConstraints)
 {
   MOZ_ASSERT(NS_IsMainThread());
   RefPtr<PledgeVoid> p = new PledgeVoid();
 
-  if (!(((aIsAudio && mAudioDevice) ||
-         (!aIsAudio && mVideoDevice)) && !mStopped))
+  // XXX to support multiple tracks of a type in a stream, this should key off
+  // the TrackID and not just the type
+  RefPtr<AudioDevice> audioDevice =
+    aTrackID == kAudioTrack ? mAudioDevice.get() : nullptr;
+  RefPtr<VideoDevice> videoDevice =
+    aTrackID == kVideoTrack ? mVideoDevice.get() : nullptr;
+
+  if (mStopped || (!audioDevice && !videoDevice))
   {
     LOG(("gUM track %d applyConstraints, but we don't have type %s",
-         aTrackID, aIsAudio ? "audio" : "video"));
+         aTrackID, aTrackID == kAudioTrack ? "audio" : "video"));
     p->Resolve(false);
     return p.forget();
   }
 
-  // XXX to support multiple tracks of a type in a stream, this should key off
-  // the TrackID and not just the type
-  RefPtr<AudioDevice> audioDevice = aIsAudio ? mAudioDevice.get() : nullptr;
-  RefPtr<VideoDevice> videoDevice = !aIsAudio ? mVideoDevice.get() : nullptr;
-
   RefPtr<MediaManager> mgr = MediaManager::GetInstance();
   uint32_t id = mgr->mOutstandingVoidPledges.Append(*p);
   uint64_t windowId = aWindow->WindowID();
 
   MediaManager::PostTask(FROM_HERE, NewTaskFrom([id, windowId,
                                                  audioDevice, videoDevice,
                                                  aConstraints]() mutable {
     MOZ_ASSERT(MediaManager::IsInMediaThread());
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -121,19 +121,21 @@ public:
  * to Start() and Stop() the underlying MediaEngineSource when MediaStreams
  * are assigned and deassigned in content.
  */
 class GetUserMediaCallbackMediaStreamListener : public MediaStreamListener
 {
 public:
   // Create in an inactive state
   GetUserMediaCallbackMediaStreamListener(base::Thread *aThread,
-    uint64_t aWindowID)
+    uint64_t aWindowID,
+    const PrincipalHandle& aPrincipalHandle)
     : mMediaThread(aThread)
     , mWindowID(aWindowID)
+    , mPrincipalHandle(aPrincipalHandle)
     , mStopped(false)
     , mFinished(false)
     , mRemoved(false)
     , mAudioStopped(false)
     , mVideoStopped(false) {}
 
   ~GetUserMediaCallbackMediaStreamListener()
   {
@@ -170,17 +172,17 @@ public:
   void StopSharing();
 
   void StopTrack(TrackID aID);
 
   typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
 
   already_AddRefed<PledgeVoid>
   ApplyConstraintsToTrack(nsPIDOMWindowInner* aWindow,
-                          TrackID aID, bool aIsAudio,
+                          TrackID aID,
                           const dom::MediaTrackConstraints& aConstraints);
 
   // mVideo/AudioDevice are set by Activate(), so we assume they're capturing
   // if set and represent a real capture device.
   bool CapturingVideo()
   {
     MOZ_ASSERT(NS_IsMainThread());
     return mVideoDevice && !mStopped &&
@@ -255,21 +257,21 @@ public:
   // Proxy NotifyPull() to sources
   void
   NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override
   {
     // Currently audio sources ignore NotifyPull, but they could
     // watch it especially for fake audio.
     if (mAudioDevice) {
       mAudioDevice->GetSource()->NotifyPull(aGraph, mStream, kAudioTrack,
-                                            aDesiredTime);
+                                            aDesiredTime, mPrincipalHandle);
     }
     if (mVideoDevice) {
       mVideoDevice->GetSource()->NotifyPull(aGraph, mStream, kVideoTrack,
-                                            aDesiredTime);
+                                            aDesiredTime, mPrincipalHandle);
     }
   }
 
   void
   NotifyEvent(MediaStreamGraph* aGraph,
               MediaStreamListener::MediaStreamGraphEvent aEvent) override
   {
     switch (aEvent) {
@@ -296,20 +298,23 @@ public:
   NotifyFinished();
 
   void
   NotifyRemoved();
 
   void
   NotifyDirectListeners(MediaStreamGraph* aGraph, bool aHasListeners);
 
+  PrincipalHandle GetPrincipalHandle() const { return mPrincipalHandle; }
+
 private:
   // Set at construction
   base::Thread* mMediaThread;
   uint64_t mWindowID;
+  const PrincipalHandle mPrincipalHandle;
 
   // true after this listener has sent MEDIA_STOP. MainThread only.
   bool mStopped;
 
   // true after the stream this listener is listening to has finished in the
   // MediaStreamGraph. MainThread only.
   bool mFinished;
 
@@ -345,33 +350,33 @@ class GetUserMediaNotificationEvent: pub
     GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener,
                                   GetUserMediaStatus aStatus,
                                   bool aIsAudio, bool aIsVideo, uint64_t aWindowID)
     : mListener(aListener) , mStatus(aStatus) , mIsAudio(aIsAudio)
     , mIsVideo(aIsVideo), mWindowID(aWindowID) {}
 
     GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
                                   already_AddRefed<DOMMediaStream> aStream,
-                                  DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
+                                  OnTracksAvailableCallback* aOnTracksAvailableCallback,
                                   bool aIsAudio, bool aIsVideo, uint64_t aWindowID,
                                   already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError)
     : mStream(aStream), mOnTracksAvailableCallback(aOnTracksAvailableCallback),
       mStatus(aStatus), mIsAudio(aIsAudio), mIsVideo(aIsVideo), mWindowID(aWindowID),
       mOnFailure(aError) {}
     virtual ~GetUserMediaNotificationEvent()
     {
 
     }
 
     NS_IMETHOD Run() override;
 
   protected:
     RefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
     RefPtr<DOMMediaStream> mStream;
-    nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
+    nsAutoPtr<OnTracksAvailableCallback> mOnTracksAvailableCallback;
     GetUserMediaStatus mStatus;
     bool mIsAudio;
     bool mIsVideo;
     uint64_t mWindowID;
     RefPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
 };
 
 typedef enum {
@@ -383,23 +388,23 @@ typedef enum {
 
 class MediaManager;
 class GetUserMediaTask;
 
 class ReleaseMediaOperationResource : public nsRunnable
 {
 public:
   ReleaseMediaOperationResource(already_AddRefed<DOMMediaStream> aStream,
-    DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback):
+    OnTracksAvailableCallback* aOnTracksAvailableCallback):
     mStream(aStream),
     mOnTracksAvailableCallback(aOnTracksAvailableCallback) {}
   NS_IMETHOD Run() override {return NS_OK;}
 private:
   RefPtr<DOMMediaStream> mStream;
-  nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
+  nsAutoPtr<OnTracksAvailableCallback> mOnTracksAvailableCallback;
 };
 
 typedef nsTArray<RefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
 typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
 
 // we could add MediaManager if needed
 typedef void (*WindowListenerCallback)(MediaManager *aThis,
                                        uint64_t aWindowID,
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -19,16 +19,17 @@
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/RecordErrorEvent.h"
 #include "mozilla/dom/VideoStreamTrack.h"
 #include "nsContentUtils.h"
 #include "nsError.h"
 #include "nsIDocument.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
+#include "nsIScriptError.h"
 #include "nsMimeTypes.h"
 #include "nsProxyRelease.h"
 #include "nsTArray.h"
 #include "GeckoProfiler.h"
 #include "nsContentTypeParser.h"
 #include "nsCharSeparatedTokenizer.h"
 
 #ifdef LOG
@@ -152,17 +153,19 @@ NS_IMPL_RELEASE_INHERITED(MediaRecorder,
  *    Therefore, the reference dependency in gecko is:
  *    ShutdownObserver -> Session <-> MediaRecorder, note that there is a cycle
  *    reference between Session and MediaRecorder.
  * 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being called
  *    _and_ all encoded media data been passed to OnDataAvailable handler.
  * 3) MediaRecorder::Stop is called by user or the document is going to
  *    inactive or invisible.
  */
-class MediaRecorder::Session: public nsIObserver
+class MediaRecorder::Session: public nsIObserver,
+                              public PrincipalChangeObserver<MediaStreamTrack>,
+                              public DOMMediaStream::TrackListener
 {
   NS_DECL_THREADSAFE_ISUPPORTS
 
   // Main thread task.
   // Create a blob event and send back to client.
   class PushBlobRunnable : public nsRunnable
   {
   public:
@@ -283,34 +286,68 @@ class MediaRecorder::Session: public nsI
       return NS_OK;
     }
 
   private:
     RefPtr<Session> mSession;
   };
 
   // For Ensure recorder has tracks to record.
-  class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback
+  class TracksAvailableCallback : public OnTracksAvailableCallback
   {
   public:
     explicit TracksAvailableCallback(Session *aSession)
      : mSession(aSession) {}
     virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
     {
+      if (mSession->mStopIssued) {
+        return;
+      }
+
+      MOZ_RELEASE_ASSERT(aStream);
+      mSession->MediaStreamReady(*aStream);
+
       uint8_t trackTypes = 0;
       nsTArray<RefPtr<mozilla::dom::AudioStreamTrack>> audioTracks;
       aStream->GetAudioTracks(audioTracks);
       if (!audioTracks.IsEmpty()) {
         trackTypes |= ContainerWriter::CREATE_AUDIO_TRACK;
+        mSession->ConnectMediaStreamTrack(*audioTracks[0]);
       }
 
       nsTArray<RefPtr<mozilla::dom::VideoStreamTrack>> videoTracks;
       aStream->GetVideoTracks(videoTracks);
       if (!videoTracks.IsEmpty()) {
         trackTypes |= ContainerWriter::CREATE_VIDEO_TRACK;
+        mSession->ConnectMediaStreamTrack(*videoTracks[0]);
+      }
+
+      if (audioTracks.Length() > 1 ||
+          videoTracks.Length() > 1) {
+        // When MediaRecorder supports multiple tracks, we should set up a single
+        // MediaInputPort from the input stream, and let main thread check
+        // track principals async later.
+        nsPIDOMWindowInner* window = mSession->mRecorder->GetParentObject();
+        nsIDocument* document = window ? window->GetExtantDoc() : nullptr;
+        nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                        NS_LITERAL_CSTRING("Media"),
+                                        document,
+                                        nsContentUtils::eDOM_PROPERTIES,
+                                        "MediaRecorderMultiTracksNotSupported");
+        mSession->DoSessionEndTask(NS_ERROR_ABORT);
+        return;
+      }
+
+      NS_ASSERTION(trackTypes != 0, "TracksAvailableCallback without any tracks available");
+
+      // Check that we may access the tracks' content.
+      if (!mSession->MediaStreamTracksPrincipalSubsumes()) {
+        LOG(LogLevel::Warning, ("Session.NotifyTracksAvailable MediaStreamTracks principal check failed"));
+        mSession->DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
+        return;
       }
 
       LOG(LogLevel::Debug, ("Session.NotifyTracksAvailable track type = (%d)", trackTypes));
       mSession->InitEncoder(trackTypes);
     }
   private:
     RefPtr<Session> mSession;
   };
@@ -371,29 +408,97 @@ public:
     : mRecorder(aRecorder)
     , mTimeSlice(aTimeSlice)
     , mStopIssued(false)
     , mIsStartEventFired(false)
     , mIsRegisterProfiler(false)
     , mNeedSessionEndTask(true)
   {
     MOZ_ASSERT(NS_IsMainThread());
+    MOZ_COUNT_CTOR(MediaRecorder::Session);
 
     uint32_t maxMem = Preferences::GetUint("media.recorder.max_memory",
                                            MAX_ALLOW_MEMORY_BUFFER);
     mEncodedBufferCache = new EncodedBufferCache(maxMem);
     mLastBlobTimeStamp = TimeStamp::Now();
   }
 
+  void PrincipalChanged(MediaStreamTrack* aTrack) override
+  {
+    NS_ASSERTION(mMediaStreamTracks.Contains(aTrack),
+                 "Principal changed for unrecorded track");
+    if (!MediaStreamTracksPrincipalSubsumes()) {
+      DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
+    }
+  }
+
+  void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override
+  {
+    LOG(LogLevel::Warning, ("Session.NotifyTrackAdded %p Raising error due to track set change", this));
+    DoSessionEndTask(NS_ERROR_ABORT);
+  }
+
+  void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override
+  {
+    RefPtr<MediaInputPort> foundInputPort;
+    for (RefPtr<MediaInputPort> inputPort : mInputPorts) {
+      if (aTrack->IsForwardedThrough(inputPort)) {
+        foundInputPort = inputPort;
+        break;
+      }
+    }
+
+    if (foundInputPort) {
+      // A recorded track was removed or ended. End it in the recording.
+      // Don't raise an error.
+      foundInputPort->Destroy();
+      DebugOnly<bool> removed = mInputPorts.RemoveElement(foundInputPort);
+      MOZ_ASSERT(removed);
+      return;
+    }
+
+    LOG(LogLevel::Warning, ("Session.NotifyTrackRemoved %p Raising error due to track set change", this));
+    DoSessionEndTask(NS_ERROR_ABORT);
+  }
+
   void Start()
   {
     LOG(LogLevel::Debug, ("Session.Start %p", this));
     MOZ_ASSERT(NS_IsMainThread());
 
-    SetupStreams();
+    // Create a Track Union Stream
+    MediaStreamGraph* gm = mRecorder->GetSourceMediaStream()->Graph();
+    mTrackUnionStream = gm->CreateTrackUnionStream(nullptr);
+    MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
+
+    mTrackUnionStream->SetAutofinish(true);
+
+    DOMMediaStream* domStream = mRecorder->Stream();
+    if (domStream) {
+      // Get the available tracks from the DOMMediaStream.
+      // The callback will report back tracks that we have to connect to
+      // mTrackUnionStream and listen to principal changes on.
+      TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(this);
+      domStream->OnTracksAvailable(tracksAvailableCallback);
+    } else {
+      // Check that we may access the audio node's content.
+      if (!AudioNodePrincipalSubsumes()) {
+        LOG(LogLevel::Warning, ("Session.Start AudioNode principal check failed"));
+        DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
+        return;
+      }
+      // Bind this Track Union Stream with Source Media.
+      RefPtr<MediaInputPort> inputPort =
+        mTrackUnionStream->AllocateInputPort(mRecorder->GetSourceMediaStream());
+      mInputPorts.AppendElement(inputPort.forget());
+      MOZ_ASSERT(mInputPorts[mInputPorts.Length()-1]);
+
+      // Web Audio node has only audio.
+      InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK);
+    }
   }
 
   void Stop()
   {
     LOG(LogLevel::Debug, ("Session.Stop %p", this));
     MOZ_ASSERT(NS_IsMainThread());
     mStopIssued = true;
     CleanupStreams();
@@ -468,16 +573,17 @@ public:
     return (mEncoder ?  mEncoder->SizeOfExcludingThis(aMallocSizeOf) : 0);
   }
 
 
 private:
   // Only DestroyRunnable is allowed to delete Session object.
   virtual ~Session()
   {
+    MOZ_COUNT_DTOR(MediaRecorder::Session);
     LOG(LogLevel::Debug, ("Session.~Session (%p)", this));
     CleanupStreams();
   }
   // Pull encoded media data from MediaEncoder and put into EncodedBufferCache.
   // Destroy this session object in the end of this function.
   // If the bool aForceFlush is true, we will force to dispatch a
   // PushBlobRunnable to main thread.
   void Extract(bool aForceFlush)
@@ -531,40 +637,68 @@ private:
       if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
         MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
       } else {
         mLastBlobTimeStamp = TimeStamp::Now();
       }
     }
   }
 
-  // Bind media source with MediaEncoder to receive raw media data.
-  void SetupStreams()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
+  void MediaStreamReady(DOMMediaStream& aStream) {
+    mMediaStream = &aStream;
+    aStream.RegisterTrackListener(this);
+  }
 
-    // Create a Track Union Stream
-    MediaStreamGraph* gm = mRecorder->GetSourceMediaStream()->Graph();
-    mTrackUnionStream = gm->CreateTrackUnionStream(nullptr);
-    MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
-
-    mTrackUnionStream->SetAutofinish(true);
+  void ConnectMediaStreamTrack(MediaStreamTrack& aTrack)
+  {
+    mMediaStreamTracks.AppendElement(&aTrack);
+    aTrack.AddPrincipalChangeObserver(this);
+    RefPtr<MediaInputPort> inputPort =
+      aTrack.ForwardTrackContentsTo(mTrackUnionStream);
+    MOZ_ASSERT(inputPort);
+    mInputPorts.AppendElement(inputPort.forget());
+    MOZ_ASSERT(mInputPorts[mInputPorts.Length()-1]);
+  }
 
-    // Bind this Track Union Stream with Source Media.
-    mInputPort = mTrackUnionStream->AllocateInputPort(mRecorder->GetSourceMediaStream());
+  bool PrincipalSubsumes(nsIPrincipal* aPrincipal)
+  {
+    if (!mRecorder->GetOwner())
+      return false;
+    nsCOMPtr<nsIDocument> doc = mRecorder->GetOwner()->GetExtantDoc();
+    if (!doc) {
+      return false;
+    }
+    if (!aPrincipal) {
+      return false;
+    }
+    bool subsumes;
+    if (NS_FAILED(doc->NodePrincipal()->Subsumes(aPrincipal, &subsumes))) {
+      return false;
+    }
+    return subsumes;
+  }
 
-    DOMMediaStream* domStream = mRecorder->Stream();
-    if (domStream) {
-      // Get the track type hint from DOM media stream.
-      TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(this);
-      domStream->OnTracksAvailable(tracksAvailableCallback);
-    } else {
-      // Web Audio node has only audio.
-      InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK);
+  bool MediaStreamTracksPrincipalSubsumes()
+  {
+    MOZ_ASSERT(mRecorder->mDOMStream);
+    nsCOMPtr<nsIPrincipal> principal = nullptr;
+    for (RefPtr<MediaStreamTrack>& track : mMediaStreamTracks) {
+      nsContentUtils::CombineResourcePrincipals(&principal, track->GetPrincipal());
     }
+    return PrincipalSubsumes(principal);
+  }
+
+  bool AudioNodePrincipalSubsumes()
+  {
+    MOZ_ASSERT(mRecorder->mAudioNode != nullptr);
+    nsIDocument* doc = mRecorder->mAudioNode->GetOwner()
+                     ? mRecorder->mAudioNode->GetOwner()->GetExtantDoc()
+                     : nullptr;
+    nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
+    return PrincipalSubsumes(principal);
   }
 
   bool CheckPermission(const char* aType)
   {
     if (!mRecorder || !mRecorder->GetOwner()) {
       return false;
     }
 
@@ -688,44 +822,58 @@ private:
       nsCOMPtr<nsIRunnable> runnable =
         NS_NewRunnableMethodWithArg<nsresult>(mRecorder,
                                               &MediaRecorder::NotifyError, rv);
       NS_DispatchToMainThread(runnable);
     }
     if (NS_FAILED(NS_DispatchToMainThread(new EncoderErrorNotifierRunnable(this)))) {
       MOZ_ASSERT(false, "NS_DispatchToMainThread EncoderErrorNotifierRunnable failed");
     }
-    if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
-      MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
+    if (rv != NS_ERROR_DOM_SECURITY_ERR) {
+      // Don't push a blob if there was a security error.
+      if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
+        MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
+      }
     }
     if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(this)))) {
       MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
     }
     mNeedSessionEndTask = false;
   }
   void CleanupStreams()
   {
     if (mInputStream) {
       if (mEncoder) {
         mInputStream->RemoveDirectListener(mEncoder);
       }
       mInputStream = nullptr;
     }
-    if (mInputPort) {
-      mInputPort->Destroy();
-      mInputPort = nullptr;
+    for (RefPtr<MediaInputPort>& inputPort : mInputPorts) {
+      MOZ_ASSERT(inputPort);
+      inputPort->Destroy();
     }
+    mInputPorts.Clear();
 
     if (mTrackUnionStream) {
       if (mEncoder) {
         mTrackUnionStream->RemoveListener(mEncoder);
       }
       mTrackUnionStream->Destroy();
       mTrackUnionStream = nullptr;
     }
+
+    if (mMediaStream) {
+      mMediaStream->UnregisterTrackListener(this);
+      mMediaStream = nullptr;
+    }
+
+    for (RefPtr<MediaStreamTrack>& track : mMediaStreamTracks) {
+      track->RemovePrincipalChangeObserver(this);
+    }
+    mMediaStreamTracks.Clear();
   }
 
   NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
   {
     MOZ_ASSERT(NS_IsMainThread());
     LOG(LogLevel::Debug, ("Session.Observe XPCOM_SHUTDOWN %p", this));
     if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
       // Force stop Session to terminate Read Thread.
@@ -755,17 +903,24 @@ private:
   // Hold reference to MediaRecoder that ensure MediaRecorder is alive
   // if there is an active session. Access ONLY on main thread.
   RefPtr<MediaRecorder> mRecorder;
 
   // Receive track data from source and dispatch to Encoder.
   // Pause/ Resume controller.
   RefPtr<ProcessedMediaStream> mTrackUnionStream;
   RefPtr<SourceMediaStream> mInputStream;
-  RefPtr<MediaInputPort> mInputPort;
+  nsTArray<RefPtr<MediaInputPort>> mInputPorts;
+
+  // Stream currently recorded.
+  RefPtr<DOMMediaStream> mMediaStream;
+
+  // Tracks currently recorded. This should be a subset of mMediaStream's track
+  // set.
+  nsTArray<RefPtr<MediaStreamTrack>> mMediaStreamTracks;
 
   // Runnable thread for read data from MediaEncode.
   nsCOMPtr<nsIThread> mReadThread;
   // MediaEncoder pipeline.
   RefPtr<MediaEncoder> mEncoder;
   // A buffer to cache encoded meda data.
   nsAutoPtr<EncodedBufferCache> mEncodedBufferCache;
   // Current session mimeType
@@ -830,18 +985,20 @@ MediaRecorder::MediaRecorder(AudioNode& 
     AudioContext* ctx = aSrcAudioNode.Context();
     AudioNodeEngine* engine = new AudioNodeEngine(nullptr);
     AudioNodeStream::Flags flags =
       AudioNodeStream::EXTERNAL_OUTPUT |
       AudioNodeStream::NEED_MAIN_THREAD_FINISHED;
     mPipeStream = AudioNodeStream::Create(ctx, engine, flags);
     AudioNodeStream* ns = aSrcAudioNode.GetStream();
     if (ns) {
-      mInputPort = mPipeStream->AllocateInputPort(aSrcAudioNode.GetStream(),
-                                                  TRACK_ANY, 0, aSrcOutput);
+      mInputPort =
+        mPipeStream->AllocateInputPort(aSrcAudioNode.GetStream(),
+                                       TRACK_ANY, TRACK_ANY,
+                                       0, aSrcOutput);
     }
   }
   mAudioNode = &aSrcAudioNode;
 
   RegisterActivityObserver();
 }
 
 void
@@ -889,27 +1046,16 @@ MediaRecorder::Start(const Optional<int3
     return;
   }
 
   if (GetSourceMediaStream()->IsFinished() || GetSourceMediaStream()->IsDestroyed()) {
     aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  // Check if source media stream is valid. See bug 919051.
-  if (mDOMStream && !mDOMStream->GetPrincipal()) {
-    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-
-  if (!CheckPrincipal()) {
-    aResult.Throw(NS_ERROR_DOM_SECURITY_ERR);
-    return;
-  }
-
   int32_t timeSlice = 0;
   if (aTimeSlice.WasPassed()) {
     if (aTimeSlice.Value() < 0) {
       aResult.Throw(NS_ERROR_INVALID_ARG);
       return;
     }
 
     timeSlice = aTimeSlice.Value();
@@ -1178,21 +1324,17 @@ MediaRecorder::IsTypeSupported(const nsA
 
   return true;
 }
 
 nsresult
 MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
-  if (!CheckPrincipal()) {
-    // Media is not same-origin, don't allow the data out.
-    RefPtr<nsIDOMBlob> blob = aBlob;
-    return NS_ERROR_DOM_SECURITY_ERR;
-  }
+
   BlobEventInit init;
   init.mBubbles = false;
   init.mCancelable = false;
 
   nsCOMPtr<nsIDOMBlob> blob = aBlob;
   init.mData = static_cast<Blob*>(blob.get());
 
   RefPtr<BlobEvent> event =
@@ -1255,39 +1397,16 @@ MediaRecorder::NotifyError(nsresult aRv)
   rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
   if (NS_FAILED(rv)) {
     NS_ERROR("Failed to dispatch the error event!!!");
     return;
   }
   return;
 }
 
-bool MediaRecorder::CheckPrincipal()
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
-  if (!mDOMStream && !mAudioNode) {
-    return false;
-  }
-  if (!GetOwner())
-    return false;
-  nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
-  if (!doc) {
-    return false;
-  }
-  nsIPrincipal* srcPrincipal = GetSourcePrincipal();
-  if (!srcPrincipal) {
-    return false;
-  }
-  bool subsumes;
-  if (NS_FAILED(doc->NodePrincipal()->Subsumes(srcPrincipal, &subsumes))) {
-    return false;
-  }
-  return subsumes;
-}
-
 void
 MediaRecorder::RemoveSession(Session* aSession)
 {
   LOG(LogLevel::Debug, ("MediaRecorder.RemoveSession (%p)", aSession));
   mSessions.RemoveElement(aSession);
 }
 
 void
@@ -1313,27 +1432,16 @@ MediaRecorder::GetSourceMediaStream()
 {
   if (mDOMStream != nullptr) {
     return mDOMStream->GetPlaybackStream();
   }
   MOZ_ASSERT(mAudioNode != nullptr);
   return mPipeStream ? mPipeStream.get() : mAudioNode->GetStream();
 }
 
-nsIPrincipal*
-MediaRecorder::GetSourcePrincipal()
-{
-  if (mDOMStream != nullptr) {
-    return mDOMStream->GetPrincipal();
-  }
-  MOZ_ASSERT(mAudioNode != nullptr);
-  nsIDocument* doc = mAudioNode->GetOwner() ? mAudioNode->GetOwner()->GetExtantDoc() : nullptr;
-  return doc ? doc->NodePrincipal() : nullptr;
-}
-
 size_t
 MediaRecorder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   size_t amount = 42;
   for (size_t i = 0; i < mSessions.Length(); ++i) {
     amount += mSessions[i]->SizeOfExcludingThis(aMallocSizeOf);
   }
   return amount;
--- a/dom/media/MediaRecorder.h
+++ b/dom/media/MediaRecorder.h
@@ -118,28 +118,25 @@ protected:
 
   MediaRecorder& operator = (const MediaRecorder& x) = delete;
   // Create dataavailable event with Blob data and it runs in main thread
   nsresult CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob);
   // Creating a simple event to notify UA simple event.
   void DispatchSimpleEvent(const nsAString & aStr);
   // Creating a error event with message.
   void NotifyError(nsresult aRv);
-  // Check if the recorder's principal is the subsume of mediaStream
-  bool CheckPrincipal();
   // Set encoded MIME type.
   void SetMimeType(const nsString &aMimeType);
   void SetOptions(const MediaRecorderOptions& aInitDict);
 
   MediaRecorder(const MediaRecorder& x) = delete; // prevent bad usage
   // Remove session pointer.
   void RemoveSession(Session* aSession);
   // Functions for Session to query input source info.
   MediaStream* GetSourceMediaStream();
-  nsIPrincipal* GetSourcePrincipal();
   // DOM wrapper for source media stream. Will be null when input is audio node.
   RefPtr<DOMMediaStream> mDOMStream;
   // Source audio node. Will be null when input is a media stream.
   RefPtr<AudioNode> mAudioNode;
   // Pipe stream connecting non-destination source node and session track union
   // stream of recorder. Will be null when input is media stream or destination
   // node.
   RefPtr<AudioNodeStream> mPipeStream;
--- a/dom/media/MediaSegment.h
+++ b/dom/media/MediaSegment.h
@@ -2,16 +2,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_MEDIASEGMENT_H_
 #define MOZILLA_MEDIASEGMENT_H_
 
 #include "nsTArray.h"
+#include "nsIPrincipal.h"
+#include "nsProxyRelease.h"
 #ifdef MOZILLA_INTERNAL_API
 #include "mozilla/TimeStamp.h"
 #endif
 #include <algorithm>
 #include "Latency.h"
 
 namespace mozilla {
 
@@ -50,16 +52,60 @@ const StreamTime STREAM_TIME_MAX = MEDIA
 
 /**
  * Media time relative to the start of the graph timeline.
  */
 typedef MediaTime GraphTime;
 const GraphTime GRAPH_TIME_MAX = MEDIA_TIME_MAX;
 
 /**
+ * We pass the principal through the MediaStreamGraph by wrapping it in a thread
+ * safe nsMainThreadPtrHandle, since it cannot be used directly off the main
+ * thread. We can compare two PrincipalHandles to each other on any thread, but
+ * they can only be created and converted back to nsIPrincipal* on main thread.
+ */
+typedef nsMainThreadPtrHandle<nsIPrincipal> PrincipalHandle;
+
+inline PrincipalHandle MakePrincipalHandle(nsIPrincipal* aPrincipal)
+{
+  RefPtr<nsMainThreadPtrHolder<nsIPrincipal>> holder =
+    new nsMainThreadPtrHolder<nsIPrincipal>(aPrincipal);
+  return PrincipalHandle(holder);
+}
+
+const PrincipalHandle PRINCIPAL_HANDLE_NONE(nullptr);
+
+inline nsIPrincipal* GetPrincipalFromHandle(PrincipalHandle& aPrincipalHandle)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return aPrincipalHandle.get();
+}
+
+inline bool PrincipalHandleMatches(PrincipalHandle& aPrincipalHandle,
+                                   nsIPrincipal* aOther)
+{
+  if (!aOther) {
+    return false;
+  }
+
+  nsIPrincipal* principal = GetPrincipalFromHandle(aPrincipalHandle);
+  if (!principal) {
+    return false;
+  }
+
+  bool result;
+  if (NS_FAILED(principal->Equals(aOther, &result))) {
+    NS_ERROR("Principal check failed");
+    return false;
+  }
+
+  return result;
+}
+
+/**
  * A MediaSegment is a chunk of media data sequential in time. Different
  * types of data have different subclasses of MediaSegment, all inheriting
  * from MediaSegmentBase.
  * All MediaSegment data is timed using StreamTime. The actual tick rate
  * is defined on a per-track basis. For some track types, this can be
  * a fixed constant for all tracks of that type (e.g. 1MHz for video).
  *
  * Each media segment defines a concept of "null media data" (e.g. silence
@@ -81,16 +127,29 @@ public:
 
   /**
    * Gets the total duration of the segment.
    */
   StreamTime GetDuration() const { return mDuration; }
   Type GetType() const { return mType; }
 
   /**
+   * Gets the last principal id that was appended to this segment.
+   */
+  PrincipalHandle GetLastPrincipalHandle() const { return mLastPrincipalHandle; }
+  /**
+   * Called by the MediaStreamGraph as it appends a chunk with a different
+   * principal id than the current one.
+   */
+  void SetLastPrincipalHandle(PrincipalHandle aLastPrincipalHandle)
+  {
+    mLastPrincipalHandle = aLastPrincipalHandle;
+  }
+
+  /**
    * Create a MediaSegment of the same type.
    */
   virtual MediaSegment* CreateEmptyClone() const = 0;
   /**
    * Moves contents of aSource to the end of this segment.
    */
   virtual void AppendFrom(MediaSegment* aSource) = 0;
   /**
@@ -129,23 +188,28 @@ public:
   }
 
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
 protected:
-  explicit MediaSegment(Type aType) : mDuration(0), mType(aType)
+  explicit MediaSegment(Type aType)
+    : mDuration(0), mType(aType), mLastPrincipalHandle(PRINCIPAL_HANDLE_NONE)
   {
     MOZ_COUNT_CTOR(MediaSegment);
   }
 
   StreamTime mDuration; // total of mDurations of all chunks
   Type mType;
+
+  // The latest principal handle that the MediaStreamGraph has processed for
+  // this segment.
+  PrincipalHandle mLastPrincipalHandle;
 };
 
 /**
  * C is the implementation class subclassed from MediaSegmentBase.
  * C must contain a Chunk class.
  */
 template <class C, class Chunk> class MediaSegmentBase : public MediaSegment {
 public:
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -23,26 +23,28 @@
 #include "AudioNodeStream.h"
 #include "AudioNodeExternalInputStream.h"
 #include "mozilla/dom/AudioContextBinding.h"
 #include "mozilla/media/MediaUtils.h"
 #include <algorithm>
 #include "DOMMediaStream.h"
 #include "GeckoProfiler.h"
 #include "mozilla/unused.h"
+#include "mozilla/media/MediaUtils.h"
 #ifdef MOZ_WEBRTC
 #include "AudioOutputObserver.h"
 #endif
 #include "mtransport/runnable_utils.h"
 
 #include "webaudio/blink/HRTFDatabaseLoader.h"
 
 using namespace mozilla::layers;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
+using namespace mozilla::media;
 
 namespace mozilla {
 
 LazyLogModule gMediaStreamGraphLog("MediaStreamGraph");
 #define STREAM_LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg)
 
 // #define ENABLE_LIFECYCLE_LOG
 
@@ -178,22 +180,31 @@ MediaStreamGraphImpl::ExtractPendingInpu
         }
       }
     }
     finished = aStream->mUpdateFinished;
     bool notifiedTrackCreated = false;
     for (int32_t i = aStream->mUpdateTracks.Length() - 1; i >= 0; --i) {
       SourceMediaStream::TrackData* data = &aStream->mUpdateTracks[i];
       aStream->ApplyTrackDisabling(data->mID, data->mData);
+      StreamTime offset = (data->mCommands & SourceMediaStream::TRACK_CREATE)
+          ? data->mStart : aStream->mBuffer.FindTrack(data->mID)->GetSegment()->GetDuration();
       for (MediaStreamListener* l : aStream->mListeners) {
-        StreamTime offset = (data->mCommands & SourceMediaStream::TRACK_CREATE)
-            ? data->mStart : aStream->mBuffer.FindTrack(data->mID)->GetSegment()->GetDuration();
         l->NotifyQueuedTrackChanges(this, data->mID,
                                     offset, data->mCommands, *data->mData);
       }
+      for (TrackBound<MediaStreamTrackListener>& b : aStream->mTrackListeners) {
+        if (b.mTrackID != data->mID) {
+          continue;
+        }
+        b.mListener->NotifyQueuedChanges(this, offset, *data->mData);
+        if (data->mCommands & SourceMediaStream::TRACK_END) {
+          b.mListener->NotifyEnded();
+        }
+      }
       if (data->mCommands & SourceMediaStream::TRACK_CREATE) {
         MediaSegment* segment = data->mData.forget();
         STREAM_LOG(LogLevel::Debug, ("SourceMediaStream %p creating track %d, start %lld, initial end %lld",
                                   aStream, data->mID, int64_t(data->mStart),
                                   int64_t(segment->GetDuration())));
 
         data->mEndOfFlushedData += segment->GetDuration();
         aStream->mBuffer.AddTrack(data->mID, data->mStart, segment);
@@ -301,16 +312,82 @@ MediaStreamGraphImpl::UpdateCurrentTimeF
       for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
         MediaStreamListener* l = stream->mListeners[j];
         l->NotifyEvent(this, MediaStreamListener::EVENT_FINISHED);
       }
     }
   }
 }
 
+template<typename C, typename Chunk>
+void
+MediaStreamGraphImpl::ProcessChunkMetadataForInterval(MediaStream* aStream,
+                                                      TrackID aTrackID,
+                                                      C& aSegment,
+                                                      StreamTime aStart,
+                                                      StreamTime aEnd)
+{
+  MOZ_ASSERT(aStream);
+  MOZ_ASSERT(IsTrackIDExplicit(aTrackID));
+
+  StreamTime offset = 0;
+  for (typename C::ConstChunkIterator chunk(aSegment);
+         !chunk.IsEnded(); chunk.Next()) {
+    if (offset >= aEnd) {
+      break;
+    }
+    offset += chunk->GetDuration();
+    if (chunk->IsNull() || offset < aStart) {
+      continue;
+    }
+    PrincipalHandle principalHandle = chunk->GetPrincipalHandle();
+    if (principalHandle != aSegment.GetLastPrincipalHandle()) {
+      aSegment.SetLastPrincipalHandle(principalHandle);
+      STREAM_LOG(LogLevel::Debug, ("MediaStream %p track %d, principalHandle "
+                                   "changed in %sChunk with duration %lld",
+                                   aStream, aTrackID,
+                                   aSegment.GetType() == MediaSegment::AUDIO
+                                       ? "Audio" : "Video",
+                                   (long long) chunk->GetDuration()));
+      for (const TrackBound<MediaStreamTrackListener>& listener : aStream->mTrackListeners) {
+        if (listener.mTrackID == aTrackID) {
+          listener.mListener->NotifyPrincipalHandleChanged(this, principalHandle);
+        }
+      }
+    }
+  }
+}
+
+void
+MediaStreamGraphImpl::ProcessChunkMetadata(GraphTime aPrevCurrentTime)
+{
+  for (MediaStream* stream : AllStreams()) {
+    StreamTime iterationStart = stream->GraphTimeToStreamTime(aPrevCurrentTime);
+    StreamTime iterationEnd = stream->GraphTimeToStreamTime(mProcessedTime);
+    for (StreamBuffer::TrackIter tracks(stream->mBuffer);
+            !tracks.IsEnded(); tracks.Next()) {
+      MediaSegment* segment = tracks->GetSegment();
+      if (!segment) {
+        continue;
+      }
+      if (tracks->GetType() == MediaSegment::AUDIO) {
+        AudioSegment* audio = static_cast<AudioSegment*>(segment);
+        ProcessChunkMetadataForInterval<AudioSegment, AudioChunk>(
+            stream, tracks->GetID(), *audio, iterationStart, iterationEnd);
+      } else if (tracks->GetType() == MediaSegment::VIDEO) {
+        VideoSegment* video = static_cast<VideoSegment*>(segment);
+        ProcessChunkMetadataForInterval<VideoSegment, VideoChunk>(
+            stream, tracks->GetID(), *video, iterationStart, iterationEnd);
+      } else {
+        MOZ_CRASH("Unknown track type");
+      }
+    }
+  }
+}
+
 GraphTime
 MediaStreamGraphImpl::WillUnderrun(MediaStream* aStream,
                                    GraphTime aEndBlockingDecisions)
 {
   // Finished streams can't underrun. ProcessedMediaStreams also can't cause
   // underrun currently, since we'll always be able to produce data for them
   // unless they block on some other stream.
   if (aStream->mFinished || aStream->AsProcessedStream()) {
@@ -836,16 +913,17 @@ MediaStreamGraphImpl::PlayVideo(MediaStr
 
   if (aStream->mVideoOutputs.IsEmpty())
     return;
 
   TimeStamp currentTimeStamp = CurrentDriver()->GetCurrentTimeStamp();
 
   // Collect any new frames produced in this iteration.
   AutoTArray<ImageContainer::NonOwningImage,4> newImages;
+  PrincipalHandle lastPrincipalHandle = PRINCIPAL_HANDLE_NONE;
   RefPtr<Image> blackImage;
 
   MOZ_ASSERT(mProcessedTime >= aStream->mBufferStartTime, "frame position before buffer?");
   // We only look at the non-blocking interval
   StreamTime frameBufferTime = aStream->GraphTimeToStreamTime(mProcessedTime);
   StreamTime bufferEndTime = aStream->GraphTimeToStreamTime(aStream->mStartBlocking);
   StreamTime start;
   const VideoChunk* chunk;
@@ -899,28 +977,34 @@ MediaStreamGraphImpl::PlayVideo(MediaStr
         }
       }
       if (blackImage) {
         image = blackImage;
       }
     }
     newImages.AppendElement(ImageContainer::NonOwningImage(image, targetTime));
 
+    lastPrincipalHandle = chunk->GetPrincipalHandle();
+
     aStream->mLastPlayedVideoFrame = *frame;
   }
 
   if (!aStream->mLastPlayedVideoFrame.GetImage())
     return;
 
   AutoTArray<ImageContainer::NonOwningImage,4> images;
   bool haveMultipleImages = false;
 
   for (uint32_t i = 0; i < aStream->mVideoOutputs.Length(); ++i) {
     VideoFrameContainer* output = aStream->mVideoOutputs[i];
 
+    bool principalHandleChanged =
+      lastPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
+      lastPrincipalHandle != output->GetLastPrincipalHandle();
+
     // Find previous frames that may still be valid.
     AutoTArray<ImageContainer::OwningImage,4> previousImages;
     output->GetImageContainer()->GetCurrentImages(&previousImages);
     uint32_t j = previousImages.Length();
     if (j) {
       // Re-use the most recent frame before currentTimeStamp and subsequent,
       // always keeping at least one frame.
       do {
@@ -950,16 +1034,22 @@ MediaStreamGraphImpl::PlayVideo(MediaStr
                                           image.mTimeStamp, image.mFrameID));
     }
 
     // Add the frames from this iteration.
     for (auto& image : newImages) {
       image.mFrameID = output->NewFrameID();
       images.AppendElement(image);
     }
+
+    if (principalHandleChanged) {
+      output->UpdatePrincipalHandleForFrameID(lastPrincipalHandle,
+                                              newImages.LastElement().mFrameID);
+    }
+
     output->SetCurrentFrames(aStream->mLastPlayedVideoFrame.GetIntrinsicSize(),
                              images);
 
     nsCOMPtr<nsIRunnable> event =
       new VideoFrameContainerInvalidateRunnable(output);
     DispatchToMainThreadAfterStreamStateUpdate(event.forget());
 
     images.ClearAndRetainStorage();
@@ -1440,16 +1530,18 @@ MediaStreamGraphImpl::OneIteration(Graph
 
   Process();
 
   GraphTime oldProcessedTime = mProcessedTime;
   mProcessedTime = stateEnd;
 
   UpdateCurrentTimeForStreams(oldProcessedTime);
 
+  ProcessChunkMetadata(oldProcessedTime);
+
   // Process graph messages queued from RunMessageAfterProcessing() on this
   // thread during the iteration.
   RunMessagesInQueue();
 
   return UpdateMainThreadState();
 }
 
 void
@@ -1834,16 +1926,17 @@ MediaStream::MediaStream(DOMMediaStream*
   , mNotifiedBlocked(false)
   , mHasCurrentData(false)
   , mNotifiedHasCurrentData(false)
   , mWrapper(aWrapper)
   , mMainThreadCurrentTime(0)
   , mMainThreadFinished(false)
   , mFinishedNotificationSent(false)
   , mMainThreadDestroyed(false)
+  , mNrOfMainThreadUsers(0)
   , mGraph(nullptr)
   , mAudioChannelType(dom::AudioChannel::Normal)
 {
   MOZ_COUNT_CTOR(MediaStream);
   // aWrapper should not already be connected to a MediaStream! It needs
   // to be hooked up to this stream, and since this stream is only just
   // being created now, aWrapper must not be connected to anything.
   NS_ASSERTION(!aWrapper || !aWrapper->GetPlaybackStream(),
@@ -1936,16 +2029,22 @@ MediaStream::GraphTimeToStreamTimeWithBl
 
 void
 MediaStream::FinishOnGraphThread()
 {
   GraphImpl()->FinishStream(this);
 }
 
 StreamBuffer::Track*
+MediaStream::FindTrack(TrackID aID)
+{
+  return mBuffer.FindTrack(aID);
+}
+
+StreamBuffer::Track*
 MediaStream::EnsureTrack(TrackID aTrackId)
 {
   StreamBuffer::Track* track = mBuffer.FindTrack(aTrackId);
   if (!track) {
     nsAutoPtr<MediaSegment> segment(new AudioSegment());
     for (uint32_t j = 0; j < mListeners.Length(); ++j) {
       MediaStreamListener* l = mListeners[j];
       l->NotifyQueuedTrackChanges(Graph(), aTrackId, 0,
@@ -1977,16 +2076,18 @@ MediaStream::DestroyImpl()
     mConsumers[i]->Disconnect();
   }
   mGraph = nullptr;
 }
 
 void
 MediaStream::Destroy()
 {
+  NS_ASSERTION(mNrOfMainThreadUsers == 0,
+               "Do not mix Destroy() and RegisterUser()/UnregisterUser()");
   // Keep this stream alive until we leave this method
   RefPtr<MediaStream> kungFuDeathGrip = this;
 
   class Message : public ControlMessage {
   public:
     explicit Message(MediaStream* aStream) : ControlMessage(aStream) {}
     void Run() override
     {
@@ -2002,16 +2103,36 @@ MediaStream::Destroy()
   GraphImpl()->AppendMessage(MakeUnique<Message>(this));
   // Message::RunDuringShutdown may have removed this stream from the graph,
   // but our kungFuDeathGrip above will have kept this stream alive if
   // necessary.
   mMainThreadDestroyed = true;
 }
 
 void
+MediaStream::RegisterUser()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ++mNrOfMainThreadUsers;
+}
+
+void
+MediaStream::UnregisterUser()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  --mNrOfMainThreadUsers;
+  NS_ASSERTION(mNrOfMainThreadUsers >= 0, "Double-removal of main thread user");
+  NS_ASSERTION(!IsDestroyed(), "Do not mix Destroy() and RegisterUser()/UnregisterUser()");
+  if (mNrOfMainThreadUsers == 0) {
+    Destroy();
+  }
+}
+
+void
 MediaStream::AddAudioOutput(void* aKey)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream, void* aKey) : ControlMessage(aStream), mKey(aKey) {}
     void Run() override
     {
       mStream->AddAudioOutputImpl(mKey);
@@ -2237,16 +2358,141 @@ MediaStream::RemoveListener(MediaStreamL
   // If the stream is destroyed the Listeners have or will be
   // removed.
   if (!IsDestroyed()) {
     GraphImpl()->AppendMessage(MakeUnique<Message>(this, aListener));
   }
 }
 
 void
+MediaStream::AddTrackListenerImpl(already_AddRefed<MediaStreamTrackListener> aListener,
+                                  TrackID aTrackID)
+{
+  TrackBound<MediaStreamTrackListener>* l = mTrackListeners.AppendElement();
+  l->mListener = aListener;
+  l->mTrackID = aTrackID;
+
+  StreamBuffer::Track* track = FindTrack(aTrackID);
+  if (!track) {
+    return;
+  }
+  PrincipalHandle lastPrincipalHandle =
+    track->GetSegment()->GetLastPrincipalHandle();
+  l->mListener->NotifyPrincipalHandleChanged(Graph(), lastPrincipalHandle);
+}
+
+void
+MediaStream::AddTrackListener(MediaStreamTrackListener* aListener,
+                              TrackID aTrackID)
+{
+  class Message : public ControlMessage {
+  public:
+    Message(MediaStream* aStream, MediaStreamTrackListener* aListener,
+            TrackID aTrackID) :
+      ControlMessage(aStream), mListener(aListener), mTrackID(aTrackID) {}
+    virtual void Run()
+    {
+      mStream->AddTrackListenerImpl(mListener.forget(), mTrackID);
+    }
+    RefPtr<MediaStreamTrackListener> mListener;
+    TrackID mTrackID;
+  };
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aListener, aTrackID));
+}
+
+void
+MediaStream::RemoveTrackListenerImpl(MediaStreamTrackListener* aListener,
+                                     TrackID aTrackID)
+{
+  for (size_t i = 0; i < mTrackListeners.Length(); ++i) {
+    if (mTrackListeners[i].mListener == aListener &&
+        mTrackListeners[i].mTrackID == aTrackID) {
+      mTrackListeners[i].mListener->NotifyRemoved();
+      mTrackListeners.RemoveElementAt(i);
+      return;
+    }
+  }
+}
+
+void
+MediaStream::RemoveTrackListener(MediaStreamTrackListener* aListener,
+                                 TrackID aTrackID)
+{
+  class Message : public ControlMessage {
+  public:
+    Message(MediaStream* aStream, MediaStreamTrackListener* aListener,
+            TrackID aTrackID) :
+      ControlMessage(aStream), mListener(aListener), mTrackID(aTrackID) {}
+    virtual void Run()
+    {
+      mStream->RemoveTrackListenerImpl(mListener, mTrackID);
+    }
+    RefPtr<MediaStreamTrackListener> mListener;
+    TrackID mTrackID;
+  };
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aListener, aTrackID));
+}
+
+void
+MediaStream::AddDirectTrackListenerImpl(already_AddRefed<MediaStreamTrackDirectListener> aListener,
+                                        TrackID aTrackID)
+{
+  // Base implementation, for streams that don't support direct track listeners.
+  RefPtr<MediaStreamTrackDirectListener> listener = aListener;
+  listener->NotifyDirectListenerInstalled(
+    MediaStreamTrackDirectListener::InstallationResult::STREAM_NOT_SUPPORTED);
+}
+
+void
+MediaStream::AddDirectTrackListener(MediaStreamTrackDirectListener* aListener,
+                                    TrackID aTrackID)
+{
+  class Message : public ControlMessage {
+  public:
+    Message(MediaStream* aStream, MediaStreamTrackDirectListener* aListener,
+            TrackID aTrackID) :
+      ControlMessage(aStream), mListener(aListener), mTrackID(aTrackID) {}
+    virtual void Run()
+    {
+      mStream->AddDirectTrackListenerImpl(mListener.forget(), mTrackID);
+    }
+    RefPtr<MediaStreamTrackDirectListener> mListener;
+    TrackID mTrackID;
+  };
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aListener, aTrackID));
+}
+
+void
+MediaStream::RemoveDirectTrackListenerImpl(MediaStreamTrackDirectListener* aListener,
+                                           TrackID aTrackID)
+{
+  // Base implementation, the listener was never added so nothing to do.
+  RefPtr<MediaStreamTrackDirectListener> listener = aListener;
+}
+
+void
+MediaStream::RemoveDirectTrackListener(MediaStreamTrackDirectListener* aListener,
+                                       TrackID aTrackID)
+{
+  class Message : public ControlMessage {
+  public:
+    Message(MediaStream* aStream, MediaStreamTrackDirectListener* aListener,
+            TrackID aTrackID) :
+      ControlMessage(aStream), mListener(aListener), mTrackID(aTrackID) {}
+    virtual void Run()
+    {
+      mStream->RemoveDirectTrackListenerImpl(mListener, mTrackID);
+    }
+    RefPtr<MediaStreamTrackDirectListener> mListener;
+    TrackID mTrackID;
+  };
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aListener, aTrackID));
+}
+
+void
 MediaStream::RunAfterPendingUpdates(already_AddRefed<nsIRunnable> aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MediaStreamGraphImpl* graph = GraphImpl();
   nsCOMPtr<nsIRunnable> runnable(aRunnable);
 
   // Special case when a non-realtime graph has not started, to ensure the
   // runnable will run in finite time.
@@ -2428,22 +2674,16 @@ SourceMediaStream::FinishAddTracks()
   MutexAutoLock lock(mMutex);
   mUpdateTracks.AppendElements(Move(mPendingTracks));
   LIFECYCLE_LOG("FinishAddTracks: %lu/%lu", mPendingTracks.Length(), mUpdateTracks.Length());
   if (GraphImpl()) {
     GraphImpl()->EnsureNextIteration();
   }
 }
 
-StreamBuffer::Track*
-SourceMediaStream::FindTrack(TrackID aID)
-{
-  return mBuffer.FindTrack(aID);
-}
-
 void
 SourceMediaStream::ResampleAudioToGraphSampleRate(TrackData* aTrackData, MediaSegment* aSegment)
 {
   if (aSegment->GetType() != MediaSegment::AUDIO ||
       aTrackData->mInputRate == GraphImpl()->GraphRate()) {
     return;
   }
   AudioSegment* segment = static_cast<AudioSegment*>(aSegment);
@@ -2499,25 +2739,34 @@ SourceMediaStream::AppendToTrack(TrackID
   }
   return appended;
 }
 
 void
 SourceMediaStream::NotifyDirectConsumers(TrackData *aTrack,
                                          MediaSegment *aSegment)
 {
-  // Call with mMutex locked
+  mMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(aTrack);
 
   for (uint32_t j = 0; j < mDirectListeners.Length(); ++j) {
     MediaStreamDirectListener* l = mDirectListeners[j];
     StreamTime offset = 0; // FIX! need a separate StreamTime.... or the end of the internal buffer
     l->NotifyRealtimeData(static_cast<MediaStreamGraph*>(GraphImpl()), aTrack->mID,
                           offset, aTrack->mCommands, *aSegment);
   }
+
+  for (const TrackBound<MediaStreamTrackDirectListener>& source
+         : mDirectTrackListeners) {
+    if (aTrack->mID != source.mTrackID) {
+      continue;
+    }
+    StreamTime offset = 0; // FIX! need a separate StreamTime.... or the end of the internal buffer
+    source.mListener->NotifyRealtimeTrackDataAndApplyTrackDisabling(Graph(), offset, *aSegment);
+  }
 }
 
 // These handle notifying all the listeners of an event
 void
 SourceMediaStream::NotifyListenersEventImpl(MediaStreamListener::MediaStreamGraphEvent aEvent)
 {
   for (uint32_t j = 0; j < mListeners.Length(); ++j) {
     MediaStreamListener* l = mListeners[j];
@@ -2568,16 +2817,73 @@ SourceMediaStream::RemoveDirectListener(
   }
 
   if (isEmpty) {
     // Async
     NotifyListenersEvent(MediaStreamListener::EVENT_HAS_NO_DIRECT_LISTENERS);
   }
 }
 
+void
+SourceMediaStream::AddDirectTrackListenerImpl(already_AddRefed<MediaStreamTrackDirectListener> aListener,
+                                              TrackID aTrackID)
+{
+  MOZ_ASSERT(IsTrackIDExplicit(aTrackID));
+  TrackData* data;
+  bool found;
+  bool isAudio;
+  RefPtr<MediaStreamTrackDirectListener> listener = aListener;
+  STREAM_LOG(LogLevel::Debug, ("Adding direct track listener %p bound to track %d to source stream %p",
+             listener.get(), aTrackID, this));
+  {
+    MutexAutoLock lock(mMutex);
+    data = FindDataForTrack(aTrackID);
+    found = !!data;
+    isAudio = found && data->mData->GetType() == MediaSegment::AUDIO;
+    if (found && isAudio) {
+      TrackBound<MediaStreamTrackDirectListener>* sourceListener =
+        mDirectTrackListeners.AppendElement();
+      sourceListener->mListener = listener;
+      sourceListener->mTrackID = aTrackID;
+    }
+  }
+  if (!found) {
+    STREAM_LOG(LogLevel::Warning, ("Couldn't find source track for direct track listener %p",
+                                   listener.get()));
+    listener->NotifyDirectListenerInstalled(
+      MediaStreamTrackDirectListener::InstallationResult::TRACK_NOT_FOUND_AT_SOURCE);
+    return;
+  }
+  if (!isAudio) {
+    STREAM_LOG(LogLevel::Warning, ("Source track for direct track listener %p is not audio",
+                                   listener.get()));
+    listener->NotifyDirectListenerInstalled(
+      MediaStreamTrackDirectListener::InstallationResult::TRACK_TYPE_NOT_SUPPORTED);
+    return;
+  }
+  STREAM_LOG(LogLevel::Debug, ("Added direct track listener %p", listener.get()));
+  listener->NotifyDirectListenerInstalled(
+    MediaStreamTrackDirectListener::InstallationResult::SUCCESS);
+}
+
+void
+SourceMediaStream::RemoveDirectTrackListenerImpl(MediaStreamTrackDirectListener* aListener,
+                                                 TrackID aTrackID)
+{
+  MutexAutoLock lock(mMutex);
+  for (int32_t i = mDirectTrackListeners.Length() - 1; i >= 0; --i) {
+    const TrackBound<MediaStreamTrackDirectListener>& source =
+      mDirectTrackListeners[i];
+    if (source.mListener == aListener && source.mTrackID == aTrackID) {
+      aListener->NotifyDirectListenerUninstalled();
+      mDirectTrackListeners.RemoveElementAt(i);
+    }
+  }
+}
+
 StreamTime
 SourceMediaStream::GetEndOfAppendedData(TrackID aID)
 {
   MutexAutoLock lock(mMutex);
   TrackData *track = FindDataForTrack(aID);
   if (track) {
     return track->mEndOfFlushedData + track->mData->GetDuration();
   }
@@ -2615,16 +2921,39 @@ SourceMediaStream::FinishWithLockHeld()
   mMutex.AssertCurrentThreadOwns();
   mUpdateFinished = true;
   if (auto graph = GraphImpl()) {
     graph->EnsureNextIteration();
   }
 }
 
 void
+SourceMediaStream::SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled)
+{
+  MutexAutoLock lock(mMutex);
+  for (TrackBound<MediaStreamTrackDirectListener>& l: mDirectTrackListeners) {
+    if (l.mTrackID == aTrackID) {
+      bool oldEnabled = !mDisabledTrackIDs.Contains(aTrackID);
+      if (!oldEnabled && aEnabled) {
+        STREAM_LOG(LogLevel::Debug, ("SourceMediaStream %p track %d setting "
+                                     "direct listener enabled",
+                                     this, aTrackID));
+        l.mListener->DecreaseDisabled();
+      } else if (oldEnabled && !aEnabled) {
+        STREAM_LOG(LogLevel::Debug, ("SourceMediaStream %p track %d setting "
+                                     "direct listener disabled",
+                                     this, aTrackID));
+        l.mListener->IncreaseDisabled();
+      }
+    }
+  }
+  MediaStream::SetTrackEnabledImpl(aTrackID, aEnabled);
+}
+
+void
 SourceMediaStream::EndAllTrackAndFinish()
 {
   MutexAutoLock lock(mMutex);
   for (uint32_t i = 0; i < mUpdateTracks.Length(); ++i) {
     SourceMediaStream::TrackData* data = &mUpdateTracks[i];
     data->mCommands |= TRACK_END;
   }
   mPendingTracks.Clear();
@@ -2733,44 +3062,60 @@ MediaInputPort::SetGraphImpl(MediaStream
 }
 
 void
 MediaInputPort::BlockTrackIdImpl(TrackID aTrackId)
 {
   mBlockedTracks.AppendElement(aTrackId);
 }
 
-void
+already_AddRefed<Pledge<bool>>
 MediaInputPort::BlockTrackId(TrackID aTrackId)
 {
   class Message : public ControlMessage {
   public:
-    explicit Message(MediaInputPort* aPort, TrackID aTrackId)
+    explicit Message(MediaInputPort* aPort,
+                     TrackID aTrackId,
+                     already_AddRefed<nsIRunnable> aRunnable)
       : ControlMessage(aPort->GetDestination()),
-        mPort(aPort), mTrackId(aTrackId) {}
+        mPort(aPort), mTrackId(aTrackId), mRunnable(aRunnable) {}
     void Run() override
     {
       mPort->BlockTrackIdImpl(mTrackId);
+      if (mRunnable) {
+        mStream->Graph()->DispatchToMainThreadAfterStreamStateUpdate(mRunnable.forget());
+      }
     }
     void RunDuringShutdown() override
     {
       Run();
     }
     RefPtr<MediaInputPort> mPort;
     TrackID mTrackId;
+    nsCOMPtr<nsIRunnable> mRunnable;
   };
 
-  MOZ_ASSERT(aTrackId != TRACK_NONE && aTrackId != TRACK_INVALID && aTrackId != TRACK_ANY,
+  MOZ_ASSERT(IsTrackIDExplicit(aTrackId),
              "Only explicit TrackID is allowed");
-  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aTrackId));
+
+  RefPtr<Pledge<bool>> pledge = new Pledge<bool>();
+  nsCOMPtr<nsIRunnable> runnable = NewRunnableFrom([pledge]() {
+    MOZ_ASSERT(NS_IsMainThread());
+    pledge->Resolve(true);
+    return NS_OK;
+  });
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aTrackId, runnable.forget()));
+  return pledge.forget();
 }
 
 already_AddRefed<MediaInputPort>
 ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, TrackID aTrackID,
-                                        uint16_t aInputNumber, uint16_t aOutputNumber)
+                                        TrackID aDestTrackID,
+                                        uint16_t aInputNumber, uint16_t aOutputNumber,
+                                        nsTArray<TrackID>* aBlockedTracks)
 {
   // This method creates two references to the MediaInputPort: one for
   // the main thread, and one for the MediaStreamGraph.
   class Message : public ControlMessage {
   public:
     explicit Message(MediaInputPort* aPort)
       : ControlMessage(aPort->GetDestination()),
         mPort(aPort) {}
@@ -2784,20 +3129,30 @@ ProcessedMediaStream::AllocateInputPort(
     void RunDuringShutdown() override
     {
       Run();
     }
     RefPtr<MediaInputPort> mPort;
   };
 
   MOZ_ASSERT(aStream->GraphImpl() == GraphImpl());
-  MOZ_ASSERT(aTrackID != TRACK_NONE && aTrackID != TRACK_INVALID,
-             "Only TRACK_ANY and explicit ID are allowed");
-  RefPtr<MediaInputPort> port = new MediaInputPort(aStream, aTrackID, this,
-                                                     aInputNumber, aOutputNumber);
+  MOZ_ASSERT(aTrackID == TRACK_ANY || IsTrackIDExplicit(aTrackID),
+             "Only TRACK_ANY and explicit ID are allowed for source track");
+  MOZ_ASSERT(aDestTrackID == TRACK_ANY || IsTrackIDExplicit(aDestTrackID),
+             "Only TRACK_ANY and explicit ID are allowed for destination track");
+  MOZ_ASSERT(aTrackID != TRACK_ANY || aDestTrackID == TRACK_ANY,
+             "Generic MediaInputPort cannot produce a single destination track");
+  RefPtr<MediaInputPort> port =
+    new MediaInputPort(aStream, aTrackID, this, aDestTrackID,
+                       aInputNumber, aOutputNumber);
+  if (aBlockedTracks) {
+    for (TrackID trackID : *aBlockedTracks) {
+      port->BlockTrackIdImpl(trackID);
+    }
+  }
   port->SetGraphImpl(GraphImpl());
   GraphImpl()->AppendMessage(MakeUnique<Message>(port));
   return port.forget();
 }
 
 void
 ProcessedMediaStream::Finish()
 {
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -36,16 +36,20 @@ class nsAutoRefTraits<SpeexResamplerStat
 namespace mozilla {
 
 extern LazyLogModule gMediaStreamGraphLog;
 
 namespace dom {
   enum class AudioContextOperation;
 }
 
+namespace media {
+  template<typename V, typename E> class Pledge;
+}
+
 /*
  * MediaStreamGraph is a framework for synchronized audio/video processing
  * and playback. It is designed to be used by other browser components such as
  * HTML media elements, media capture APIs, real-time media streaming APIs,
  * multitrack media APIs, and advanced audio APIs.
  *
  * The MediaStreamGraph uses a dedicated thread to process media --- the media
  * graph thread. This ensures that we can process media through the graph
@@ -212,16 +216,54 @@ protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~AudioDataListener() {}
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioDataListener)
 };
 
 /**
+ * This is a base class for media graph thread listener callbacks locked to
+ * specific tracks. Override methods to be notified of audio or video data or
+ * changes in track state.
+ *
+ * All notification methods are called from the media graph thread. Overriders
+ * of these methods are responsible for all synchronization. Beware!
+ * These methods are called without the media graph monitor held, so
+ * reentry into media graph methods is possible, although very much discouraged!
+ * You should do something non-blocking and non-reentrant (e.g. dispatch an
+ * event to some thread) and return.
+ * The listener is not allowed to add/remove any listeners from the parent
+ * stream.
+ *
+ * If a listener is attached to a track that has already ended, we guarantee
+ * to call NotifyEnded.
+ */
+class MediaStreamTrackListener
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStreamTrackListener)
+
+public:
+  virtual void NotifyQueuedChanges(MediaStreamGraph* aGraph,
+                                   StreamTime aTrackOffset,
+                                   const MediaSegment& aQueuedMedia) {}
+
+  virtual void NotifyPrincipalHandleChanged(MediaStreamGraph* aGraph,
+                                            const PrincipalHandle& aNewPrincipalHandle) {}
+
+  virtual void NotifyEnded() {}
+
+  virtual void NotifyRemoved() {}
+
+protected:
+  virtual ~MediaStreamTrackListener() {}
+};
+
+
+/**
  * This is a base class for media graph thread listener direct callbacks
  * from within AppendToTrack().  Note that your regular listener will
  * still get NotifyQueuedTrackChanges() callbacks from the MSG thread, so
  * you must be careful to ignore them if AddDirectListener was successful.
  */
 class MediaStreamDirectListener : public MediaStreamListener
 {
 public:
@@ -235,16 +277,127 @@ public:
    */
   virtual void NotifyRealtimeData(MediaStreamGraph* aGraph, TrackID aID,
                                   StreamTime aTrackOffset,
                                   uint32_t aTrackEvents,
                                   const MediaSegment& aMedia) {}
 };
 
 /**
+ * This is a base class for media graph thread listener direct callbacks from
+ * within AppendToTrack(). It is bound to a certain track and can only be
+ * installed on audio tracks. Once added to a track on any stream in the graph,
+ * the graph will try to install it at that track's source of media data.
+ *
+ * This works for TrackUnionStreams, which will forward the listener to the
+ * track's input track if it exists, or wait for it to be created before
+ * forwarding if it doesn't.
+ * Once it reaches a SourceMediaStream, it can be successfully installed.
+ * Other types of streams will fail installation since they are not supported.
+ *
+ * Note that this listener and others for the same track will still get
+ * NotifyQueuedChanges() callbacks from the MSG tread, so you must be careful
+ * to ignore them if this listener was successfully installed.
+ */
+class MediaStreamTrackDirectListener : public MediaStreamTrackListener
+{
+  friend class SourceMediaStream;
+  friend class TrackUnionStream;
+
+public:
+  /*
+   * This will be called on any MediaStreamTrackDirectListener added to a
+   * SourceMediaStream when AppendToTrack() is called for the listener's bound
+   * track. The MediaSegment will be the RawSegment (unresampled) if available
+   * in AppendToTrack().
+   * If the track is enabled at the source but has been disabled in one of the
+   * streams in between the source and where it was originally added, aMedia
+   * will be a disabled version of the one passed to AppendToTrack() as well.
+   * Note that NotifyQueuedTrackChanges() calls will also
+   * still occur.
+   */
+  virtual void NotifyRealtimeTrackData(MediaStreamGraph* aGraph,
+                                       StreamTime aTrackOffset,
+                                       const MediaSegment& aMedia) {}
+
+  /**
+   * When a direct listener is processed for installation by the
+   * MediaStreamGraph it will be notified with whether the installation was
+   * successful or not. The results of this installation are the following:
+   * TRACK_NOT_FOUND_AT_SOURCE
+   *    We found the source stream of media data for this track, but the track
+   *    didn't exist. This should only happen if you try to install the listener
+   *    directly to a SourceMediaStream that doesn't contain the given TrackID.
+   * TRACK_TYPE_NOT_SUPPORTED
+   *    This is the failure when you install the listener to a non-audio track.
+   * STREAM_NOT_SUPPORTED
+   *    While looking for the data source of this track, we found a MediaStream
+   *    that is not a SourceMediaStream or a TrackUnionStream.
+   * SUCCESS
+   *    Installation was successful and this listener will start receiving
+   *    NotifyRealtimeData on the next AppendToTrack().
+   */
+  enum class InstallationResult {
+    TRACK_NOT_FOUND_AT_SOURCE,
+    TRACK_TYPE_NOT_SUPPORTED,
+    STREAM_NOT_SUPPORTED,
+    SUCCESS
+  };
+  virtual void NotifyDirectListenerInstalled(InstallationResult aResult) {}
+  virtual void NotifyDirectListenerUninstalled() {}
+
+protected:
+  virtual ~MediaStreamTrackDirectListener() {}
+
+  void MirrorAndDisableSegment(AudioSegment& aFrom, AudioSegment& aTo)
+  {
+    aTo.Clear();
+    aTo.AppendNullData(aFrom.GetDuration());
+  }
+
+  void NotifyRealtimeTrackDataAndApplyTrackDisabling(MediaStreamGraph* aGraph,
+                                                     StreamTime aTrackOffset,
+                                                     MediaSegment& aMedia)
+  {
+    if (mDisabledCount == 0) {
+      NotifyRealtimeTrackData(aGraph, aTrackOffset, aMedia);
+      return;
+    }
+
+    if (!mMedia) {
+      mMedia = aMedia.CreateEmptyClone();
+    }
+    if (aMedia.GetType() == MediaSegment::AUDIO) {
+      MirrorAndDisableSegment(static_cast<AudioSegment&>(aMedia),
+                              static_cast<AudioSegment&>(*mMedia));
+    } else {
+      MOZ_CRASH("Unsupported media type");
+    }
+    NotifyRealtimeTrackData(aGraph, aTrackOffset, *mMedia);
+  }
+
+  void IncreaseDisabled()
+  {
+    ++mDisabledCount;
+  }
+  void DecreaseDisabled()
+  {
+    --mDisabledCount;
+    MOZ_ASSERT(mDisabledCount >= 0, "Double decrease");
+  }
+
+  // Matches the number of disabled streams to which this listener is attached.
+  // The number of streams are those between the stream the listener was added
+  // and the SourceMediaStream that is the input of the data.
+  Atomic<int32_t> mDisabledCount;
+
+  nsAutoPtr<MediaSegment> mMedia;
+};
+
+/**
  * This is a base class for main-thread listener callbacks.
  * This callback is invoked on the main thread when the main-thread-visible
  * state of a stream has changed.
  *
  * These methods are called with the media graph monitor held, so
  * reentry into general media graph methods is not possible.
  * You should do something non-blocking and non-reentrant (e.g. dispatch an
  * event) and return. DispatchFromMainThreadAfterNextStreamStateUpdate
@@ -274,16 +427,26 @@ class SourceMediaStream;
 class ProcessedMediaStream;
 class MediaInputPort;
 class AudioNodeEngine;
 class AudioNodeExternalInputStream;
 class AudioNodeStream;
 class CameraPreviewMediaStream;
 
 /**
+ * Helper struct for binding a track listener to a specific TrackID.
+ */
+template<typename Listener>
+struct TrackBound
+{
+  RefPtr<Listener> mListener;
+  TrackID mTrackID;
+};
+
+/**
  * A stream of synchronized audio and video data. All (not blocked) streams
  * progress at the same rate --- "real time". Streams cannot seek. The only
  * operation readers can perform on a stream is to read the next data.
  *
  * Consumers of a stream can be reading from it at different offsets, but that
  * should only happen due to the order in which consumers are being run.
  * Those offsets must not diverge in the long term, otherwise we would require
  * unbounded buffering.
@@ -395,16 +558,43 @@ public:
   // Suspend message reaches the graph, the stream stops processing. It
   // ignores its inputs and produces silence/no video until Resumed. Its
   // current time does not advance.
   virtual void Suspend();
   virtual void Resume();
   // Events will be dispatched by calling methods of aListener.
   virtual void AddListener(MediaStreamListener* aListener);
   virtual void RemoveListener(MediaStreamListener* aListener);
+  virtual void AddTrackListener(MediaStreamTrackListener* aListener,
+                                TrackID aTrackID);
+  virtual void RemoveTrackListener(MediaStreamTrackListener* aListener,
+                                   TrackID aTrackID);
+
+  /**
+   * Adds aListener to the source stream of track aTrackID in this stream.
+   * When the MediaStreamGraph processes the added listener, it will traverse
+   * the graph and add it to the track's source stream (remapping the TrackID
+   * along the way).
+   * Note that the listener will be notified on the MediaStreamGraph thread
+   * with whether the installation of it at the source was successful or not.
+   */
+  virtual void AddDirectTrackListener(MediaStreamTrackDirectListener* aListener,
+                                      TrackID aTrackID);
+
+  /**
+   * Removes aListener from the source stream of track aTrackID in this stream.
+   * Note that the listener has already been removed if the link between the
+   * source of track aTrackID and this stream has been broken (and made track
+   * aTrackID end). The caller doesn't have to care about this, removing when
+   * the source cannot be found, or when the listener had already been removed
+   * does nothing.
+   */
+  virtual void RemoveDirectTrackListener(MediaStreamTrackDirectListener* aListener,
+                                         TrackID aTrackID);
+
   // A disabled track has video replaced by black, and audio replaced by
   // silence.
   void SetTrackEnabled(TrackID aTrackID, bool aEnabled);
 
   // Finish event will be notified by calling methods of aListener. It is the
   // responsibility of the caller to remove aListener before it is destroyed.
   void AddMainThreadListener(MainThreadMediaStreamListener* aListener);
   // It's safe to call this even if aListener is not currently a listener;
@@ -426,18 +616,29 @@ public:
    * and has not started, then the runnable will be run
    * synchronously/immediately.  (There are no pending updates in these
    * situations.)
    *
    * Main thread only.
    */
   void RunAfterPendingUpdates(already_AddRefed<nsIRunnable> aRunnable);
 
-  // Signal that the client is done with this MediaStream. It will be deleted later.
+  // Signal that the client is done with this MediaStream. It will be deleted
+  // later. Do not mix usage of Destroy() with RegisterUser()/UnregisterUser().
+  // That will cause the MediaStream to be destroyed twice, which will cause
+  // some assertions to fail.
   virtual void Destroy();
+  // Signal that a client is using this MediaStream. Useful to not have to
+  // explicitly manage ownership (responsibility to Destroy()) when there are
+  // multiple clients using a MediaStream.
+  void RegisterUser();
+  // Signal that a client no longer needs this MediaStream. When the number of
+  // clients using this MediaStream reaches 0, it will be destroyed.
+  void UnregisterUser();
+
   // Returns the main-thread's view of how much data has been processed by
   // this stream.
   StreamTime GetCurrentTime()
   {
     NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
     return mMainThreadCurrentTime;
   }
   // Return the main thread's view of whether this stream has finished.
@@ -480,16 +681,24 @@ public:
     return !mAudioOutputs.IsEmpty();
   }
   void RemoveAudioOutputImpl(void* aKey);
   void AddVideoOutputImpl(already_AddRefed<VideoFrameContainer> aContainer);
   void RemoveVideoOutputImpl(VideoFrameContainer* aContainer);
   void AddListenerImpl(already_AddRefed<MediaStreamListener> aListener);
   void RemoveListenerImpl(MediaStreamListener* aListener);
   void RemoveAllListenersImpl();
+  virtual void AddTrackListenerImpl(already_AddRefed<MediaStreamTrackListener> aListener,
+                                    TrackID aTrackID);
+  virtual void RemoveTrackListenerImpl(MediaStreamTrackListener* aListener,
+                                       TrackID aTrackID);
+  virtual void AddDirectTrackListenerImpl(already_AddRefed<MediaStreamTrackDirectListener> aListener,
+                                          TrackID aTrackID);
+  virtual void RemoveDirectTrackListenerImpl(MediaStreamTrackDirectListener* aListener,
+                                             TrackID aTrackID);
   virtual void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled);
 
   void AddConsumer(MediaInputPort* aPort)
   {
     mConsumers.AppendElement(aPort);
   }
   void RemoveConsumer(MediaInputPort* aPort)
   {
@@ -551,16 +760,21 @@ public:
    */
   GraphTime StreamTimeToGraphTime(StreamTime aTime);
 
   bool IsFinishedOnGraphThread() { return mFinished; }
   void FinishOnGraphThread();
 
   bool HasCurrentData() { return mHasCurrentData; }
 
+  /**
+   * Find track by track id.
+   */
+  StreamBuffer::Track* FindTrack(TrackID aID);
+
   StreamBuffer::Track* EnsureTrack(TrackID aTrack);
 
   virtual void ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment, MediaSegment* aRawSegment = nullptr);
 
   DOMMediaStream* GetWrapper()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only use DOMMediaStream on main thread");
     return mWrapper;
@@ -633,16 +847,17 @@ protected:
     float mVolume;
   };
   nsTArray<AudioOutput> mAudioOutputs;
   nsTArray<RefPtr<VideoFrameContainer> > mVideoOutputs;
   // We record the last played video frame to avoid playing the frame again
   // with a different frame id.
   VideoFrame mLastPlayedVideoFrame;
   nsTArray<RefPtr<MediaStreamListener> > mListeners;
+  nsTArray<TrackBound<MediaStreamTrackListener>> mTrackListeners;
   nsTArray<MainThreadMediaStreamListener*> mMainThreadListeners;
   nsTArray<TrackID> mDisabledTrackIDs;
 
   // GraphTime at which this stream starts blocking.
   // This is only valid up to mStateComputedTime. The stream is considered to
   // have not been blocked before mCurrentTime (its mBufferStartTime is increased
   // as necessary to account for that time instead).
   GraphTime mStartBlocking;
@@ -701,16 +916,17 @@ protected:
 
   // This state is only used on the main thread.
   DOMMediaStream* mWrapper;
   // Main-thread views of state
   StreamTime mMainThreadCurrentTime;
   bool mMainThreadFinished;
   bool mFinishedNotificationSent;
   bool mMainThreadDestroyed;
+  int mNrOfMainThreadUsers;
 
   // Our media stream graph.  null if destroyed on the graph thread.
   MediaStreamGraphImpl* mGraph;
 
   dom::AudioChannel mAudioChannelType;
 };
 
 /**
@@ -792,21 +1008,16 @@ public:
 
   /**
    * Call after a series of AddTrack or AddAudioTrack calls to implement
    * any pending track adds.
    */
   void FinishAddTracks();
 
   /**
-   * Find track by track id.
-   */
-  StreamBuffer::Track* FindTrack(TrackID aID);
-
-  /**
    * Append media data to a track. Ownership of aSegment remains with the caller,
    * but aSegment is emptied.
    * Returns false if the data was not appended because no such track exists
    * or the stream was already finished.
    */
   bool AppendToTrack(TrackID aID, MediaSegment* aSegment, MediaSegment *aRawSegment = nullptr);
   /**
    * Get the stream time of the end of the data that has been appended so far.
@@ -834,21 +1045,17 @@ public:
   void FinishWithLockHeld();
   void Finish()
   {
     MutexAutoLock lock(mMutex);
     FinishWithLockHeld();
   }
 
   // Overriding allows us to hold the mMutex lock while changing the track enable status
-  void
-  SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) override {
-    MutexAutoLock lock(mMutex);
-    MediaStream::SetTrackEnabledImpl(aTrackID, aEnabled);
-  }
+  void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) override;
 
   // Overriding allows us to ensure mMutex is locked while changing the track enable status
   void
   ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment,
                       MediaSegment* aRawSegment = nullptr) override {
     mMutex.AssertCurrentThreadOwns();
     MediaStream::ApplyTrackDisabling(aTrackID, aSegment, aRawSegment);
   }
@@ -901,16 +1108,21 @@ protected:
     // this is cleared.
     uint32_t mCommands;
   };
 
   bool NeedsMixing();
 
   void ResampleAudioToGraphSampleRate(TrackData* aTrackData, MediaSegment* aSegment);
 
+  void AddDirectTrackListenerImpl(already_AddRefed<MediaStreamTrackDirectListener> aListener,
+                                  TrackID aTrackID) override;
+  void RemoveDirectTrackListenerImpl(MediaStreamTrackDirectListener* aListener,
+                                     TrackID aTrackID) override;
+
   void AddTrackInternal(TrackID aID, TrackRate aRate,
                         StreamTime aStart, MediaSegment* aSegment,
                         uint32_t aFlags);
 
   TrackData* FindDataForTrack(TrackID aID)
   {
     mMutex.AssertCurrentThreadOwns();
     for (uint32_t i = 0; i < mUpdateTracks.Length(); ++i) {
@@ -939,52 +1151,62 @@ protected:
   // This must be acquired *before* MediaStreamGraphImpl's lock, if they are
   // held together.
   Mutex mMutex;
   // protected by mMutex
   StreamTime mUpdateKnownTracksTime;
   nsTArray<TrackData> mUpdateTracks;
   nsTArray<TrackData> mPendingTracks;
   nsTArray<RefPtr<MediaStreamDirectListener> > mDirectListeners;
+  nsTArray<TrackBound<MediaStreamTrackDirectListener>> mDirectTrackListeners;
   bool mPullEnabled;
   bool mUpdateFinished;
   bool mNeedsMixing;
 };
 
 /**
  * Represents a connection between a ProcessedMediaStream and one of its
  * input streams.
  * We make these refcounted so that stream-related messages with MediaInputPort*
  * pointers can be sent to the main thread safely.
  *
  * A port can be locked to a specific track in the source stream, in which case
  * only this track will be forwarded to the destination stream. TRACK_ANY
  * can used to signal that all tracks shall be forwarded.
  *
+ * When a port is locked to a specific track in the source stream, it may also
+ * indicate a TrackID to map this source track to in the destination stream
+ * by setting aDestTrack to an explicit ID. When we do this, we must know
+ * that this TrackID in the destination stream is available. We assert during
+ * processing that the ID is available and that there are no generic input
+ * ports already attached to the destination stream.
+ * Note that this is currently only handled by TrackUnionStreams.
+ *
  * When a port's source or destination stream dies, the stream's DestroyImpl
  * calls MediaInputPort::Disconnect to disconnect the port from
  * the source and destination streams.
  *
  * The lifetimes of MediaInputPort are controlled from the main thread.
  * The media graph adds a reference to the port. When a MediaInputPort is no
  * longer needed, main-thread code sends a Destroy message for the port and
  * clears its reference (the last main-thread reference to the object). When
  * the Destroy message is processed on the graph manager thread we disconnect
  * the port and drop the graph's reference, destroying the object.
  */
 class MediaInputPort final
 {
 private:
   // Do not call this constructor directly. Instead call aDest->AllocateInputPort.
   MediaInputPort(MediaStream* aSource, TrackID& aSourceTrack,
-                 ProcessedMediaStream* aDest,
+                 ProcessedMediaStream* aDest, TrackID& aDestTrack,
                  uint16_t aInputNumber, uint16_t aOutputNumber)
     : mSource(aSource)
     , mSourceTrack(aSourceTrack)
     , mDest(aDest)
+    , mDestTrack(aDestTrack)
     , mInputNumber(aInputNumber)
     , mOutputNumber(aOutputNumber)
     , mGraph(nullptr)
   {
     MOZ_COUNT_CTOR(MediaInputPort);
   }
 
   // Private destructor, to discourage deletion outside of Release():
@@ -1008,19 +1230,24 @@ public:
    * object again.
    */
   void Destroy();
 
   // Any thread
   MediaStream* GetSource() { return mSource; }
   TrackID GetSourceTrackId() { return mSourceTrack; }
   ProcessedMediaStream* GetDestination() { return mDest; }
+  TrackID GetDestinationTrackId() { return mDestTrack; }
 
-  // Block aTrackId in the port. Consumers will interpret this track as ended.
-  void BlockTrackId(TrackID aTrackId);
+  /**
+   * Block aTrackId in the port. Consumers will interpret this track as ended.
+   * Returns a pledge that resolves on the main thread after the track block has
+   * been applied by the MSG.
+   */
+  already_AddRefed<media::Pledge<bool, nsresult>> BlockTrackId(TrackID aTrackId);
 private:
   void BlockTrackIdImpl(TrackID aTrackId);
 
 public:
   // Returns true if aTrackId has not been blocked and this port has not
   // been locked to another track.
   bool PassTrackThrough(TrackID aTrackId) {
     return !mBlockedTracks.Contains(aTrackId) &&
@@ -1069,16 +1296,17 @@ public:
 private:
   friend class MediaStreamGraphImpl;
   friend class MediaStream;
   friend class ProcessedMediaStream;
   // Never modified after Init()
   MediaStream* mSource;
   TrackID mSourceTrack;
   ProcessedMediaStream* mDest;
+  TrackID mDestTrack;
   // The input and output numbers are optional, and are currently only used by
   // Web Audio.
   const uint16_t mInputNumber;
   const uint16_t mOutputNumber;
   nsTArray<TrackID> mBlockedTracks;
 
   // Our media stream graph
   MediaStreamGraphImpl* mGraph;
@@ -1095,25 +1323,41 @@ public:
   explicit ProcessedMediaStream(DOMMediaStream* aWrapper)
     : MediaStream(aWrapper), mAutofinish(false), mCycleMarker(0)
   {}
 
   // Control API.
   /**
    * Allocates a new input port attached to source aStream.
    * This stream can be removed by calling MediaInputPort::Remove().
+   *
    * The input port is tied to aTrackID in the source stream.
    * aTrackID can be set to TRACK_ANY to automatically forward all tracks from
-   * aStream. To end a track in the destination stream forwarded with TRACK_ANY,
+   * aStream.
+   *
+   * If aTrackID is an explicit ID, aDestTrackID can also be made explicit
+   * to ensure that the track is assigned this ID in the destination stream.
+   * To avoid intermittent TrackID collisions the destination stream may not
+   * have any existing generic input ports (with TRACK_ANY source track) when
+   * you allocate an input port with a destination TrackID.
+   *
+   * To end a track in the destination stream forwarded with TRACK_ANY,
    * it can be blocked in the input port through MediaInputPort::BlockTrackId().
+   *
+   * Tracks in aBlockedTracks will be blocked in the input port initially. This
+   * ensures that they don't get created by the MSG-thread before we can
+   * BlockTrackId() on the main thread.
    */
-  already_AddRefed<MediaInputPort> AllocateInputPort(MediaStream* aStream,
-                                                     TrackID aTrackID = TRACK_ANY,
-                                                     uint16_t aInputNumber = 0,
-                                                     uint16_t aOutputNumber = 0);
+  already_AddRefed<MediaInputPort>
+  AllocateInputPort(MediaStream* aStream,
+                    TrackID aTrackID = TRACK_ANY,
+                    TrackID aDestTrackID = TRACK_ANY,
+                    uint16_t aInputNumber = 0,
+                    uint16_t aOutputNumber = 0,
+                    nsTArray<TrackID>* aBlockedTracks = nullptr);
   /**
    * Force this stream into the finished state.
    */
   void Finish();
   /**
    * Set the autofinish flag on this stream (defaults to false). When this flag
    * is set, and all input streams are in the finished state (including if there
    * are no input streams), this stream automatically enters the finished state.
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -253,16 +253,31 @@ public:
    */
   bool ShouldUpdateMainThread();
   // The following methods are the various stages of RunThread processing.
   /**
    * Advance all stream state to mStateComputedTime.
    */
   void UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime);
   /**
+   * Process chunks for all streams and raise events for properties that have
+   * changed, such as principalId.
+   */
+  void ProcessChunkMetadata(GraphTime aPrevCurrentTime);
+  /**
+   * Process chunks for the given stream and interval, and raise events for
+   * properties that have changed, such as principalId.
+   */
+  template<typename C, typename Chunk>
+  void ProcessChunkMetadataForInterval(MediaStream* aStream,
+                                       TrackID aTrackID,
+                                       C& aSegment,
+                                       StreamTime aStart,
+                                       StreamTime aEnd);
+  /**
    * Process graph messages in mFrontMessageQueue.
    */
   void RunMessagesInQueue();
   /**
    * Update stream processing order and recompute stream blocking until
    * aEndBlockingDecisions.
    */
   void UpdateGraph(GraphTime aEndBlockingDecisions);
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -1,25 +1,126 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaStreamTrack.h"
 
 #include "DOMMediaStream.h"
+#include "MediaStreamGraph.h"
 #include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 
+#ifdef LOG
+#undef LOG
+#endif
+
+static PRLogModuleInfo* gMediaStreamTrackLog;
+#define LOG(type, msg) MOZ_LOG(gMediaStreamTrackLog, type, msg)
+
 namespace mozilla {
 namespace dom {
 
-MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
-  : mOwningStream(aStream), mTrackID(aTrackID), mLabel(aLabel), mEnded(false), mEnabled(true)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackSource)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION(MediaStreamTrackSource, mPrincipal)
+
+already_AddRefed<Promise>
+MediaStreamTrackSource::ApplyConstraints(nsPIDOMWindowInner* aWindow,
+                                         const dom::MediaTrackConstraints& aConstraints,
+                                         ErrorResult &aRv)
+{
+  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(aWindow);
+  RefPtr<Promise> promise = Promise::Create(go, aRv);
+  MOZ_RELEASE_ASSERT(!aRv.Failed());
+
+  promise->MaybeReject(new MediaStreamError(
+    aWindow,
+    NS_LITERAL_STRING("OverconstrainedError"),
+    NS_LITERAL_STRING(""),
+    NS_LITERAL_STRING("")));
+  return promise.forget();
+}
+
+/**
<