merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 19 Jul 2016 16:09:20 +0200
changeset 347598 5a91e5b49be3c1ba401b057e90c92d7488e3647d
parent 347581 37cc0da01187534ea9f8d384c0c9c5c57eb67b56 (diff)
parent 347597 e2ddf53209916302a81ca331f88b4df8df0ca7c7 (current diff)
child 347649 3383b0da1a14340ec6096aca542eb73b0f7341d5
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.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 fx-team to mozilla-central a=merge
dom/html/HTMLMediaElement.cpp
toolkit/components/telemetry/Histograms.json
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -53,21 +53,18 @@ pageInfoTreeView.prototype = {
     this.rowCountChanged(this.rows - 1, 1);
     if (this.selection.count == 0 && this.rowCount && !gImageElement) {
       this.selection.select(0);
     }
   },
 
   addRows: function(rows)
   {
-    this.data = this.data.concat(rows);
-    this.rowCountChanged(this.rows, rows.length);
-    this.rows = this.data.length;
-    if (this.selection.count == 0 && this.rowCount && !gImageElement) {
-      this.selection.select(0);
+    for (let row of rows) {
+      this.addRow(row);
     }
   },
 
   rowCountChanged: function(index, count)
   {
     this.tree.rowCountChanged(index, count);
   },
 
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -34,16 +34,42 @@ typedef ASTConsumer *ASTConsumerPtr;
 // ones for compatibility with older versions.
 #define cxxConstructExpr constructExpr
 #define cxxConstructorDecl constructorDecl
 #define cxxMethodDecl methodDecl
 #define cxxNewExpr newExpr
 #define cxxRecordDecl recordDecl
 #endif
 
+// Check if the given expression contains an assignment expression.
+// This can either take the form of a Binary Operator or a
+// Overloaded Operator Call.
+bool HasSideEffectAssignment(const Expr *expr) {
+  if (auto opCallExpr = dyn_cast_or_null<CXXOperatorCallExpr>(expr)) {
+    auto binOp = opCallExpr->getOperator();
+    if (binOp == OO_Equal || (binOp >= OO_PlusEqual && binOp <= OO_PipeEqual)) {
+      return true;
+    }
+  } else if (auto binOpExpr = dyn_cast_or_null<BinaryOperator>(expr)) {
+    if (binOpExpr->isAssignmentOp()) {
+      return true;
+    }
+  }
+
+  // Recurse to children.
+  for (const Stmt *SubStmt : expr->children()) {
+    auto childExpr = dyn_cast_or_null<Expr>(SubStmt);
+    if (childExpr && HasSideEffectAssignment(childExpr)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 namespace {
 
 using namespace clang::ast_matchers;
 class DiagnosticsMatcher {
 public:
   DiagnosticsMatcher();
 
   ASTConsumerPtr makeASTConsumer() { return astMatcher.newASTConsumer(); }
@@ -119,31 +145,37 @@ private:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
   class RefCountedCopyConstructorChecker : public MatchFinder::MatchCallback {
   public:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
+  class AssertAssignmentChecker : public MatchFinder::MatchCallback {
+  public:
+    virtual void run(const MatchFinder::MatchResult &Result);
+  };
+
   ScopeChecker scopeChecker;
   ArithmeticArgChecker arithmeticArgChecker;
   TrivialCtorDtorChecker trivialCtorDtorChecker;
   NaNExprChecker nanExprChecker;
   NoAddRefReleaseOnReturnChecker noAddRefReleaseOnReturnChecker;
   RefCountedInsideLambdaChecker refCountedInsideLambdaChecker;
   ExplicitOperatorBoolChecker explicitOperatorBoolChecker;
   NoDuplicateRefCntMemberChecker noDuplicateRefCntMemberChecker;
   NeedsNoVTableTypeChecker needsNoVTableTypeChecker;
   NonMemMovableTemplateArgChecker nonMemMovableTemplateArgChecker;
   NonMemMovableMemberChecker nonMemMovableMemberChecker;
   ExplicitImplicitChecker explicitImplicitChecker;
   NoAutoTypeChecker noAutoTypeChecker;
   NoExplicitMoveConstructorChecker noExplicitMoveConstructorChecker;
   RefCountedCopyConstructorChecker refCountedCopyConstructorChecker;
+  AssertAssignmentChecker assertAttributionChecker;
   MatchFinder astMatcher;
 };
 
 namespace {
 
 std::string getDeclarationNamespace(const Decl *decl) {
   const DeclContext *DC =
       decl->getDeclContext()->getEnclosingNamespaceContext();
@@ -757,16 +789,25 @@ AST_MATCHER(QualType, autoNonAutoableTyp
 
 AST_MATCHER(CXXConstructorDecl, isExplicitMoveConstructor) {
   return Node.isExplicit() && Node.isMoveConstructor();
 }
 
 AST_MATCHER(CXXConstructorDecl, isCompilerProvidedCopyConstructor) {
   return !Node.isUserProvided() && Node.isCopyConstructor();
 }
+
+AST_MATCHER(CallExpr, isAssertAssignmentTestFunc) {
+  static const std::string assertName = "MOZ_AssertAssignmentTest";
+  const FunctionDecl *method = Node.getDirectCallee();
+
+  return method
+      && method->getDeclName().isIdentifier()
+      && method->getName() == assertName;
+}
 }
 }
 
 namespace {
 
 void CustomTypeAnnotation::dumpAnnotationReason(DiagnosticsEngine &Diag,
                                                 QualType T,
                                                 SourceLocation Loc) {
@@ -1065,16 +1106,20 @@ DiagnosticsMatcher::DiagnosticsMatcher()
       &noExplicitMoveConstructorChecker);
 
   astMatcher.addMatcher(
       cxxConstructExpr(
           hasDeclaration(cxxConstructorDecl(isCompilerProvidedCopyConstructor(),
                                             ofClass(hasRefCntMember()))))
           .bind("node"),
       &refCountedCopyConstructorChecker);
+
+  astMatcher.addMatcher(
+      callExpr(isAssertAssignmentTestFunc()).bind("funcCall"),
+      &assertAttributionChecker);
 }
 
 // These enum variants determine whether an allocation has occured in the code.
 enum AllocationVariety {
   AV_None,
   AV_Global,
   AV_Automatic,
   AV_Temporary,
@@ -1540,16 +1585,28 @@ void DiagnosticsMatcher::RefCountedCopyC
   // Everything we needed to know was checked in the matcher - we just report
   // the error here
   const CXXConstructExpr *E = Result.Nodes.getNodeAs<CXXConstructExpr>("node");
 
   Diag.Report(E->getLocation(), ErrorID);
   Diag.Report(E->getLocation(), NoteID);
 }
 
+void DiagnosticsMatcher::AssertAssignmentChecker::run(
+    const MatchFinder::MatchResult &Result) {
+  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
+  unsigned assignInsteadOfComp = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Error, "Forbidden assignment in assert expression");
+  const CallExpr *funcCall = Result.Nodes.getNodeAs<CallExpr>("funcCall");
+
+  if (funcCall && HasSideEffectAssignment(funcCall)) {
+    Diag.Report(funcCall->getLocStart(), assignInsteadOfComp);
+  }
+}
+
 class MozCheckAction : public PluginASTAction {
 public:
   ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI,
                                    StringRef fileName) override {
 #if CLANG_VERSION_FULL >= 306
     std::unique_ptr<MozChecker> checker(llvm::make_unique<MozChecker>(CI));
     ASTConsumerPtr other(checker->getOtherConsumer());
 
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestAssertWithAssignment.cpp
@@ -0,0 +1,68 @@
+#include "mozilla/MacroArgs.h"
+
+static __attribute__((always_inline)) bool MOZ_AssertAssignmentTest(bool expr) {
+  return expr;
+}
+
+#define MOZ_UNLIKELY(x) (__builtin_expect(!!(x), 0))
+#define MOZ_CRASH() do { } while(0)
+#define MOZ_CHECK_ASSERT_ASSIGNMENT(expr) MOZ_AssertAssignmentTest(!!(expr))
+
+#define MOZ_ASSERT_HELPER1(expr) \
+  do { \
+    if (MOZ_UNLIKELY(!MOZ_CHECK_ASSERT_ASSIGNMENT(expr))) { \
+      MOZ_CRASH();\
+    } \
+  } while(0) \
+
+/* Now the two-argument form. */
+#define MOZ_ASSERT_HELPER2(expr, explain) \
+  do { \
+    if (MOZ_UNLIKELY(!MOZ_CHECK_ASSERT_ASSIGNMENT(expr))) { \
+      MOZ_CRASH();\
+    } \
+  } while(0) \
+
+#define MOZ_RELEASE_ASSERT_GLUE(a, b) a b
+#define MOZ_RELEASE_ASSERT(...) \
+  MOZ_RELEASE_ASSERT_GLUE( \
+    MOZ_PASTE_PREFIX_AND_ARG_COUNT(MOZ_ASSERT_HELPER, __VA_ARGS__), \
+    (__VA_ARGS__))
+
+#define MOZ_ASSERT(...) MOZ_RELEASE_ASSERT(__VA_ARGS__)
+
+void FunctionTest(int p) {
+  MOZ_ASSERT(p = 1); // expected-error {{Forbidden assignment in assert expression}}
+}
+
+void FunctionTest2(int p) {
+  MOZ_ASSERT(((p = 1)));  // expected-error {{Forbidden assignment in assert expression}}
+}
+
+void FunctionTest3(int p) {
+  MOZ_ASSERT(p != 3);
+}
+
+class TestOverloading {
+  int value;
+public:
+  explicit TestOverloading(int _val) : value(_val) {}
+  // different operators
+  explicit operator bool() const { return true; }
+  TestOverloading& operator=(const int _val) { value = _val; return *this; }
+
+  int& GetInt() {return value;}
+};
+
+void TestOverloadingFunc() {
+  TestOverloading p(2);
+  int f;
+
+  MOZ_ASSERT(p);
+  MOZ_ASSERT(p = 3); // expected-error {{Forbidden assignment in assert expression}}
+  MOZ_ASSERT(p, "p is not valid");
+  MOZ_ASSERT(p = 3, "p different than 3"); // expected-error {{Forbidden assignment in assert expression}}
+  MOZ_ASSERT(p.GetInt() = 2); // expected-error {{Forbidden assignment in assert expression}}
+  MOZ_ASSERT(p.GetInt() == 2);
+  MOZ_ASSERT(p.GetInt() == 2, f = 3);
+}
--- a/build/clang-plugin/tests/moz.build
+++ b/build/clang-plugin/tests/moz.build
@@ -3,16 +3,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/.
 
 # dummy library name to avoid skipping building the sources here.
 Library('clang-plugin-tests')
 
 SOURCES += [
+    'TestAssertWithAssignment.cpp',
     'TestBadImplicitConversionCtor.cpp',
     'TestCustomHeap.cpp',
     'TestExplicitOperatorBool.cpp',
     'TestGlobalClass.cpp',
     'TestHeapClass.cpp',
     'TestInheritTypeAnnotationsFromTemplateArgs.cpp',
     'TestMultipleAnnotations.cpp',
     'TestMustOverride.cpp',
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -227,17 +227,17 @@ def get_compiler_info(compiler, language
         %COMPILER clang-cl
         %VERSION _MSC_FULL_VER
         #else
         %COMPILER msvc
         %VERSION _MSC_FULL_VER
         #endif
         #elif defined(__clang__)
         %COMPILER clang
-        #  if !__cplusplus || __cpp_static_assert
+        #  if !__cplusplus || __has_feature(cxx_alignof)
         %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
         #  endif
         #elif defined(__GNUC__)
         %COMPILER gcc
         %VERSION __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
         #endif
 
         #if __cplusplus
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -5,24 +5,26 @@ support-files =
 [test_frame_01.html]
 [test_HSplitBox_01.html]
 [test_notification_box_01.html]
 [test_notification_box_02.html]
 [test_notification_box_03.html]
 [test_reps_array.html]
 [test_reps_attribute.html]
 [test_reps_date-time.html]
+[test_reps_document.html]
 [test_reps_function.html]
 [test_reps_grip.html]
 [test_reps_grip-array.html]
 [test_reps_null.html]
 [test_reps_number.html]
 [test_reps_object.html]
 [test_reps_object-with-text.html]
 [test_reps_object-with-url.html]
+[test_reps_regexp.html]
 [test_reps_string.html]
 [test_reps_stylesheet.html]
 [test_reps_text-node.html]
 [test_reps_undefined.html]
 [test_reps_window.html]
 [test_sidebar_toggle.html]
 [test_tree_01.html]
 [test_tree_02.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_reps_document.html
@@ -0,0 +1,54 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+Test Document rep
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Rep test - Document</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
+window.onload = Task.async(function* () {
+  let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
+  let { Document } = browserRequire("devtools/client/shared/components/reps/document");
+
+  try {
+    let gripStub = {
+      "type": "object",
+      "class": "HTMLDocument",
+      "actor": "server1.conn17.obj115",
+      "extensible": true,
+      "frozen": false,
+      "sealed": false,
+      "ownPropertyLength": 1,
+      "preview": {
+        "kind": "DOMNode",
+        "nodeType": 9,
+        "nodeName": "#document",
+        "location": "https://www.mozilla.org/en-US/firefox/new/"
+      }
+    };
+
+    // Test that correct rep is chosen
+    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
+    is(renderedRep.type, Document.rep, `Rep correctly selects ${Document.rep.displayName}`);
+
+    // Test rendering
+    const renderedComponent = renderComponent(Document.rep, { object: gripStub });
+    is(renderedComponent.textContent, "/en-US/firefox/new/", "Document rep has expected text content");
+  } catch(e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+});
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_reps_regexp.html
@@ -0,0 +1,49 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+Test RegExp rep
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Rep test - RegExp</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
+window.onload = Task.async(function* () {
+  try {
+    let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
+    let { RegExp } = browserRequire("devtools/client/shared/components/reps/regexp");
+
+    let gripStub = {
+      "type": "object",
+      "class": "RegExp",
+      "actor": "server1.conn22.obj39",
+      "extensible": true,
+      "frozen": false,
+      "sealed": false,
+      "ownPropertyLength": 1,
+      "displayString": "/ab+c/i"
+    };
+
+    // Test that correct rep is chosen
+    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
+    is(renderedRep.type, RegExp.rep, `Rep correctly selects ${RegExp.rep.displayName}`);
+
+    // Test rendering
+    const renderedComponent = renderComponent(RegExp.rep, { object: gripStub });
+    is(renderedComponent.textContent, "/ab+c/i", "RegExp rep has expected text content");
+  } catch(e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+});
+</script>
+</pre>
+</body>
+</html>
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -69,16 +69,17 @@
 #include "mozilla/dom/MediaSource.h"
 #include "MediaMetadataManager.h"
 #include "MediaSourceDecoder.h"
 #include "MediaStreamListener.h"
 #include "DOMMediaStream.h"
 #include "AudioStreamTrack.h"
 #include "VideoStreamTrack.h"
 #include "MediaTrackList.h"
+#include "MediaStreamError.h"
 
 #include "AudioChannelService.h"
 
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/WakeLock.h"
 
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
@@ -2115,23 +2116,25 @@ public:
     return MediaSourceEnum::Other;
   }
 
   CORSMode GetCORSMode() const override
   {
     return mElement->GetCORSMode();
   }
 
-  already_AddRefed<Promise>
+  already_AddRefed<PledgeVoid>
   ApplyConstraints(nsPIDOMWindowInner* aWindow,
-                   const dom::MediaTrackConstraints& aConstraints,
-                   ErrorResult &aRv) override
+                   const dom::MediaTrackConstraints& aConstraints) override
   {
-    NS_ERROR("ApplyConstraints not implemented for media element capture");
-    return nullptr;
+    RefPtr<PledgeVoid> p = new PledgeVoid();
+    p->Reject(new dom::MediaStreamError(aWindow,
+                                        NS_LITERAL_STRING("OverconstrainedError"),
+                                        NS_LITERAL_STRING("")));
+    return p.forget();
   }
 
   void Stop() override
   {
     NS_ERROR("We're reporting remote=true to not be stoppable. "
              "Stop() should not be called.");
   }
 
--- a/dom/media/AccurateSeekTask.cpp
+++ b/dom/media/AccurateSeekTask.cpp
@@ -319,16 +319,17 @@ AccurateSeekTask::OnAudioDecoded(MediaDa
 
   AdjustFastSeekIfNeeded(audio);
 
   if (mTarget.IsFast()) {
     // Non-precise seek; we can stop the seek at the first sample.
     mSeekedAudioData = audio;
     mDoneAudioSeeking = true;
   } else if (NS_FAILED(DropAudioUpToSeekTarget(audio))) {
+    CancelCallbacks();
     RejectIfExist(__func__);
     return;
   }
 
   if (!mDoneAudioSeeking) {
     RequestAudioData();
     return;
   }
@@ -346,16 +347,17 @@ AccurateSeekTask::OnNotDecoded(MediaData
 
   // Ignore pending requests from video-only seek.
   if (aType == MediaData::AUDIO_DATA && mTarget.IsVideoOnly()) {
     return;
   }
 
   if (aReason == MediaDecoderReader::DECODE_ERROR) {
     // If this is a decode error, delegate to the generic error path.
+    CancelCallbacks();
     RejectIfExist(__func__);
     return;
   }
 
   // If the decoder is waiting for data, we tell it to call us back when the
   // data arrives.
   if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
     mReader->WaitForData(aType);
@@ -410,16 +412,17 @@ AccurateSeekTask::OnVideoDecoded(MediaDa
 
   AdjustFastSeekIfNeeded(video);
 
   if (mTarget.IsFast()) {
     // Non-precise seek. We can stop the seek at the first sample.
     mSeekedVideoData = video;
     mDoneVideoSeeking = true;
   } else if (NS_FAILED(DropVideoUpToSeekTarget(video.get()))) {
+    CancelCallbacks();
     RejectIfExist(__func__);
     return;
   }
 
   if (!mDoneVideoSeeking) {
     RequestVideoData();
     return;
   }
@@ -470,14 +473,14 @@ AccurateSeekTask::SetCallbacks()
     }
   });
 }
 
 void
 AccurateSeekTask::CancelCallbacks()
 {
   AssertOwnerThread();
-  mAudioCallback.Disconnect();
-  mVideoCallback.Disconnect();
-  mAudioWaitCallback.Disconnect();
-  mVideoWaitCallback.Disconnect();
+  mAudioCallback.DisconnectIfExists();
+  mVideoCallback.DisconnectIfExists();
+  mAudioWaitCallback.DisconnectIfExists();
+  mVideoWaitCallback.DisconnectIfExists();
 }
 } // namespace mozilla
--- a/dom/media/AudioStreamTrack.h
+++ b/dom/media/AudioStreamTrack.h
@@ -11,18 +11,19 @@
 
 namespace mozilla {
 namespace dom {
 
 class AudioStreamTrack : public MediaStreamTrack {
 public:
   AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
                    TrackID aInputTrackID,
-                   MediaStreamTrackSource* aSource)
-    : MediaStreamTrack(aStream, aTrackID, aInputTrackID, aSource) {}
+                   MediaStreamTrackSource* aSource,
+                   const MediaTrackConstraints& aConstraints = MediaTrackConstraints())
+    : MediaStreamTrack(aStream, aTrackID, aInputTrackID, aSource, aConstraints) {}
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   AudioStreamTrack* AsAudioStreamTrack() override { return this; }
 
   const AudioStreamTrack* AsAudioStreamTrack() const override { return this; }
 
   // WebIDL
@@ -30,16 +31,17 @@ public:
 
 protected:
   already_AddRefed<MediaStreamTrack> CloneInternal(DOMMediaStream* aOwningStream,
                                                    TrackID aTrackID) override
   {
     return do_AddRef(new AudioStreamTrack(aOwningStream,
                                           aTrackID,
                                           mInputTrackID,
-                                          mSource));
+                                          mSource,
+                                          mConstraints));
   }
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* AUDIOSTREAMTRACK_H_ */
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -974,30 +974,31 @@ bool
 DOMMediaStream::RemovePrincipalChangeObserver(
   PrincipalChangeObserver<DOMMediaStream>* aObserver)
 {
   return mPrincipalChangeObservers.RemoveElement(aObserver);
 }
 
 MediaStreamTrack*
 DOMMediaStream::CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType,
-                               MediaStreamTrackSource* aSource)
+                               MediaStreamTrackSource* aSource,
+                               const MediaTrackConstraints& aConstraints)
 {
   MOZ_RELEASE_ASSERT(mInputStream);
   MOZ_RELEASE_ASSERT(mOwnedStream);
 
   MOZ_ASSERT(FindOwnedDOMTrack(GetInputStream(), aTrackID) == nullptr);
 
   MediaStreamTrack* track;
   switch (aType) {
   case MediaSegment::AUDIO:
-    track = new AudioStreamTrack(this, aTrackID, aTrackID, aSource);
+    track = new AudioStreamTrack(this, aTrackID, aTrackID, aSource, aConstraints);
     break;
   case MediaSegment::VIDEO:
-    track = new VideoStreamTrack(this, aTrackID, aTrackID, aSource);
+    track = new VideoStreamTrack(this, aTrackID, aTrackID, aSource, aConstraints);
     break;
   default:
     MOZ_CRASH("Unhandled track type");
   }
 
   LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p with ID %u", this, track, aTrackID));
 
   mOwnedTracks.AppendElement(
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -9,16 +9,17 @@
 #include "ImageContainer.h"
 
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "StreamTracks.h"
 #include "nsIDOMWindow.h"
 #include "nsIPrincipal.h"
+#include "MediaTrackConstraints.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "PrincipalChangeObserver.h"
 
 // X11 has a #define for CurrentTime. Unbelievable :-(.
 // See dom/media/webaudio/AudioContext.h for more fun!
 #ifdef CurrentTime
 #undef CurrentTime
 #endif
@@ -43,17 +44,16 @@ 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
 
 namespace media {
@@ -517,17 +517,18 @@ public:
    * Creates a MediaStreamTrack, adds it to mTracks, raises "addtrack" and
    * returns it.
    *
    * Note that "addtrack" is raised synchronously and only has an effect if
    * this MediaStream is already exposed to script. For spec compliance this is
    * to be called from an async task.
    */
   MediaStreamTrack* CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType,
-                                   MediaStreamTrackSource* aSource);
+      MediaStreamTrackSource* aSource,
+      const MediaTrackConstraints& aConstraints = MediaTrackConstraints());
 
   /**
    * 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);
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -621,16 +621,19 @@ MediaDecoder::Shutdown()
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown) {
     return;
   }
 
   mShuttingDown = true;
 
+  // Unwatch all watch targets to prevent further notifications.
+  mWatchManager.Shutdown();
+
   mResourceCallback->Disconnect();
 
 #ifdef MOZ_EME
   mCDMProxyPromiseHolder.RejectIfExists(true, __func__);
 #endif
 
   // This changes the decoder state to SHUTDOWN and does other things
   // necessary to unblock the state machine thread if it's blocked, so
@@ -638,18 +641,16 @@ MediaDecoder::Shutdown()
   if (mDecoderStateMachine) {
     mTimedMetadataListener.Disconnect();
     mMetadataLoadedListener.Disconnect();
     mFirstFrameLoadedListener.Disconnect();
     mOnPlaybackEvent.Disconnect();
     mOnSeekingStart.Disconnect();
     mOnMediaNotSeekable.Disconnect();
 
-    mWatchManager.Unwatch(mIsAudioDataAudible, &MediaDecoder::NotifyAudibleStateChanged);
-
     mDecoderStateMachine->BeginShutdown()
       ->Then(AbstractThread::MainThread(), __func__, this,
              &MediaDecoder::FinishShutdown,
              &MediaDecoder::FinishShutdown);
   } else {
     // Ensure we always unregister asynchronously in order not to disrupt
     // the hashtable iterating in MediaShutdownManager::Shutdown().
     RefPtr<MediaDecoder> self = this;
@@ -1576,38 +1577,52 @@ MediaDecoder::SetPlaybackRate(double aPl
 void
 MediaDecoder::SetPreservesPitch(bool aPreservesPitch)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mPreservesPitch = aPreservesPitch;
 }
 
 void
+MediaDecoder::ConnectMirrors(MediaDecoderStateMachine* aObject)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aObject);
+  mStateMachineDuration.Connect(aObject->CanonicalDuration());
+  mBuffered.Connect(aObject->CanonicalBuffered());
+  mStateMachineIsShutdown.Connect(aObject->CanonicalIsShutdown());
+  mNextFrameStatus.Connect(aObject->CanonicalNextFrameStatus());
+  mCurrentPosition.Connect(aObject->CanonicalCurrentPosition());
+  mPlaybackPosition.Connect(aObject->CanonicalPlaybackOffset());
+  mIsAudioDataAudible.Connect(aObject->CanonicalIsAudioDataAudible());
+}
+
+void
+MediaDecoder::DisconnectMirrors()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mStateMachineDuration.DisconnectIfConnected();
+  mBuffered.DisconnectIfConnected();
+  mStateMachineIsShutdown.DisconnectIfConnected();
+  mNextFrameStatus.DisconnectIfConnected();
+  mCurrentPosition.DisconnectIfConnected();
+  mPlaybackPosition.DisconnectIfConnected();
+  mIsAudioDataAudible.DisconnectIfConnected();
+}
+
+void
 MediaDecoder::SetStateMachine(MediaDecoderStateMachine* aStateMachine)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT_IF(aStateMachine, !mDecoderStateMachine);
   mDecoderStateMachine = aStateMachine;
-
-  if (mDecoderStateMachine) {
-    mStateMachineDuration.Connect(mDecoderStateMachine->CanonicalDuration());
-    mBuffered.Connect(mDecoderStateMachine->CanonicalBuffered());
-    mStateMachineIsShutdown.Connect(mDecoderStateMachine->CanonicalIsShutdown());
-    mNextFrameStatus.Connect(mDecoderStateMachine->CanonicalNextFrameStatus());
-    mCurrentPosition.Connect(mDecoderStateMachine->CanonicalCurrentPosition());
-    mPlaybackPosition.Connect(mDecoderStateMachine->CanonicalPlaybackOffset());
-    mIsAudioDataAudible.Connect(mDecoderStateMachine->CanonicalIsAudioDataAudible());
+  if (aStateMachine) {
+    ConnectMirrors(aStateMachine);
   } else {
-    mStateMachineDuration.DisconnectIfConnected();
-    mBuffered.DisconnectIfConnected();
-    mStateMachineIsShutdown.DisconnectIfConnected();
-    mNextFrameStatus.DisconnectIfConnected();
-    mCurrentPosition.DisconnectIfConnected();
-    mPlaybackPosition.DisconnectIfConnected();
-    mIsAudioDataAudible.DisconnectIfConnected();
+    DisconnectMirrors();
   }
 }
 
 ImageContainer*
 MediaDecoder::GetImageContainer()
 {
   return mVideoFrameContainer ? mVideoFrameContainer->GetImageContainer() : nullptr;
 }
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -598,16 +598,19 @@ private:
 
   void OnMediaNotSeekable()
   {
     SetMediaSeekable(false);
   }
 
   void FinishShutdown();
 
+  void ConnectMirrors(MediaDecoderStateMachine* aObject);
+  void DisconnectMirrors();
+
   MediaEventProducer<void> mDataArrivedEvent;
 
   // The state machine object for handling the decoding. It is safe to
   // call methods of this object from other threads. Its internal data
   // is synchronised on a monitor. The lifetime of this object is
   // after mPlayState is LOADING and before mPlayState is SHUTDOWN. It
   // is safe to access it during this period.
   //
--- a/dom/media/MediaDecoderReaderWrapper.h
+++ b/dom/media/MediaDecoderReaderWrapper.h
@@ -4,16 +4,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/. */
 
 #ifndef MediaDecoderReaderWrapper_h_
 #define MediaDecoderReaderWrapper_h_
 
 #include "mozilla/AbstractThread.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/Variant.h"
 #include "nsISupportsImpl.h"
 
 #include "MediaDecoderReader.h"
 #include "MediaEventSource.h"
 
 namespace mozilla {
 
 class StartTimeRendezvous;
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1137,26 +1137,19 @@ MediaDecoderStateMachine::SetDormant(boo
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (IsShutdown()) {
     return;
   }
 
   if (mMetadataRequest.Exists()) {
-    if (mPendingDormant && mPendingDormant.ref() != aDormant && !aDormant) {
-      // We already have a dormant request pending; the new request would have
-      // resumed from dormant, we can just cancel any pending dormant requests.
-      mPendingDormant.reset();
-    } else {
-      mPendingDormant = Some(aDormant);
-    }
+    mPendingDormant = aDormant;
     return;
   }
-  mPendingDormant.reset();
 
   DECODER_LOG("SetDormant=%d", aDormant);
 
   if (aDormant) {
     if (mState == DECODER_STATE_SEEKING) {
       if (mQueuedSeek.Exists()) {
         // Keep latest seek target
       } else if (mCurrentSeek.Exists()) {
@@ -1198,17 +1191,17 @@ MediaDecoderStateMachine::SetDormant(boo
 
     // Note that we do not wait for the decode task queue to go idle before
     // queuing the ReleaseMediaResources task - instead, we disconnect promises,
     // reset state, and put a ResetDecode in the decode task queue. Any tasks
     // that run after ResetDecode are supposed to run with a clean slate. We rely
     // on that in other places (i.e. seeking), so it seems reasonable to rely on
     // it here as well.
     mReader->ReleaseMediaResources();
-  } else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) {
+  } else if (mState == DECODER_STATE_DORMANT) {
     mDecodingFirstFrame = true;
     SetState(DECODER_STATE_DECODING_METADATA);
     ReadMetadata();
   }
 }
 
 RefPtr<ShutdownPromise>
 MediaDecoderStateMachine::Shutdown()
@@ -1693,24 +1686,16 @@ MediaDecoderStateMachine::OnSeekTaskReso
     StopPrerollingAudio();
   }
 
   if (aValue.mIsVideoQueueFinished) {
     VideoQueue().Finish();
     StopPrerollingVideo();
   }
 
-  if (aValue.mNeedToStopPrerollingAudio) {
-    StopPrerollingAudio();
-  }
-
-  if (aValue.mNeedToStopPrerollingVideo) {
-    StopPrerollingVideo();
-  }
-
   SeekCompleted();
 }
 
 void
 MediaDecoderStateMachine::OnSeekTaskRejected(SeekTaskRejectValue aValue)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mState == DECODER_STATE_SEEKING);
@@ -1722,24 +1707,16 @@ MediaDecoderStateMachine::OnSeekTaskReje
     StopPrerollingAudio();
   }
 
   if (aValue.mIsVideoQueueFinished) {
     VideoQueue().Finish();
     StopPrerollingVideo();
   }
 
-  if (aValue.mNeedToStopPrerollingAudio) {
-    StopPrerollingAudio();
-  }
-
-  if (aValue.mNeedToStopPrerollingVideo) {
-    StopPrerollingVideo();
-  }
-
   DecodeError();
 
   DiscardSeekTaskIfExist();
 }
 
 void
 MediaDecoderStateMachine::DiscardSeekTaskIfExist()
 {
@@ -1987,17 +1964,18 @@ MediaDecoderStateMachine::DecodeError()
 void
 MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
   mMetadataRequest.Complete();
 
   if (mPendingDormant) {
-    SetDormant(mPendingDormant.ref());
+    mPendingDormant = false;
+    SetDormant(true);
     return;
   }
 
   // Set mode to PLAYBACK after reading metadata.
   mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
   mInfo = aMetadata->mInfo;
   mMetadataTags = aMetadata->mTags.forget();
   RefPtr<MediaDecoderStateMachine> self = this;
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -92,16 +92,17 @@ hardware (via AudioStream).
 #include "MediaDecoder.h"
 #include "MediaDecoderReader.h"
 #include "MediaDecoderOwner.h"
 #include "MediaEventSource.h"
 #include "MediaMetadataManager.h"
 #include "MediaStatistics.h"
 #include "MediaTimer.h"
 #include "ImageContainer.h"
+#include "SeekJob.h"
 #include "SeekTask.h"
 
 namespace mozilla {
 
 namespace media {
 class MediaSink;
 }
 
@@ -851,18 +852,20 @@ private:
   // the state machine thread. Synchronised via decoder monitor.
   // When data is being sent to a MediaStream, this is true when all data has
   // been written to the MediaStream.
   Watchable<bool> mAudioCompleted;
 
   // True if all video frames are already rendered.
   Watchable<bool> mVideoCompleted;
 
-  // Set if MDSM receives dormant request during reading metadata.
-  Maybe<bool> mPendingDormant;
+  // True if we need to enter dormant state after reading metadata. Note that
+  // we can't enter dormant state until reading metadata is done for some
+  // limitations of the reader.
+  bool mPendingDormant = false;
 
   // Flag whether we notify metadata before decoding the first frame or after.
   //
   // Note that the odd semantics here are designed to replicate the current
   // behavior where we notify the decoder each time we come out of dormant, but
   // send suppressed event visibility for those cases. This code can probably be
   // simplified.
   bool mNotifyMetadataBeforeFirstFrame;
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -42,16 +42,17 @@
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/GetUserMediaRequestBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/Base64.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/media/MediaChild.h"
+#include "mozilla/media/MediaTaskUtils.h"
 #include "MediaTrackConstraints.h"
 #include "VideoUtils.h"
 #include "Latency.h"
 #include "nsProxyRelease.h"
 #include "nsNullPrincipal.h"
 #include "nsVariant.h"
 
 // For snprintf
@@ -350,16 +351,23 @@ public:
   bool CapturingBrowser()
   {
     MOZ_ASSERT(NS_IsMainThread());
     return mVideoDevice && !mStopped &&
            mVideoDevice->GetSource()->IsAvailable() &&
            mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Browser;
   }
 
+  void GetSettings(dom::MediaTrackSettings& aOutSettings)
+  {
+    if (mVideoDevice) {
+      mVideoDevice->GetSource()->GetSettings(aOutSettings);
+    }
+  }
+
   // implement in .cpp to avoid circular dependency with MediaOperationTask
   // Can be invoked from EITHER MainThread or MSG thread
   void Stop();
 
   void
   AudioConfig(bool aEchoOn, uint32_t aEcho,
               bool aAgcOn, uint32_t aAGC,
               bool aNoiseOn, uint32_t aNoise,
@@ -580,21 +588,21 @@ public:
         break;
 
       case MEDIA_STOP:
       case MEDIA_STOP_TRACK:
         {
           NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
           if (mAudioDevice) {
             mAudioDevice->GetSource()->Stop(source, kAudioTrack);
-            mAudioDevice->GetSource()->Deallocate();
+            mAudioDevice->Deallocate();
           }
           if (mVideoDevice) {
             mVideoDevice->GetSource()->Stop(source, kVideoTrack);
-            mVideoDevice->GetSource()->Deallocate();
+            mVideoDevice->Deallocate();
           }
           if (mType == MEDIA_STOP) {
             source->EndAllTrackAndFinish();
           }
 
           nsIRunnable *event =
             new GetUserMediaNotificationEvent(mListener,
                                               mType == MEDIA_STOP ?
@@ -780,31 +788,30 @@ MediaDevice::FitnessDistance(nsString aN
     params.mIdeal.Construct();
     params.mIdeal.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
     return FitnessDistance(aN, params);
   } else {
     return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters());
   }
 }
 
-// Reminder: add handling for new constraints both here and in GetSources below!
-
 uint32_t
 MediaDevice::GetBestFitnessDistance(
-    const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets)
 {
   nsString mediaSource;
   GetMediaSource(mediaSource);
 
   // This code is reused for audio, where the mediaSource constraint does
   // not currently have a function, but because it defaults to "camera" in
   // webidl, we ignore it for audio here.
   if (!mediaSource.EqualsASCII("microphone")) {
     for (const auto& constraint : aConstraintSets) {
-      if (mediaSource != constraint->mMediaSource) {
+      if (constraint->mMediaSource.mIdeal.find(mediaSource) ==
+          constraint->mMediaSource.mIdeal.end()) {
         return UINT32_MAX;
       }
     }
   }
   // Forward request to underlying object to interrogate per-mode capabilities.
   // Pass in device's origin-specific id for deviceId constraint comparison.
   nsString id;
   GetId(id);
@@ -880,36 +887,34 @@ VideoDevice::GetSource()
 }
 
 AudioDevice::Source*
 AudioDevice::GetSource()
 {
   return static_cast<Source*>(&*mSource);
 }
 
-nsresult VideoDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
-                               const MediaEnginePrefs &aPrefs,
-                               const nsACString& aOrigin) {
-  return GetSource()->Allocate(aConstraints, aPrefs, mID, aOrigin);
-}
-
-nsresult AudioDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
+nsresult MediaDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
                                const MediaEnginePrefs &aPrefs,
-                               const nsACString& aOrigin) {
-  return GetSource()->Allocate(aConstraints, aPrefs, mID, aOrigin);
+                               const nsACString& aOrigin,
+                               const char** aOutBadConstraint) {
+  return GetSource()->Allocate(aConstraints, aPrefs, mID, aOrigin,
+                               getter_AddRefs(mAllocationHandle),
+                               aOutBadConstraint);
 }
 
-nsresult VideoDevice::Restart(const dom::MediaTrackConstraints &aConstraints,
-                              const MediaEnginePrefs &aPrefs) {
-  return GetSource()->Restart(aConstraints, aPrefs, mID);
+nsresult MediaDevice::Restart(const dom::MediaTrackConstraints &aConstraints,
+                              const MediaEnginePrefs &aPrefs,
+                              const char** aOutBadConstraint) {
+  return GetSource()->Restart(mAllocationHandle, aConstraints, aPrefs, mID,
+                              aOutBadConstraint);
 }
 
-nsresult AudioDevice::Restart(const dom::MediaTrackConstraints &aConstraints,
-                              const MediaEnginePrefs &aPrefs) {
-  return GetSource()->Restart(aConstraints, aPrefs, mID);
+nsresult MediaDevice::Deallocate() {
+  return GetSource()->Deallocate(mAllocationHandle);
 }
 
 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())));
@@ -921,16 +926,28 @@ MediaOperationTask::ReturnCallbackError(
     NS_LITERAL_STRING("InternalError"), log);
   NS_DispatchToMainThread(do_AddRef(
     new ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>(onSuccess,
                                                                  mOnFailure,
                                                                  *error,
                                                                  mWindowID)));
 }
 
+static bool
+IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) {
+  return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
+}
+
+static const MediaTrackConstraints&
+GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) {
+  static const MediaTrackConstraints empty;
+  return aUnion.IsMediaTrackConstraints() ?
+      aUnion.GetAsMediaTrackConstraints() : empty;
+}
+
 /**
  * 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
@@ -985,20 +1002,22 @@ class GetUserMediaStreamRunnable : publi
 {
 public:
   GetUserMediaStreamRunnable(
     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aOnSuccess,
     nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aOnFailure,
     uint64_t aWindowID,
     GetUserMediaCallbackMediaStreamListener* aListener,
     const nsCString& aOrigin,
+    const MediaStreamConstraints& aConstraints,
     AudioDevice* aAudioDevice,
     VideoDevice* aVideoDevice,
     PeerIdentity* aPeerIdentity)
-    : mAudioDevice(aAudioDevice)
+    : mConstraints(aConstraints)
+    , mAudioDevice(aAudioDevice)
     , mVideoDevice(aVideoDevice)
     , mWindowID(aWindowID)
     , mListener(aListener)
     , mOrigin(aOrigin)
     , mPeerIdentity(aPeerIdentity)
     , mManager(MediaManager::GetInstance())
   {
     mOnSuccess.swap(aOnSuccess);
@@ -1104,44 +1123,37 @@ public:
           return mSource;
         }
 
         const PeerIdentity* GetPeerIdentity() const override
         {
           return mPeerIdentity;
         }
 
-        already_AddRefed<Promise>
+        already_AddRefed<PledgeVoid>
         ApplyConstraints(nsPIDOMWindowInner* aWindow,
-                         const MediaTrackConstraints& aConstraints,
-                         ErrorResult &aRv) override
+                         const MediaTrackConstraints& aConstraints) 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();
+          if (sInShutdown || !mListener) {
+            // Track has been stopped, or we are in shutdown. In either case
+            // there's no observable outcome, so pretend we succeeded.
+            RefPtr<PledgeVoid> p = new PledgeVoid();
+            p->Resolve(false);
+            return p.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();
+          return mListener->ApplyConstraintsToTrack(aWindow, mTrackID, aConstraints);
         }
 
+        void
+        GetSettings(dom::MediaTrackSettings& aOutSettings) override
+        {
+          if (mListener) {
+            mListener->GetSettings(aOutSettings);
+          }
+        }
 
         void Stop() override
         {
           if (mListener) {
             mListener->StopTrack(mTrackID);
             mListener = nullptr;
           }
         }
@@ -1172,27 +1184,31 @@ public:
       if (mAudioDevice) {
         nsString audioDeviceName;
         mAudioDevice->GetName(audioDeviceName);
         const MediaSourceEnum source =
           mAudioDevice->GetSource()->GetMediaSource();
         RefPtr<MediaStreamTrackSource> audioSource =
           new LocalTrackSource(principal, audioDeviceName, mListener, source,
                                kAudioTrack, mPeerIdentity);
-        domStream->CreateDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioSource);
+        MOZ_ASSERT(IsOn(mConstraints.mAudio));
+        domStream->CreateDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioSource,
+                                  GetInvariant(mConstraints.mAudio));
       }
       if (mVideoDevice) {
         nsString videoDeviceName;
         mVideoDevice->GetName(videoDeviceName);
         const MediaSourceEnum source =
           mVideoDevice->GetSource()->GetMediaSource();
         RefPtr<MediaStreamTrackSource> videoSource =
           new LocalTrackSource(principal, videoDeviceName, mListener, source,
                                kVideoTrack, mPeerIdentity);
-        domStream->CreateDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoSource);
+        MOZ_ASSERT(IsOn(mConstraints.mVideo));
+        domStream->CreateDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoSource,
+                                  GetInvariant(mConstraints.mVideo));
       }
       stream = domStream->GetInputStream()->AsSourceStream();
     }
 
     if (!domStream || sInShutdown) {
       nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure = mOnFailure.forget();
       LOG(("Returning error for getUserMedia() - no stream"));
 
@@ -1236,37 +1252,26 @@ public:
       RefPtr<Pledge<nsCString>> p = media::GetOriginKey(mOrigin, false, true);
     }
     return NS_OK;
   }
 
 private:
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
+  MediaStreamConstraints mConstraints;
   RefPtr<AudioDevice> mAudioDevice;
   RefPtr<VideoDevice> mVideoDevice;
   uint64_t mWindowID;
   RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   nsCString mOrigin;
   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();
-}
-
-static const MediaTrackConstraints&
-GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) {
-  static const MediaTrackConstraints empty;
-  return aUnion.IsMediaTrackConstraints() ?
-      aUnion.GetAsMediaTrackConstraints() : empty;
-}
-
 // Source getter returning full list
 
 template<class DeviceType>
 static void
 GetSources(MediaEngine *engine, MediaSourceEnum aSrcType,
            void (MediaEngine::* aEnumerate)(MediaSourceEnum,
                nsTArray<RefPtr<typename DeviceType::Source> >*),
            nsTArray<RefPtr<DeviceType>>& aResult,
@@ -1337,21 +1342,21 @@ MediaManager::SelectSettings(
     }
     sources.Clear();
     const char* badConstraint = nullptr;
     bool needVideo = IsOn(aConstraints.mVideo);
     bool needAudio = IsOn(aConstraints.mAudio);
 
     if (needVideo && videos.Length()) {
       badConstraint = MediaConstraintsHelper::SelectSettings(
-          GetInvariant(aConstraints.mVideo), videos);
+          NormalizedConstraints(GetInvariant(aConstraints.mVideo)), videos);
     }
     if (!badConstraint && needAudio && audios.Length()) {
       badConstraint = MediaConstraintsHelper::SelectSettings(
-          GetInvariant(aConstraints.mAudio), audios);
+          NormalizedConstraints(GetInvariant(aConstraints.mAudio)), audios);
     }
     if (!badConstraint &&
         !needVideo == !videos.Length() &&
         !needAudio == !audios.Length()) {
       for (auto& video : videos) {
         sources.AppendElement(video);
       }
       for (auto& audio : audios) {
@@ -1400,18 +1405,20 @@ public:
     , mSourceSet(aSourceSet)
     , mManager(MediaManager::GetInstance())
   {}
 
   ~GetUserMediaTask() {
   }
 
   void
-  Fail(const nsAString& aName, const nsAString& aMessage = EmptyString()) {
-    RefPtr<MediaMgrError> error = new MediaMgrError(aName, aMessage);
+  Fail(const nsAString& aName,
+       const nsAString& aMessage = EmptyString(),
+       const nsAString& aConstraint = EmptyString()) {
+    RefPtr<MediaMgrError> error = new MediaMgrError(aName, aMessage, aConstraint);
     RefPtr<ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>> runnable =
       new ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>(mOnSuccess,
                                                                    mOnFailure,
                                                                    *error,
                                                                    mWindowID);
     // These should be empty now
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
@@ -1428,49 +1435,75 @@ public:
     MOZ_ASSERT(mOnSuccess);
     MOZ_ASSERT(mOnFailure);
     MOZ_ASSERT(mDeviceChosen);
 
     // Allocate a video or audio device and return a MediaStream via
     // a GetUserMediaStreamRunnable.
 
     nsresult rv;
+    const char* errorMsg = nullptr;
+    const char* badConstraint = nullptr;
 
     if (mAudioDevice) {
-      rv = mAudioDevice->Allocate(GetInvariant(mConstraints.mAudio),
-                                  mPrefs, mOrigin);
+      auto& constraints = GetInvariant(mConstraints.mAudio);
+      rv = mAudioDevice->Allocate(constraints, mPrefs, mOrigin, &badConstraint);
       if (NS_FAILED(rv)) {
-        LOG(("Failed to allocate audiosource %d",rv));
-        Fail(NS_LITERAL_STRING("NotReadableError"),
-             NS_LITERAL_STRING("Failed to allocate audiosource"));
-        return NS_OK;
+        errorMsg = "Failed to allocate audiosource";
+        if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
+          nsTArray<RefPtr<AudioDevice>> audios;
+          audios.AppendElement(mAudioDevice);
+          badConstraint = MediaConstraintsHelper::SelectSettings(
+              NormalizedConstraints(constraints), audios);
+        }
       }
     }
-    if (mVideoDevice) {
-      rv = mVideoDevice->Allocate(GetInvariant(mConstraints.mVideo),
-                                  mPrefs, mOrigin);
+    if (!errorMsg && mVideoDevice) {
+      auto& constraints = GetInvariant(mConstraints.mVideo);
+      rv = mVideoDevice->Allocate(constraints, mPrefs, mOrigin, &badConstraint);
       if (NS_FAILED(rv)) {
-        LOG(("Failed to allocate videosource %d\n",rv));
+        errorMsg = "Failed to allocate videosource";
+        if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
+          nsTArray<RefPtr<VideoDevice>> videos;
+          videos.AppendElement(mVideoDevice);
+          badConstraint = MediaConstraintsHelper::SelectSettings(
+              NormalizedConstraints(constraints), videos);
+        }
         if (mAudioDevice) {
-          mAudioDevice->GetSource()->Deallocate();
+          mAudioDevice->Deallocate();
         }
-        Fail(NS_LITERAL_STRING("NotReadableError"),
-             NS_LITERAL_STRING("Failed to allocate videosource"));
-        return NS_OK;
       }
     }
+    if (errorMsg) {
+      LOG(("%s %d", errorMsg, rv));
+      switch (rv) {
+        case NS_ERROR_NOT_AVAILABLE: {
+          MOZ_ASSERT(badConstraint);
+          Fail(NS_LITERAL_STRING("OverconstrainedError"),
+               NS_LITERAL_STRING(""),
+               NS_ConvertUTF8toUTF16(badConstraint));
+          break;
+        }
+        default:
+          Fail(NS_LITERAL_STRING("NotReadableError"),
+               NS_ConvertUTF8toUTF16(errorMsg));
+          break;
+      }
+      return NS_OK;
+    }
     PeerIdentity* peerIdentity = nullptr;
     if (!mConstraints.mPeerIdentity.IsEmpty()) {
       peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
     }
 
     NS_DispatchToMainThread(do_AddRef(
         new GetUserMediaStreamRunnable(mOnSuccess, mOnFailure, mWindowID,
-                                       mListener, mOrigin, mAudioDevice,
-                                       mVideoDevice, peerIdentity)));
+                                       mListener, mOrigin, mConstraints,
+                                       mAudioDevice, mVideoDevice,
+                                       peerIdentity)));
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
     return NS_OK;
   }
 
   nsresult
   Denied(const nsAString& aName,
          const nsAString& aMessage = EmptyString())
@@ -3377,24 +3410,24 @@ GetUserMediaCallbackMediaStreamListener:
                                     dom::AudioChannel::Normal);
     graph->UnregisterCaptureStreamForWindow(mWindowID);
     mStream->Destroy();
   }
 }
 
 // ApplyConstraints for track
 
-already_AddRefed<media::Pledge<bool, dom::MediaStreamError*>>
+auto
 GetUserMediaCallbackMediaStreamListener::ApplyConstraintsToTrack(
     nsPIDOMWindowInner* aWindow,
     TrackID aTrackID,
-    const MediaTrackConstraints& aConstraints)
+    const MediaTrackConstraints& aConstraints) -> already_AddRefed<PledgeVoid>
 {
   MOZ_ASSERT(NS_IsMainThread());
-  RefPtr<media::Pledge<bool, dom::MediaStreamError*>> p = new media::Pledge<bool, dom::MediaStreamError*>();
+  RefPtr<PledgeVoid> p = new PledgeVoid();
 
   // 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;
 
@@ -3414,40 +3447,40 @@ GetUserMediaCallbackMediaStreamListener:
                                       audioDevice, videoDevice,
                                       aConstraints]() mutable {
     MOZ_ASSERT(MediaManager::IsInMediaThread());
     RefPtr<MediaManager> mgr = MediaManager::GetInstance();
     const char* badConstraint = nullptr;
     nsresult rv = NS_OK;
 
     if (audioDevice) {
-      rv = audioDevice->Restart(aConstraints, mgr->mPrefs);
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+      rv = audioDevice->Restart(aConstraints, mgr->mPrefs, &badConstraint);
+      if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
         nsTArray<RefPtr<AudioDevice>> audios;
         audios.AppendElement(audioDevice);
-        badConstraint = MediaConstraintsHelper::SelectSettings(aConstraints,
-                                                               audios);
+        badConstraint = MediaConstraintsHelper::SelectSettings(
+            NormalizedConstraints(aConstraints), audios);
       }
     } else {
-      rv = videoDevice->Restart(aConstraints, mgr->mPrefs);
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+      rv = videoDevice->Restart(aConstraints, mgr->mPrefs, &badConstraint);
+      if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
         nsTArray<RefPtr<VideoDevice>> videos;
         videos.AppendElement(videoDevice);
-        badConstraint = MediaConstraintsHelper::SelectSettings(aConstraints,
-                                                               videos);
+        badConstraint = MediaConstraintsHelper::SelectSettings(
+            NormalizedConstraints(aConstraints), videos);
       }
     }
     NS_DispatchToMainThread(NewRunnableFrom([id, windowId, rv,
                                              badConstraint]() mutable {
       MOZ_ASSERT(NS_IsMainThread());
       RefPtr<MediaManager> mgr = MediaManager_GetInstance();
       if (!mgr) {
         return NS_OK;
       }
-      RefPtr<media::Pledge<bool, dom::MediaStreamError*>> p = mgr->mOutstandingVoidPledges.Remove(id);
+      RefPtr<PledgeVoid> p = mgr->mOutstandingVoidPledges.Remove(id);
       if (p) {
         if (NS_SUCCEEDED(rv)) {
           p->Resolve(false);
         } else {
           auto* window = nsGlobalWindow::GetInnerWindowWithId(windowId);
           if (window) {
             if (rv == NS_ERROR_NOT_AVAILABLE) {
               nsString constraint;
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -58,72 +58,75 @@ class GetUserMediaCallbackMediaStreamLis
 class GetUserMediaTask;
 
 extern LogModule* GetMediaManagerLog();
 #define MM_LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
 
 class MediaDevice : public nsIMediaDevice
 {
 public:
+  typedef MediaEngineSource Source;
+
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEDIADEVICE
 
   void SetId(const nsAString& aID);
   virtual uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets);
+  virtual Source* GetSource() = 0;
+  nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
+                    const MediaEnginePrefs &aPrefs,
+                    const nsACString& aOrigin,
+                    const char** aOutBadConstraint);
+  nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
+                   const MediaEnginePrefs &aPrefs,
+                   const char** aOutBadConstraint);
+  nsresult Deallocate();
 protected:
   virtual ~MediaDevice() {}
   explicit MediaDevice(MediaEngineSource* aSource, bool aIsVideo);
+
   static uint32_t FitnessDistance(nsString aN,
     const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint);
 private:
   static bool StringsContain(const dom::OwningStringOrStringSequence& aStrings,
                              nsString aN);
   static uint32_t FitnessDistance(nsString aN,
       const dom::ConstrainDOMStringParameters& aParams);
 protected:
   nsString mName;
   nsString mID;
   dom::MediaSourceEnum mMediaSource;
   RefPtr<MediaEngineSource> mSource;
+  RefPtr<MediaEngineSource::BaseAllocationHandle> mAllocationHandle;
 public:
   dom::MediaSourceEnum GetMediaSource() {
     return mMediaSource;
   }
   bool mIsVideo;
 };
 
 class VideoDevice : public MediaDevice
 {
 public:
   typedef MediaEngineVideoSource Source;
 
   explicit VideoDevice(Source* aSource);
-  NS_IMETHOD GetType(nsAString& aType);
-  Source* GetSource();
-  nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
-                    const MediaEnginePrefs &aPrefs,
-                    const nsACString& aOrigin);
-  nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
-                   const MediaEnginePrefs &aPrefs);
+  NS_IMETHOD GetType(nsAString& aType) override;
+  Source* GetSource() override;
 };
 
 class AudioDevice : public MediaDevice
 {
 public:
   typedef MediaEngineAudioSource Source;
 
   explicit AudioDevice(Source* aSource);
-  NS_IMETHOD GetType(nsAString& aType);
-  Source* GetSource();
-  nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
-                    const MediaEnginePrefs &aPrefs,
-                    const nsACString& aOrigin);
-  nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
-                   const MediaEnginePrefs &aPrefs);
+  NS_IMETHOD GetType(nsAString& aType) override;
+  Source* GetSource() override;
 };
 
 class GetUserMediaNotificationEvent: public Runnable
 {
   public:
     enum GetUserMediaStatus {
       STARTING,
       STOPPING,
@@ -251,16 +254,17 @@ public:
 
   MediaEnginePrefs mPrefs;
 
   typedef nsTArray<RefPtr<MediaDevice>> SourceSet;
   static bool IsPrivateBrowsing(nsPIDOMWindowInner* window);
 private:
   typedef media::Pledge<SourceSet*, dom::MediaStreamError*> PledgeSourceSet;
   typedef media::Pledge<const char*, dom::MediaStreamError*> PledgeChar;
+  typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
 
   static bool IsPrivileged();
   static bool IsLoop(nsIURI* aDocURI);
   static nsresult GenerateUUID(nsAString& aResult);
   static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey);
 public: // TODO: make private once we upgrade to GCC 4.8+ on linux.
   static void AnonymizeDevices(SourceSet& aDevices, const nsACString& aOriginKey);
   static already_AddRefed<nsIWritableVariant> ToJSArray(SourceSet& aDevices);
@@ -316,17 +320,17 @@ private:
 
   // ONLY accessed from MediaManagerThread
   RefPtr<MediaEngine> mBackend;
 
   static StaticRefPtr<MediaManager> sSingleton;
 
   media::CoatCheck<PledgeSourceSet> mOutstandingPledges;
   media::CoatCheck<PledgeChar> mOutstandingCharPledges;
-  media::CoatCheck<media::Pledge<bool, dom::MediaStreamError*>> mOutstandingVoidPledges;
+  media::CoatCheck<PledgeVoid> mOutstandingVoidPledges;
 #if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK)
   RefPtr<nsDOMCameraManager> mCameraManager;
 #endif
 public:
   media::CoatCheck<media::Pledge<nsCString>> mGetOriginKeyPledges;
   UniquePtr<media::Parent<media::NonE10s>> mNonE10sParent;
 };
 
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -5,16 +5,19 @@
 
 #include "MediaStreamTrack.h"
 
 #include "DOMMediaStream.h"
 #include "MediaStreamGraph.h"
 #include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 #include "MediaStreamListener.h"
+#include "systemservices/MediaUtils.h"
+
+#include "mozilla/dom/Promise.h"
 
 #ifdef LOG
 #undef LOG
 #endif
 
 static PRLogModuleInfo* gMediaStreamTrackLog;
 #define LOG(type, msg) MOZ_LOG(gMediaStreamTrackLog, type, msg)
 
@@ -33,31 +36,26 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Me
   tmp->Destroy();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamTrackSource)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
-already_AddRefed<Promise>
-MediaStreamTrackSource::ApplyConstraints(nsPIDOMWindowInner* aWindow,
-                                         const dom::MediaTrackConstraints& aConstraints,
-                                         ErrorResult &aRv)
+auto
+MediaStreamTrackSource::ApplyConstraints(
+    nsPIDOMWindowInner* aWindow,
+    const dom::MediaTrackConstraints& aConstraints) -> already_AddRefed<PledgeVoid>
 {
-  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();
+  RefPtr<PledgeVoid> p = new PledgeVoid();
+  p->Reject(new MediaStreamError(aWindow,
+                                 NS_LITERAL_STRING("OverconstrainedError"),
+                                 NS_LITERAL_STRING("")));
+  return p.forget();
 }
 
 /**
  * PrincipalHandleListener monitors changes in PrincipalHandle of the media flowing
  * through the MediaStreamGraph.
  *
  * When the main thread principal for a MediaStreamTrack changes, its principal
  * will be set to the combination of the previous principal and the new one.
@@ -107,22 +105,24 @@ public:
 
 protected:
   // These fields may only be accessed on the main thread
   MediaStreamTrack* mTrack;
 };
 
 MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
                                    TrackID aInputTrackID,
-                                   MediaStreamTrackSource* aSource)
+                                   MediaStreamTrackSource* aSource,
+                                   const MediaTrackConstraints& aConstraints)
   : mOwningStream(aStream), mTrackID(aTrackID),
     mInputTrackID(aInputTrackID), mSource(aSource),
     mPrincipal(aSource->GetPrincipal()),
     mReadyState(MediaStreamTrackState::Live),
-    mEnabled(true), mRemote(aSource->IsRemote())
+    mEnabled(true), mRemote(aSource->IsRemote()),
+    mConstraints(aConstraints)
 {
 
   if (!gMediaStreamTrackLog) {
     gMediaStreamTrackLog = PR_NewLogModule("MediaStreamTrack");
   }
 
   GetSource().RegisterSink(this);
 
@@ -245,30 +245,62 @@ MediaStreamTrack::Stop()
   DOMMediaStream::TrackPort* port = mOwningStream->FindOwnedTrackPort(*this);
   MOZ_ASSERT(port, "A MediaStreamTrack must exist in its owning DOMMediaStream");
   RefPtr<Pledge<bool>> p = port->BlockSourceTrackId(mInputTrackID, BlockingMode::CREATION);
   Unused << p;
 
   mReadyState = MediaStreamTrackState::Ended;
 }
 
+void
+MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints& aResult)
+{
+  aResult = mConstraints;
+}
+
+void
+MediaStreamTrack::GetSettings(dom::MediaTrackSettings& aResult)
+{
+  GetSource().GetSettings(aResult);
+}
+
 already_AddRefed<Promise>
 MediaStreamTrack::ApplyConstraints(const MediaTrackConstraints& aConstraints,
                                    ErrorResult &aRv)
 {
   if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
     nsString str;
     aConstraints.ToJSON(str);
 
     LOG(LogLevel::Info, ("MediaStreamTrack %p ApplyConstraints() with "
                          "constraints %s", this, NS_ConvertUTF16toUTF8(str).get()));
   }
 
+  typedef media::Pledge<bool, MediaStreamError*> PledgeVoid;
+
   nsPIDOMWindowInner* window = mOwningStream->GetParentObject();
-  return GetSource().ApplyConstraints(window, aConstraints, aRv);
+
+  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
+  RefPtr<Promise> promise = Promise::Create(go, aRv);
+
+  // Forward constraints to the source.
+  //
+  // After GetSource().ApplyConstraints succeeds (after it's been to media-thread
+  // and back), and no sooner, do we set mConstraints to the newly applied values.
+
+  // Keep a reference to this, to make sure it's still here when we get back.
+  RefPtr<MediaStreamTrack> that = this;
+  RefPtr<PledgeVoid> p = GetSource().ApplyConstraints(window, aConstraints);
+  p->Then([this, that, promise, aConstraints](bool& aDummy) mutable {
+    mConstraints = aConstraints;
+    promise->MaybeResolve(false);
+  }, [promise](MediaStreamError*& reason) mutable {
+    promise->MaybeReject(reason);
+  });
+  return promise.forget();
 }
 
 MediaStreamGraph*
 MediaStreamTrack::Graph()
 {
   return GetOwnedStream()->Graph();
 }
 
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -9,16 +9,19 @@
 #include "mozilla/DOMEventTargetHelper.h"
 #include "nsError.h"
 #include "nsID.h"
 #include "nsIPrincipal.h"
 #include "StreamTracks.h"
 #include "MediaTrackConstraints.h"
 #include "mozilla/CORSMode.h"
 #include "PrincipalChangeObserver.h"
+#include "mozilla/dom/MediaStreamTrackBinding.h"
+#include "mozilla/dom/MediaTrackSettingsBinding.h"
+#include "mozilla/media/MediaUtils.h"
 
 namespace mozilla {
 
 class DOMMediaStream;
 class MediaEnginePhotoCallback;
 class MediaInputPort;
 class MediaStream;
 class MediaStreamGraph;
@@ -31,16 +34,17 @@ class PeerIdentity;
 class ProcessedMediaStream;
 class RemoteSourceStreamInfo;
 class SourceStreamInfo;
 
 namespace dom {
 
 class AudioStreamTrack;
 class VideoStreamTrack;
+class MediaStreamError;
 
 /**
  * Common interface through which a MediaStreamTrack can communicate with its
  * producer on the main thread.
  *
  * Kept alive by a strong ref in all MediaStreamTracks (original and clones)
  * sharing this source.
  */
@@ -115,24 +119,31 @@ public:
 
   /**
    * Forwards a photo request to backends that support it. Other backends return
    * NS_ERROR_NOT_IMPLEMENTED to indicate that a MediaStreamGraph-based fallback
    * should be used.
    */
   virtual nsresult TakePhoto(MediaEnginePhotoCallback*) const { return NS_ERROR_NOT_IMPLEMENTED; }
 
+  typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
+
   /**
    * We provide a fallback solution to ApplyConstraints() here.
    * Sources that support ApplyConstraints() will have to override it.
    */
-  virtual already_AddRefed<Promise>
+  virtual already_AddRefed<PledgeVoid>
   ApplyConstraints(nsPIDOMWindowInner* aWindow,
-                   const dom::MediaTrackConstraints& aConstraints,
-                   ErrorResult &aRv);
+                   const dom::MediaTrackConstraints& aConstraints);
+
+  /**
+   * Same for GetSettings (no-op).
+   */
+  virtual void
+  GetSettings(dom::MediaTrackSettings& aResult) {};
 
   /**
    * Called by the source interface when all registered sinks have unregistered.
    */
   virtual void Stop() = 0;
 
   /**
    * Called by each MediaStreamTrack clone on initialization.
@@ -203,16 +214,19 @@ public:
                                        const MediaSourceEnum aMediaSource =
                                          MediaSourceEnum::Other)
     : MediaStreamTrackSource(aPrincipal, true, nsString())
     , mMediaSource(aMediaSource)
   {}
 
   MediaSourceEnum GetMediaSource() const override { return mMediaSource; }
 
+  void
+  GetSettings(dom::MediaTrackSettings& aResult) override {}
+
   void Stop() override {}
 
 protected:
   ~BasicUnstoppableTrackSource() {}
 
   const MediaSourceEnum mMediaSource;
 };
 
@@ -235,18 +249,19 @@ class MediaStreamTrack : public DOMEvent
   class PrincipalHandleListener;
 
 public:
   /**
    * aTrackID is the MediaStreamGraph track ID for the track in the
    * MediaStream owned by aStream.
    */
   MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
-                   TrackID aInputTrackID,
-                   MediaStreamTrackSource* aSource);
+      TrackID aInputTrackID,
+      MediaStreamTrackSource* aSource,
+      const MediaTrackConstraints& aConstraints = MediaTrackConstraints());
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrack,
                                            DOMEventTargetHelper)
 
   nsPIDOMWindowInner* GetParentObject() const;
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override = 0;
 
@@ -258,16 +273,19 @@ public:
 
   // WebIDL
   virtual void GetKind(nsAString& aKind) = 0;
   void GetId(nsAString& aID) const;
   void GetLabel(nsAString& aLabel) { GetSource().GetLabel(aLabel); }
   bool Enabled() { return mEnabled; }
   void SetEnabled(bool aEnabled);
   void Stop();
+  void GetConstraints(dom::MediaTrackConstraints& aResult);
+  void GetSettings(dom::MediaTrackSettings& aResult);
+
   already_AddRefed<Promise>
   ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv);
   already_AddRefed<MediaStreamTrack> Clone();
   MediaStreamTrackState ReadyState() { return mReadyState; }
 
   IMPL_EVENT_HANDLER(ended)
 
   /**
@@ -421,14 +439,15 @@ protected:
   // Keep tracking MediaStreamTrackListener and DirectMediaStreamTrackListener,
   // so we can remove them in |Destory|.
   nsTArray<RefPtr<MediaStreamTrackListener>> mTrackListeners;
   nsTArray<RefPtr<DirectMediaStreamTrackListener>> mDirectTrackListeners;
   nsString mID;
   MediaStreamTrackState mReadyState;
   bool mEnabled;
   const bool mRemote;
+  dom::MediaTrackConstraints mConstraints;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* MEDIASTREAMTRACK_H_ */
--- a/dom/media/SeekTask.cpp
+++ b/dom/media/SeekTask.cpp
@@ -16,18 +16,16 @@ SeekTask::SeekTask(const void* aDecoderI
                    const SeekTarget& aTarget)
   : mDecoderID(aDecoderID)
   , mOwnerThread(aThread)
   , mReader(aReader)
   , mTarget(aTarget)
   , mIsDiscarded(false)
   , mIsAudioQueueFinished(false)
   , mIsVideoQueueFinished(false)
-  , mNeedToStopPrerollingAudio(false)
-  , mNeedToStopPrerollingVideo(false)
 {
   AssertOwnerThread();
 }
 
 SeekTask::~SeekTask()
 {
   AssertOwnerThread();
   MOZ_ASSERT(mIsDiscarded);
@@ -38,32 +36,28 @@ SeekTask::Resolve(const char* aCallSite)
 {
   AssertOwnerThread();
 
   SeekTaskResolveValue val;
   val.mSeekedAudioData = mSeekedAudioData;
   val.mSeekedVideoData = mSeekedVideoData;
   val.mIsAudioQueueFinished = mIsAudioQueueFinished;
   val.mIsVideoQueueFinished = mIsVideoQueueFinished;
-  val.mNeedToStopPrerollingAudio = mNeedToStopPrerollingAudio;
-  val.mNeedToStopPrerollingVideo = mNeedToStopPrerollingVideo;
 
   mSeekTaskPromise.Resolve(val, aCallSite);
 }
 
 void
 SeekTask::RejectIfExist(const char* aCallSite)
 {
   AssertOwnerThread();
 
   SeekTaskRejectValue val;
   val.mIsAudioQueueFinished = mIsAudioQueueFinished;
   val.mIsVideoQueueFinished = mIsVideoQueueFinished;
-  val.mNeedToStopPrerollingAudio = mNeedToStopPrerollingAudio;
-  val.mNeedToStopPrerollingVideo = mNeedToStopPrerollingVideo;
 
   mSeekTaskPromise.RejectIfExists(val, aCallSite);
 }
 
 void
 SeekTask::AssertOwnerThread() const
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
--- a/dom/media/SeekTask.h
+++ b/dom/media/SeekTask.h
@@ -4,45 +4,40 @@
  * 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 SEEK_TASK_H
 #define SEEK_TASK_H
 
 #include "mozilla/MozPromise.h"
 #include "MediaCallbackID.h"
-#include "SeekJob.h"
+#include "SeekTarget.h"
 
 namespace mozilla {
 
 class AbstractThread;
 class MediaData;
 class MediaDecoderReaderWrapper;
-class MediaInfo;
 
 namespace media {
 class TimeUnit;
 }
 
 struct SeekTaskResolveValue
 {
   RefPtr<MediaData> mSeekedAudioData;
   RefPtr<MediaData> mSeekedVideoData;
   bool mIsAudioQueueFinished;
   bool mIsVideoQueueFinished;
-  bool mNeedToStopPrerollingAudio;
-  bool mNeedToStopPrerollingVideo;
 };
 
 struct SeekTaskRejectValue
 {
   bool mIsAudioQueueFinished;
   bool mIsVideoQueueFinished;
-  bool mNeedToStopPrerollingAudio;
-  bool mNeedToStopPrerollingVideo;
 };
 
 class SeekTask {
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SeekTask)
 
 public:
   static const bool IsExclusive = true;
@@ -90,15 +85,13 @@ protected:
 
   /*
    * Information which are going to be returned to MDSM.
    */
   RefPtr<MediaData> mSeekedAudioData;
   RefPtr<MediaData> mSeekedVideoData;
   bool mIsAudioQueueFinished;
   bool mIsVideoQueueFinished;
-  bool mNeedToStopPrerollingAudio;
-  bool mNeedToStopPrerollingVideo;
 };
 
 } // namespace mozilla
 
 #endif /* SEEK_TASK_H */
--- a/dom/media/VideoStreamTrack.h
+++ b/dom/media/VideoStreamTrack.h
@@ -11,18 +11,19 @@
 
 namespace mozilla {
 namespace dom {
 
 class VideoStreamTrack : public MediaStreamTrack {
 public:
   VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
                    TrackID aInputTrackID,
-                   MediaStreamTrackSource* aSource)
-    : MediaStreamTrack(aStream, aTrackID, aInputTrackID, aSource) {}
+                   MediaStreamTrackSource* aSource,
+                   const MediaTrackConstraints& aConstraints = MediaTrackConstraints())
+    : MediaStreamTrack(aStream, aTrackID, aInputTrackID, aSource, aConstraints) {}
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   VideoStreamTrack* AsVideoStreamTrack() override { return this; }
 
   const VideoStreamTrack* AsVideoStreamTrack() const override { return this; }
 
   // WebIDL
@@ -30,16 +31,17 @@ public:
 
 protected:
   already_AddRefed<MediaStreamTrack> CloneInternal(DOMMediaStream* aOwningStream,
                                                    TrackID aTrackID) override
   {
     return do_AddRef(new VideoStreamTrack(aOwningStream,
                                           aTrackID,
                                           mInputTrackID,
-                                          mSource));
+                                          mSource,
+                                          mConstraints));
   }
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* VIDEOSTREAMTRACK_H_ */
--- a/dom/media/platforms/apple/AppleDecoderModule.cpp
+++ b/dom/media/platforms/apple/AppleDecoderModule.cpp
@@ -2,32 +2,29 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "AppleATDecoder.h"
 #include "AppleCMLinker.h"
 #include "AppleDecoderModule.h"
-#include "AppleVDADecoder.h"
-#include "AppleVDALinker.h"
 #include "AppleVTDecoder.h"
 #include "AppleVTLinker.h"
 #include "MacIOSurfaceImage.h"
 #include "MediaPrefs.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Logging.h"
 
 namespace mozilla {
 
 bool AppleDecoderModule::sInitialized = false;
 bool AppleDecoderModule::sIsCoreMediaAvailable = false;
 bool AppleDecoderModule::sIsVTAvailable = false;
 bool AppleDecoderModule::sIsVTHWAvailable = false;
-bool AppleDecoderModule::sIsVDAAvailable = false;
 bool AppleDecoderModule::sCanUseHardwareVideoDecoder = true;
 
 AppleDecoderModule::AppleDecoderModule()
 {
 }
 
 AppleDecoderModule::~AppleDecoderModule()
 {
@@ -40,19 +37,16 @@ AppleDecoderModule::Init()
   if (sInitialized) {
     return;
   }
 
   // Ensure IOSurface framework is loaded.
   MacIOSurfaceLib::LoadLibrary();
   const bool loaded = MacIOSurfaceLib::isInit();
 
-  // dlopen VideoDecodeAcceleration.framework if it's available.
-  sIsVDAAvailable = loaded && AppleVDALinker::Link();
-
   // dlopen CoreMedia.framework if it's available.
   sIsCoreMediaAvailable = AppleCMLinker::Link();
   // dlopen VideoToolbox.framework if it's available.
   // We must link both CM and VideoToolbox framework to allow for proper
   // paired Link/Unlink calls
   bool haveVideoToolbox = loaded && AppleVTLinker::Link();
   sIsVTAvailable = sIsCoreMediaAvailable && haveVideoToolbox;
 
@@ -62,46 +56,30 @@ AppleDecoderModule::Init()
     gfxPlatform::GetPlatform()->CanUseHardwareVideoDecoding();
 
   sInitialized = true;
 }
 
 nsresult
 AppleDecoderModule::Startup()
 {
-  if (!sInitialized || (!sIsVDAAvailable && !sIsVTAvailable)) {
+  if (!sInitialized || !sIsVTAvailable) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 already_AddRefed<MediaDataDecoder>
 AppleDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
-  RefPtr<MediaDataDecoder> decoder;
-
-  if (sIsVDAAvailable && (!sIsVTHWAvailable || MediaPrefs::AppleForceVDA())) {
-    decoder =
-      AppleVDADecoder::CreateVDADecoder(aParams.VideoConfig(),
-                                        aParams.mTaskQueue,
-                                        aParams.mCallback,
-                                        aParams.mImageContainer);
-    if (decoder) {
-      return decoder.forget();
-    }
-  }
-  // We fallback here if VDA isn't available, or is available but isn't
-  // supported by the current platform.
-  if (sIsVTAvailable) {
-    decoder =
-      new AppleVTDecoder(aParams.VideoConfig(),
-                         aParams.mTaskQueue,
-                         aParams.mCallback,
-                         aParams.mImageContainer);
-  }
+  RefPtr<MediaDataDecoder> decoder =
+    new AppleVTDecoder(aParams.VideoConfig(),
+                       aParams.mTaskQueue,
+                       aParams.mCallback,
+                       aParams.mImageContainer);
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 AppleDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> decoder =
     new AppleATDecoder(aParams.AudioConfig(),
@@ -112,19 +90,18 @@ AppleDecoderModule::CreateAudioDecoder(c
 
 bool
 AppleDecoderModule::SupportsMimeType(const nsACString& aMimeType,
                                      DecoderDoctorDiagnostics* aDiagnostics) const
 {
   return (sIsCoreMediaAvailable &&
           (aMimeType.EqualsLiteral("audio/mpeg") ||
            aMimeType.EqualsLiteral("audio/mp4a-latm"))) ||
-    ((sIsVTAvailable || sIsVDAAvailable) &&
-     (aMimeType.EqualsLiteral("video/mp4") ||
-      aMimeType.EqualsLiteral("video/avc")));
+    (sIsVTAvailable && (aMimeType.EqualsLiteral("video/mp4") ||
+                        aMimeType.EqualsLiteral("video/avc")));
 }
 
 PlatformDecoderModule::ConversionRequired
 AppleDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
 {
   if (aConfig.IsVideo()) {
     return kNeedAVCC;
   } else {
--- a/dom/media/platforms/apple/AppleDecoderModule.h
+++ b/dom/media/platforms/apple/AppleDecoderModule.h
@@ -36,14 +36,13 @@ public:
 
   static bool sCanUseHardwareVideoDecoder;
 
 private:
   static bool sInitialized;
   static bool sIsCoreMediaAvailable;
   static bool sIsVTAvailable;
   static bool sIsVTHWAvailable;
-  static bool sIsVDAAvailable;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_AppleDecoderModule_h
deleted file mode 100644
--- a/dom/media/platforms/apple/AppleVDADecoder.cpp
+++ /dev/null
@@ -1,700 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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 <CoreFoundation/CFString.h>
-
-#include "AppleDecoderModule.h"
-#include "AppleUtils.h"
-#include "AppleVDADecoder.h"
-#include "AppleVDALinker.h"
-#include "MediaInfo.h"
-#include "mp4_demuxer/H264.h"
-#include "MP4Decoder.h"
-#include "MediaData.h"
-#include "mozilla/ArrayUtils.h"
-#include "mozilla/SyncRunnable.h"
-#include "nsThreadUtils.h"
-#include "mozilla/Logging.h"
-#include "VideoUtils.h"
-#include <algorithm>
-#include "gfxPlatform.h"
-
-#ifndef MOZ_WIDGET_UIKIT
-#include "MacIOSurfaceImage.h"
-#endif
-
-#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
-//#define LOG_MEDIA_SHA1
-
-namespace mozilla {
-
-static uint32_t ComputeMaxRefFrames(const MediaByteBuffer* aExtraData)
-{
-  uint32_t maxRefFrames = 4;
-  // Retrieve video dimensions from H264 SPS NAL.
-  mp4_demuxer::SPSData spsdata;
-  if (mp4_demuxer::H264::DecodeSPSFromExtraData(aExtraData, spsdata)) {
-    // max_num_ref_frames determines the size of the sliding window
-    // we need to queue that many frames in order to guarantee proper
-    // pts frames ordering. Use a minimum of 4 to ensure proper playback of
-    // non compliant videos.
-    maxRefFrames =
-      std::min(std::max(maxRefFrames, spsdata.max_num_ref_frames + 1), 16u);
-  }
-  return maxRefFrames;
-}
-
-AppleVDADecoder::AppleVDADecoder(const VideoInfo& aConfig,
-                                 TaskQueue* aTaskQueue,
-                                 MediaDataDecoderCallback* aCallback,
-                                 layers::ImageContainer* aImageContainer)
-  : mExtraData(aConfig.mExtraData)
-  , mCallback(aCallback)
-  , mPictureWidth(aConfig.mImage.width)
-  , mPictureHeight(aConfig.mImage.height)
-  , mDisplayWidth(aConfig.mDisplay.width)
-  , mDisplayHeight(aConfig.mDisplay.height)
-  , mQueuedSamples(0)
-  , mTaskQueue(aTaskQueue)
-  , mDecoder(nullptr)
-  , mMaxRefFrames(ComputeMaxRefFrames(aConfig.mExtraData))
-  , mImageContainer(aImageContainer)
-  , mInputIncoming(0)
-  , mIsShutDown(false)
-#ifdef MOZ_WIDGET_UIKIT
-  , mUseSoftwareImages(true)
-#else
-  , mUseSoftwareImages(false)
-#endif
-  , mMonitor("AppleVideoDecoder")
-  , mIsFlushing(false)
-{
-  MOZ_COUNT_CTOR(AppleVDADecoder);
-  // TODO: Verify aConfig.mime_type.
-
-  LOG("Creating AppleVDADecoder for %dx%d (%dx%d) h.264 video",
-      mPictureWidth,
-      mPictureHeight,
-      mDisplayWidth,
-      mDisplayHeight
-     );
-}
-
-AppleVDADecoder::~AppleVDADecoder()
-{
-  MOZ_COUNT_DTOR(AppleVDADecoder);
-}
-
-RefPtr<MediaDataDecoder::InitPromise>
-AppleVDADecoder::Init()
-{
-  return InitPromise::CreateAndResolve(TrackType::kVideoTrack, __func__);
-}
-
-nsresult
-AppleVDADecoder::Shutdown()
-{
-  MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
-  mIsShutDown = true;
-  if (mTaskQueue) {
-    nsCOMPtr<nsIRunnable> runnable =
-      NewRunnableMethod(this, &AppleVDADecoder::ProcessShutdown);
-    mTaskQueue->Dispatch(runnable.forget());
-  } else {
-    ProcessShutdown();
-  }
-  return NS_OK;
-}
-
-void
-AppleVDADecoder::ProcessShutdown()
-{
-  if (mDecoder) {
-    LOG("%s: cleaning up decoder %p", __func__, mDecoder);
-    VDADecoderDestroy(mDecoder);
-    mDecoder = nullptr;
-  }
-}
-
-nsresult
-AppleVDADecoder::Input(MediaRawData* aSample)
-{
-  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
-
-  LOG("mp4 input sample %p pts %lld duration %lld us%s %d bytes",
-      aSample,
-      aSample->mTime,
-      aSample->mDuration,
-      aSample->mKeyframe ? " keyframe" : "",
-      aSample->Size());
-
-  mInputIncoming++;
-
-  mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
-    this, &AppleVDADecoder::ProcessDecode, aSample));
-  return NS_OK;
-}
-
-nsresult
-AppleVDADecoder::Flush()
-{
-  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
-  mIsFlushing = true;
-  nsCOMPtr<nsIRunnable> runnable =
-    NewRunnableMethod(this, &AppleVDADecoder::ProcessFlush);
-  SyncRunnable::DispatchToThread(mTaskQueue, runnable);
-  mIsFlushing = false;
-  // All ProcessDecode() tasks should be done.
-  MOZ_ASSERT(mInputIncoming == 0);
-
-  mSeekTargetThreshold.reset();
-
-  return NS_OK;
-}
-
-nsresult
-AppleVDADecoder::Drain()
-{
-  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
-  nsCOMPtr<nsIRunnable> runnable =
-    NewRunnableMethod(this, &AppleVDADecoder::ProcessDrain);
-  mTaskQueue->Dispatch(runnable.forget());
-  return NS_OK;
-}
-
-void
-AppleVDADecoder::ProcessFlush()
-{
-  AssertOnTaskQueueThread();
-
-  OSStatus rv = VDADecoderFlush(mDecoder, 0 /*dont emit*/);
-  if (rv != noErr) {
-    LOG("AppleVDADecoder::Flush failed waiting for platform decoder "
-        "with error:%d.", rv);
-  }
-  ClearReorderedFrames();
-}
-
-void
-AppleVDADecoder::ProcessDrain()
-{
-  AssertOnTaskQueueThread();
-
-  OSStatus rv = VDADecoderFlush(mDecoder, kVDADecoderFlush_EmitFrames);
-  if (rv != noErr) {
-    LOG("AppleVDADecoder::Drain failed waiting for platform decoder "
-        "with error:%d.", rv);
-  }
-  DrainReorderedFrames();
-  mCallback->DrainComplete();
-}
-
-//
-// Implementation details.
-//
-
-// Callback passed to the VideoToolbox decoder for returning data.
-// This needs to be static because the API takes a C-style pair of
-// function and userdata pointers. This validates parameters and
-// forwards the decoded image back to an object method.
-static void
-PlatformCallback(void* decompressionOutputRefCon,
-                 CFDictionaryRef frameInfo,
-                 OSStatus status,
-                 VDADecodeInfoFlags infoFlags,
-                 CVImageBufferRef image)
-{
-  LOG("AppleVDADecoder[%s] status %d flags %d retainCount %ld",
-      __func__, status, infoFlags, CFGetRetainCount(frameInfo));
-
-  // Validate our arguments.
-  // According to Apple's TN2267
-  // The output callback is still called for all flushed frames,
-  // but no image buffers will be returned.
-  // FIXME: Distinguish between errors and empty flushed frames.
-  if (status != noErr || !image) {
-    NS_WARNING("AppleVDADecoder decoder returned no data");
-    image = nullptr;
-  } else if (infoFlags & kVDADecodeInfo_FrameDropped) {
-    NS_WARNING("  ...frame dropped...");
-    image = nullptr;
-  } else {
-    MOZ_ASSERT(image || CFGetTypeID(image) == CVPixelBufferGetTypeID(),
-               "AppleVDADecoder returned an unexpected image type");
-  }
-
-  AppleVDADecoder* decoder =
-    static_cast<AppleVDADecoder*>(decompressionOutputRefCon);
-
-  AutoCFRelease<CFNumberRef> ptsref =
-    (CFNumberRef)CFDictionaryGetValue(frameInfo, CFSTR("FRAME_PTS"));
-  AutoCFRelease<CFNumberRef> dtsref =
-    (CFNumberRef)CFDictionaryGetValue(frameInfo, CFSTR("FRAME_DTS"));
-  AutoCFRelease<CFNumberRef> durref =
-    (CFNumberRef)CFDictionaryGetValue(frameInfo, CFSTR("FRAME_DURATION"));
-  AutoCFRelease<CFNumberRef> boref =
-    (CFNumberRef)CFDictionaryGetValue(frameInfo, CFSTR("FRAME_OFFSET"));
-  AutoCFRelease<CFNumberRef> kfref =
-    (CFNumberRef)CFDictionaryGetValue(frameInfo, CFSTR("FRAME_KEYFRAME"));
-
-  int64_t dts;
-  int64_t pts;
-  int64_t duration;
-  int64_t byte_offset;
-  char is_sync_point;
-
-  CFNumberGetValue(ptsref, kCFNumberSInt64Type, &pts);
-  CFNumberGetValue(dtsref, kCFNumberSInt64Type, &dts);
-  CFNumberGetValue(durref, kCFNumberSInt64Type, &duration);
-  CFNumberGetValue(boref, kCFNumberSInt64Type, &byte_offset);
-  CFNumberGetValue(kfref, kCFNumberSInt8Type, &is_sync_point);
-
-  AppleVDADecoder::AppleFrameRef frameRef(
-      media::TimeUnit::FromMicroseconds(dts),
-      media::TimeUnit::FromMicroseconds(pts),
-      media::TimeUnit::FromMicroseconds(duration),
-      byte_offset,
-      is_sync_point == 1);
-
-  decoder->OutputFrame(image, frameRef);
-}
-
-AppleVDADecoder::AppleFrameRef*
-AppleVDADecoder::CreateAppleFrameRef(const MediaRawData* aSample)
-{
-  MOZ_ASSERT(aSample);
-  return new AppleFrameRef(*aSample);
-}
-
-void
-AppleVDADecoder::DrainReorderedFrames()
-{
-  MonitorAutoLock mon(mMonitor);
-  while (!mReorderQueue.IsEmpty()) {
-    mCallback->Output(mReorderQueue.Pop().get());
-  }
-  mQueuedSamples = 0;
-}
-
-void
-AppleVDADecoder::ClearReorderedFrames()
-{
-  MonitorAutoLock mon(mMonitor);
-  while (!mReorderQueue.IsEmpty()) {
-    mReorderQueue.Pop();
-  }
-  mQueuedSamples = 0;
-}
-
-void
-AppleVDADecoder::SetSeekThreshold(const media::TimeUnit& aTime)
-{
-  LOG("SetSeekThreshold %lld", aTime.ToMicroseconds());
-  mSeekTargetThreshold = Some(aTime);
-}
-
-// Copy and return a decoded frame.
-nsresult
-AppleVDADecoder::OutputFrame(CVPixelBufferRef aImage,
-                             AppleVDADecoder::AppleFrameRef aFrameRef)
-{
-  if (mIsShutDown || mIsFlushing) {
-    // We are in the process of flushing or shutting down; ignore frame.
-    return NS_OK;
-  }
-
-  LOG("mp4 output frame %lld dts %lld pts %lld duration %lld us%s",
-      aFrameRef.byte_offset,
-      aFrameRef.decode_timestamp.ToMicroseconds(),
-      aFrameRef.composition_timestamp.ToMicroseconds(),
-      aFrameRef.duration.ToMicroseconds(),
-      aFrameRef.is_sync_point ? " keyframe" : ""
-  );
-
-  if (mQueuedSamples > mMaxRefFrames) {
-    // We had stopped requesting more input because we had received too much at
-    // the time. We can ask for more once again.
-    mCallback->InputExhausted();
-  }
-  MOZ_ASSERT(mQueuedSamples);
-  mQueuedSamples--;
-
-  if (!aImage) {
-    // Image was dropped by decoder.
-    return NS_OK;
-  }
-
-  bool useNullSample = false;
-  if (mSeekTargetThreshold.isSome()) {
-    if ((aFrameRef.composition_timestamp + aFrameRef.duration) < mSeekTargetThreshold.ref()) {
-      useNullSample = true;
-    } else {
-      mSeekTargetThreshold.reset();
-    }
-  }
-
-  // Where our resulting image will end up.
-  RefPtr<MediaData> data;
-  // Bounds.
-  VideoInfo info;
-  info.mDisplay = nsIntSize(mDisplayWidth, mDisplayHeight);
-  gfx::IntRect visible = gfx::IntRect(0,
-                                      0,
-                                      mPictureWidth,
-                                      mPictureHeight);
-
-  if (useNullSample) {
-    data = new NullData(aFrameRef.byte_offset,
-                        aFrameRef.composition_timestamp.ToMicroseconds(),
-                        aFrameRef.duration.ToMicroseconds());
-  } else if (mUseSoftwareImages) {
-    size_t width = CVPixelBufferGetWidth(aImage);
-    size_t height = CVPixelBufferGetHeight(aImage);
-    DebugOnly<size_t> planes = CVPixelBufferGetPlaneCount(aImage);
-    MOZ_ASSERT(planes == 2, "Likely not NV12 format and it must be.");
-
-    VideoData::YCbCrBuffer buffer;
-
-    // Lock the returned image data.
-    CVReturn rv = CVPixelBufferLockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
-    if (rv != kCVReturnSuccess) {
-      NS_ERROR("error locking pixel data");
-      mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
-      return NS_ERROR_FAILURE;
-    }
-    // Y plane.
-    buffer.mPlanes[0].mData =
-      static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(aImage, 0));
-    buffer.mPlanes[0].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 0);
-    buffer.mPlanes[0].mWidth = width;
-    buffer.mPlanes[0].mHeight = height;
-    buffer.mPlanes[0].mOffset = 0;
-    buffer.mPlanes[0].mSkip = 0;
-    // Cb plane.
-    buffer.mPlanes[1].mData =
-      static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(aImage, 1));
-    buffer.mPlanes[1].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 1);
-    buffer.mPlanes[1].mWidth = (width+1) / 2;
-    buffer.mPlanes[1].mHeight = (height+1) / 2;
-    buffer.mPlanes[1].mOffset = 0;
-    buffer.mPlanes[1].mSkip = 1;
-    // Cr plane.
-    buffer.mPlanes[2].mData =
-      static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(aImage, 1));
-    buffer.mPlanes[2].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 1);
-    buffer.mPlanes[2].mWidth = (width+1) / 2;
-    buffer.mPlanes[2].mHeight = (height+1) / 2;
-    buffer.mPlanes[2].mOffset = 1;
-    buffer.mPlanes[2].mSkip = 1;
-
-    // Copy the image data into our own format.
-    data =
-      VideoData::Create(info,
-                        mImageContainer,
-                        nullptr,
-                        aFrameRef.byte_offset,
-                        aFrameRef.composition_timestamp.ToMicroseconds(),
-                        aFrameRef.duration.ToMicroseconds(),
-                        buffer,
-                        aFrameRef.is_sync_point,
-                        aFrameRef.decode_timestamp.ToMicroseconds(),
-                        visible);
-    // Unlock the returned image data.
-    CVPixelBufferUnlockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
-  } else {
-#ifndef MOZ_WIDGET_UIKIT
-    IOSurfacePtr surface = MacIOSurfaceLib::CVPixelBufferGetIOSurface(aImage);
-    MOZ_ASSERT(surface, "Decoder didn't return an IOSurface backed buffer");
-
-    RefPtr<MacIOSurface> macSurface = new MacIOSurface(surface);
-
-    RefPtr<layers::Image> image = new MacIOSurfaceImage(macSurface);
-
-    data =
-      VideoData::CreateFromImage(info,
-                                 mImageContainer,
-                                 aFrameRef.byte_offset,
-                                 aFrameRef.composition_timestamp.ToMicroseconds(),
-                                 aFrameRef.duration.ToMicroseconds(),
-                                 image.forget(),
-                                 aFrameRef.is_sync_point,
-                                 aFrameRef.decode_timestamp.ToMicroseconds(),
-                                 visible);
-#else
-    MOZ_ASSERT_UNREACHABLE("No MacIOSurface on iOS");
-#endif
-  }
-
-  if (!data) {
-    NS_ERROR("Couldn't create VideoData for frame");
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
-    return NS_ERROR_FAILURE;
-  }
-
-  // Frames come out in DTS order but we need to output them
-  // in composition order.
-  MonitorAutoLock mon(mMonitor);
-  mReorderQueue.Push(data);
-  while (mReorderQueue.Length() > mMaxRefFrames) {
-    mCallback->Output(mReorderQueue.Pop().get());
-  }
-  LOG("%llu decoded frames queued",
-      static_cast<unsigned long long>(mReorderQueue.Length()));
-
-  return NS_OK;
-}
-
-nsresult
-AppleVDADecoder::ProcessDecode(MediaRawData* aSample)
-{
-  AssertOnTaskQueueThread();
-
-  mInputIncoming--;
-
-  if (mIsFlushing) {
-    return NS_OK;
-  }
-
-  auto rv = DoDecode(aSample);
-  // Ask for more data.
-  if (NS_SUCCEEDED(rv) && !mInputIncoming && mQueuedSamples <= mMaxRefFrames) {
-    LOG("%s task queue empty; requesting more data", GetDescriptionName());
-    mCallback->InputExhausted();
-  }
-
-  return rv;
-}
-
-nsresult
-AppleVDADecoder::DoDecode(MediaRawData* aSample)
-{
-  AssertOnTaskQueueThread();
-
-  AutoCFRelease<CFDataRef> block =
-    CFDataCreate(kCFAllocatorDefault, aSample->Data(), aSample->Size());
-  if (!block) {
-    NS_ERROR("Couldn't create CFData");
-    return NS_ERROR_FAILURE;
-  }
-
-  AutoCFRelease<CFNumberRef> pts =
-    CFNumberCreate(kCFAllocatorDefault,
-                   kCFNumberSInt64Type,
-                   &aSample->mTime);
-  AutoCFRelease<CFNumberRef> dts =
-    CFNumberCreate(kCFAllocatorDefault,
-                   kCFNumberSInt64Type,
-                   &aSample->mTimecode);
-  AutoCFRelease<CFNumberRef> duration =
-    CFNumberCreate(kCFAllocatorDefault,
-                   kCFNumberSInt64Type,
-                   &aSample->mDuration);
-  AutoCFRelease<CFNumberRef> byte_offset =
-    CFNumberCreate(kCFAllocatorDefault,
-                   kCFNumberSInt64Type,
-                   &aSample->mOffset);
-  char keyframe = aSample->mKeyframe ? 1 : 0;
-  AutoCFRelease<CFNumberRef> cfkeyframe =
-    CFNumberCreate(kCFAllocatorDefault,
-                   kCFNumberSInt8Type,
-                   &keyframe);
-
-  const void* keys[] = { CFSTR("FRAME_PTS"),
-                         CFSTR("FRAME_DTS"),
-                         CFSTR("FRAME_DURATION"),
-                         CFSTR("FRAME_OFFSET"),
-                         CFSTR("FRAME_KEYFRAME") };
-  const void* values[] = { pts,
-                           dts,
-                           duration,
-                           byte_offset,
-                           cfkeyframe };
-  static_assert(ArrayLength(keys) == ArrayLength(values),
-                "Non matching keys/values array size");
-
-  AutoCFRelease<CFDictionaryRef> frameInfo =
-    CFDictionaryCreate(kCFAllocatorDefault,
-                       keys,
-                       values,
-                       ArrayLength(keys),
-                       &kCFTypeDictionaryKeyCallBacks,
-                       &kCFTypeDictionaryValueCallBacks);
-
-  mQueuedSamples++;
-
-  OSStatus rv = VDADecoderDecode(mDecoder,
-                                 0,
-                                 block,
-                                 frameInfo);
-
-  if (rv != noErr) {
-    NS_WARNING("AppleVDADecoder: Couldn't pass frame to decoder");
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
-}
-
-nsresult
-AppleVDADecoder::InitializeSession()
-{
-  OSStatus rv;
-
-  AutoCFRelease<CFDictionaryRef> decoderConfig =
-    CreateDecoderSpecification();
-
-  AutoCFRelease<CFDictionaryRef> outputConfiguration =
-    CreateOutputConfiguration();
-
-  rv =
-    VDADecoderCreate(decoderConfig,
-                     outputConfiguration,
-                     (VDADecoderOutputCallback*)PlatformCallback,
-                     this,
-                     &mDecoder);
-
-  if (rv != noErr) {
-    NS_WARNING("AppleVDADecoder: Couldn't create hardware VDA decoder");
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
-}
-
-CFDictionaryRef
-AppleVDADecoder::CreateDecoderSpecification()
-{
-  const uint8_t* extradata = mExtraData->Elements();
-  int extrasize = mExtraData->Length();
-
-  OSType format = 'avc1';
-  AutoCFRelease<CFNumberRef> avc_width  =
-    CFNumberCreate(kCFAllocatorDefault,
-                   kCFNumberSInt32Type,
-                   &mPictureWidth);
-  AutoCFRelease<CFNumberRef> avc_height =
-    CFNumberCreate(kCFAllocatorDefault,
-                   kCFNumberSInt32Type,
-                   &mPictureHeight);
-  AutoCFRelease<CFNumberRef> avc_format =
-    CFNumberCreate(kCFAllocatorDefault,
-                   kCFNumberSInt32Type,
-                   &format);
-  AutoCFRelease<CFDataRef> avc_data =
-    CFDataCreate(kCFAllocatorDefault,
-                 extradata,
-                 extrasize);
-
-  const void* decoderKeys[] = { AppleVDALinker::skPropWidth,
-                                AppleVDALinker::skPropHeight,
-                                AppleVDALinker::skPropSourceFormat,
-                                AppleVDALinker::skPropAVCCData };
-  const void* decoderValue[] = { avc_width,
-                                 avc_height,
-                                 avc_format,
-                                 avc_data };
-  static_assert(ArrayLength(decoderKeys) == ArrayLength(decoderValue),
-                "Non matching keys/values array size");
-
-  return CFDictionaryCreate(kCFAllocatorDefault,
-                            decoderKeys,
-                            decoderValue,
-                            ArrayLength(decoderKeys),
-                            &kCFTypeDictionaryKeyCallBacks,
-                            &kCFTypeDictionaryValueCallBacks);
-}
-
-CFDictionaryRef
-AppleVDADecoder::CreateOutputConfiguration()
-{
-  if (mUseSoftwareImages) {
-    // Output format type:
-    SInt32 PixelFormatTypeValue =
-      kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
-    AutoCFRelease<CFNumberRef> PixelFormatTypeNumber =
-      CFNumberCreate(kCFAllocatorDefault,
-                     kCFNumberSInt32Type,
-                     &PixelFormatTypeValue);
-    const void* outputKeys[] = { kCVPixelBufferPixelFormatTypeKey };
-    const void* outputValues[] = { PixelFormatTypeNumber };
-    static_assert(ArrayLength(outputKeys) == ArrayLength(outputValues),
-                  "Non matching keys/values array size");
-
-    return CFDictionaryCreate(kCFAllocatorDefault,
-                              outputKeys,
-                              outputValues,
-                              ArrayLength(outputKeys),
-                              &kCFTypeDictionaryKeyCallBacks,
-                              &kCFTypeDictionaryValueCallBacks);
-  }
-
-#ifndef MOZ_WIDGET_UIKIT
-  // Output format type:
-  SInt32 PixelFormatTypeValue = kCVPixelFormatType_422YpCbCr8;
-  AutoCFRelease<CFNumberRef> PixelFormatTypeNumber =
-    CFNumberCreate(kCFAllocatorDefault,
-                   kCFNumberSInt32Type,
-                   &PixelFormatTypeValue);
-  // Construct IOSurface Properties
-  const void* IOSurfaceKeys[] = { MacIOSurfaceLib::kPropIsGlobal };
-  const void* IOSurfaceValues[] = { kCFBooleanTrue };
-  static_assert(ArrayLength(IOSurfaceKeys) == ArrayLength(IOSurfaceValues),
-                "Non matching keys/values array size");
-
-  // Contruct output configuration.
-  AutoCFRelease<CFDictionaryRef> IOSurfaceProperties =
-    CFDictionaryCreate(kCFAllocatorDefault,
-                       IOSurfaceKeys,
-                       IOSurfaceValues,
-                       ArrayLength(IOSurfaceKeys),
-                       &kCFTypeDictionaryKeyCallBacks,
-                       &kCFTypeDictionaryValueCallBacks);
-
-  const void* outputKeys[] = { kCVPixelBufferIOSurfacePropertiesKey,
-                               kCVPixelBufferPixelFormatTypeKey,
-                               kCVPixelBufferOpenGLCompatibilityKey };
-  const void* outputValues[] = { IOSurfaceProperties,
-                                 PixelFormatTypeNumber,
-                                 kCFBooleanTrue };
-  static_assert(ArrayLength(outputKeys) == ArrayLength(outputValues),
-                "Non matching keys/values array size");
-
-  return CFDictionaryCreate(kCFAllocatorDefault,
-                            outputKeys,
-                            outputValues,
-                            ArrayLength(outputKeys),
-                            &kCFTypeDictionaryKeyCallBacks,
-                            &kCFTypeDictionaryValueCallBacks);
-#else
-  MOZ_ASSERT_UNREACHABLE("No MacIOSurface on iOS");
-#endif
-}
-
-/* static */
-already_AddRefed<AppleVDADecoder>
-AppleVDADecoder::CreateVDADecoder(
-  const VideoInfo& aConfig,
-  TaskQueue* aTaskQueue,
-  MediaDataDecoderCallback* aCallback,
-  layers::ImageContainer* aImageContainer)
-{
-  if (!AppleDecoderModule::sCanUseHardwareVideoDecoder) {
-    // This GPU is blacklisted for hardware decoding.
-    return nullptr;
-  }
-
-  RefPtr<AppleVDADecoder> decoder =
-    new AppleVDADecoder(aConfig, aTaskQueue, aCallback, aImageContainer);
-
-  if (NS_FAILED(decoder->InitializeSession())) {
-    return nullptr;
-  }
-
-  return decoder.forget();
-}
-
-} // namespace mozilla
deleted file mode 100644
--- a/dom/media/platforms/apple/AppleVDADecoder.h
+++ /dev/null
@@ -1,159 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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_AppleVDADecoder_h
-#define mozilla_AppleVDADecoder_h
-
-#include "PlatformDecoderModule.h"
-#include "mozilla/Atomics.h"
-#include "mozilla/ReentrantMonitor.h"
-#include "MP4Decoder.h"
-#include "nsIThread.h"
-#include "ReorderQueue.h"
-#include "TimeUnits.h"
-
-#include "VideoDecodeAcceleration/VDADecoder.h"
-
-namespace mozilla {
-
-class TaskQueue;
-class MediaDataDecoderCallback;
-namespace layers {
-  class ImageContainer;
-} // namespace layers
-
-class AppleVDADecoder : public MediaDataDecoder {
-public:
-  class AppleFrameRef {
-  public:
-    media::TimeUnit decode_timestamp;
-    media::TimeUnit composition_timestamp;
-    media::TimeUnit duration;
-    int64_t byte_offset;
-    bool is_sync_point;
-
-    explicit AppleFrameRef(const MediaRawData& aSample)
-      : decode_timestamp(media::TimeUnit::FromMicroseconds(aSample.mTimecode))
-      , composition_timestamp(media::TimeUnit::FromMicroseconds(aSample.mTime))
-      , duration(media::TimeUnit::FromMicroseconds(aSample.mDuration))
-      , byte_offset(aSample.mOffset)
-      , is_sync_point(aSample.mKeyframe)
-    {
-    }
-
-    AppleFrameRef(const media::TimeUnit& aDts,
-                  const media::TimeUnit& aPts,
-                  const media::TimeUnit& aDuration,
-                  int64_t aByte_offset,
-                  bool aIs_sync_point)
-      : decode_timestamp(aDts)
-      , composition_timestamp(aPts)
-      , duration(aDuration)
-      , byte_offset(aByte_offset)
-      , is_sync_point(aIs_sync_point)
-    {
-    }
-  };
-
-  // Return a new created AppleVDADecoder or nullptr if media or hardware is
-  // not supported by current configuration.
-  static already_AddRefed<AppleVDADecoder> CreateVDADecoder(
-    const VideoInfo& aConfig,
-    TaskQueue* aTaskQueue,
-    MediaDataDecoderCallback* aCallback,
-    layers::ImageContainer* aImageContainer);
-
-  // Access from the taskqueue and the decoder's thread.
-  // OutputFrame is thread-safe.
-  nsresult OutputFrame(CVPixelBufferRef aImage,
-                       AppleFrameRef aFrameRef);
-
-  RefPtr<InitPromise> Init() override;
-  nsresult Input(MediaRawData* aSample) override;
-  nsresult Flush() override;
-  nsresult Drain() override;
-  nsresult Shutdown() override;
-  bool IsHardwareAccelerated(nsACString& aFailureReason) const override
-  {
-    return true;
-  }
-
-  const char* GetDescriptionName() const override
-  {
-    return "apple VDA decoder";
-  }
-
-  void SetSeekThreshold(const media::TimeUnit& aTime) override;
-
-protected:
-  AppleVDADecoder(const VideoInfo& aConfig,
-                  TaskQueue* aTaskQueue,
-                  MediaDataDecoderCallback* aCallback,
-                  layers::ImageContainer* aImageContainer);
-  virtual ~AppleVDADecoder();
-
-  void AssertOnTaskQueueThread()
-  {
-    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
-  }
-
-  AppleFrameRef* CreateAppleFrameRef(const MediaRawData* aSample);
-  void DrainReorderedFrames();
-  void ClearReorderedFrames();
-  CFDictionaryRef CreateOutputConfiguration();
-
-  const RefPtr<MediaByteBuffer> mExtraData;
-  MediaDataDecoderCallback* mCallback;
-  const uint32_t mPictureWidth;
-  const uint32_t mPictureHeight;
-  const uint32_t mDisplayWidth;
-  const uint32_t mDisplayHeight;
-
-  // Number of times a sample was queued via Input(). Will be decreased upon
-  // the decoder's callback being invoked.
-  // This is used to calculate how many frames has been buffered by the decoder.
-  Atomic<uint32_t> mQueuedSamples;
-
-private:
-  // Flush and Drain operation, always run
-  virtual void ProcessFlush();
-  virtual void ProcessDrain();
-  virtual void ProcessShutdown();
-
-  const RefPtr<TaskQueue> mTaskQueue;
-  VDADecoder mDecoder;
-  const uint32_t mMaxRefFrames;
-  const RefPtr<layers::ImageContainer> mImageContainer;
-  // Increased when Input is called, and decreased when ProcessFrame runs.
-  // Reaching 0 indicates that there's no pending Input.
-  Atomic<uint32_t> mInputIncoming;
-  Atomic<bool> mIsShutDown;
-  const bool mUseSoftwareImages;
-
-  // Protects mReorderQueue.
-  Monitor mMonitor;
-  // Set on reader/decode thread calling Flush() to indicate that output is
-  // not required and so input samples on mTaskQueue need not be processed.
-  // Cleared on mTaskQueue in ProcessDrain().
-  Atomic<bool> mIsFlushing;
-  ReorderQueue mReorderQueue;
-  // Decoded frame will be dropped if its pts is smaller than this
-  // value. It shold be initialized before Input() or after Flush(). So it is
-  // safe to access it in OutputFrame without protecting.
-  Maybe<media::TimeUnit> mSeekTargetThreshold;
-
-  // Method to set up the decompression session.
-  nsresult InitializeSession();
-
-  // Method to pass a frame to VideoToolbox for decoding.
-  nsresult ProcessDecode(MediaRawData* aSample);
-  virtual nsresult DoDecode(MediaRawData* aSample);
-  CFDictionaryRef CreateDecoderSpecification();
-};
-
-} // namespace mozilla
-
-#endif // mozilla_AppleVDADecoder_h
deleted file mode 100644
--- a/dom/media/platforms/apple/AppleVDAFunctions.h
+++ /dev/null
@@ -1,12 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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/. */
-
-// Construct references to each of the VDA symbols we use.
-
-LINK_FUNC(VDADecoderCreate)
-LINK_FUNC(VDADecoderDecode)
-LINK_FUNC(VDADecoderFlush)
-LINK_FUNC(VDADecoderDestroy)
deleted file mode 100644
--- a/dom/media/platforms/apple/AppleVDALinker.cpp
+++ /dev/null
@@ -1,103 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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 <dlfcn.h>
-
-#include "AppleVDALinker.h"
-#include "nsDebug.h"
-
-#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
-
-namespace mozilla {
-
-AppleVDALinker::LinkStatus
-AppleVDALinker::sLinkStatus = LinkStatus_INIT;
-
-void* AppleVDALinker::sLink = nullptr;
-CFStringRef AppleVDALinker::skPropWidth = nullptr;
-CFStringRef AppleVDALinker::skPropHeight = nullptr;
-CFStringRef AppleVDALinker::skPropSourceFormat = nullptr;
-CFStringRef AppleVDALinker::skPropAVCCData = nullptr;
-
-#define LINK_FUNC(func) typeof(func) func;
-#include "AppleVDAFunctions.h"
-#undef LINK_FUNC
-
-/* static */ bool
-AppleVDALinker::Link()
-{
-  if (sLinkStatus) {
-    return sLinkStatus == LinkStatus_SUCCEEDED;
-  }
-
-  const char* dlname =
-    "/System/Library/Frameworks/VideoDecodeAcceleration.framework/VideoDecodeAcceleration";
-
-  if (!(sLink = dlopen(dlname, RTLD_NOW | RTLD_LOCAL))) {
-    NS_WARNING("Couldn't load VideoDecodeAcceleration framework");
-    goto fail;
-  }
-
-#define LINK_FUNC(func)                                                   \
-  func = (typeof(func))dlsym(sLink, #func);                               \
-  if (!func) {                                                            \
-    NS_WARNING("Couldn't load VideoDecodeAcceleration function " #func ); \
-    goto fail;                                                            \
-  }
-#include "AppleVDAFunctions.h"
-#undef LINK_FUNC
-
-  skPropWidth = GetIOConst("kVDADecoderConfiguration_Width");
-  skPropHeight = GetIOConst("kVDADecoderConfiguration_Height");
-  skPropSourceFormat = GetIOConst("kVDADecoderConfiguration_SourceFormat");
-  skPropAVCCData = GetIOConst("kVDADecoderConfiguration_avcCData");
-
-  if (!skPropWidth || !skPropHeight || !skPropSourceFormat || !skPropAVCCData) {
-    goto fail;
-  }
-
-  LOG("Loaded VideoDecodeAcceleration framework.");
-  sLinkStatus = LinkStatus_SUCCEEDED;
-  return true;
-
-fail:
-  Unlink();
-
-  sLinkStatus = LinkStatus_FAILED;
-  return false;
-}
-
-/* static */ void
-AppleVDALinker::Unlink()
-{
-  if (sLink) {
-    LOG("Unlinking VideoDecodeAcceleration framework.");
-#define LINK_FUNC(func)                                                   \
-    func = nullptr;
-#include "AppleVDAFunctions.h"
-#undef LINK_FUNC
-    dlclose(sLink);
-    sLink = nullptr;
-    skPropWidth = nullptr;
-    skPropHeight = nullptr;
-    skPropSourceFormat = nullptr;
-    skPropAVCCData = nullptr;
-    sLinkStatus = LinkStatus_INIT;
-  }
-}
-
-/* static */ CFStringRef
-AppleVDALinker::GetIOConst(const char* symbol)
-{
-  CFStringRef* address = (CFStringRef*)dlsym(sLink, symbol);
-  if (!address) {
-    return nullptr;
-  }
-
-  return *address;
-}
-
-} // namespace mozilla
deleted file mode 100644
--- a/dom/media/platforms/apple/AppleVDALinker.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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 AppleVDALinker_h
-#define AppleVDALinker_h
-
-extern "C" {
-#pragma GCC visibility push(default)
-#include "VideoDecodeAcceleration/VDADecoder.h"
-#pragma GCC visibility pop
-}
-
-#include "nscore.h"
-
-namespace mozilla {
-
-class AppleVDALinker
-{
-public:
-  static bool Link();
-  static void Unlink();
-  static CFStringRef skPropWidth;
-  static CFStringRef skPropHeight;
-  static CFStringRef skPropSourceFormat;
-  static CFStringRef skPropAVCCData;
-
-private:
-  static void* sLink;
-  static nsrefcnt sRefCount;
-
-  static enum LinkStatus {
-    LinkStatus_INIT = 0,
-    LinkStatus_FAILED,
-    LinkStatus_SUCCEEDED
-  } sLinkStatus;
-
-  static CFStringRef GetIOConst(const char* symbol);
-};
-
-#define LINK_FUNC(func) extern typeof(func)* func;
-#include "AppleVDAFunctions.h"
-#undef LINK_FUNC
-
-} // namespace mozilla
-
-#endif // AppleVDALinker_h
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -19,21 +19,55 @@
 #include "mozilla/Logging.h"
 #include "VideoUtils.h"
 #include "gfxPlatform.h"
 
 #define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
 
 namespace mozilla {
 
+static uint32_t ComputeMaxRefFrames(const MediaByteBuffer* aExtraData)
+{
+  uint32_t maxRefFrames = 4;
+  // Retrieve video dimensions from H264 SPS NAL.
+  mp4_demuxer::SPSData spsdata;
+  if (mp4_demuxer::H264::DecodeSPSFromExtraData(aExtraData, spsdata)) {
+    // max_num_ref_frames determines the size of the sliding window
+    // we need to queue that many frames in order to guarantee proper
+    // pts frames ordering. Use a minimum of 4 to ensure proper playback of
+    // non compliant videos.
+    maxRefFrames =
+      std::min(std::max(maxRefFrames, spsdata.max_num_ref_frames + 1), 16u);
+  }
+  return maxRefFrames;
+}
+
 AppleVTDecoder::AppleVTDecoder(const VideoInfo& aConfig,
                                TaskQueue* aTaskQueue,
                                MediaDataDecoderCallback* aCallback,
                                layers::ImageContainer* aImageContainer)
-  : AppleVDADecoder(aConfig, aTaskQueue, aCallback, aImageContainer)
+  : mExtraData(aConfig.mExtraData)
+  , mCallback(aCallback)
+  , mPictureWidth(aConfig.mImage.width)
+  , mPictureHeight(aConfig.mImage.height)
+  , mDisplayWidth(aConfig.mDisplay.width)
+  , mDisplayHeight(aConfig.mDisplay.height)
+  , mQueuedSamples(0)
+  , mTaskQueue(aTaskQueue)
+  , mMaxRefFrames(ComputeMaxRefFrames(aConfig.mExtraData))
+  , mImageContainer(aImageContainer)
+  , mInputIncoming(0)
+  , mIsShutDown(false)
+#ifdef MOZ_WIDGET_UIKIT
+  , mUseSoftwareImages(true)
+#else
+  , mUseSoftwareImages(false)
+#endif
+  , mIsFlushing(false)
+  , mMonitor("AppleVideoDecoder")
   , mFormat(nullptr)
   , mSession(nullptr)
   , mIsHardwareAccelerated(false)
 {
   MOZ_COUNT_CTOR(AppleVTDecoder);
   // TODO: Verify aConfig.mime_type.
   LOG("Creating AppleVTDecoder for %dx%d h.264 video",
       mDisplayWidth,
@@ -53,16 +87,98 @@ AppleVTDecoder::Init()
 
   if (NS_SUCCEEDED(rv)) {
     return InitPromise::CreateAndResolve(TrackType::kVideoTrack, __func__);
   }
 
   return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
 }
 
+nsresult
+AppleVTDecoder::Input(MediaRawData* aSample)
+{
+  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+
+  LOG("mp4 input sample %p pts %lld duration %lld us%s %d bytes",
+      aSample,
+      aSample->mTime,
+      aSample->mDuration,
+      aSample->mKeyframe ? " keyframe" : "",
+      aSample->Size());
+
+  mInputIncoming++;
+
+  mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
+    this, &AppleVTDecoder::ProcessDecode, aSample));
+  return NS_OK;
+}
+
+nsresult
+AppleVTDecoder::Flush()
+{
+  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+  mIsFlushing = true;
+  nsCOMPtr<nsIRunnable> runnable =
+    NewRunnableMethod(this, &AppleVTDecoder::ProcessFlush);
+  SyncRunnable::DispatchToThread(mTaskQueue, runnable);
+  mIsFlushing = false;
+  // All ProcessDecode() tasks should be done.
+  MOZ_ASSERT(mInputIncoming == 0);
+
+  mSeekTargetThreshold.reset();
+
+  return NS_OK;
+}
+
+nsresult
+AppleVTDecoder::Drain()
+{
+  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+  nsCOMPtr<nsIRunnable> runnable =
+    NewRunnableMethod(this, &AppleVTDecoder::ProcessDrain);
+  mTaskQueue->Dispatch(runnable.forget());
+  return NS_OK;
+}
+
+nsresult
+AppleVTDecoder::Shutdown()
+{
+  MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
+  mIsShutDown = true;
+  if (mTaskQueue) {
+    nsCOMPtr<nsIRunnable> runnable =
+      NewRunnableMethod(this, &AppleVTDecoder::ProcessShutdown);
+    mTaskQueue->Dispatch(runnable.forget());
+  } else {
+    ProcessShutdown();
+  }
+  return NS_OK;
+}
+
+nsresult
+AppleVTDecoder::ProcessDecode(MediaRawData* aSample)
+{
+  AssertOnTaskQueueThread();
+
+  mInputIncoming--;
+
+  if (mIsFlushing) {
+    return NS_OK;
+  }
+
+  auto rv = DoDecode(aSample);
+  // Ask for more data.
+  if (NS_SUCCEEDED(rv) && !mInputIncoming && mQueuedSamples <= mMaxRefFrames) {
+    LOG("%s task queue empty; requesting more data", GetDescriptionName());
+    mCallback->InputExhausted();
+  }
+
+  return rv;
+}
+
 void
 AppleVTDecoder::ProcessShutdown()
 {
   if (mSession) {
     LOG("%s: cleaning up session %p", __func__, mSession);
     VTDecompressionSessionInvalidate(mSession);
     CFRelease(mSession);
     mSession = nullptr;
@@ -94,16 +210,50 @@ AppleVTDecoder::ProcessDrain()
   if (NS_FAILED(rv)) {
     LOG("AppleVTDecoder::Drain failed waiting for platform decoder "
         "with error:%d.", rv);
   }
   DrainReorderedFrames();
   mCallback->DrainComplete();
 }
 
+AppleVTDecoder::AppleFrameRef*
+AppleVTDecoder::CreateAppleFrameRef(const MediaRawData* aSample)
+{
+  MOZ_ASSERT(aSample);
+  return new AppleFrameRef(*aSample);
+}
+
+void
+AppleVTDecoder::DrainReorderedFrames()
+{
+  MonitorAutoLock mon(mMonitor);
+  while (!mReorderQueue.IsEmpty()) {
+    mCallback->Output(mReorderQueue.Pop().get());
+  }
+  mQueuedSamples = 0;
+}
+
+void
+AppleVTDecoder::ClearReorderedFrames()
+{
+  MonitorAutoLock mon(mMonitor);
+  while (!mReorderQueue.IsEmpty()) {
+    mReorderQueue.Pop();
+  }
+  mQueuedSamples = 0;
+}
+
+void
+AppleVTDecoder::SetSeekThreshold(const media::TimeUnit& aTime)
+{
+  LOG("SetSeekThreshold %lld", aTime.ToMicroseconds());
+  mSeekTargetThreshold = Some(aTime);
+}
+
 //
 // Implementation details.
 //
 
 // Callback passed to the VideoToolbox decoder for returning data.
 // This needs to be static because the API takes a C-style pair of
 // function and userdata pointers. This validates parameters and
 // forwards the decoded image back to an object method.
@@ -131,16 +281,167 @@ PlatformCallback(void* decompressionOutp
     NS_WARNING("  ...frame tagged as dropped...");
   } else {
     MOZ_ASSERT(CFGetTypeID(image) == CVPixelBufferGetTypeID(),
       "VideoToolbox returned an unexpected image type");
   }
   decoder->OutputFrame(image, *frameRef);
 }
 
+// Copy and return a decoded frame.
+nsresult
+AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage,
+                            AppleVTDecoder::AppleFrameRef aFrameRef)
+{
+  if (mIsShutDown || mIsFlushing) {
+    // We are in the process of flushing or shutting down; ignore frame.
+    return NS_OK;
+  }
+
+  LOG("mp4 output frame %lld dts %lld pts %lld duration %lld us%s",
+      aFrameRef.byte_offset,
+      aFrameRef.decode_timestamp.ToMicroseconds(),
+      aFrameRef.composition_timestamp.ToMicroseconds(),
+      aFrameRef.duration.ToMicroseconds(),
+      aFrameRef.is_sync_point ? " keyframe" : ""
+  );
+
+  if (mQueuedSamples > mMaxRefFrames) {
+    // We had stopped requesting more input because we had received too much at
+    // the time. We can ask for more once again.
+    mCallback->InputExhausted();
+  }
+  MOZ_ASSERT(mQueuedSamples);
+  mQueuedSamples--;
+
+  if (!aImage) {
+    // Image was dropped by decoder.
+    return NS_OK;
+  }
+
+  bool useNullSample = false;
+  if (mSeekTargetThreshold.isSome()) {
+    if ((aFrameRef.composition_timestamp + aFrameRef.duration) < mSeekTargetThreshold.ref()) {
+      useNullSample = true;
+    } else {
+      mSeekTargetThreshold.reset();
+    }
+  }
+
+  // Where our resulting image will end up.
+  RefPtr<MediaData> data;
+  // Bounds.
+  VideoInfo info;
+  info.mDisplay = nsIntSize(mDisplayWidth, mDisplayHeight);
+  gfx::IntRect visible = gfx::IntRect(0,
+                                      0,
+                                      mPictureWidth,
+                                      mPictureHeight);
+
+  if (useNullSample) {
+    data = new NullData(aFrameRef.byte_offset,
+                        aFrameRef.composition_timestamp.ToMicroseconds(),
+                        aFrameRef.duration.ToMicroseconds());
+  } else if (mUseSoftwareImages) {
+    size_t width = CVPixelBufferGetWidth(aImage);
+    size_t height = CVPixelBufferGetHeight(aImage);
+    DebugOnly<size_t> planes = CVPixelBufferGetPlaneCount(aImage);
+    MOZ_ASSERT(planes == 2, "Likely not NV12 format and it must be.");
+
+    VideoData::YCbCrBuffer buffer;
+
+    // Lock the returned image data.
+    CVReturn rv = CVPixelBufferLockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
+    if (rv != kCVReturnSuccess) {
+      NS_ERROR("error locking pixel data");
+      mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+    // Y plane.
+    buffer.mPlanes[0].mData =
+      static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(aImage, 0));
+    buffer.mPlanes[0].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 0);
+    buffer.mPlanes[0].mWidth = width;
+    buffer.mPlanes[0].mHeight = height;
+    buffer.mPlanes[0].mOffset = 0;
+    buffer.mPlanes[0].mSkip = 0;
+    // Cb plane.
+    buffer.mPlanes[1].mData =
+      static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(aImage, 1));
+    buffer.mPlanes[1].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 1);
+    buffer.mPlanes[1].mWidth = (width+1) / 2;
+    buffer.mPlanes[1].mHeight = (height+1) / 2;
+    buffer.mPlanes[1].mOffset = 0;
+    buffer.mPlanes[1].mSkip = 1;
+    // Cr plane.
+    buffer.mPlanes[2].mData =
+      static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(aImage, 1));
+    buffer.mPlanes[2].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 1);
+    buffer.mPlanes[2].mWidth = (width+1) / 2;
+    buffer.mPlanes[2].mHeight = (height+1) / 2;
+    buffer.mPlanes[2].mOffset = 1;
+    buffer.mPlanes[2].mSkip = 1;
+
+    // Copy the image data into our own format.
+    data =
+      VideoData::Create(info,
+                        mImageContainer,
+                        nullptr,
+                        aFrameRef.byte_offset,
+                        aFrameRef.composition_timestamp.ToMicroseconds(),
+                        aFrameRef.duration.ToMicroseconds(),
+                        buffer,
+                        aFrameRef.is_sync_point,
+                        aFrameRef.decode_timestamp.ToMicroseconds(),
+                        visible);
+    // Unlock the returned image data.
+    CVPixelBufferUnlockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
+  } else {
+#ifndef MOZ_WIDGET_UIKIT
+    IOSurfacePtr surface = MacIOSurfaceLib::CVPixelBufferGetIOSurface(aImage);
+    MOZ_ASSERT(surface, "Decoder didn't return an IOSurface backed buffer");
+
+    RefPtr<MacIOSurface> macSurface = new MacIOSurface(surface);
+
+    RefPtr<layers::Image> image = new MacIOSurfaceImage(macSurface);
+
+    data =
+      VideoData::CreateFromImage(info,
+                                 mImageContainer,
+                                 aFrameRef.byte_offset,
+                                 aFrameRef.composition_timestamp.ToMicroseconds(),
+                                 aFrameRef.duration.ToMicroseconds(),
+                                 image.forget(),
+                                 aFrameRef.is_sync_point,
+                                 aFrameRef.decode_timestamp.ToMicroseconds(),
+                                 visible);
+#else
+    MOZ_ASSERT_UNREACHABLE("No MacIOSurface on iOS");
+#endif
+  }
+
+  if (!data) {
+    NS_ERROR("Couldn't create VideoData for frame");
+    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    return NS_ERROR_FAILURE;
+  }
+
+  // Frames come out in DTS order but we need to output them
+  // in composition order.
+  MonitorAutoLock mon(mMonitor);
+  mReorderQueue.Push(data);
+  while (mReorderQueue.Length() > mMaxRefFrames) {
+    mCallback->Output(mReorderQueue.Pop().get());
+  }
+  LOG("%llu decoded frames queued",
+      static_cast<unsigned long long>(mReorderQueue.Length()));
+
+  return NS_OK;
+}
+
 nsresult
 AppleVTDecoder::WaitForAsynchronousFrames()
 {
   OSStatus rv = VTDecompressionSessionWaitForAsynchronousFrames(mSession);
   if (rv != noErr) {
     LOG("AppleVTDecoder: Error %d waiting for asynchronous frames", rv);
     return NS_ERROR_FAILURE;
   }
@@ -334,9 +635,76 @@ AppleVTDecoder::CreateDecoderSpecificati
   return CFDictionaryCreate(kCFAllocatorDefault,
                             specKeys,
                             specValues,
                             ArrayLength(specKeys),
                             &kCFTypeDictionaryKeyCallBacks,
                             &kCFTypeDictionaryValueCallBacks);
 }
 
+CFDictionaryRef
+AppleVTDecoder::CreateOutputConfiguration()
+{
+  if (mUseSoftwareImages) {
+    // Output format type:
+    SInt32 PixelFormatTypeValue =
+      kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
+    AutoCFRelease<CFNumberRef> PixelFormatTypeNumber =
+      CFNumberCreate(kCFAllocatorDefault,
+                     kCFNumberSInt32Type,
+                     &PixelFormatTypeValue);
+    const void* outputKeys[] = { kCVPixelBufferPixelFormatTypeKey };
+    const void* outputValues[] = { PixelFormatTypeNumber };
+    static_assert(ArrayLength(outputKeys) == ArrayLength(outputValues),
+                  "Non matching keys/values array size");
+
+    return CFDictionaryCreate(kCFAllocatorDefault,
+                              outputKeys,
+                              outputValues,
+                              ArrayLength(outputKeys),
+                              &kCFTypeDictionaryKeyCallBacks,
+                              &kCFTypeDictionaryValueCallBacks);
+  }
+
+#ifndef MOZ_WIDGET_UIKIT
+  // Output format type:
+  SInt32 PixelFormatTypeValue = kCVPixelFormatType_422YpCbCr8;
+  AutoCFRelease<CFNumberRef> PixelFormatTypeNumber =
+    CFNumberCreate(kCFAllocatorDefault,
+                   kCFNumberSInt32Type,
+                   &PixelFormatTypeValue);
+  // Construct IOSurface Properties
+  const void* IOSurfaceKeys[] = { MacIOSurfaceLib::kPropIsGlobal };
+  const void* IOSurfaceValues[] = { kCFBooleanTrue };
+  static_assert(ArrayLength(IOSurfaceKeys) == ArrayLength(IOSurfaceValues),
+                "Non matching keys/values array size");
+
+  // Contruct output configuration.
+  AutoCFRelease<CFDictionaryRef> IOSurfaceProperties =
+    CFDictionaryCreate(kCFAllocatorDefault,
+                       IOSurfaceKeys,
+                       IOSurfaceValues,
+                       ArrayLength(IOSurfaceKeys),
+                       &kCFTypeDictionaryKeyCallBacks,
+                       &kCFTypeDictionaryValueCallBacks);
+
+  const void* outputKeys[] = { kCVPixelBufferIOSurfacePropertiesKey,
+                               kCVPixelBufferPixelFormatTypeKey,
+                               kCVPixelBufferOpenGLCompatibilityKey };
+  const void* outputValues[] = { IOSurfaceProperties,
+                                 PixelFormatTypeNumber,
+                                 kCFBooleanTrue };
+  static_assert(ArrayLength(outputKeys) == ArrayLength(outputValues),
+                "Non matching keys/values array size");
+
+  return CFDictionaryCreate(kCFAllocatorDefault,
+                            outputKeys,
+                            outputValues,
+                            ArrayLength(outputKeys),
+                            &kCFTypeDictionaryKeyCallBacks,
+                            &kCFTypeDictionaryValueCallBacks);
+#else
+  MOZ_ASSERT_UNREACHABLE("No MacIOSurface on iOS");
+#endif
+}
+
+
 } // namespace mozilla
--- a/dom/media/platforms/apple/AppleVTDecoder.h
+++ b/dom/media/platforms/apple/AppleVTDecoder.h
@@ -2,56 +2,133 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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_AppleVTDecoder_h
 #define mozilla_AppleVTDecoder_h
 
-#include "AppleVDADecoder.h"
+#include "PlatformDecoderModule.h"
+#include "mozilla/Atomics.h"
+#include "nsIThread.h"
+#include "ReorderQueue.h"
+#include "TimeUnits.h"
 
 #include "VideoToolbox/VideoToolbox.h"
 
 namespace mozilla {
 
-class AppleVTDecoder : public AppleVDADecoder {
+class AppleVTDecoder : public MediaDataDecoder {
 public:
   AppleVTDecoder(const VideoInfo& aConfig,
                  TaskQueue* aTaskQueue,
                  MediaDataDecoderCallback* aCallback,
                  layers::ImageContainer* aImageContainer);
 
+  class AppleFrameRef {
+  public:
+    media::TimeUnit decode_timestamp;
+    media::TimeUnit composition_timestamp;
+    media::TimeUnit duration;
+    int64_t byte_offset;
+    bool is_sync_point;
+
+    explicit AppleFrameRef(const MediaRawData& aSample)
+      : decode_timestamp(media::TimeUnit::FromMicroseconds(aSample.mTimecode))
+      , composition_timestamp(media::TimeUnit::FromMicroseconds(aSample.mTime))
+      , duration(media::TimeUnit::FromMicroseconds(aSample.mDuration))
+      , byte_offset(aSample.mOffset)
+      , is_sync_point(aSample.mKeyframe)
+    {
+    }
+  };
+
   RefPtr<InitPromise> Init() override;
+  nsresult Input(MediaRawData* aSample) override;
+  nsresult Flush() override;
+  nsresult Drain() override;
+  nsresult Shutdown() override;
+  void SetSeekThreshold(const media::TimeUnit& aTime) override;
+
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override
   {
     return mIsHardwareAccelerated;
   }
 
   const char* GetDescriptionName() const override
   {
     return mIsHardwareAccelerated
       ? "apple hardware VT decoder"
       : "apple software VT decoder";
   }
 
+  // Access from the taskqueue and the decoder's thread.
+  // OutputFrame is thread-safe.
+  nsresult OutputFrame(CVPixelBufferRef aImage,
+                       AppleFrameRef aFrameRef);
+
 private:
   virtual ~AppleVTDecoder();
-  void ProcessFlush() override;
-  void ProcessDrain() override;
-  void ProcessShutdown() override;
+  void ProcessFlush();
+  void ProcessDrain();
+  void ProcessShutdown();
+  nsresult ProcessDecode(MediaRawData* aSample);
+
+  void AssertOnTaskQueueThread()
+  {
+    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+  }
 
-  CMVideoFormatDescriptionRef mFormat;
-  VTDecompressionSessionRef mSession;
+  AppleFrameRef* CreateAppleFrameRef(const MediaRawData* aSample);
+  void DrainReorderedFrames();
+  void ClearReorderedFrames();
+  CFDictionaryRef CreateOutputConfiguration();
 
-  // Method to pass a frame to VideoToolbox for decoding.
-  nsresult DoDecode(MediaRawData* aSample) override;
+  const RefPtr<MediaByteBuffer> mExtraData;
+  MediaDataDecoderCallback* mCallback;
+  const uint32_t mPictureWidth;
+  const uint32_t mPictureHeight;
+  const uint32_t mDisplayWidth;
+  const uint32_t mDisplayHeight;
+
+  // Number of times a sample was queued via Input(). Will be decreased upon
+  // the decoder's callback being invoked.
+  // This is used to calculate how many frames has been buffered by the decoder.
+  Atomic<uint32_t> mQueuedSamples;
+
   // Method to set up the decompression session.
   nsresult InitializeSession();
   nsresult WaitForAsynchronousFrames();
   CFDictionaryRef CreateDecoderSpecification();
   CFDictionaryRef CreateDecoderExtensions();
+  // Method to pass a frame to VideoToolbox for decoding.
+  nsresult DoDecode(MediaRawData* aSample);
+
+  const RefPtr<TaskQueue> mTaskQueue;
+  const uint32_t mMaxRefFrames;
+  const RefPtr<layers::ImageContainer> mImageContainer;
+  // Increased when Input is called, and decreased when ProcessFrame runs.
+  // Reaching 0 indicates that there's no pending Input.
+  Atomic<uint32_t> mInputIncoming;
+  Atomic<bool> mIsShutDown;
+  const bool mUseSoftwareImages;
+
+  // Set on reader/decode thread calling Flush() to indicate that output is
+  // not required and so input samples on mTaskQueue need not be processed.
+  // Cleared on mTaskQueue in ProcessDrain().
+  Atomic<bool> mIsFlushing;
+  // Protects mReorderQueue.
+  Monitor mMonitor;
+  ReorderQueue mReorderQueue;
+  // Decoded frame will be dropped if its pts is smaller than this
+  // value. It shold be initialized before Input() or after Flush(). So it is
+  // safe to access it in OutputFrame without protecting.
+  Maybe<media::TimeUnit> mSeekTargetThreshold;
+
+  CMVideoFormatDescriptionRef mFormat;
+  VTDecompressionSessionRef mSession;
   Atomic<bool> mIsHardwareAccelerated;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_AppleVTDecoder_h
deleted file mode 100644
--- a/dom/media/platforms/apple/VideoDecodeAcceleration/VDADecoder.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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/. */
-
-// Stub header for VideoDecodeAcceleration framework API.
-// This is a private Framework on 10.6 see:
-// https://developer.apple.com/library/mac/technotes/tn2267/_index.html
-// We include our own copy so we can build on MacOS versions
-// where it's not available.
-
-#ifndef mozilla_VideoDecodeAcceleration_VDADecoder_h
-#define mozilla_VideoDecodeAcceleration_VDADecoder_h
-
-#include <CoreFoundation/CoreFoundation.h>
-#include <CoreVideo/CoreVideo.h>
-
-typedef uint32_t VDADecodeFrameFlags;
-typedef uint32_t VDADecodeInfoFlags;
-
-enum {
-  kVDADecodeInfo_Asynchronous = 1UL << 0,
-  kVDADecodeInfo_FrameDropped = 1UL << 1
-};
-
-enum {
-  kVDADecoderFlush_EmitFrames = 1 << 0
-};
-
-typedef struct OpaqueVDADecoder* VDADecoder;
-
-typedef void (*VDADecoderOutputCallback)
-  (void* decompressionOutputRefCon,
-   CFDictionaryRef frameInfo,
-   OSStatus status,
-   uint32_t infoFlags,
-   CVImageBufferRef imageBuffer);
-
-OSStatus
-VDADecoderCreate(
-  CFDictionaryRef decoderConfiguration,
-  CFDictionaryRef destinationImageBufferAttributes, /* can be NULL */
-  VDADecoderOutputCallback* outputCallback,
-  void* decoderOutputCallbackRefcon,
-  VDADecoder* decoderOut);
-
-OSStatus
-VDADecoderDecode(
-  VDADecoder decoder,
-  uint32_t decodeFlags,
-  CFTypeRef compressedBuffer,
-  CFDictionaryRef frameInfo); /* can be NULL */
-
-OSStatus
-VDADecoderFlush(
-  VDADecoder decoder,
-  uint32_t flushFlags);
-
-OSStatus
-VDADecoderDestroy(VDADecoder decoder);
-
-#endif // mozilla_VideoDecodeAcceleration_VDADecoder_h
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -58,18 +58,16 @@ if CONFIG['MOZ_FFMPEG']:
 if CONFIG['MOZ_APPLEMEDIA']:
   EXPORTS += [
       'apple/AppleDecoderModule.h',
   ]
   UNIFIED_SOURCES += [
       'apple/AppleATDecoder.cpp',
       'apple/AppleCMLinker.cpp',
       'apple/AppleDecoderModule.cpp',
-      'apple/AppleVDADecoder.cpp',
-      'apple/AppleVDALinker.cpp',
       'apple/AppleVTDecoder.cpp',
       'apple/AppleVTLinker.cpp',
   ]
   OS_LIBS += [
       '-framework AudioToolbox',
   ]
 
 if CONFIG['MOZ_GONK_MEDIACODEC']:
copy from dom/media/systemservices/MediaUtils.h
copy to dom/media/systemservices/MediaTaskUtils.h
--- a/dom/media/systemservices/MediaUtils.h
+++ b/dom/media/systemservices/MediaTaskUtils.h
@@ -1,217 +1,30 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set sw=2 ts=8 et ft=cpp : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef mozilla_MediaUtils_h
-#define mozilla_MediaUtils_h
+#ifndef mozilla_MediaTaskUtils_h
+#define mozilla_MediaTaskUtils_h
 
 #include "nsThreadUtils.h"
-#include "nsIAsyncShutdown.h"
-#include "mozilla/UniquePtr.h"
+
+// The main reason this file is separate from MediaUtils.h
 #include "base/task.h"
 
 namespace mozilla {
 namespace media {
 
-/*
- * media::Pledge - A promise-like pattern for c++ that takes lambda functions.
- *
- * Asynchronous APIs that proxy to another thread or to the chrome process and
- * back may find it useful to return a pledge to callers who then use
- * pledge.Then(func) to specify a lambda function to be invoked with the result
- * later back on this same thread.
- *
- * Callers will enjoy that lambdas allow "capturing" of local variables, much
- * like closures in JavaScript (safely by-copy by default).
+/* media::NewTaskFrom() - Create a Task from a lambda.
  *
- * Callers will also enjoy that they do not need to be thread-safe (their code
- * runs on the same thread after all).
- *
- * Advantageously, pledges are non-threadsafe by design (because locking and
- * event queues are redundant). This means none of the lambdas you pass in,
- * or variables you lambda-capture into them, need be threasafe or support
- * threadsafe refcounting. After all, they'll run later on the same thread.
- *
- *   RefPtr<media::Pledge<Foo>> p = GetFooAsynchronously(); // returns a pledge
- *   p->Then([](const Foo& foo) {
- *     // use foo here (same thread. Need not be thread-safe!)
- *   });
- *
- * See media::CoatCheck below for an example of GetFooAsynchronously().
+ * Similar to media::NewRunnableFrom() - Create an nsRunnable from a lambda.
  */
 
-class PledgeBase
-{
-public:
-  NS_INLINE_DECL_REFCOUNTING(PledgeBase);
-protected:
-  virtual ~PledgeBase() {};
-};
-
-template<typename ValueType, typename ErrorType = nsresult>
-class Pledge : public PledgeBase
-{
-  // TODO: Remove workaround once mozilla allows std::function from <functional>
-  // wo/std::function support, do template + virtual trick to accept lambdas
-  class FunctorsBase
-  {
-  public:
-    FunctorsBase() {}
-    virtual void Succeed(ValueType& result) = 0;
-    virtual void Fail(ErrorType& error) = 0;
-    virtual ~FunctorsBase() {};
-  };
-
-public:
-  explicit Pledge() : mDone(false), mRejected(false) {}
-  Pledge(const Pledge& aOther) = delete;
-  Pledge& operator = (const Pledge&) = delete;
-
-  template<typename OnSuccessType>
-  void Then(OnSuccessType&& aOnSuccess)
-  {
-    Then(Forward<OnSuccessType>(aOnSuccess), [](ErrorType&){});
-  }
-
-  template<typename OnSuccessType, typename OnFailureType>
-  void Then(OnSuccessType&& aOnSuccess, OnFailureType&& aOnFailure)
-  {
-    class Functors : public FunctorsBase
-    {
-    public:
-      Functors(OnSuccessType&& aOnSuccessRef, OnFailureType&& aOnFailureRef)
-        : mOnSuccess(Move(aOnSuccessRef)), mOnFailure(Move(aOnFailureRef)) {}
-
-      void Succeed(ValueType& result)
-      {
-        mOnSuccess(result);
-      }
-      void Fail(ErrorType& error)
-      {
-        mOnFailure(error);
-      };
-
-      OnSuccessType mOnSuccess;
-      OnFailureType mOnFailure;
-    };
-    mFunctors = MakeUnique<Functors>(Forward<OnSuccessType>(aOnSuccess),
-                                     Forward<OnFailureType>(aOnFailure));
-    if (mDone) {
-      if (!mRejected) {
-        mFunctors->Succeed(mValue);
-      } else {
-        mFunctors->Fail(mError);
-      }
-    }
-  }
-
-  void Resolve(const ValueType& aValue)
-  {
-    mValue = aValue;
-    Resolve();
-  }
-
-  void Reject(ErrorType rv)
-  {
-    if (!mDone) {
-      mDone = mRejected = true;
-      mError = rv;
-      if (mFunctors) {
-        mFunctors->Fail(mError);
-      }
-    }
-  }
-
-protected:
-  void Resolve()
-  {
-    if (!mDone) {
-      mDone = true;
-      MOZ_ASSERT(!mRejected);
-      if (mFunctors) {
-        mFunctors->Succeed(mValue);
-      }
-    }
-  }
-
-  ValueType mValue;
-private:
-  ~Pledge() {};
-  bool mDone;
-  bool mRejected;
-  ErrorType mError;
-  UniquePtr<FunctorsBase> mFunctors;
-};
-
-/* media::NewRunnableFrom() - Create a Runnable from a lambda.
- * media::NewTaskFrom()     - Create a Task from a lambda.
- *
- * Passing variables (closures) to an async function is clunky with Runnable:
- *
- *   void Foo()
- *   {
- *     class FooRunnable : public Runnable
- *     {
- *     public:
- *       FooRunnable(const Bar &aBar) : mBar(aBar) {}
- *       NS_IMETHOD Run()
- *       {
- *         // Use mBar
- *       }
- *     private:
- *       RefPtr<Bar> mBar;
- *     };
- *
- *     RefPtr<Bar> bar = new Bar();
- *     NS_DispatchToMainThread(new FooRunnable(bar);
- *   }
- *
- * It's worse with more variables. Lambdas have a leg up with variable capture:
- *
- *   void Foo()
- *   {
- *     RefPtr<Bar> bar = new Bar();
- *     NS_DispatchToMainThread(media::NewRunnableFrom([bar]() mutable {
- *       // use bar
- *     });
- *   }
- *
- * Capture is by-copy by default, so the nsRefPtr 'bar' is safely copied for
- * access on the other thread (threadsafe refcounting in bar is assumed).
- *
- * The 'mutable' keyword is only needed for non-const access to bar.
- */
-
-template<typename OnRunType>
-class LambdaRunnable : public Runnable
-{
-public:
-  explicit LambdaRunnable(OnRunType&& aOnRun) : mOnRun(Move(aOnRun)) {}
-private:
-  NS_IMETHODIMP
-  Run()
-  {
-    return mOnRun();
-  }
-  OnRunType mOnRun;
-};
-
-template<typename OnRunType>
-already_AddRefed<LambdaRunnable<OnRunType>>
-NewRunnableFrom(OnRunType&& aOnRun)
-{
-  typedef LambdaRunnable<OnRunType> LambdaType;
-  RefPtr<LambdaType> lambda = new LambdaType(Forward<OnRunType>(aOnRun));
-  return lambda.forget();
-}
-
 template<typename OnRunType>
 class LambdaTask : public Runnable
 {
 public:
   explicit LambdaTask(OnRunType&& aOnRun) : mOnRun(Move(aOnRun)) {}
 private:
   NS_IMETHOD
   Run() override
@@ -226,174 +39,12 @@ template<typename OnRunType>
 already_AddRefed<LambdaTask<OnRunType>>
 NewTaskFrom(OnRunType&& aOnRun)
 {
   typedef LambdaTask<OnRunType> LambdaType;
   RefPtr<LambdaType> lambda = new LambdaType(Forward<OnRunType>(aOnRun));
   return lambda.forget();
 }
 
-/* media::CoatCheck - There and back again. Park an object in exchange for an id.
- *
- * A common problem with calling asynchronous functions that do work on other
- * threads or processes is how to pass in a heap object for use once the
- * function completes, without requiring that object to have threadsafe
- * refcounting, contain mutexes, be marshaled, or leak if things fail
- * (or worse, intermittent use-after-free because of lifetime issues).
- *
- * One solution is to set up a coat-check on the caller side, park your object
- * in exchange for an id, and send the id. Common in IPC, but equally useful
- * for same-process thread-hops, because by never leaving the thread there's
- * no need for objects to be threadsafe or use threadsafe refcounting. E.g.
- *
- *   class FooDoer
- *   {
- *     CoatCheck<Foo> mOutstandingFoos;
- *
- *   public:
- *     void DoFoo()
- *     {
- *       RefPtr<Foo> foo = new Foo();
- *       uint32_t requestId = mOutstandingFoos.Append(*foo);
- *       sChild->SendFoo(requestId);
- *     }
- *
- *     void RecvFooResponse(uint32_t requestId)
- *     {
- *       RefPtr<Foo> foo = mOutstandingFoos.Remove(requestId);
- *       if (foo) {
- *         // use foo
- *       }
- *     }
- *   };
- *
- * If you read media::Pledge earlier, here's how this is useful for pledges:
- *
- *   class FooGetter
- *   {
- *     CoatCheck<Pledge<Foo>> mOutstandingPledges;
- *
- *   public:
- *     already_addRefed<Pledge<Foo>> GetFooAsynchronously()
- *     {
- *       RefPtr<Pledge<Foo>> p = new Pledge<Foo>();
- *       uint32_t requestId = mOutstandingPledges.Append(*p);
- *       sChild->SendFoo(requestId);
- *       return p.forget();
- *     }
- *
- *     void RecvFooResponse(uint32_t requestId, const Foo& fooResult)
- *     {
- *       RefPtr<Foo> p = mOutstandingPledges.Remove(requestId);
- *       if (p) {
- *         p->Resolve(fooResult);
- *       }
- *     }
- *   };
- *
- * This helper is currently optimized for very small sets (i.e. not optimized).
- * It is also not thread-safe as the whole point is to stay on the same thread.
- */
-
-template<class T>
-class CoatCheck
-{
-public:
-  typedef std::pair<uint32_t, RefPtr<T>> Element;
-
-  uint32_t Append(T& t)
-  {
-    uint32_t id = GetNextId();
-    mElements.AppendElement(Element(id, RefPtr<T>(&t)));
-    return id;
-  }
-
-  already_AddRefed<T> Remove(uint32_t aId)
-  {
-    for (auto& element : mElements) {
-      if (element.first == aId) {
-        RefPtr<T> ref;
-        ref.swap(element.second);
-        mElements.RemoveElement(element);
-        return ref.forget();
-      }
-    }
-    MOZ_ASSERT_UNREACHABLE("Received id with no matching parked object!");
-    return nullptr;
-  }
-
-private:
-  static uint32_t GetNextId()
-  {
-    static uint32_t counter = 0;
-    return ++counter;
-  };
-  AutoTArray<Element, 3> mElements;
-};
-
-/* media::Refcountable - Add threadsafe ref-counting to something that isn't.
- *
- * Often, reference counting is the most practical way to share an object with
- * another thread without imposing lifetime restrictions, even if there's
- * otherwise no concurrent access happening on the object.  For instance, an
- * algorithm on another thread may find it more expedient to modify a passed-in
- * object, rather than pass expensive copies back and forth.
- *
- * Lists in particular often aren't ref-countable, yet are expensive to copy,
- * e.g. nsTArray<RefPtr<Foo>>. Refcountable can be used to make such objects
- * (or owning smart-pointers to such objects) refcountable.
- *
- * Technical limitation: A template specialization is needed for types that take
- * a constructor. Please add below (UniquePtr covers a lot of ground though).
- */
-
-template<typename T>
-class Refcountable : public T
-{
-public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Refcountable<T>)
-private:
-  ~Refcountable<T>() {}
-};
-
-template<typename T>
-class Refcountable<UniquePtr<T>> : public UniquePtr<T>
-{
-public:
-  explicit Refcountable<UniquePtr<T>>(T* aPtr) : UniquePtr<T>(aPtr) {}
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Refcountable<T>)
-private:
-  ~Refcountable<UniquePtr<T>>() {}
-};
-
-/* media::ShutdownBlocker - Async shutdown helper.
- */
-
-class ShutdownBlocker : public nsIAsyncShutdownBlocker
-{
-public:
-  ShutdownBlocker(const nsString& aName) : mName(aName) {}
-
-  NS_IMETHOD
-  BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override = 0;
-
-  NS_IMETHOD GetName(nsAString& aName) override
-  {
-    aName = mName;
-    return NS_OK;
-  }
-
-  NS_IMETHOD GetState(nsIPropertyBag**) override
-  {
-    return NS_OK;
-  }
-
-  NS_DECL_ISUPPORTS
-protected:
-  virtual ~ShutdownBlocker() {}
-private:
-  const nsString mName;
-};
-
 } // namespace media
 } // namespace mozilla
 
-#endif // mozilla_MediaUtils_h
+#endif // mozilla_MediaTaskUtils_h
--- a/dom/media/systemservices/MediaUtils.h
+++ b/dom/media/systemservices/MediaUtils.h
@@ -5,17 +5,16 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_MediaUtils_h
 #define mozilla_MediaUtils_h
 
 #include "nsThreadUtils.h"
 #include "nsIAsyncShutdown.h"
 #include "mozilla/UniquePtr.h"
-#include "base/task.h"
 
 namespace mozilla {
 namespace media {
 
 /*
  * media::Pledge - A promise-like pattern for c++ that takes lambda functions.
  *
  * Asynchronous APIs that proxy to another thread or to the chrome process and
@@ -141,17 +140,16 @@ private:
   ~Pledge() {};
   bool mDone;
   bool mRejected;
   ErrorType mError;
   UniquePtr<FunctorsBase> mFunctors;
 };
 
 /* media::NewRunnableFrom() - Create a Runnable from a lambda.
- * media::NewTaskFrom()     - Create a Task from a lambda.
  *
  * Passing variables (closures) to an async function is clunky with Runnable:
  *
  *   void Foo()
  *   {
  *     class FooRunnable : public Runnable
  *     {
  *     public:
@@ -202,40 +200,16 @@ template<typename OnRunType>
 already_AddRefed<LambdaRunnable<OnRunType>>
 NewRunnableFrom(OnRunType&& aOnRun)
 {
   typedef LambdaRunnable<OnRunType> LambdaType;
   RefPtr<LambdaType> lambda = new LambdaType(Forward<OnRunType>(aOnRun));
   return lambda.forget();
 }
 
-template<typename OnRunType>
-class LambdaTask : public Runnable
-{
-public:
-  explicit LambdaTask(OnRunType&& aOnRun) : mOnRun(Move(aOnRun)) {}
-private:
-  NS_IMETHOD
-  Run() override
-  {
-    mOnRun();
-    return NS_OK;
-  }
-  OnRunType mOnRun;
-};
-
-template<typename OnRunType>
-already_AddRefed<LambdaTask<OnRunType>>
-NewTaskFrom(OnRunType&& aOnRun)
-{
-  typedef LambdaTask<OnRunType> LambdaType;
-  RefPtr<LambdaType> lambda = new LambdaType(Forward<OnRunType>(aOnRun));
-  return lambda.forget();
-}
-
 /* media::CoatCheck - There and back again. Park an object in exchange for an id.
  *
  * A common problem with calling asynchronous functions that do work on other
  * threads or processes is how to pass in a heap object for use once the
  * function completes, without requiring that object to have threadsafe
  * refcounting, contain mutexes, be marshaled, or leak if things fail
  * (or worse, intermittent use-after-free because of lifetime issues).
  *
--- a/dom/media/systemservices/moz.build
+++ b/dom/media/systemservices/moz.build
@@ -66,16 +66,17 @@ EXPORTS.mozilla.media += ['MediaChild.h'
                           'MediaParent.h',
                           'MediaSystemResourceClient.h',
                           'MediaSystemResourceManager.h',
                           'MediaSystemResourceManagerChild.h',
                           'MediaSystemResourceManagerParent.h',
                           'MediaSystemResourceMessageUtils.h',
                           'MediaSystemResourceService.h',
                           'MediaSystemResourceTypes.h',
+                          'MediaTaskUtils.h',
                           'MediaUtils.h',
 ]
 UNIFIED_SOURCES += [
     'MediaChild.cpp',
     'MediaParent.cpp',
     'MediaSystemResourceClient.cpp',
     'MediaSystemResourceManager.cpp',
     'MediaSystemResourceManagerChild.cpp',
--- a/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
@@ -35,17 +35,18 @@ MediaStreamAudioDestinationNode::MediaSt
                                                              aContext->Graph()))
 {
   // Ensure an audio track with the correct ID is exposed to JS
   nsIDocument* doc = aContext->GetParentObject()->GetExtantDoc();
   RefPtr<MediaStreamTrackSource> source =
     new BasicUnstoppableTrackSource(doc->NodePrincipal(),
                                     MediaSourceEnum::AudioCapture);
   mDOMStream->CreateDOMTrack(AudioNodeStream::AUDIO_TRACK,
-                             MediaSegment::AUDIO, source);
+                             MediaSegment::AUDIO, source,
+                             MediaTrackConstraints());
 
   ProcessedMediaStream* outputStream = mDOMStream->GetInputStream()->AsProcessedStream();
   MOZ_ASSERT(!!outputStream);
   AudioNodeEngine* engine = new AudioNodeEngine(this);
   mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::EXTERNAL_OUTPUT);
   mPort = outputStream->AllocateInputPort(mStream, AudioNodeStream::AUDIO_TRACK);
 }
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -109,23 +109,31 @@ public:
   static const unsigned int kMaxDeviceNameLength = 128;
   static const unsigned int kMaxUniqueIdLength = 256;
 
   virtual ~MediaEngineSource() {}
 
   virtual void Shutdown() = 0;
 
   /* Populate the human readable name of this device in the nsAString */
-  virtual void GetName(nsAString&) = 0;
+  virtual void GetName(nsAString&) const = 0;
 
   /* Populate the UUID of this device in the nsACString */
-  virtual void GetUUID(nsACString&) = 0;
+  virtual void GetUUID(nsACString&) const = 0;
+
+  class BaseAllocationHandle
+  {
+  public:
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BaseAllocationHandle);
+  protected:
+    virtual ~BaseAllocationHandle() {}
+  };
 
   /* Release the device back to the system. */
-  virtual nsresult Deallocate() = 0;
+  virtual nsresult Deallocate(BaseAllocationHandle* aHandle) = 0;
 
   /* Start the device and add the track to the provided SourceMediaStream, with
    * the provided TrackID. You may start appending data to the track
    * immediately after. */
   virtual nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) = 0;
 
   /* tell the source if there are any direct listeners attached */
   virtual void SetDirectListeners(bool) = 0;
@@ -136,19 +144,21 @@ public:
                           TrackID aId,
                           StreamTime aDesiredTime,
                           const PrincipalHandle& aPrincipalHandle) = 0;
 
   /* Stop the device and release the corresponding MediaStream */
   virtual nsresult Stop(SourceMediaStream *aSource, TrackID aID) = 0;
 
   /* Restart with new capability */
-  virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+  virtual nsresult Restart(BaseAllocationHandle* aHandle,
+                           const dom::MediaTrackConstraints& aConstraints,
                            const MediaEnginePrefs &aPrefs,
-                           const nsString& aDeviceId) = 0;
+                           const nsString& aDeviceId,
+                           const char** aOutBadConstraint) = 0;
 
   /* Returns true if a source represents a fake capture device and
    * false otherwise
    */
   virtual bool IsFake() = 0;
 
   /* Returns the type of media source (camera, microphone, screen, window, etc) */
   virtual dom::MediaSourceEnum GetMediaSource() const = 0;
@@ -174,21 +184,29 @@ public:
   void SetHasFakeTracks(bool aHasFakeTracks) {
     mHasFakeTracks = aHasFakeTracks;
   }
 
   /* This call reserves but does not start the device. */
   virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                             const MediaEnginePrefs &aPrefs,
                             const nsString& aDeviceId,
-                            const nsACString& aOrigin) = 0;
+                            const nsACString& aOrigin,
+                            BaseAllocationHandle** aOutHandle,
+                            const char** aOutBadConstraint) = 0;
 
   virtual uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
-      const nsString& aDeviceId) = 0;
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) const = 0;
+
+  void GetSettings(dom::MediaTrackSettings& aOutSettings)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    aOutSettings = mSettings;
+  }
 
 protected:
   // Only class' own members can be initialized in constructor initializer list.
   explicit MediaEngineSource(MediaEngineState aState)
     : mState(aState)
 #ifdef DEBUG
     , mOwningThread(PR_GetCurrentThread())
 #endif
@@ -200,16 +218,18 @@ protected:
     MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
   }
 
   MediaEngineState mState;
 #ifdef DEBUG
   PRThread* mOwningThread;
 #endif
   bool mHasFakeTracks;
+  // Main-thread only:
+  dom::MediaTrackSettings mSettings;
 };
 
 /**
  * Video source and friends.
  */
 class MediaEnginePrefs {
 public:
   MediaEnginePrefs()
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
@@ -34,50 +34,47 @@ bool MediaEngineCameraVideoSource::Appen
   // This can fail if either a) we haven't added the track yet, or b)
   // we've removed or finished the track.
   return aSource->AppendToTrack(aID, &(segment));
 }
 
 // Sub-classes (B2G or desktop) should overload one of both of these two methods
 // to provide capabilities
 size_t
-MediaEngineCameraVideoSource::NumCapabilities()
+MediaEngineCameraVideoSource::NumCapabilities() const
 {
   return mHardcodedCapabilities.Length();
 }
 
 void
 MediaEngineCameraVideoSource::GetCapability(size_t aIndex,
-                                            webrtc::CaptureCapability& aOut)
+                                            webrtc::CaptureCapability& aOut) const
 {
   MOZ_ASSERT(aIndex < mHardcodedCapabilities.Length());
   aOut = mHardcodedCapabilities[aIndex];
 }
 
 uint32_t
-MediaEngineCameraVideoSource::GetFitnessDistance(const webrtc::CaptureCapability& aCandidate,
-                                                 const MediaTrackConstraintSet &aConstraints,
-                                                 bool aAdvanced,
-                                                 const nsString& aDeviceId)
+MediaEngineCameraVideoSource::GetFitnessDistance(
+    const webrtc::CaptureCapability& aCandidate,
+    const NormalizedConstraintSet &aConstraints,
+    const nsString& aDeviceId) const
 {
   // Treat width|height|frameRate == 0 on capability as "can do any".
   // This allows for orthogonal capabilities that are not in discrete steps.
 
   uint64_t distance =
-    uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId, aAdvanced)) +
-    uint64_t(FitnessDistance(mFacingMode, aConstraints.mFacingMode, aAdvanced)) +
+    uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId)) +
+    uint64_t(FitnessDistance(mFacingMode, aConstraints.mFacingMode)) +
     uint64_t(aCandidate.width? FitnessDistance(int32_t(aCandidate.width),
-                                               aConstraints.mWidth,
-                                               aAdvanced) : 0) +
+                                               aConstraints.mWidth) : 0) +
     uint64_t(aCandidate.height? FitnessDistance(int32_t(aCandidate.height),
-                                                aConstraints.mHeight,
-                                                aAdvanced) : 0) +
+                                                aConstraints.mHeight) : 0) +
     uint64_t(aCandidate.maxFPS? FitnessDistance(double(aCandidate.maxFPS),
-                                                aConstraints.mFrameRate,
-                                                aAdvanced) : 0);
+                                                aConstraints.mFrameRate) : 0);
   return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
 }
 
 // Find best capability by removing inferiors. May leave >1 of equal distance
 
 /* static */ void
 MediaEngineCameraVideoSource::TrimLessFitCandidates(CapabilitySet& set) {
   uint32_t best = UINT32_MAX;
@@ -101,33 +98,33 @@ MediaEngineCameraVideoSource::TrimLessFi
 // Ideal values are considered in the first ConstraintSet only.
 // Plain values are treated as Ideal in the first ConstraintSet.
 // Plain values are treated as Exact in subsequent ConstraintSets.
 // Infinity = UINT32_MAX e.g. device cannot satisfy accumulated ConstraintSets.
 // A finite result may be used to calculate this device's ranking as a choice.
 
 uint32_t
 MediaEngineCameraVideoSource::GetBestFitnessDistance(
-    const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets,
-    const nsString& aDeviceId)
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId) const
 {
   size_t num = NumCapabilities();
 
   CapabilitySet candidateSet;
   for (size_t i = 0; i < num; i++) {
     candidateSet.AppendElement(i);
   }
 
   bool first = true;
-  for (const MediaTrackConstraintSet* cs : aConstraintSets) {
+  for (const NormalizedConstraintSet* ns : aConstraintSets) {
     for (size_t i = 0; i < candidateSet.Length();  ) {
       auto& candidate = candidateSet[i];
       webrtc::CaptureCapability cap;
       GetCapability(candidate.mIndex, cap);
-      uint32_t distance = GetFitnessDistance(cap, *cs, !first, aDeviceId);
+      uint32_t distance = GetFitnessDistance(cap, *ns, aDeviceId);
       if (distance == UINT32_MAX) {
         candidateSet.RemoveElementAt(i);
       } else {
         ++i;
         if (first) {
           candidate.mDistance = distance;
         }
       }
@@ -138,34 +135,34 @@ MediaEngineCameraVideoSource::GetBestFit
     return UINT32_MAX;
   }
   TrimLessFitCandidates(candidateSet);
   return candidateSet[0].mDistance;
 }
 
 void
 MediaEngineCameraVideoSource::LogConstraints(
-    const MediaTrackConstraintSet& aConstraints, bool aAdvanced)
+    const NormalizedConstraintSet& aConstraints)
 {
-  NormalizedConstraintSet c(aConstraints, aAdvanced);
-  LOG(((c.mWidth.mIdeal.WasPassed()?
+  auto& c = aConstraints;
+  LOG(((c.mWidth.mIdeal.isSome()?
         "Constraints: width: { min: %d, max: %d, ideal: %d }" :
         "Constraints: width: { min: %d, max: %d }"),
        c.mWidth.mMin, c.mWidth.mMax,
-       c.mWidth.mIdeal.WasPassed()? c.mWidth.mIdeal.Value() : 0));
-  LOG(((c.mHeight.mIdeal.WasPassed()?
+       c.mWidth.mIdeal.valueOr(0)));
+  LOG(((c.mHeight.mIdeal.isSome()?
         "             height: { min: %d, max: %d, ideal: %d }" :
         "             height: { min: %d, max: %d }"),
        c.mHeight.mMin, c.mHeight.mMax,
-       c.mHeight.mIdeal.WasPassed()? c.mHeight.mIdeal.Value() : 0));
-  LOG(((c.mFrameRate.mIdeal.WasPassed()?
+       c.mHeight.mIdeal.valueOr(0)));
+  LOG(((c.mFrameRate.mIdeal.isSome()?
         "             frameRate: { min: %f, max: %f, ideal: %f }" :
         "             frameRate: { min: %f, max: %f }"),
        c.mFrameRate.mMin, c.mFrameRate.mMax,
-       c.mFrameRate.mIdeal.WasPassed()? c.mFrameRate.mIdeal.Value() : 0));
+       c.mFrameRate.mIdeal.valueOr(0)));
 }
 
 void
 MediaEngineCameraVideoSource::LogCapability(const char* aHeader,
     const webrtc::CaptureCapability &aCapability, uint32_t aDistance)
 {
   // RawVideoType and VideoCodecType media/webrtc/trunk/webrtc/common_types.h
   static const char* const types[] = {
@@ -203,29 +200,29 @@ MediaEngineCameraVideoSource::LogCapabil
                       uint32_t(sizeof(types) / sizeof(*types) - 1))],
        codec[std::min(std::max(uint32_t(0), uint32_t(aCapability.codecType)),
                       uint32_t(sizeof(codec) / sizeof(*codec) - 1))],
        aDistance));
 }
 
 bool
 MediaEngineCameraVideoSource::ChooseCapability(
-    const MediaTrackConstraints &aConstraints,
+    const NormalizedConstraints &aConstraints,
     const MediaEnginePrefs &aPrefs,
     const nsString& aDeviceId)
 {
   if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) {
     LOG(("ChooseCapability: prefs: %dx%d @%d-%dfps",
          aPrefs.GetWidth(), aPrefs.GetHeight(),
          aPrefs.mFPS, aPrefs.mMinFPS));
-    LogConstraints(aConstraints, false);
-    if (aConstraints.mAdvanced.WasPassed()) {
-      LOG(("Advanced array[%u]:", aConstraints.mAdvanced.Value().Length()));
-      for (auto& advanced : aConstraints.mAdvanced.Value()) {
-        LogConstraints(advanced, true);
+    LogConstraints(aConstraints);
+    if (aConstraints.mAdvanced.size()) {
+      LOG(("Advanced array[%u]:", aConstraints.mAdvanced.size()));
+      for (auto& advanced : aConstraints.mAdvanced) {
+        LogConstraints(advanced);
       }
     }
   }
 
   size_t num = NumCapabilities();
 
   CapabilitySet candidateSet;
   for (size_t i = 0; i < num; i++) {
@@ -233,68 +230,70 @@ MediaEngineCameraVideoSource::ChooseCapa
   }
 
   // First, filter capabilities by required constraints (min, max, exact).
 
   for (size_t i = 0; i < candidateSet.Length();) {
     auto& candidate = candidateSet[i];
     webrtc::CaptureCapability cap;
     GetCapability(candidate.mIndex, cap);
-    candidate.mDistance = GetFitnessDistance(cap, aConstraints, false, aDeviceId);
+    candidate.mDistance = GetFitnessDistance(cap, aConstraints, aDeviceId);
     LogCapability("Capability", cap, candidate.mDistance);
     if (candidate.mDistance == UINT32_MAX) {
       candidateSet.RemoveElementAt(i);
     } else {
       ++i;
     }
   }
 
-  // Filter further with all advanced constraints (that don't overconstrain).
-
-  if (aConstraints.mAdvanced.WasPassed()) {
-    for (const MediaTrackConstraintSet &cs : aConstraints.mAdvanced.Value()) {
-      CapabilitySet rejects;
-      for (size_t i = 0; i < candidateSet.Length();) {
-        auto& candidate = candidateSet[i];
-        webrtc::CaptureCapability cap;
-        GetCapability(candidate.mIndex, cap);
-        if (GetFitnessDistance(cap, cs, true, aDeviceId) == UINT32_MAX) {
-          rejects.AppendElement(candidate);
-          candidateSet.RemoveElementAt(i);
-        } else {
-          ++i;
-        }
-      }
-      if (!candidateSet.Length()) {
-        candidateSet.AppendElements(Move(rejects));
-      }
-    }
-  }
   if (!candidateSet.Length()) {
     LOG(("failed to find capability match from %d choices",num));
     return false;
   }
 
+  // Filter further with all advanced constraints (that don't overconstrain).
+
+  for (const auto &cs : aConstraints.mAdvanced) {
+    CapabilitySet rejects;
+    for (size_t i = 0; i < candidateSet.Length();) {
+      auto& candidate = candidateSet[i];
+      webrtc::CaptureCapability cap;
+      GetCapability(candidate.mIndex, cap);
+      if (GetFitnessDistance(cap, cs, aDeviceId) == UINT32_MAX) {
+        rejects.AppendElement(candidate);
+        candidateSet.RemoveElementAt(i);
+      } else {
+        ++i;
+      }
+    }
+    if (!candidateSet.Length()) {
+      candidateSet.AppendElements(Move(rejects));
+    }
+  }
+  MOZ_ASSERT(candidateSet.Length(),
+             "advanced constraints filtering step can't reduce candidates to zero");
+
   // Remaining algorithm is up to the UA.
 
   TrimLessFitCandidates(candidateSet);
 
   // Any remaining multiples all have the same distance. A common case of this
   // occurs when no ideal is specified. Lean toward defaults.
   uint32_t sameDistance = candidateSet[0].mDistance;
   {
     MediaTrackConstraintSet prefs;
     prefs.mWidth.SetAsLong() = aPrefs.GetWidth();
     prefs.mHeight.SetAsLong() = aPrefs.GetHeight();
     prefs.mFrameRate.SetAsDouble() = aPrefs.mFPS;
+    NormalizedConstraintSet normPrefs(prefs, false);
 
     for (auto& candidate : candidateSet) {
       webrtc::CaptureCapability cap;
       GetCapability(candidate.mIndex, cap);
-      candidate.mDistance = GetFitnessDistance(cap, prefs, false, aDeviceId);
+      candidate.mDistance = GetFitnessDistance(cap, normPrefs, aDeviceId);
     }
     TrimLessFitCandidates(candidateSet);
   }
 
   // Any remaining multiples all have the same distance, but may vary on
   // format. Some formats are more desirable for certain use like WebRTC.
   // E.g. I420 over RGB24 can remove a needless format conversion.
 
@@ -360,40 +359,57 @@ MediaEngineCameraVideoSource::SetName(ns
     mFacingMode.Assign(NS_ConvertUTF8toUTF16(
         VideoFacingModeEnumValues::strings[uint32_t(facingMode)].value));
   } else {
     mFacingMode.Truncate();
   }
 }
 
 void
-MediaEngineCameraVideoSource::GetName(nsAString& aName)
+MediaEngineCameraVideoSource::GetName(nsAString& aName) const
 {
   aName = mDeviceName;
 }
 
 void
 MediaEngineCameraVideoSource::SetUUID(const char* aUUID)
 {
   mUniqueId.Assign(aUUID);
 }
 
 void
-MediaEngineCameraVideoSource::GetUUID(nsACString& aUUID)
+MediaEngineCameraVideoSource::GetUUID(nsACString& aUUID) const
 {
   aUUID = mUniqueId;
 }
 
 const nsCString&
-MediaEngineCameraVideoSource::GetUUID()
+MediaEngineCameraVideoSource::GetUUID() const
 {
   return mUniqueId;
 }
 
-
 void
 MediaEngineCameraVideoSource::SetDirectListeners(bool aHasDirectListeners)
 {
   LOG((__FUNCTION__));
   mHasDirectListeners = aHasDirectListeners;
 }
 
+bool operator == (const webrtc::CaptureCapability& a,
+                  const webrtc::CaptureCapability& b)
+{
+  return a.width == b.width &&
+         a.height == b.height &&
+         a.maxFPS == b.maxFPS &&
+         a.rawType == b.rawType &&
+         a.codecType == b.codecType &&
+         a.expectedCaptureDelay == b.expectedCaptureDelay &&
+         a.interlaced == b.interlaced;
+};
+
+bool operator != (const webrtc::CaptureCapability& a,
+                  const webrtc::CaptureCapability& b)
+{
+  return !(a == b);
+}
+
 } // namespace mozilla
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.h
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.h
@@ -11,51 +11,56 @@
 #include "nsDirectoryServiceDefs.h"
 
 // conflicts with #include of scoped_ptr.h
 #undef FF
 #include "webrtc/video_engine/include/vie_capture.h"
 
 namespace mozilla {
 
+bool operator == (const webrtc::CaptureCapability& a,
+                  const webrtc::CaptureCapability& b);
+bool operator != (const webrtc::CaptureCapability& a,
+                  const webrtc::CaptureCapability& b);
+
 class MediaEngineCameraVideoSource : public MediaEngineVideoSource,
-                                     private MediaConstraintsHelper
+                                     protected MediaConstraintsHelper
 {
 public:
   explicit MediaEngineCameraVideoSource(int aIndex,
                                         const char* aMonitorName = "Camera.Monitor")
     : MediaEngineVideoSource(kReleased)
     , mMonitor(aMonitorName)
     , mWidth(0)
     , mHeight(0)
     , mInitDone(false)
     , mHasDirectListeners(false)
     , mNrAllocations(0)
     , mCaptureIndex(aIndex)
     , mTrackID(0)
   {}
 
 
-  void GetName(nsAString& aName) override;
-  void GetUUID(nsACString& aUUID) override;
+  void GetName(nsAString& aName) const override;
+  void GetUUID(nsACString& aUUID) const override;
   void SetDirectListeners(bool aHasListeners) override;
 
   bool IsFake() override
   {
     return false;
   }
 
   nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
-      const nsString& aDeviceId) override;
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) const override;
 
   void Shutdown() override {};
 
 protected:
   struct CapabilityCandidate {
     explicit CapabilityCandidate(uint8_t index, uint32_t distance = 0)
     : mIndex(index), mDistance(distance) {}
 
@@ -68,33 +73,31 @@ protected:
 
   // guts for appending data to the MSG track
   virtual bool AppendToTrack(SourceMediaStream* aSource,
                              layers::Image* aImage,
                              TrackID aID,
                              StreamTime delta,
                              const PrincipalHandle& aPrincipalHandle);
   uint32_t GetFitnessDistance(const webrtc::CaptureCapability& aCandidate,
-                              const dom::MediaTrackConstraintSet &aConstraints,
-                              bool aAdvanced,
-                              const nsString& aDeviceId);
+                              const NormalizedConstraintSet &aConstraints,
+                              const nsString& aDeviceId) const;
   static void TrimLessFitCandidates(CapabilitySet& set);
-  static void LogConstraints(const dom::MediaTrackConstraintSet& aConstraints,
-                             bool aAdvanced);
+  static void LogConstraints(const NormalizedConstraintSet& aConstraints);
   static void LogCapability(const char* aHeader,
                             const webrtc::CaptureCapability &aCapability,
                             uint32_t aDistance);
-  virtual size_t NumCapabilities();
-  virtual void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut);
-  virtual bool ChooseCapability(const dom::MediaTrackConstraints &aConstraints,
-                        const MediaEnginePrefs &aPrefs,
-                        const nsString& aDeviceId);
+  virtual size_t NumCapabilities() const;
+  virtual void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) const;
+  virtual bool ChooseCapability(const NormalizedConstraints &aConstraints,
+                                const MediaEnginePrefs &aPrefs,
+                                const nsString& aDeviceId);
   void SetName(nsString aName);
   void SetUUID(const char* aUUID);
-  const nsCString& GetUUID(); // protected access
+  const nsCString& GetUUID() const; // protected access
 
   // Engine variables.
 
   // mMonitor protects mImage access/changes, and transitions of mState
   // from kStarted to kStopped (which are combined with EndTrack() and
   // image changes).
   // mMonitor also protects mSources[] and mPrincipalHandles[] access/changes.
   // mSources[] and mPrincipalHandles[] are accessed from webrtc threads.
@@ -112,17 +115,17 @@ protected:
   bool mInitDone;
   bool mHasDirectListeners;
   int mNrAllocations; // When this becomes 0, we shut down HW
   int mCaptureIndex;
   TrackID mTrackID;
 
   webrtc::CaptureCapability mCapability;
 
-  nsTArray<webrtc::CaptureCapability> mHardcodedCapabilities; // For OSX & B2G
+  mutable nsTArray<webrtc::CaptureCapability> mHardcodedCapabilities;
 private:
   nsString mDeviceName;
   nsCString mUniqueId;
   nsString mFacingMode;
 };
 
 
 } // namespace mozilla
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -52,70 +52,74 @@ MediaEngineDefaultVideoSource::MediaEngi
   mImageContainer =
     layers::LayerManager::CreateImageContainer(layers::ImageContainer::ASYNCHRONOUS);
 }
 
 MediaEngineDefaultVideoSource::~MediaEngineDefaultVideoSource()
 {}
 
 void
-MediaEngineDefaultVideoSource::GetName(nsAString& aName)
+MediaEngineDefaultVideoSource::GetName(nsAString& aName) const
 {
   aName.AssignLiteral(MOZ_UTF16("Default Video Device"));
   return;
 }
 
 void
-MediaEngineDefaultVideoSource::GetUUID(nsACString& aUUID)
+MediaEngineDefaultVideoSource::GetUUID(nsACString& aUUID) const
 {
   aUUID.AssignLiteral("1041FCBD-3F12-4F7B-9E9B-1EC556DD5676");
   return;
 }
 
 uint32_t
 MediaEngineDefaultVideoSource::GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
-    const nsString& aDeviceId)
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId) const
 {
   uint32_t distance = 0;
 #ifdef MOZ_WEBRTC
-  for (const dom::MediaTrackConstraintSet* cs : aConstraintSets) {
-    distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
+  for (const auto* cs : aConstraintSets) {
+    distance = GetMinimumFitnessDistance(*cs, aDeviceId);
     break; // distance is read from first entry only
   }
 #endif
   return distance;
 }
 
 nsresult
 MediaEngineDefaultVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
                                         const MediaEnginePrefs &aPrefs,
                                         const nsString& aDeviceId,
-                                        const nsACString& aOrigin)
+                                        const nsACString& aOrigin,
+                                        BaseAllocationHandle** aOutHandle,
+                                        const char** aOutBadConstraint)
 {
   if (mState != kReleased) {
     return NS_ERROR_FAILURE;
   }
 
   // Mock failure for automated tests.
   if (aConstraints.mDeviceId.IsString() &&
       aConstraints.mDeviceId.GetAsString().EqualsASCII("bad device")) {
     return NS_ERROR_FAILURE;
   }
 
   mOpts = aPrefs;
   mOpts.mWidth = mOpts.mWidth ? mOpts.mWidth : MediaEngine::DEFAULT_43_VIDEO_WIDTH;
   mOpts.mHeight = mOpts.mHeight ? mOpts.mHeight : MediaEngine::DEFAULT_43_VIDEO_HEIGHT;
   mState = kAllocated;
+  aOutHandle = nullptr;
   return NS_OK;
 }
 
 nsresult
-MediaEngineDefaultVideoSource::Deallocate()
+MediaEngineDefaultVideoSource::Deallocate(BaseAllocationHandle* aHandle)
 {
+  MOZ_ASSERT(!aHandle);
   if (mState != kStopped && mState != kAllocated) {
     return NS_ERROR_FAILURE;
   }
   mState = kReleased;
   mImage = nullptr;
   return NS_OK;
 }
 
@@ -209,19 +213,22 @@ MediaEngineDefaultVideoSource::Stop(Sour
   }
 
   mState = kStopped;
   mImage = nullptr;
   return NS_OK;
 }
 
 nsresult
-MediaEngineDefaultVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
-                                       const MediaEnginePrefs &aPrefs,
-                                       const nsString& aDeviceId)
+MediaEngineDefaultVideoSource::Restart(
+    BaseAllocationHandle* aHandle,
+    const dom::MediaTrackConstraints& aConstraints,
+    const MediaEnginePrefs &aPrefs,
+    const nsString& aDeviceId,
+    const char** aOutBadConstraint)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MediaEngineDefaultVideoSource::Notify(nsITimer* aTimer)
 {
   // Update the target color
@@ -373,70 +380,74 @@ MediaEngineDefaultAudioSource::MediaEngi
   , mTimer(nullptr)
 {
 }
 
 MediaEngineDefaultAudioSource::~MediaEngineDefaultAudioSource()
 {}
 
 void
-MediaEngineDefaultAudioSource::GetName(nsAString& aName)
+MediaEngineDefaultAudioSource::GetName(nsAString& aName) const
 {
   aName.AssignLiteral(MOZ_UTF16("Default Audio Device"));
   return;
 }
 
 void
-MediaEngineDefaultAudioSource::GetUUID(nsACString& aUUID)
+MediaEngineDefaultAudioSource::GetUUID(nsACString& aUUID) const
 {
   aUUID.AssignLiteral("B7CBD7C1-53EF-42F9-8353-73F61C70C092");
   return;
 }
 
 uint32_t
 MediaEngineDefaultAudioSource::GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
-    const nsString& aDeviceId)
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId) const
 {
   uint32_t distance = 0;
 #ifdef MOZ_WEBRTC
-  for (const dom::MediaTrackConstraintSet* cs : aConstraintSets) {
-    distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
+  for (const auto* cs : aConstraintSets) {
+    distance = GetMinimumFitnessDistance(*cs, aDeviceId);
     break; // distance is read from first entry only
   }
 #endif
   return distance;
 }
 
 nsresult
 MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
                                         const MediaEnginePrefs &aPrefs,
                                         const nsString& aDeviceId,
-                                        const nsACString& aOrigin)
+                                        const nsACString& aOrigin,
+                                        BaseAllocationHandle** aOutHandle,
+                                        const char** aOutBadConstraint)
 {
   if (mState != kReleased) {
     return NS_ERROR_FAILURE;
   }
 
   // Mock failure for automated tests.
   if (aConstraints.mDeviceId.IsString() &&
       aConstraints.mDeviceId.GetAsString().EqualsASCII("bad device")) {
     return NS_ERROR_FAILURE;
   }
 
   mState = kAllocated;
   // generate sine wave (default 1KHz)
   mSineGenerator = new SineWaveGenerator(AUDIO_RATE,
                                          static_cast<uint32_t>(aPrefs.mFreq ? aPrefs.mFreq : 1000));
+  aOutHandle = nullptr;
   return NS_OK;
 }
 
 nsresult
-MediaEngineDefaultAudioSource::Deallocate()
+MediaEngineDefaultAudioSource::Deallocate(BaseAllocationHandle* aHandle)
 {
+  MOZ_ASSERT(!aHandle);
   if (mState != kStopped && mState != kAllocated) {
     return NS_ERROR_FAILURE;
   }
   mState = kReleased;
   return NS_OK;
 }
 
 nsresult
@@ -514,19 +525,21 @@ MediaEngineDefaultAudioSource::Stop(Sour
     }
   }
 
   mState = kStopped;
   return NS_OK;
 }
 
 nsresult
-MediaEngineDefaultAudioSource::Restart(const dom::MediaTrackConstraints& aConstraints,
+MediaEngineDefaultAudioSource::Restart(BaseAllocationHandle* aHandle,
+                                       const dom::MediaTrackConstraints& aConstraints,
                                        const MediaEnginePrefs &aPrefs,
-                                       const nsString& aDeviceId)
+                                       const nsString& aDeviceId,
+                                       const char** aOutBadConstraint)
 {
   return NS_OK;
 }
 
 void
 MediaEngineDefaultAudioSource::AppendToSegment(AudioSegment& aSegment,
                                                TrackTicks aSamples)
 {
--- a/dom/media/webrtc/MediaEngineDefault.h
+++ b/dom/media/webrtc/MediaEngineDefault.h
@@ -36,38 +36,42 @@ class MediaEngineDefaultVideoSource : pu
                                       public MediaEngineVideoSource,
                                       private MediaConstraintsHelper
 {
 public:
   MediaEngineDefaultVideoSource();
 
   void Shutdown() override {};
 
-  void GetName(nsAString&) override;
-  void GetUUID(nsACString&) override;
+  void GetName(nsAString&) const override;
+  void GetUUID(nsACString&) const override;
 
   nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                     const MediaEnginePrefs &aPrefs,
                     const nsString& aDeviceId,
-                    const nsACString& aOrigin) override;
-  nsresult Deallocate() override;
+                    const nsACString& aOrigin,
+                    BaseAllocationHandle** aOutHandle,
+                    const char** aOutBadConstraint) override;
+  nsresult Deallocate(BaseAllocationHandle* aHandle) override;
   nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) override;
   nsresult Stop(SourceMediaStream*, TrackID) override;
-  nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+  nsresult Restart(BaseAllocationHandle* aHandle,
+                   const dom::MediaTrackConstraints& aConstraints,
                    const MediaEnginePrefs &aPrefs,
-                   const nsString& aDeviceId) override;
+                   const nsString& aDeviceId,
+                   const char** aOutBadConstraint) override;
   void SetDirectListeners(bool aHasDirectListeners) override {};
   void NotifyPull(MediaStreamGraph* aGraph,
                   SourceMediaStream *aSource,
                   TrackID aId,
                   StreamTime aDesiredTime,
                   const PrincipalHandle& aPrincipalHandle) override;
   uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
-      const nsString& aDeviceId) override;
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) const override;
 
   bool IsFake() override {
     return true;
   }
 
   dom::MediaSourceEnum GetMediaSource() const override {
     return dom::MediaSourceEnum::Camera;
   }
@@ -107,29 +111,33 @@ class MediaEngineDefaultAudioSource : pu
                                       public MediaEngineAudioSource,
                                       private MediaConstraintsHelper
 {
 public:
   MediaEngineDefaultAudioSource();
 
   void Shutdown() override {};
 
-  void GetName(nsAString&) override;
-  void GetUUID(nsACString&) override;
+  void GetName(nsAString&) const override;
+  void GetUUID(nsACString&) const override;
 
   nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                     const MediaEnginePrefs &aPrefs,
                     const nsString& aDeviceId,
-                    const nsACString& aOrigin) override;
-  nsresult Deallocate() override;
+                    const nsACString& aOrigin,
+                    BaseAllocationHandle** aOutHandle,
+                    const char** aOutBadConstraint) override;
+  nsresult Deallocate(BaseAllocationHandle* aHandle) override;
   nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) override;
   nsresult Stop(SourceMediaStream*, TrackID) override;
-  nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+  nsresult Restart(BaseAllocationHandle* aHandle,
+                   const dom::MediaTrackConstraints& aConstraints,
                    const MediaEnginePrefs &aPrefs,
-                   const nsString& aDeviceId) override;
+                   const nsString& aDeviceId,
+                   const char** aOutBadConstraint) override;
   void SetDirectListeners(bool aHasDirectListeners) override {};
   void AppendToSegment(AudioSegment& aSegment,
                        TrackTicks aSamples);
   void NotifyPull(MediaStreamGraph* aGraph,
                   SourceMediaStream *aSource,
                   TrackID aId,
                   StreamTime aDesiredTime,
                   const PrincipalHandle& aPrincipalHandle) override
@@ -161,18 +169,18 @@ public:
   }
 
   nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
-      const nsString& aDeviceId) override;
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) const override;
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSITIMERCALLBACK
 
 protected:
   ~MediaEngineDefaultAudioSource();
 
   TrackID mTrackID;
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -27,19 +27,23 @@ using dom::ConstrainLongRange;
 
 NS_IMPL_ISUPPORTS0(MediaEngineRemoteVideoSource)
 
 MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource(
   int aIndex, mozilla::camera::CaptureEngine aCapEngine,
   dom::MediaSourceEnum aMediaSource, const char* aMonitorName)
   : MediaEngineCameraVideoSource(aIndex, aMonitorName),
     mMediaSource(aMediaSource),
-    mCapEngine(aCapEngine)
+    mCapEngine(aCapEngine),
+    mInShutdown(false)
 {
   MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other);
+  mSettings.mWidth.Construct(0);
+  mSettings.mHeight.Construct(0);
+  mSettings.mFrameRate.Construct(0);
   Init();
 }
 
 void
 MediaEngineRemoteVideoSource::Init()
 {
   LOG((__PRETTY_FUNCTION__));
   char deviceName[kMaxDeviceNameLength];
@@ -63,16 +67,17 @@ MediaEngineRemoteVideoSource::Init()
 
 void
 MediaEngineRemoteVideoSource::Shutdown()
 {
   LOG((__PRETTY_FUNCTION__));
   if (!mInitDone) {
     return;
   }
+  mInShutdown = true;
   if (mState == kStarted) {
     SourceMediaStream *source;
     bool empty;
 
     while (1) {
       {
         MonitorAutoLock lock(mMonitor);
         empty = mSources.IsEmpty();
@@ -82,90 +87,106 @@ MediaEngineRemoteVideoSource::Shutdown()
         }
         source = mSources[0];
       }
       Stop(source, kVideoTrack); // XXX change to support multiple tracks
     }
     MOZ_ASSERT(mState == kStopped);
   }
 
-  if (mState == kAllocated || mState == kStopped) {
-    Deallocate();
+  for (auto& registered : mRegisteredHandles) {
+    MOZ_ASSERT(mState == kAllocated || mState == kStopped);
+    Deallocate(registered.get());
   }
 
-  mState = kReleased;
+  MOZ_ASSERT(mState == kReleased);
   mInitDone = false;
   return;
 }
 
 nsresult
-MediaEngineRemoteVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
-                                       const MediaEnginePrefs& aPrefs,
-                                       const nsString& aDeviceId,
-                                       const nsACString& aOrigin)
+MediaEngineRemoteVideoSource::Allocate(
+    const dom::MediaTrackConstraints& aConstraints,
+    const MediaEnginePrefs& aPrefs,
+    const nsString& aDeviceId,
+    const nsACString& aOrigin,
+    BaseAllocationHandle** aOutHandle,
+    const char** aOutBadConstraint)
 {
   LOG((__PRETTY_FUNCTION__));
   AssertIsOnOwningThread();
 
   if (!mInitDone) {
     LOG(("Init not done"));
     return NS_ERROR_FAILURE;
   }
 
-  if (mState == kReleased) {
-    // Note: if shared, we don't allow a later opener to affect the resolution.
-    // (This may change depending on spec changes for Constraints/settings)
-
-    if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) {
-      return NS_ERROR_UNEXPECTED;
-    }
+  RefPtr<AllocationHandle> handle = new AllocationHandle(aConstraints, aOrigin,
+                                                         aPrefs, aDeviceId);
 
-    if (mozilla::camera::GetChildAndCall(
-      &mozilla::camera::CamerasChild::AllocateCaptureDevice,
-      mCapEngine, GetUUID().get(), kMaxUniqueIdLength, mCaptureIndex, aOrigin)) {
-      return NS_ERROR_FAILURE;
-    }
-    mState = kAllocated;
-    LOG(("Video device %d allocated for %s", mCaptureIndex,
-         PromiseFlatCString(aOrigin).get()));
-  } else if (MOZ_LOG_TEST(GetMediaManagerLog(), mozilla::LogLevel::Debug)) {
+  nsresult rv = UpdateNew(handle, aPrefs, aDeviceId, aOutBadConstraint);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (mState == kStarted &&
+      MOZ_LOG_TEST(GetMediaManagerLog(), mozilla::LogLevel::Debug)) {
     MonitorAutoLock lock(mMonitor);
     if (mSources.IsEmpty()) {
       MOZ_ASSERT(mPrincipalHandles.IsEmpty());
       LOG(("Video device %d reallocated", mCaptureIndex));
     } else {
       LOG(("Video device %d allocated shared", mCaptureIndex));
     }
   }
-
+  mRegisteredHandles.AppendElement(handle);
   ++mNrAllocations;
-
+  handle.forget(aOutHandle);
   return NS_OK;
 }
 
 nsresult
-MediaEngineRemoteVideoSource::Deallocate()
+MediaEngineRemoteVideoSource::Deallocate(BaseAllocationHandle* aHandle)
 {
   LOG((__PRETTY_FUNCTION__));
   AssertIsOnOwningThread();
+  MOZ_ASSERT(aHandle);
+  RefPtr<AllocationHandle> handle = static_cast<AllocationHandle*>(aHandle);
 
+  class Comparator {
+  public:
+    static bool Equals(const RefPtr<AllocationHandle>& a,
+                       const RefPtr<AllocationHandle>& b) {
+      return a.get() == b.get();
+    }
+  };
+  MOZ_ASSERT(mRegisteredHandles.Contains(handle, Comparator()));
+  mRegisteredHandles.RemoveElementAt(mRegisteredHandles.IndexOf(handle, 0,
+                                                                Comparator()));
   --mNrAllocations;
   MOZ_ASSERT(mNrAllocations >= 0, "Double-deallocations are prohibited");
 
   if (mNrAllocations == 0) {
+    MOZ_ASSERT(!mRegisteredHandles.Length());
     if (mState != kStopped && mState != kAllocated) {
       return NS_ERROR_FAILURE;
     }
     mozilla::camera::GetChildAndCall(
       &mozilla::camera::CamerasChild::ReleaseCaptureDevice,
       mCapEngine, mCaptureIndex);
     mState = kReleased;
     LOG(("Video device %d deallocated", mCaptureIndex));
   } else {
     LOG(("Video device %d deallocated but still in use", mCaptureIndex));
+    MOZ_ASSERT(mRegisteredHandles.Length());
+    if (!mInShutdown) {
+      // Whenever constraints are removed, other parties may get closer to ideal.
+      auto& first = mRegisteredHandles[0];
+      const char* badConstraint = nullptr;
+      return UpdateRemove(first->mPrefs, first->mDeviceId, &badConstraint);
+    }
   }
   return NS_OK;
 }
 
 nsresult
 MediaEngineRemoteVideoSource::Start(SourceMediaStream* aStream, TrackID aID,
                                     const PrincipalHandle& aPrincipalHandle)
 {
@@ -241,45 +262,127 @@ MediaEngineRemoteVideoSource::Stop(mozil
   mozilla::camera::GetChildAndCall(
     &mozilla::camera::CamerasChild::StopCapture,
     mCapEngine, mCaptureIndex);
 
   return NS_OK;
 }
 
 nsresult
-MediaEngineRemoteVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
+MediaEngineRemoteVideoSource::Restart(BaseAllocationHandle* aHandle,
+                                      const dom::MediaTrackConstraints& aConstraints,
                                       const MediaEnginePrefs& aPrefs,
-                                      const nsString& aDeviceId)
+                                      const nsString& aDeviceId,
+                                      const char** aOutBadConstraint)
 {
   AssertIsOnOwningThread();
   if (!mInitDone) {
     LOG(("Init not done"));
     return NS_ERROR_FAILURE;
   }
-  if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) {
-    return NS_ERROR_NOT_AVAILABLE;
+  MOZ_ASSERT(aHandle);
+  NormalizedConstraints constraints(aConstraints);
+  return UpdateExisting(static_cast<AllocationHandle*>(aHandle), &constraints,
+                        aPrefs, aDeviceId, aOutBadConstraint);
+}
+
+nsresult
+MediaEngineRemoteVideoSource::UpdateExisting(AllocationHandle* aHandle,
+                                             NormalizedConstraints* aNewConstraints,
+                                             const MediaEnginePrefs& aPrefs,
+                                             const nsString& aDeviceId,
+                                             const char** aOutBadConstraint)
+{
+  // aHandle and/or aNewConstraints may be nullptr
+
+  AutoTArray<const NormalizedConstraints*, 10> allConstraints;
+  for (auto& registered : mRegisteredHandles) {
+    if (aNewConstraints && registered.get() == aHandle) {
+      continue; // Don't count old constraints
+    }
+    allConstraints.AppendElement(&registered->mConstraints);
   }
-  if (mState != kStarted) {
-    return NS_OK;
+  if (aNewConstraints) {
+    allConstraints.AppendElement(aNewConstraints);
+  } else if (aHandle) {
+    // In the case of UpdateNew, the handle isn't registered yet.
+    allConstraints.AppendElement(&aHandle->mConstraints);
+  }
+
+  NormalizedConstraints netConstraints(allConstraints);
+  if (netConstraints.mBadConstraint) {
+    *aOutBadConstraint = netConstraints.mBadConstraint;
+    return NS_ERROR_FAILURE;
+  }
+
+  if (!ChooseCapability(netConstraints, aPrefs, aDeviceId)) {
+    *aOutBadConstraint = FindBadConstraint(netConstraints, *this, aDeviceId);
+    return NS_ERROR_FAILURE;
   }
 
-  mozilla::camera::GetChildAndCall(
-    &mozilla::camera::CamerasChild::StopCapture,
-    mCapEngine, mCaptureIndex);
-  if (mozilla::camera::GetChildAndCall(
-    &mozilla::camera::CamerasChild::StartCapture,
-    mCapEngine, mCaptureIndex, mCapability, this)) {
-    LOG(("StartCapture failed"));
-    return NS_ERROR_FAILURE;
+  switch (mState) {
+    case kReleased:
+      MOZ_ASSERT(aHandle);
+      MOZ_ASSERT(!aNewConstraints);
+      MOZ_ASSERT(!mRegisteredHandles.Length());
+      if (camera::GetChildAndCall(&camera::CamerasChild::AllocateCaptureDevice,
+                                  mCapEngine, GetUUID().get(),
+                                  kMaxUniqueIdLength, mCaptureIndex,
+                                  aHandle->mOrigin)) {
+        return NS_ERROR_FAILURE;
+      }
+      mState = kAllocated;
+      SetLastCapability(mCapability);
+      LOG(("Video device %d allocated for %s", mCaptureIndex,
+           aHandle->mOrigin.get()));
+      break;
+
+    case kStarted:
+      if (mCapability != mLastCapability) {
+        camera::GetChildAndCall(&camera::CamerasChild::StopCapture,
+                                mCapEngine, mCaptureIndex);
+        if (camera::GetChildAndCall(&camera::CamerasChild::StartCapture,
+                                    mCapEngine, mCaptureIndex, mCapability,
+                                    this)) {
+          LOG(("StartCapture failed"));
+          return NS_ERROR_FAILURE;
+        }
+        SetLastCapability(mCapability);
+      }
+      break;
+
+    default:
+      LOG(("Video device %d %s in ignored state %d", mCaptureIndex,
+             (aHandle? aHandle->mOrigin.get() : ""), mState));
+      break;
+  }
+  if (aHandle && aNewConstraints) {
+    aHandle->mConstraints = *aNewConstraints;
   }
   return NS_OK;
 }
 
 void
+MediaEngineRemoteVideoSource::SetLastCapability(
+    const webrtc::CaptureCapability& aCapability)
+{
+  mLastCapability = mCapability;
+
+  webrtc::CaptureCapability cap = aCapability;
+  RefPtr<MediaEngineRemoteVideoSource> that = this;
+
+  NS_DispatchToMainThread(media::NewRunnableFrom([this, that, cap]() mutable {
+    mSettings.mWidth.Value() = cap.width;
+    mSettings.mHeight.Value() = cap.height;
+    mSettings.mFrameRate.Value() = cap.maxFPS;
+    return NS_OK;
+  }));
+}
+
+void
 MediaEngineRemoteVideoSource::NotifyPull(MediaStreamGraph* aGraph,
                                          SourceMediaStream* aSource,
                                          TrackID aID, StreamTime aDesiredTime,
                                          const PrincipalHandle& aPrincipalHandle)
 {
   VideoSegment segment;
 
   MonitorAutoLock lock(mMonitor);
@@ -362,17 +465,17 @@ MediaEngineRemoteVideoSource::DeliverFra
   // We'll push the frame into the MSG on the next NotifyPull. This will avoid
   // swamping the MSG with frames should it be taking longer than normal to run
   // an iteration.
 
   return 0;
 }
 
 size_t
-MediaEngineRemoteVideoSource::NumCapabilities()
+MediaEngineRemoteVideoSource::NumCapabilities() const
 {
   int num = mozilla::camera::GetChildAndCall(
       &mozilla::camera::CamerasChild::NumberOfCapabilities,
       mCapEngine,
       GetUUID().get());
   if (num > 0) {
     return num;
   }
@@ -414,44 +517,48 @@ MediaEngineRemoteVideoSource::NumCapabil
     mHardcodedCapabilities.AppendElement(c);
     break;
   }
 
   return mHardcodedCapabilities.Length();
 }
 
 bool
-MediaEngineRemoteVideoSource::ChooseCapability(const MediaTrackConstraints &aConstraints,
+MediaEngineRemoteVideoSource::ChooseCapability(
+    const NormalizedConstraints &aConstraints,
     const MediaEnginePrefs &aPrefs,
     const nsString& aDeviceId)
 {
   AssertIsOnOwningThread();
 
   switch(mMediaSource) {
     case dom::MediaSourceEnum::Screen:
     case dom::MediaSourceEnum::Window:
     case dom::MediaSourceEnum::Application: {
       FlattenedConstraints c(aConstraints);
-      mCapability.width = ((c.mWidth.mIdeal.WasPassed() ?
-        c.mWidth.mIdeal.Value() : 0) & 0xffff) << 16 | (c.mWidth.mMax & 0xffff);
-      mCapability.height = ((c.mHeight.mIdeal.WasPassed() ?
-        c.mHeight.mIdeal.Value() : 0) & 0xffff) << 16 | (c.mHeight.mMax & 0xffff);
-      mCapability.maxFPS = c.mFrameRate.Clamp(c.mFrameRate.mIdeal.WasPassed() ?
-        c.mFrameRate.mIdeal.Value() : aPrefs.mFPS);
+      // The actual resolution to constrain around is not easy to find ahead of
+      // time (and may in fact change over time), so as a hack, we push ideal
+      // and max constraints down to desktop_capture_impl.cc and finish the
+      // algorithm there.
+      mCapability.width = (c.mWidth.mIdeal.valueOr(0) & 0xffff) << 16 |
+                          (c.mWidth.mMax & 0xffff);
+      mCapability.height = (c.mHeight.mIdeal.valueOr(0) & 0xffff) << 16 |
+                           (c.mHeight.mMax & 0xffff);
+      mCapability.maxFPS = c.mFrameRate.Clamp(c.mFrameRate.mIdeal.valueOr(aPrefs.mFPS));
       return true;
     }
     default:
       return MediaEngineCameraVideoSource::ChooseCapability(aConstraints, aPrefs, aDeviceId);
   }
 
 }
 
 void
 MediaEngineRemoteVideoSource::GetCapability(size_t aIndex,
-                                            webrtc::CaptureCapability& aOut)
+                                            webrtc::CaptureCapability& aOut) const
 {
   if (!mHardcodedCapabilities.IsEmpty()) {
     MediaEngineCameraVideoSource::GetCapability(aIndex, aOut);
   }
   mozilla::camera::GetChildAndCall(
     &mozilla::camera::CamerasChild::GetCaptureCapability,
     mCapEngine,
     GetUUID().get(),
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.h
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.h
@@ -66,51 +66,114 @@ public:
   int DeliverI420Frame(const webrtc::I420VideoFrame& webrtc_frame) override { return 0; };
   bool IsTextureSupported() override { return false; };
 
   // MediaEngineCameraVideoSource
   MediaEngineRemoteVideoSource(int aIndex, mozilla::camera::CaptureEngine aCapEngine,
                                dom::MediaSourceEnum aMediaSource,
                                const char* aMonitorName = "RemoteVideo.Monitor");
 
+  class AllocationHandle : public BaseAllocationHandle
+  {
+  public:
+    AllocationHandle(const dom::MediaTrackConstraints& aConstraints,
+                     const nsACString& aOrigin,
+                     const MediaEnginePrefs& aPrefs,
+                     const nsString& aDeviceId)
+    : mConstraints(aConstraints),
+      mOrigin(aOrigin),
+      mPrefs(aPrefs),
+      mDeviceId(aDeviceId) {}
+  private:
+    ~AllocationHandle() override {}
+  public:
+    NormalizedConstraints mConstraints;
+    nsCString mOrigin;
+    MediaEnginePrefs mPrefs;
+    nsString mDeviceId;
+  };
+
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs,
                     const nsString& aDeviceId,
-                    const nsACString& aOrigin) override;
-  nsresult Deallocate() override;;
+                    const nsACString& aOrigin,
+                    BaseAllocationHandle** aOutHandle,
+                    const char** aOutBadConstraint) override;
+  nsresult Deallocate(BaseAllocationHandle* aHandle) override;
   nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) override;
   nsresult Stop(SourceMediaStream*, TrackID) override;
-  nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+  nsresult Restart(BaseAllocationHandle* aHandle,
+                   const dom::MediaTrackConstraints& aConstraints,
                    const MediaEnginePrefs &aPrefs,
-                   const nsString& aDeviceId) override;
+                   const nsString& aDeviceId,
+                   const char** aOutBadConstraint) override;
   void NotifyPull(MediaStreamGraph* aGraph,
                   SourceMediaStream* aSource,
                   TrackID aId,
                   StreamTime aDesiredTime,
                   const PrincipalHandle& aPrincipalHandle) override;
   dom::MediaSourceEnum GetMediaSource() const override {
     return mMediaSource;
   }
 
-  bool ChooseCapability(const dom::MediaTrackConstraints &aConstraints,
+  bool ChooseCapability(const NormalizedConstraints &aConstraints,
                         const MediaEnginePrefs &aPrefs,
                         const nsString& aDeviceId) override;
 
   void Refresh(int aIndex);
 
   void Shutdown() override;
 
 protected:
   ~MediaEngineRemoteVideoSource() { Shutdown(); }
 
 private:
   // Initialize the needed Video engine interfaces.
   void Init();
-  size_t NumCapabilities() override;
-  void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) override;
+  size_t NumCapabilities() const override;
+  void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) const override;
+  void SetLastCapability(const webrtc::CaptureCapability& aCapability);
+
+  /* UpdateExisting - Centralized function to apply constraints and restart
+   * device as needed, considering all allocations and changes to one.
+   *
+   * aHandle           - New or existing handle, or null to update after removal.
+   * aNewConstraints   - Constraints to be applied to existing handle, or null.
+   * aPrefs            - As passed in (in case of changes in about:config).
+   * aDeviceId         - As passed in (origin dependent).
+   * aOutBadConstraint - Result: nonzero if failed to apply. Name of culprit.
+   */
+
+  nsresult
+  UpdateExisting(AllocationHandle* aHandle,
+                 NormalizedConstraints* aNewConstraints,
+                 const MediaEnginePrefs& aPrefs,
+                 const nsString& aDeviceId,
+                 const char** aOutBadConstraint);
+
+  nsresult
+  UpdateNew(AllocationHandle* aHandle,
+            const MediaEnginePrefs& aPrefs,
+            const nsString& aDeviceId,
+            const char** aOutBadConstraint) {
+    return UpdateExisting(aHandle, nullptr, aPrefs, aDeviceId, aOutBadConstraint);
+  }
+
+  nsresult
+  UpdateRemove(const MediaEnginePrefs& aPrefs,
+               const nsString& aDeviceId,
+               const char** aOutBadConstraint) {
+    return UpdateExisting(nullptr, nullptr, aPrefs, aDeviceId, aOutBadConstraint);
+  }
 
   dom::MediaSourceEnum mMediaSource; // source of media (camera | application | screen)
   mozilla::camera::CaptureEngine mCapEngine;
+
+  nsTArray<RefPtr<AllocationHandle>> mRegisteredHandles;
+
+  // To only restart camera when needed, we keep track previous settings.
+  webrtc::CaptureCapability mLastCapability;
+  bool mInShutdown;
 };
 
 }
 
 #endif /* MEDIAENGINE_REMOTE_VIDEO_SOURCE_H_ */
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -115,51 +115,57 @@ MediaEngineTabVideoSource::InitRunnable:
     MOZ_ASSERT(mVideoSource->mWindow);
   }
   nsCOMPtr<nsIRunnable> start(new StartRunnable(mVideoSource));
   start->Run();
   return NS_OK;
 }
 
 void
-MediaEngineTabVideoSource::GetName(nsAString_internal& aName)
+MediaEngineTabVideoSource::GetName(nsAString_internal& aName) const
 {
   aName.AssignLiteral(MOZ_UTF16("&getUserMedia.videoSource.tabShare;"));
 }
 
 void
-MediaEngineTabVideoSource::GetUUID(nsACString_internal& aUuid)
+MediaEngineTabVideoSource::GetUUID(nsACString_internal& aUuid) const
 {
   aUuid.AssignLiteral("tab");
 }
 
 #define DEFAULT_TABSHARE_VIDEO_MAX_WIDTH 4096
 #define DEFAULT_TABSHARE_VIDEO_MAX_HEIGHT 4096
 #define DEFAULT_TABSHARE_VIDEO_FRAMERATE 30
 
 nsresult
 MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
                                     const MediaEnginePrefs& aPrefs,
                                     const nsString& aDeviceId,
-                                    const nsACString& aOrigin)
+                                    const nsACString& aOrigin,
+                                    BaseAllocationHandle** aOutHandle,
+                                    const char** aOutBadConstraint)
 {
   // windowId is not a proper constraint, so just read it.
   // It has no well-defined behavior in advanced, so ignore it there.
 
   mWindowId = aConstraints.mBrowserWindow.WasPassed() ?
               aConstraints.mBrowserWindow.Value() : -1;
-
-  return Restart(aConstraints, aPrefs, aDeviceId);
+  aOutHandle = nullptr;
+  return Restart(nullptr, aConstraints, aPrefs, aDeviceId, aOutBadConstraint);
 }
 
 nsresult
-MediaEngineTabVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
+MediaEngineTabVideoSource::Restart(BaseAllocationHandle* aHandle,
+                                   const dom::MediaTrackConstraints& aConstraints,
                                    const mozilla::MediaEnginePrefs& aPrefs,
-                                   const nsString& aDeviceId)
+                                   const nsString& aDeviceId,
+                                   const char** aOutBadConstraint)
 {
+  MOZ_ASSERT(!aHandle);
+
   // scrollWithPage is not proper a constraint, so just read it.
   // It has no well-defined behavior in advanced, so ignore it there.
 
   mScrollWithPage = aConstraints.mScrollWithPage.WasPassed() ?
                     aConstraints.mScrollWithPage.Value() : false;
 
   FlattenedConstraints c(aConstraints);
 
@@ -173,18 +179,19 @@ MediaEngineTabVideoSource::Restart(const
     mViewportOffsetY = c.mViewportOffsetY.Get(0);
     mViewportWidth = c.mViewportWidth.Get(INT32_MAX);
     mViewportHeight = c.mViewportHeight.Get(INT32_MAX);
   }
   return NS_OK;
 }
 
 nsresult
-MediaEngineTabVideoSource::Deallocate()
+MediaEngineTabVideoSource::Deallocate(BaseAllocationHandle* aHandle)
 {
+  MOZ_ASSERT(!aHandle);
   return NS_OK;
 }
 
 nsresult
 MediaEngineTabVideoSource::Start(SourceMediaStream* aStream, TrackID aID,
                                  const PrincipalHandle& aPrincipalHandle)
 {
   nsCOMPtr<nsIRunnable> runnable;
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -15,37 +15,41 @@ namespace mozilla {
 class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventListener, nsITimerCallback {
   public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIDOMEVENTLISTENER
     NS_DECL_NSITIMERCALLBACK
     MediaEngineTabVideoSource();
 
     void Shutdown() override {};
-    void GetName(nsAString_internal&) override;
-    void GetUUID(nsACString_internal&) override;
+    void GetName(nsAString_internal&) const override;
+    void GetUUID(nsACString_internal&) const override;
     nsresult Allocate(const dom::MediaTrackConstraints &,
                       const mozilla::MediaEnginePrefs&,
                       const nsString& aDeviceId,
-                      const nsACString& aOrigin) override;
-    nsresult Deallocate() override;
+                      const nsACString& aOrigin,
+                      BaseAllocationHandle** aOutHandle,
+                      const char** aOutBadConstraint) override;
+    nsresult Deallocate(BaseAllocationHandle* aHandle) override;
     nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID, const mozilla::PrincipalHandle&) override;
     void SetDirectListeners(bool aHasDirectListeners) override {};
     void NotifyPull(mozilla::MediaStreamGraph*, mozilla::SourceMediaStream*, mozilla::TrackID, mozilla::StreamTime, const mozilla::PrincipalHandle& aPrincipalHandle) override;
     nsresult Stop(mozilla::SourceMediaStream*, mozilla::TrackID) override;
-    nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+    nsresult Restart(BaseAllocationHandle* aHandle,
+                     const dom::MediaTrackConstraints& aConstraints,
                      const mozilla::MediaEnginePrefs& aPrefs,
-                     const nsString& aDeviceId) override;
+                     const nsString& aDeviceId,
+                     const char** aOutBadConstraint) override;
     bool IsFake() override;
     dom::MediaSourceEnum GetMediaSource() const override {
       return dom::MediaSourceEnum::Browser;
     }
     uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
-      const nsString& aDeviceId) override
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) const override
     {
       return 0;
     }
 
     nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override
     {
       return NS_ERROR_NOT_IMPLEMENTED;
     }
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -67,42 +67,48 @@ class MediaEngineWebRTCAudioCaptureSourc
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   explicit MediaEngineWebRTCAudioCaptureSource(const char* aUuid)
     : MediaEngineAudioSource(kReleased)
   {
   }
-  void GetName(nsAString& aName) override;
-  void GetUUID(nsACString& aUUID) override;
+  void GetName(nsAString& aName) const override;
+  void GetUUID(nsACString& aUUID) const override;
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs,
                     const nsString& aDeviceId,
-                    const nsACString& aOrigin) override
+                    const nsACString& aOrigin,
+                    BaseAllocationHandle** aOutHandle,
+                    const char** aOutBadConstraint) override
   {
     // Nothing to do here, everything is managed in MediaManager.cpp
+    aOutHandle = nullptr;
     return NS_OK;
   }
-  nsresult Deallocate() override
+  nsresult Deallocate(BaseAllocationHandle* aHandle) override
   {
     // Nothing to do here, everything is managed in MediaManager.cpp
+    MOZ_ASSERT(!aHandle);
     return NS_OK;
   }
   void Shutdown() override
   {
     // Nothing to do here, everything is managed in MediaManager.cpp
   }
   nsresult Start(SourceMediaStream* aMediaStream,
                  TrackID aId,
                  const PrincipalHandle& aPrincipalHandle) override;
   nsresult Stop(SourceMediaStream* aMediaStream, TrackID aId) override;
-  nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+  nsresult Restart(BaseAllocationHandle* aHandle,
+                   const dom::MediaTrackConstraints& aConstraints,
                    const MediaEnginePrefs &aPrefs,
-                   const nsString& aDeviceId) override;
+                   const nsString& aDeviceId,
+                   const char** aOutBadConstraint) override;
   void SetDirectListeners(bool aDirect) override
   {}
   void NotifyOutputData(MediaStreamGraph* aGraph,
                         AudioDataValue* aBuffer, size_t aFrames,
                         TrackRate aRate, uint32_t aChannels) override
   {}
   void DeviceChanged() override
   {}
@@ -124,18 +130,18 @@ public:
   {
     return false;
   }
   nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   uint32_t GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
-    const nsString& aDeviceId) override;
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId) const override;
 
 protected:
   virtual ~MediaEngineWebRTCAudioCaptureSource() { Shutdown(); }
   nsCString mUUID;
 };
 
 // Small subset of VoEHardware
 class AudioInput
@@ -440,31 +446,35 @@ public:
     MOZ_ASSERT(aVoiceEnginePtr);
     MOZ_ASSERT(aAudioInput);
     mDeviceName.Assign(NS_ConvertUTF8toUTF16(name));
     mDeviceUUID.Assign(uuid);
     mListener = new mozilla::WebRTCAudioDataListener(this);
     // We'll init lazily as needed
   }
 
-  void GetName(nsAString& aName) override;
-  void GetUUID(nsACString& aUUID) override;
+  void GetName(nsAString& aName) const override;
+  void GetUUID(nsACString& aUUID) const override;
 
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs,
                     const nsString& aDeviceId,
-                    const nsACString& aOrigin) override;
-  nsresult Deallocate() override;
+                    const nsACString& aOrigin,
+                    BaseAllocationHandle** aOutHandle,
+                    const char** aOutBadConstraint) override;
+  nsresult Deallocate(BaseAllocationHandle* aHandle) override;
   nsresult Start(SourceMediaStream* aStream,
                  TrackID aID,
                  const PrincipalHandle& aPrincipalHandle) override;
   nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
-  nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+  nsresult Restart(BaseAllocationHandle* aHandle,
+                   const dom::MediaTrackConstraints& aConstraints,
                    const MediaEnginePrefs &aPrefs,
-                   const nsString& aDeviceId) override;
+                   const nsString& aDeviceId,
+                   const char** aOutBadConstraint) override;
   void SetDirectListeners(bool aHasDirectListeners) override {};
 
   void NotifyPull(MediaStreamGraph* aGraph,
                   SourceMediaStream* aSource,
                   TrackID aId,
                   StreamTime aDesiredTime,
                   const PrincipalHandle& aPrincipalHandle) override;
 
@@ -487,18 +497,18 @@ public:
   }
 
   nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
-      const nsString& aDeviceId) override;
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) const override;
 
   // VoEMediaProcess.
   void Process(int channel, webrtc::ProcessingTypes type,
                int16_t audio10ms[], int length,
                int samplingFreq, bool isStereo) override;
 
   void Shutdown() override;
 
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -179,55 +179,57 @@ AudioOutputObserver::InsertFarEnd(const 
         mSaved = nullptr;
         mSamplesSaved = 0;
       }
     }
   }
 }
 
 void
-MediaEngineWebRTCMicrophoneSource::GetName(nsAString& aName)
+MediaEngineWebRTCMicrophoneSource::GetName(nsAString& aName) const
 {
   aName.Assign(mDeviceName);
   return;
 }
 
 void
-MediaEngineWebRTCMicrophoneSource::GetUUID(nsACString& aUUID)
+MediaEngineWebRTCMicrophoneSource::GetUUID(nsACString& aUUID) const
 {
   aUUID.Assign(mDeviceUUID);
   return;
 }
 
 // GetBestFitnessDistance returns the best distance the capture device can offer
 // as a whole, given an accumulated number of ConstraintSets.
 // Ideal values are considered in the first ConstraintSet only.
 // Plain values are treated as Ideal in the first ConstraintSet.
 // Plain values are treated as Exact in subsequent ConstraintSets.
 // Infinity = UINT32_MAX e.g. device cannot satisfy accumulated ConstraintSets.
 // A finite result may be used to calculate this device's ranking as a choice.
 
 uint32_t MediaEngineWebRTCMicrophoneSource::GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
-    const nsString& aDeviceId)
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId) const
 {
   uint32_t distance = 0;
 
-  for (const MediaTrackConstraintSet* cs : aConstraintSets) {
-    distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
+  for (const auto* cs : aConstraintSets) {
+    distance = GetMinimumFitnessDistance(*cs, aDeviceId);
     break; // distance is read from first entry only
   }
   return distance;
 }
 
 nsresult
 MediaEngineWebRTCMicrophoneSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
                                             const MediaEnginePrefs &aPrefs,
                                             const nsString& aDeviceId,
-                                            const nsACString& aOrigin)
+                                            const nsACString& aOrigin,
+                                            BaseAllocationHandle** aOutHandle,
+                                            const char** aOutBadConstraint)
 {
   AssertIsOnOwningThread();
   if (mState == kReleased) {
     if (sChannelsOpen == 0) {
       if (!InitEngine()) {
         LOG(("Audio engine is not initalized"));
         return NS_ERROR_FAILURE;
       }
@@ -253,24 +255,28 @@ MediaEngineWebRTCMicrophoneSource::Alloc
     MonitorAutoLock lock(mMonitor);
     if (mSources.IsEmpty()) {
       LOG(("Audio device %d reallocated", mCapIndex));
     } else {
       LOG(("Audio device %d allocated shared", mCapIndex));
     }
   }
   ++mNrAllocations;
-  return Restart(aConstraints, aPrefs, aDeviceId);
+  aOutHandle = nullptr;
+  return Restart(nullptr, aConstraints, aPrefs, aDeviceId, aOutBadConstraint);
 }
 
 nsresult
-MediaEngineWebRTCMicrophoneSource::Restart(const dom::MediaTrackConstraints& aConstraints,
+MediaEngineWebRTCMicrophoneSource::Restart(BaseAllocationHandle* aHandle,
+                                           const dom::MediaTrackConstraints& aConstraints,
                                            const MediaEnginePrefs &aPrefs,
-                                           const nsString& aDeviceId)
+                                           const nsString& aDeviceId,
+                                           const char** aOutBadConstraint)
 {
+  MOZ_ASSERT(!aHandle);
   FlattenedConstraints c(aConstraints);
 
   bool aec_on = c.mEchoCancellation.Get(aPrefs.mAecOn);
   bool agc_on = c.mMozAutoGainControl.Get(aPrefs.mAgcOn);
   bool noise_on = c.mMozNoiseSuppression.Get(aPrefs.mNoiseOn);
 
   LOG(("Audio config: aec: %d, agc: %d, noise: %d, delay: %d",
        aec_on ? aPrefs.mAec : -1,
@@ -304,19 +310,20 @@ MediaEngineWebRTCMicrophoneSource::Resta
   if (mSkipProcessing) {
     mSampleFrequency = MediaEngine::USE_GRAPH_RATE;
   }
 
   return NS_OK;
 }
 
 nsresult
-MediaEngineWebRTCMicrophoneSource::Deallocate()
+MediaEngineWebRTCMicrophoneSource::Deallocate(BaseAllocationHandle* aHandle)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(!aHandle);
   --mNrAllocations;
   MOZ_ASSERT(mNrAllocations >= 0, "Double-deallocations are prohibited");
   if (mNrAllocations == 0) {
     // If empty, no callbacks to deliver data should be occuring
     if (mState != kStopped && mState != kAllocated) {
       return NS_ERROR_FAILURE;
     }
 
@@ -727,18 +734,19 @@ MediaEngineWebRTCMicrophoneSource::Shutd
         }
         source = mSources[0];
       }
       Stop(source, kAudioTrack); // XXX change to support multiple tracks
     }
     MOZ_ASSERT(mState == kStopped);
   }
 
-  if (mState == kAllocated || mState == kStopped) {
-    Deallocate();
+  while (mNrAllocations) {
+    MOZ_ASSERT(mState == kAllocated || mState == kStopped);
+    Deallocate(nullptr); // XXX Extend concurrent constraints code to mics.
   }
 
   FreeChannel();
   DeInitEngine();
 
   mAudioInput = nullptr;
 }
 
@@ -786,23 +794,23 @@ MediaEngineWebRTCMicrophoneSource::Proce
     MOZ_ASSERT(!isStereo);
     InsertInGraph<int16_t>(audio10ms, length, 1);
   }
 
   return;
 }
 
 void
-MediaEngineWebRTCAudioCaptureSource::GetName(nsAString &aName)
+MediaEngineWebRTCAudioCaptureSource::GetName(nsAString &aName) const
 {
   aName.AssignLiteral("AudioCapture");
 }
 
 void
-MediaEngineWebRTCAudioCaptureSource::GetUUID(nsACString &aUUID)
+MediaEngineWebRTCAudioCaptureSource::GetUUID(nsACString &aUUID) const
 {
   nsID uuid;
   char uuidBuffer[NSID_LENGTH];
   nsCString asciiString;
   ErrorResult rv;
 
   rv = nsContentUtils::GenerateUUIDInPlace(uuid);
   if (rv.Failed()) {
@@ -834,25 +842,28 @@ MediaEngineWebRTCAudioCaptureSource::Sto
 {
   AssertIsOnOwningThread();
   aMediaStream->EndAllTrackAndFinish();
   return NS_OK;
 }
 
 nsresult
 MediaEngineWebRTCAudioCaptureSource::Restart(
+    BaseAllocationHandle* aHandle,
     const dom::MediaTrackConstraints& aConstraints,
     const MediaEnginePrefs &aPrefs,
-    const nsString& aDeviceId)
+    const nsString& aDeviceId,
+    const char** aOutBadConstraint)
 {
+  MOZ_ASSERT(!aHandle);
   return NS_OK;
 }
 
 uint32_t
 MediaEngineWebRTCAudioCaptureSource::GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
-    const nsString& aDeviceId)
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId) const
 {
   // There is only one way of capturing audio for now, and it's always adequate.
   return 0;
 }
 
 }
--- a/dom/media/webrtc/MediaTrackConstraints.cpp
+++ b/dom/media/webrtc/MediaTrackConstraints.cpp
@@ -1,241 +1,463 @@
 /* -*- 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 "MediaTrackConstraints.h"
 
 #include <limits>
+#include <algorithm>
+#include <iterator>
 
 namespace mozilla {
 
 template<class ValueType>
 template<class ConstrainRange>
 void
 NormalizedConstraintSet::Range<ValueType>::SetFrom(const ConstrainRange& aOther)
 {
   if (aOther.mIdeal.WasPassed()) {
-    mIdeal.Construct(aOther.mIdeal.Value());
+    mIdeal.emplace(aOther.mIdeal.Value());
   }
   if (aOther.mExact.WasPassed()) {
     mMin = aOther.mExact.Value();
     mMax = aOther.mExact.Value();
   } else {
     if (aOther.mMin.WasPassed()) {
       mMin = aOther.mMin.Value();
     }
     if (aOther.mMax.WasPassed()) {
       mMax = aOther.mMax.Value();
     }
   }
 }
 
+// The Range code works surprisingly well for bool, except when averaging ideals.
+template<>
+bool
+NormalizedConstraintSet::Range<bool>::Merge(const Range& aOther) {
+  if (!Intersects(aOther)) {
+    return false;
+  }
+  Intersect(aOther);
+
+  // To avoid "unsafe use of type 'bool'", we keep counter in mMergeDenominator
+  uint32_t counter = mMergeDenominator >> 16;
+  uint32_t denominator = mMergeDenominator & 0xffff;
+
+  if (aOther.mIdeal.isSome()) {
+    if (mIdeal.isNothing()) {
+      mIdeal.emplace(aOther.Get(false));
+      counter = aOther.Get(false);
+      denominator = 1;
+    } else {
+      if (!denominator) {
+        counter = Get(false);
+        denominator = 1;
+      }
+      counter += aOther.Get(false);
+      denominator++;
+    }
+  }
+  mMergeDenominator = ((counter & 0xffff) << 16) + (denominator & 0xffff);
+  return true;
+}
+
+template<>
+void
+NormalizedConstraintSet::Range<bool>::FinalizeMerge()
+{
+  if (mMergeDenominator) {
+    uint32_t counter = mMergeDenominator >> 16;
+    uint32_t denominator = mMergeDenominator & 0xffff;
+
+    *mIdeal = !!(counter / denominator);
+    mMergeDenominator = 0;
+  }
+}
+
 NormalizedConstraintSet::LongRange::LongRange(
-    const dom::OwningLongOrConstrainLongRange& aOther, bool advanced)
-: Range<int32_t>(1 + INT32_MIN, INT32_MAX) // +1 avoids Windows compiler bug
+    LongPtrType aMemberPtr,
+    const char* aName,
+    const dom::OwningLongOrConstrainLongRange& aOther,
+    bool advanced,
+    nsTArray<MemberPtrType>* aList)
+: Range<int32_t>((MemberPtrType)aMemberPtr, aName,
+                 1 + INT32_MIN, INT32_MAX, // +1 avoids Windows compiler bug
+                 aList)
 {
   if (aOther.IsLong()) {
     if (advanced) {
       mMin = mMax = aOther.GetAsLong();
     } else {
-      mIdeal.Construct(aOther.GetAsLong());
+      mIdeal.emplace(aOther.GetAsLong());
     }
   } else {
     SetFrom(aOther.GetAsConstrainLongRange());
   }
 }
 
+NormalizedConstraintSet::LongLongRange::LongLongRange(
+    LongLongPtrType aMemberPtr,
+    const char* aName,
+    const long long& aOther,
+    nsTArray<MemberPtrType>* aList)
+: Range<int64_t>((MemberPtrType)aMemberPtr, aName,
+                 1 + INT64_MIN, INT64_MAX, // +1 avoids Windows compiler bug
+                 aList)
+{
+  mIdeal.emplace(aOther);
+}
+
 NormalizedConstraintSet::DoubleRange::DoubleRange(
-    const dom::OwningDoubleOrConstrainDoubleRange& aOther, bool advanced)
-: Range<double>(-std::numeric_limits<double>::infinity(),
-                std::numeric_limits<double>::infinity())
+    DoublePtrType aMemberPtr,
+    const char* aName,
+    const dom::OwningDoubleOrConstrainDoubleRange& aOther, bool advanced,
+    nsTArray<MemberPtrType>* aList)
+: Range<double>((MemberPtrType)aMemberPtr, aName,
+                -std::numeric_limits<double>::infinity(),
+                std::numeric_limits<double>::infinity(), aList)
 {
   if (aOther.IsDouble()) {
     if (advanced) {
       mMin = mMax = aOther.GetAsDouble();
     } else {
-      mIdeal.Construct(aOther.GetAsDouble());
+      mIdeal.emplace(aOther.GetAsDouble());
     }
   } else {
     SetFrom(aOther.GetAsConstrainDoubleRange());
   }
 }
 
 NormalizedConstraintSet::BooleanRange::BooleanRange(
-    const dom::OwningBooleanOrConstrainBooleanParameters& aOther, bool advanced)
-: Range<bool>(false, true)
+    BooleanPtrType aMemberPtr,
+    const char* aName,
+    const dom::OwningBooleanOrConstrainBooleanParameters& aOther,
+    bool advanced,
+    nsTArray<MemberPtrType>* aList)
+: Range<bool>((MemberPtrType)aMemberPtr, aName, false, true, aList)
 {
   if (aOther.IsBoolean()) {
     if (advanced) {
       mMin = mMax = aOther.GetAsBoolean();
     } else {
-      mIdeal.Construct(aOther.GetAsBoolean());
+      mIdeal.emplace(aOther.GetAsBoolean());
     }
   } else {
     const ConstrainBooleanParameters& r = aOther.GetAsConstrainBooleanParameters();
     if (r.mIdeal.WasPassed()) {
-      mIdeal.Construct(r.mIdeal.Value());
+      mIdeal.emplace(r.mIdeal.Value());
     }
     if (r.mExact.WasPassed()) {
       mMin = r.mExact.Value();
       mMax = r.mExact.Value();
     }
   }
 }
 
-FlattenedConstraints::FlattenedConstraints(const dom::MediaTrackConstraints& aOther)
-: NormalizedConstraintSet(aOther, false)
+NormalizedConstraintSet::StringRange::StringRange(
+    StringPtrType aMemberPtr,
+    const char* aName,
+    const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aOther,
+    bool advanced,
+    nsTArray<MemberPtrType>* aList)
+  : BaseRange((MemberPtrType)aMemberPtr, aName, aList)
+{
+  if (aOther.IsString()) {
+    if (advanced) {
+      mExact.insert(aOther.GetAsString());
+    } else {
+      mIdeal.insert(aOther.GetAsString());
+    }
+  } else if (aOther.IsStringSequence()) {
+    if (advanced) {
+      mExact.clear();
+      for (auto& str : aOther.GetAsStringSequence()) {
+        mExact.insert(str);
+      }
+    } else {
+      mIdeal.clear();
+      for (auto& str : aOther.GetAsStringSequence()) {
+        mIdeal.insert(str);
+      }
+    }
+  } else {
+    SetFrom(aOther.GetAsConstrainDOMStringParameters());
+  }
+}
+
+void
+NormalizedConstraintSet::StringRange::SetFrom(
+    const ConstrainDOMStringParameters& aOther)
+{
+  if (aOther.mIdeal.WasPassed()) {
+    mIdeal.clear();
+    if (aOther.mIdeal.Value().IsString()) {
+      mIdeal.insert(aOther.mIdeal.Value().GetAsString());
+    } else {
+      for (auto& str : aOther.mIdeal.Value().GetAsStringSequence()) {
+        mIdeal.insert(str);
+      }
+    }
+  }
+  if (aOther.mExact.WasPassed()) {
+    mExact.clear();
+    if (aOther.mExact.Value().IsString()) {
+      mExact.insert(aOther.mExact.Value().GetAsString());
+    } else {
+      for (auto& str : aOther.mExact.Value().GetAsStringSequence()) {
+        mIdeal.insert(str);
+      }
+    }
+  }
+}
+
+auto
+NormalizedConstraintSet::StringRange::Clamp(const ValueType& n) const -> ValueType
+{
+  if (!mExact.size()) {
+    return n;
+  }
+  ValueType result;
+  for (auto& entry : n) {
+    if (mExact.find(entry) != mExact.end()) {
+      result.insert(entry);
+    }
+  }
+  return result;
+}
+
+bool
+NormalizedConstraintSet::StringRange::Intersects(const StringRange& aOther) const
+{
+  if (!mExact.size() || !aOther.mExact.size()) {
+    return true;
+  }
+
+  ValueType intersection;
+  set_intersection(mExact.begin(), mExact.end(),
+                   aOther.mExact.begin(), aOther.mExact.end(),
+                   std::inserter(intersection, intersection.begin()));
+  return !!intersection.size();
+}
+
+void
+NormalizedConstraintSet::StringRange::Intersect(const StringRange& aOther)
+{
+  if (!aOther.mExact.size()) {
+    return;
+  }
+
+  ValueType intersection;
+  set_intersection(mExact.begin(), mExact.end(),
+                   aOther.mExact.begin(), aOther.mExact.end(),
+                   std::inserter(intersection, intersection.begin()));
+  mExact = intersection;
+}
+
+bool
+NormalizedConstraintSet::StringRange::Merge(const StringRange& aOther)
+{
+  if (!Intersects(aOther)) {
+    return false;
+  }
+  Intersect(aOther);
+
+  ValueType unioned;
+  set_union(mIdeal.begin(), mIdeal.end(),
+            aOther.mIdeal.begin(), aOther.mIdeal.end(),
+            std::inserter(unioned, unioned.begin()));
+  mIdeal = unioned;
+  return true;
+}
+
+NormalizedConstraints::NormalizedConstraints(
+    const dom::MediaTrackConstraints& aOther,
+    nsTArray<MemberPtrType>* aList)
+  : NormalizedConstraintSet(aOther, false, aList)
+  , mBadConstraint(nullptr)
 {
   if (aOther.mAdvanced.WasPassed()) {
-    const auto& advanced = aOther.mAdvanced.Value();
-    for (size_t i = 0; i < advanced.Length(); i++) {
-      NormalizedConstraintSet set(advanced[i], true);
-      // Must only apply compatible i.e. inherently non-overconstraining sets
-      // This rule is pretty much why this code is centralized here.
-      if (mWidth.Intersects(set.mWidth) &&
-          mHeight.Intersects(set.mHeight) &&
-          mFrameRate.Intersects(set.mFrameRate)) {
-        mWidth.Intersect(set.mWidth);
-        mHeight.Intersect(set.mHeight);
-        mFrameRate.Intersect(set.mFrameRate);
+    for (auto& entry : aOther.mAdvanced.Value()) {
+      mAdvanced.push_back(NormalizedConstraintSet(entry, true));
+    }
+  }
+}
+
+// Merge constructor. Create net constraints out of merging a set of others.
+// This is only used to resolve competing constraints from concurrent requests,
+// something the spec doesn't cover.
+
+NormalizedConstraints::NormalizedConstraints(
+    const nsTArray<const NormalizedConstraints*>& aOthers)
+  : NormalizedConstraintSet(*aOthers[0])
+  , mBadConstraint(nullptr)
+{
+  // Create a list of member pointers.
+  nsTArray<MemberPtrType> list;
+  NormalizedConstraints dummy(MediaTrackConstraints(), &list);
+
+  // Do intersection of all required constraints, and average of ideals,
+
+  for (uint32_t i = 1; i < aOthers.Length(); i++) {
+    auto& other = *aOthers[i];
+
+    for (auto& memberPtr : list) {
+      auto& member = this->*memberPtr;
+      auto& otherMember = other.*memberPtr;
+
+      if (!member.Merge(otherMember)) {
+        mBadConstraint = member.mName;
+        return;
       }
-      if (mEchoCancellation.Intersects(set.mEchoCancellation)) {
-          mEchoCancellation.Intersect(set.mEchoCancellation);
-      }
-      if (mMozNoiseSuppression.Intersects(set.mMozNoiseSuppression)) {
-          mMozNoiseSuppression.Intersect(set.mMozNoiseSuppression);
-      }
-      if (mMozAutoGainControl.Intersects(set.mMozAutoGainControl)) {
-          mMozAutoGainControl.Intersect(set.mMozAutoGainControl);
-      }
+    }
+
+    for (auto& entry : other.mAdvanced) {
+      mAdvanced.push_back(entry);
+    }
+  }
+  for (auto& memberPtr : list) {
+    (this->*memberPtr).FinalizeMerge();
+  }
+
+  // ...except for resolution and frame rate where we take the highest ideal.
+  // This is a bit of a hack based on the perception that people would be more
+  // surprised if they were to get lower resolution than they ideally requested.
+  //
+  // The spec gives browsers leeway here, saying they "SHOULD use the one with
+  // the smallest fitness distance", and also does not directly address the
+  // problem of competing constraints at all. There is no real web interop issue
+  // here since this is more about interop with other tabs on the same browser.
+  //
+  // We should revisit this logic once we support downscaling of resolutions and
+  // decimating of frame rates, per track.
+
+  for (auto& other : aOthers) {
+    mWidth.TakeHighestIdeal(other->mWidth);
+    mHeight.TakeHighestIdeal(other->mHeight);
+
+    // Consider implicit 30 fps default in comparison of competing constraints.
+    // Avoids 160x90x10 and 640x480 becoming 1024x768x10 (fitness distance flaw)
+    // This pretty much locks in 30 fps or higher, except for single-tab use.
+    auto frameRate = other->mFrameRate;
+    if (frameRate.mIdeal.isNothing()) {
+      frameRate.mIdeal.emplace(30);
+    }
+    mFrameRate.TakeHighestIdeal(frameRate);
+  }
+}
+
+FlattenedConstraints::FlattenedConstraints(const NormalizedConstraints& aOther)
+: NormalizedConstraintSet(aOther)
+{
+  for (auto& set : aOther.mAdvanced) {
+    // Must only apply compatible i.e. inherently non-overconstraining sets
+    // This rule is pretty much why this code is centralized here.
+    if (mWidth.Intersects(set.mWidth) &&
+        mHeight.Intersects(set.mHeight) &&
+        mFrameRate.Intersects(set.mFrameRate)) {
+      mWidth.Intersect(set.mWidth);
+      mHeight.Intersect(set.mHeight);
+      mFrameRate.Intersect(set.mFrameRate);
+    }
+    if (mEchoCancellation.Intersects(set.mEchoCancellation)) {
+        mEchoCancellation.Intersect(set.mEchoCancellation);
+    }
+    if (mMozNoiseSuppression.Intersects(set.mMozNoiseSuppression)) {
+        mMozNoiseSuppression.Intersect(set.mMozNoiseSuppression);
+    }
+    if (mMozAutoGainControl.Intersects(set.mMozAutoGainControl)) {
+        mMozAutoGainControl.Intersect(set.mMozAutoGainControl);
     }
   }
 }
 
 // MediaEngine helper
 //
 // The full algorithm for all devices. Sources that don't list capabilities
 // need to fake it and hardcode some by populating mHardcodedCapabilities above.
 //
 // Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
 
 // First, all devices have a minimum distance based on their deviceId.
 // If you have no other constraints, use this one. Reused by all device types.
 
 uint32_t
 MediaConstraintsHelper::GetMinimumFitnessDistance(
-    const dom::MediaTrackConstraintSet &aConstraints,
-    bool aAdvanced,
+    const NormalizedConstraintSet &aConstraints,
     const nsString& aDeviceId)
 {
-  uint64_t distance =
-    uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId, aAdvanced));
-
-  // This function is modeled on MediaEngineCameraVideoSource::GetFitnessDistance
-  // and will make more sense once more audio constraints are added.
-
-  return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
+  return FitnessDistance(aDeviceId, aConstraints.mDeviceId);
 }
 
-template<class ValueType, class ConstrainRange>
+template<class ValueType, class NormalizedRange>
 /* static */ uint32_t
 MediaConstraintsHelper::FitnessDistance(ValueType aN,
-                                        const ConstrainRange& aRange)
+                                        const NormalizedRange& aRange)
 {
-  if ((aRange.mExact.WasPassed() && aRange.mExact.Value() != aN) ||
-      (aRange.mMin.WasPassed() && aRange.mMin.Value() > aN) ||
-      (aRange.mMax.WasPassed() && aRange.mMax.Value() < aN)) {
+  if (aRange.mMin > aN || aRange.mMax < aN) {
     return UINT32_MAX;
   }
-  if (!aRange.mIdeal.WasPassed() || aN == aRange.mIdeal.Value()) {
+  if (aN == aRange.mIdeal.valueOr(aN)) {
     return 0;
   }
-  return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.Value()) * 1000) /
-                            std::max(std::abs(aN), std::abs(aRange.mIdeal.Value()))));
-}
-
-// Binding code doesn't templatize well...
-
-/*static*/ uint32_t
-MediaConstraintsHelper::FitnessDistance(int32_t aN,
-    const OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced)
-{
-  if (aConstraint.IsLong()) {
-    ConstrainLongRange range;
-    (aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsLong());
-    return FitnessDistance(aN, range);
-  } else {
-    return FitnessDistance(aN, aConstraint.GetAsConstrainLongRange());
-  }
-}
-
-/*static*/ uint32_t
-MediaConstraintsHelper::FitnessDistance(double aN,
-    const OwningDoubleOrConstrainDoubleRange& aConstraint,
-    bool aAdvanced)
-{
-  if (aConstraint.IsDouble()) {
-    ConstrainDoubleRange range;
-    (aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsDouble());
-    return FitnessDistance(aN, range);
-  } else {
-    return FitnessDistance(aN, aConstraint.GetAsConstrainDoubleRange());
-  }
+  return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) /
+                            std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
 }
 
 // Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
 
 /* static */ uint32_t
-MediaConstraintsHelper::FitnessDistance(nsString aN,
-                             const ConstrainDOMStringParameters& aParams)
+MediaConstraintsHelper::FitnessDistance(
+    nsString aN,
+    const NormalizedConstraintSet::StringRange& aParams)
 {
-  struct Func
-  {
-    static bool
-    Contains(const OwningStringOrStringSequence& aStrings, nsString aN)
-    {
-      return aStrings.IsString() ? aStrings.GetAsString() == aN
-                                 : aStrings.GetAsStringSequence().Contains(aN);
-    }
-  };
-
-  if (aParams.mExact.WasPassed() && !Func::Contains(aParams.mExact.Value(), aN)) {
+  if (aParams.mExact.size() && aParams.mExact.find(aN) == aParams.mExact.end()) {
     return UINT32_MAX;
   }
-  if (aParams.mIdeal.WasPassed() && !Func::Contains(aParams.mIdeal.Value(), aN)) {
+  if (aParams.mIdeal.size() && aParams.mIdeal.find(aN) == aParams.mIdeal.end()) {
     return 1000;
   }
   return 0;
 }
 
-/* static */ uint32_t
-MediaConstraintsHelper::FitnessDistance(nsString aN,
-    const OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint,
-    bool aAdvanced)
+template<class MediaEngineSourceType>
+const char*
+MediaConstraintsHelper::FindBadConstraint(
+    const NormalizedConstraints& aConstraints,
+    const MediaEngineSourceType& aMediaEngineSource,
+    const nsString& aDeviceId)
 {
-  if (aConstraint.IsString()) {
-    ConstrainDOMStringParameters params;
-    if (aAdvanced) {
-      params.mExact.Construct();
-      params.mExact.Value().SetAsString() = aConstraint.GetAsString();
-    } else {
-      params.mIdeal.Construct();
-      params.mIdeal.Value().SetAsString() = aConstraint.GetAsString();
+  class MockDevice
+  {
+  public:
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockDevice);
+
+    explicit MockDevice(const MediaEngineSourceType* aMediaEngineSource,
+                        const nsString& aDeviceId)
+    : mMediaEngineSource(aMediaEngineSource),
+      // The following dud code exists to avoid 'unused typedef' error on linux.
+      mDeviceId(MockDevice::HasThreadSafeRefCnt::value ? aDeviceId : nsString()) {}
+
+    uint32_t GetBestFitnessDistance(
+        const nsTArray<const NormalizedConstraintSet*>& aConstraintSets)
+    {
+      return mMediaEngineSource->GetBestFitnessDistance(aConstraintSets,
+                                                        mDeviceId);
     }
-    return FitnessDistance(aN, params);
-  } else if (aConstraint.IsStringSequence()) {
-    ConstrainDOMStringParameters params;
-    if (aAdvanced) {
-      params.mExact.Construct();
-      params.mExact.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
-    } else {
-      params.mIdeal.Construct();
-      params.mIdeal.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
-    }
-    return FitnessDistance(aN, params);
-  } else {
-    return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters());
-  }
+
+  private:
+    ~MockDevice() {}
+
+    const MediaEngineSourceType* mMediaEngineSource;
+    nsString mDeviceId;
+  };
+
+  Unused << typename MockDevice::HasThreadSafeRefCnt();
+
+  nsTArray<RefPtr<MockDevice>> devices;
+  devices.AppendElement(new MockDevice(&aMediaEngineSource, aDeviceId));
+  return FindBadConstraint(aConstraints, devices);
 }
 
 }
--- a/dom/media/webrtc/MediaTrackConstraints.h
+++ b/dom/media/webrtc/MediaTrackConstraints.h
@@ -8,16 +8,17 @@
 #define MEDIATRACKCONSTRAINTS_H_
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/MediaTrackConstraintSetBinding.h"
 #include "mozilla/dom/MediaTrackSupportedConstraintsBinding.h"
 
 #include <map>
+#include <set>
 
 namespace mozilla {
 
 template<class EnumValuesStrings, class Enum>
 static const char* EnumToASCII(const EnumValuesStrings& aStrings, Enum aValue) {
   return aStrings[uint32_t(aValue)].value;
 }
 
@@ -30,229 +31,415 @@ static Enum StringToEnum(const EnumValue
     }
   }
   return aDefaultValue;
 }
 
 // Helper classes for orthogonal constraints without interdependencies.
 // Instead of constraining values, constrain the constraints themselves.
 
-struct NormalizedConstraintSet
+class NormalizedConstraintSet
 {
-  template<class ValueType>
-  struct Range
+protected:
+  class BaseRange
   {
+  protected:
+    typedef BaseRange NormalizedConstraintSet::* MemberPtrType;
+
+    BaseRange(MemberPtrType aMemberPtr, const char* aName,
+              nsTArray<MemberPtrType>* aList) : mName(aName) {
+      if (aList) {
+        aList->AppendElement(aMemberPtr);
+      }
+    }
+    virtual ~BaseRange() {}
+  public:
+    virtual bool Merge(const BaseRange& aOther) = 0;
+    virtual void FinalizeMerge() = 0;
+
+    const char* mName;
+  };
+
+  typedef BaseRange NormalizedConstraintSet::* MemberPtrType;
+
+public:
+  template<class ValueType>
+  class Range : public BaseRange
+  {
+  public:
     ValueType mMin, mMax;
-    dom::Optional<ValueType> mIdeal;
+    Maybe<ValueType> mIdeal;
 
-    Range(ValueType aMin, ValueType aMax) : mMin(aMin), mMax(aMax) {}
+    Range(MemberPtrType aMemberPtr, const char* aName, ValueType aMin,
+          ValueType aMax, nsTArray<MemberPtrType>* aList)
+      : BaseRange(aMemberPtr, aName, aList)
+      , mMin(aMin), mMax(aMax), mMergeDenominator(0) {}
+    virtual ~Range() {};
 
     template<class ConstrainRange>
     void SetFrom(const ConstrainRange& aOther);
     ValueType Clamp(ValueType n) const { return std::max(mMin, std::min(n, mMax)); }
     ValueType Get(ValueType defaultValue) const {
-      return Clamp(mIdeal.WasPassed() ? mIdeal.Value() : defaultValue);
+      return Clamp(mIdeal.valueOr(defaultValue));
     }
     bool Intersects(const Range& aOther) const {
       return mMax >= aOther.mMin && mMin <= aOther.mMax;
     }
     void Intersect(const Range& aOther) {
       MOZ_ASSERT(Intersects(aOther));
       mMin = std::max(mMin, aOther.mMin);
       mMax = std::min(mMax, aOther.mMax);
     }
+    bool Merge(const Range& aOther) {
+      if (!Intersects(aOther)) {
+        return false;
+      }
+      Intersect(aOther);
+
+      if (aOther.mIdeal.isSome()) {
+        // Ideal values, as stored, may be outside their min max range, so use
+        // clamped values in averaging, to avoid extreme outliers skewing results.
+        if (mIdeal.isNothing()) {
+          mIdeal.emplace(aOther.Get(0));
+          mMergeDenominator = 1;
+        } else {
+          if (!mMergeDenominator) {
+            *mIdeal = Get(0);
+            mMergeDenominator = 1;
+          }
+          *mIdeal += aOther.Get(0);
+          mMergeDenominator++;
+        }
+      }
+      return true;
+    }
+    void FinalizeMerge() override
+    {
+      if (mMergeDenominator) {
+        *mIdeal /= mMergeDenominator;
+        mMergeDenominator = 0;
+      }
+    }
+    void TakeHighestIdeal(const Range& aOther) {
+      if (aOther.mIdeal.isSome()) {
+        if (mIdeal.isNothing()) {
+          mIdeal.emplace(aOther.Get(0));
+        } else {
+          *mIdeal = std::max(Get(0), aOther.Get(0));
+        }
+      }
+    }
+  private:
+    bool Merge(const BaseRange& aOther) override {
+      return Merge(static_cast<const Range&>(aOther));
+    }
+
+    uint32_t mMergeDenominator;
   };
 
   struct LongRange : public Range<int32_t>
   {
-    LongRange(const dom::OwningLongOrConstrainLongRange& aOther, bool advanced);
+    typedef LongRange NormalizedConstraintSet::* LongPtrType;
+
+    LongRange(LongPtrType aMemberPtr, const char* aName,
+              const dom::OwningLongOrConstrainLongRange& aOther, bool advanced,
+              nsTArray<MemberPtrType>* aList);
+  };
+
+  struct LongLongRange : public Range<int64_t>
+  {
+    typedef LongLongRange NormalizedConstraintSet::* LongLongPtrType;
+
+    LongLongRange(LongLongPtrType aMemberPtr, const char* aName,
+                  const long long& aOther,
+                  nsTArray<MemberPtrType>* aList);
   };
 
   struct DoubleRange : public Range<double>
   {
-    DoubleRange(const dom::OwningDoubleOrConstrainDoubleRange& aOther,
-                bool advanced);
+    typedef DoubleRange NormalizedConstraintSet::* DoublePtrType;
+
+    DoubleRange(DoublePtrType aMemberPtr,
+                const char* aName,
+                const dom::OwningDoubleOrConstrainDoubleRange& aOther,
+                bool advanced,
+                nsTArray<MemberPtrType>* aList);
   };
 
   struct BooleanRange : public Range<bool>
   {
-    BooleanRange(const dom::OwningBooleanOrConstrainBooleanParameters& aOther,
-                 bool advanced);
+    typedef BooleanRange NormalizedConstraintSet::* BooleanPtrType;
+
+    BooleanRange(BooleanPtrType aMemberPtr, const char* aName,
+                 const dom::OwningBooleanOrConstrainBooleanParameters& aOther,
+                 bool advanced,
+                 nsTArray<MemberPtrType>* aList);
+
+    BooleanRange(BooleanPtrType aMemberPtr, const char* aName, const bool& aOther,
+                 nsTArray<MemberPtrType>* aList)
+      : Range<bool>((MemberPtrType)aMemberPtr, aName, false, true, aList) {
+      mIdeal.emplace(aOther);
+    }
   };
 
-  // Do you need to add your constraint here? Only if your code uses flattening
+  struct StringRange : public BaseRange
+  {
+    typedef std::set<nsString> ValueType;
+    ValueType mExact, mIdeal;
+
+    typedef StringRange NormalizedConstraintSet::* StringPtrType;
+
+    StringRange(StringPtrType aMemberPtr,  const char* aName,
+        const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aOther,
+        bool advanced,
+        nsTArray<MemberPtrType>* aList);
+
+    StringRange(StringPtrType aMemberPtr, const char* aName,
+                const nsString& aOther, nsTArray<MemberPtrType>* aList)
+      : BaseRange((MemberPtrType)aMemberPtr, aName, aList) {
+      mIdeal.insert(aOther);
+    }
+
+    ~StringRange() {}
+
+    void SetFrom(const dom::ConstrainDOMStringParameters& aOther);
+    ValueType Clamp(const ValueType& n) const;
+    ValueType Get(const ValueType& defaultValue) const {
+      return Clamp(mIdeal.size() ? mIdeal : defaultValue);
+    }
+    bool Intersects(const StringRange& aOther) const;
+    void Intersect(const StringRange& aOther);
+    bool Merge(const StringRange& aOther);
+    void FinalizeMerge() override {}
+  private:
+    bool Merge(const BaseRange& aOther) override {
+      return Merge(static_cast<const StringRange&>(aOther));
+    }
+  };
+
+  // All new constraints should be added here whether they use flattening or not
   LongRange mWidth, mHeight;
   DoubleRange mFrameRate;
+  StringRange mFacingMode;
+  StringRange mMediaSource;
+  LongLongRange mBrowserWindow;
+  BooleanRange mScrollWithPage;
+  StringRange mDeviceId;
   LongRange mViewportOffsetX, mViewportOffsetY, mViewportWidth, mViewportHeight;
   BooleanRange mEchoCancellation, mMozNoiseSuppression, mMozAutoGainControl;
-
+private:
+  typedef NormalizedConstraintSet T;
+public:
   NormalizedConstraintSet(const dom::MediaTrackConstraintSet& aOther,
-                          bool advanced)
-  : mWidth(aOther.mWidth, advanced)
-  , mHeight(aOther.mHeight, advanced)
-  , mFrameRate(aOther.mFrameRate, advanced)
-  , mViewportOffsetX(aOther.mViewportOffsetX, advanced)
-  , mViewportOffsetY(aOther.mViewportOffsetY, advanced)
-  , mViewportWidth(aOther.mViewportWidth, advanced)
-  , mViewportHeight(aOther.mViewportHeight, advanced)
-  , mEchoCancellation(aOther.mEchoCancellation, advanced)
-  , mMozNoiseSuppression(aOther.mMozNoiseSuppression, advanced)
-  , mMozAutoGainControl(aOther.mMozAutoGainControl, advanced) {}
+                          bool advanced,
+                          nsTArray<MemberPtrType>* aList = nullptr)
+  : mWidth(&T::mWidth, "width", aOther.mWidth, advanced, aList)
+  , mHeight(&T::mHeight, "height", aOther.mHeight, advanced, aList)
+  , mFrameRate(&T::mFrameRate, "frameRate", aOther.mFrameRate, advanced, aList)
+  , mFacingMode(&T::mFacingMode, "facingMode", aOther.mFacingMode, advanced, aList)
+  , mMediaSource(&T::mMediaSource, "mediaSource", aOther.mMediaSource, aList)
+  , mBrowserWindow(&T::mBrowserWindow, "browserWindow",
+                   aOther.mBrowserWindow.WasPassed() ?
+                   aOther.mBrowserWindow.Value() : 0, aList)
+  , mScrollWithPage(&T::mScrollWithPage, "scrollWithPage",
+                    aOther.mScrollWithPage.WasPassed() ?
+                    aOther.mScrollWithPage.Value() : false, aList)
+  , mDeviceId(&T::mDeviceId, "deviceId", aOther.mDeviceId, advanced, aList)
+  , mViewportOffsetX(&T::mViewportOffsetX, "viewportOffsetX",
+                     aOther.mViewportOffsetX, advanced, aList)
+  , mViewportOffsetY(&T::mViewportOffsetY, "viewportOffsetY",
+                     aOther.mViewportOffsetY, advanced, aList)
+  , mViewportWidth(&T::mViewportWidth, "viewportWidth",
+                   aOther.mViewportWidth, advanced, aList)
+  , mViewportHeight(&T::mViewportHeight, "viewportHeight",
+                    aOther.mViewportHeight, advanced, aList)
+  , mEchoCancellation(&T::mEchoCancellation, "echoCancellation",
+                      aOther.mEchoCancellation, advanced, aList)
+  , mMozNoiseSuppression(&T::mMozNoiseSuppression, "mozNoiseSuppression",
+                         aOther.mMozNoiseSuppression,
+                         advanced, aList)
+  , mMozAutoGainControl(&T::mMozAutoGainControl, "mozAutoGainControl",
+                        aOther.mMozAutoGainControl, advanced, aList) {}
 };
 
+template<> bool NormalizedConstraintSet::Range<bool>::Merge(const Range& aOther);
+template<> void NormalizedConstraintSet::Range<bool>::FinalizeMerge();
+
+// Used instead of MediaTrackConstraints in lower-level code.
+struct NormalizedConstraints : public NormalizedConstraintSet
+{
+  explicit NormalizedConstraints(const dom::MediaTrackConstraints& aOther,
+                        nsTArray<MemberPtrType>* aList = nullptr);
+
+  // Merge constructor
+  explicit NormalizedConstraints(
+      const nsTArray<const NormalizedConstraints*>& aOthers);
+
+  std::vector<NormalizedConstraintSet> mAdvanced;
+  const char* mBadConstraint;
+};
+
+// Flattened version is used in low-level code with orthogonal constraints only.
 struct FlattenedConstraints : public NormalizedConstraintSet
 {
-  explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther);
+  explicit FlattenedConstraints(const NormalizedConstraints& aOther);
+
+  explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther)
+    : FlattenedConstraints(NormalizedConstraints(aOther)) {}
 };
 
 // A helper class for MediaEngines
 
 class MediaConstraintsHelper
 {
 protected:
-  template<class ValueType, class ConstrainRange>
-  static uint32_t FitnessDistance(ValueType aN, const ConstrainRange& aRange);
-  static uint32_t FitnessDistance(int32_t aN,
-      const dom::OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced);
-  static uint32_t FitnessDistance(double aN,
-      const dom::OwningDoubleOrConstrainDoubleRange& aConstraint, bool aAdvanced);
+  template<class ValueType, class NormalizedRange>
+  static uint32_t FitnessDistance(ValueType aN, const NormalizedRange& aRange);
   static uint32_t FitnessDistance(nsString aN,
-    const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint,
-    bool aAdvanced);
-  static uint32_t FitnessDistance(nsString aN,
-      const dom::ConstrainDOMStringParameters& aParams);
+      const NormalizedConstraintSet::StringRange& aConstraint);
 
   static uint32_t
-  GetMinimumFitnessDistance(const dom::MediaTrackConstraintSet &aConstraints,
-                            bool aAdvanced,
+  GetMinimumFitnessDistance(const NormalizedConstraintSet &aConstraints,
                             const nsString& aDeviceId);
 
   template<class DeviceType>
   static bool
-  SomeSettingsFit(const dom::MediaTrackConstraints &aConstraints,
-                  nsTArray<RefPtr<DeviceType>>& aSources)
+  SomeSettingsFit(const NormalizedConstraints &aConstraints,
+                  nsTArray<RefPtr<DeviceType>>& aDevices)
   {
-    nsTArray<const dom::MediaTrackConstraintSet*> aggregateConstraints;
-    aggregateConstraints.AppendElement(&aConstraints);
+    nsTArray<const NormalizedConstraintSet*> sets;
+    sets.AppendElement(&aConstraints);
 
-    MOZ_ASSERT(aSources.Length());
-    for (auto& source : aSources) {
-      if (source->GetBestFitnessDistance(aggregateConstraints) != UINT32_MAX) {
+    MOZ_ASSERT(aDevices.Length());
+    for (auto& device : aDevices) {
+      if (device->GetBestFitnessDistance(sets) != UINT32_MAX) {
         return true;
       }
     }
     return false;
   }
 
 public:
-  // Apply constrains to a supplied list of sources (removes items from the list)
+  // Apply constrains to a supplied list of devices (removes items from the list)
 
   template<class DeviceType>
   static const char*
-  SelectSettings(const dom::MediaTrackConstraints &aConstraints,
-                 nsTArray<RefPtr<DeviceType>>& aSources)
+  SelectSettings(const NormalizedConstraints &aConstraints,
+                 nsTArray<RefPtr<DeviceType>>& aDevices)
   {
     auto& c = aConstraints;
 
     // First apply top-level constraints.
 
     // Stack constraintSets that pass, starting with the required one, because the
     // whole stack must be re-satisfied each time a capability-set is ruled out
     // (this avoids storing state or pushing algorithm into the lower-level code).
     nsTArray<RefPtr<DeviceType>> unsatisfactory;
-    nsTArray<const dom::MediaTrackConstraintSet*> aggregateConstraints;
+    nsTArray<const NormalizedConstraintSet*> aggregateConstraints;
     aggregateConstraints.AppendElement(&c);
 
     std::multimap<uint32_t, RefPtr<DeviceType>> ordered;
 
-    for (uint32_t i = 0; i < aSources.Length();) {
-      uint32_t distance = aSources[i]->GetBestFitnessDistance(aggregateConstraints);
+    for (uint32_t i = 0; i < aDevices.Length();) {
+      uint32_t distance = aDevices[i]->GetBestFitnessDistance(aggregateConstraints);
       if (distance == UINT32_MAX) {
-        unsatisfactory.AppendElement(aSources[i]);
-        aSources.RemoveElementAt(i);
+        unsatisfactory.AppendElement(aDevices[i]);
+        aDevices.RemoveElementAt(i);
       } else {
         ordered.insert(std::pair<uint32_t, RefPtr<DeviceType>>(distance,
-                                                                 aSources[i]));
+                                                               aDevices[i]));
         ++i;
       }
     }
-    if (!aSources.Length()) {
-      // None selected. The spec says to report a constraint that satisfies NONE
-      // of the sources. Unfortunately, this is a bit laborious to find out, and
-      // requires updating as new constraints are added!
-
-      if (!unsatisfactory.Length() ||
-          !SomeSettingsFit(dom::MediaTrackConstraints(), unsatisfactory)) {
-        return "";
-      }
-      if (c.mDeviceId.IsConstrainDOMStringParameters()) {
-        dom::MediaTrackConstraints fresh;
-        fresh.mDeviceId = c.mDeviceId;
-        if (!SomeSettingsFit(fresh, unsatisfactory)) {
-          return "deviceId";
-        }
-      }
-      if (c.mWidth.IsConstrainLongRange()) {
-        dom::MediaTrackConstraints fresh;
-        fresh.mWidth = c.mWidth;
-        if (!SomeSettingsFit(fresh, unsatisfactory)) {
-          return "width";
-        }
-      }
-      if (c.mHeight.IsConstrainLongRange()) {
-        dom::MediaTrackConstraints fresh;
-        fresh.mHeight = c.mHeight;
-        if (!SomeSettingsFit(fresh, unsatisfactory)) {
-          return "height";
-        }
-      }
-      if (c.mFrameRate.IsConstrainDoubleRange()) {
-        dom::MediaTrackConstraints fresh;
-        fresh.mFrameRate = c.mFrameRate;
-        if (!SomeSettingsFit(fresh, unsatisfactory)) {
-          return "frameRate";
-        }
-      }
-      if (c.mFacingMode.IsConstrainDOMStringParameters()) {
-        dom::MediaTrackConstraints fresh;
-        fresh.mFacingMode = c.mFacingMode;
-        if (!SomeSettingsFit(fresh, unsatisfactory)) {
-          return "facingMode";
-        }
-      }
-      return "";
+    if (!aDevices.Length()) {
+      return FindBadConstraint(c, unsatisfactory);
     }
 
     // Order devices by shortest distance
     for (auto& ordinal : ordered) {
-      aSources.RemoveElement(ordinal.second);
-      aSources.AppendElement(ordinal.second);
+      aDevices.RemoveElement(ordinal.second);
+      aDevices.AppendElement(ordinal.second);
     }
 
     // Then apply advanced constraints.
 
-    if (c.mAdvanced.WasPassed()) {
-      auto &array = c.mAdvanced.Value();
-
-      for (int i = 0; i < int(array.Length()); i++) {
-        aggregateConstraints.AppendElement(&array[i]);
-        nsTArray<RefPtr<DeviceType>> rejects;
-        for (uint32_t j = 0; j < aSources.Length();) {
-          if (aSources[j]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) {
-            rejects.AppendElement(aSources[j]);
-            aSources.RemoveElementAt(j);
-          } else {
-            ++j;
-          }
+    for (int i = 0; i < int(c.mAdvanced.size()); i++) {
+      aggregateConstraints.AppendElement(&c.mAdvanced[i]);
+      nsTArray<RefPtr<DeviceType>> rejects;
+      for (uint32_t j = 0; j < aDevices.Length();) {
+        if (aDevices[j]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) {
+          rejects.AppendElement(aDevices[j]);
+          aDevices.RemoveElementAt(j);
+        } else {
+          ++j;
         }
-        if (!aSources.Length()) {
-          aSources.AppendElements(Move(rejects));
-          aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1);
-        }
+      }
+      if (!aDevices.Length()) {
+        aDevices.AppendElements(Move(rejects));
+        aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1);
       }
     }
     return nullptr;
   }
+
+  template<class DeviceType>
+  static const char*
+  FindBadConstraint(const NormalizedConstraints& aConstraints,
+                    nsTArray<RefPtr<DeviceType>>& aDevices)
+  {
+    // The spec says to report a constraint that satisfies NONE
+    // of the sources. Unfortunately, this is a bit laborious to find out, and
+    // requires updating as new constraints are added!
+    auto& c = aConstraints;
+    dom::MediaTrackConstraints empty;
+
+    if (!aDevices.Length() ||
+        !SomeSettingsFit(NormalizedConstraints(empty), aDevices)) {
+      return "";
+    }
+    {
+      NormalizedConstraints fresh(empty);
+      fresh.mDeviceId = c.mDeviceId;
+      if (!SomeSettingsFit(fresh, aDevices)) {
+        return "deviceId";
+      }
+    }
+    {
+      NormalizedConstraints fresh(empty);
+      fresh.mWidth = c.mWidth;
+      if (!SomeSettingsFit(fresh, aDevices)) {
+        return "width";
+      }
+    }
+    {
+      NormalizedConstraints fresh(empty);
+      fresh.mHeight = c.mHeight;
+      if (!SomeSettingsFit(fresh, aDevices)) {
+        return "height";
+      }
+    }
+    {
+      NormalizedConstraints fresh(empty);
+      fresh.mFrameRate = c.mFrameRate;
+      if (!SomeSettingsFit(fresh, aDevices)) {
+        return "frameRate";
+      }
+    }
+    {
+      NormalizedConstraints fresh(empty);
+      fresh.mFacingMode = c.mFacingMode;
+      if (!SomeSettingsFit(fresh, aDevices)) {
+        return "facingMode";
+      }
+    }
+    return "";
+  }
+
+  template<class MediaEngineSourceType>
+  static const char*
+  FindBadConstraint(const NormalizedConstraints& aConstraints,
+                    const MediaEngineSourceType& aMediaEngineSource,
+                    const nsString& aDeviceId);
 };
 
 } // namespace mozilla
 
 #endif /* MEDIATRACKCONSTRAINTS_H_ */
--- a/dom/webidl/MediaStreamTrack.webidl
+++ b/dom/webidl/MediaStreamTrack.webidl
@@ -79,15 +79,15 @@ interface MediaStreamTrack : EventTarget
 //              attribute EventHandler          onunmute;
 //  readonly    attribute boolean               _readonly;
 //  readonly    attribute boolean               remote;
     readonly    attribute MediaStreamTrackState readyState;
                 attribute EventHandler          onended;
     MediaStreamTrack       clone ();
     void                   stop ();
 //  MediaTrackCapabilities getCapabilities ();
-//  MediaTrackConstraints  getConstraints ();
-//  MediaTrackSettings     getSettings ();
+    MediaTrackConstraints  getConstraints ();
+    MediaTrackSettings     getSettings ();
 
     [Throws]
     Promise<void>          applyConstraints (optional MediaTrackConstraints constraints);
 //              attribute EventHandler          onoverconstrained;
 };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MediaTrackSettings.webidl
@@ -0,0 +1,33 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * http://w3c.github.io/mediacapture-main/getusermedia.html
+ */
+
+dictionary MediaTrackSettings {
+    long      width;
+    long      height;
+    double    frameRate;
+    DOMString facingMode;
+    DOMString deviceId;
+
+    // Mozilla-specific extensions:
+
+    // http://fluffy.github.io/w3c-screen-share/#screen-based-video-constraints
+    // OBE by http://w3c.github.io/mediacapture-screen-share
+
+    DOMString mediaSource;
+
+    // Experimental https://bugzilla.mozilla.org/show_bug.cgi?id=1131568#c3
+    //              https://bugzilla.mozilla.org/show_bug.cgi?id=1193075
+
+    long long browserWindow;
+    boolean scrollWithPage;
+    long viewportOffsetX;
+    long viewportOffsetY;
+    long viewportWidth;
+    long viewportHeight;
+};
\ No newline at end of file
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -301,16 +301,17 @@ WEBIDL_FILES = [
     'MediaRecorder.webidl',
     'MediaSource.webidl',
     'MediaStream.webidl',
     'MediaStreamAudioDestinationNode.webidl',
     'MediaStreamAudioSourceNode.webidl',
     'MediaStreamError.webidl',
     'MediaStreamTrack.webidl',
     'MediaTrackConstraintSet.webidl',
+    'MediaTrackSettings.webidl',
     'MediaTrackSupportedConstraints.webidl',
     'MenuBoxObject.webidl',
     'MessageChannel.webidl',
     'MessageEvent.webidl',
     'MessagePort.webidl',
     'MessagePortList.webidl',
     'MimeType.webidl',
     'MimeTypeArray.webidl',
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -1427,17 +1427,17 @@ nsXULElement::WalkContentStyleRules(nsRu
 {
     return NS_OK;
 }
 
 nsChangeHint
 nsXULElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
                                      int32_t aModType) const
 {
-    nsChangeHint retval(NS_STYLE_HINT_NONE);
+    nsChangeHint retval(nsChangeHint(0));
 
     if (aAttribute == nsGkAtoms::value &&
         (aModType == nsIDOMMutationEvent::REMOVAL ||
          aModType == nsIDOMMutationEvent::ADDITION)) {
       if (IsAnyOfXULElements(nsGkAtoms::label, nsGkAtoms::description))
         // Label and description dynamically morph between a normal
         // block and a cropping single-line XUL text frame.  If the
         // value attribute is being added or removed, then we need to
--- a/gfx/gl/GLLibraryEGL.cpp
+++ b/gfx/gl/GLLibraryEGL.cpp
@@ -391,45 +391,41 @@ GLLibraryEGL::EnsureInitialized(bool for
     // Check the ANGLE support the system has
     nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
     mIsANGLE = IsExtensionSupported(ANGLE_platform_angle);
 
     EGLDisplay chosenDisplay = nullptr;
 
     if (IsExtensionSupported(ANGLE_platform_angle_d3d)) {
         bool accelAngleSupport = IsAccelAngleSupported(gfxInfo, out_failureId);
+        bool shouldTryAccel = forceAccel || accelAngleSupport;
+        bool shouldTryWARP = !forceAccel; // Only if ANGLE not supported or fails
 
-        bool shouldTryAccel = forceAccel || accelAngleSupport;
-        bool shouldTryWARP = !shouldTryAccel;
+        // If WARP preferred, will override ANGLE support
         if (gfxPrefs::WebGLANGLEForceWARP()) {
             shouldTryWARP = true;
             shouldTryAccel = false;
         }
 
-        // Fallback to a WARP display if non-WARP is blacklisted, or if WARP is forced.
-        if (shouldTryWARP) {
-            chosenDisplay = GetAndInitWARPDisplay(*this, EGL_DEFAULT_DISPLAY);
-            if (chosenDisplay) {
-                mIsWARP = true;
-            }
+        // Hardware accelerated ANGLE path (supported or force accel)
+        if (shouldTryAccel) {
+            chosenDisplay = GetAndInitDisplayForAccelANGLE(*this);
         }
 
-        if (!chosenDisplay) {
-            // If falling back to WARP did not work and we don't want to try
-            // using HW accelerated ANGLE, then fail.
-            if (!shouldTryAccel) {
+        // Fallback to a WARP display if ANGLE fails, or if WARP is forced
+        if (!chosenDisplay && shouldTryWARP) {
+            chosenDisplay = GetAndInitWARPDisplay(*this, EGL_DEFAULT_DISPLAY);
+            if (!chosenDisplay) {
                 if (out_failureId->IsEmpty()) {
                     *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WARP_FALLBACK");
                 }
-                NS_ERROR("Fallback WARP ANGLE context failed to initialize.");
+                NS_ERROR("Fallback WARP context failed to initialize.");
                 return false;
             }
-
-            // Hardware accelerated ANGLE path
-            chosenDisplay = GetAndInitDisplayForAccelANGLE(*this);
+            mIsWARP = true;
         }
     } else {
         chosenDisplay = GetAndInitDisplay(*this, EGL_DEFAULT_DISPLAY);
     }
 
     if (!chosenDisplay) {
         if (out_failureId->IsEmpty()) {
             *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_NO_DISPLAY");
--- a/gfx/layers/AtomicRefCountedWithFinalize.h
+++ b/gfx/layers/AtomicRefCountedWithFinalize.h
@@ -36,16 +36,18 @@ protected:
     explicit AtomicRefCountedWithFinalize(const char* aName)
       : mRecycleCallback(nullptr)
       , mRefCount(0)
       , mMessageLoopToPostDestructionTo(nullptr)
 #ifdef DEBUG
       , mSpew(false)
       , mManualAddRefs(0)
       , mManualReleases(0)
+#endif
+#ifdef NS_BUILD_REFCNT_LOGGING
       , mName(aName)
 #endif
     {}
 
     ~AtomicRefCountedWithFinalize() {
       if (mRefCount >= 0) {
         gfxCriticalError() << "Deleting referenced object? " << mRefCount;
       }
@@ -105,18 +107,18 @@ public:
       (void)lineNum;
 #endif
       Release();
     }
 
 private:
     void AddRef() {
       MOZ_ASSERT(mRefCount >= 0, "AddRef() during/after Finalize()/dtor.");
-      DebugOnly<int> count = ++mRefCount;
-      NS_LOG_ADDREF(this, count, mName, sizeof(*this));
+      mRefCount++;
+      NS_LOG_ADDREF(this, mRefCount, mName, sizeof(*this));
     }
 
     void Release() {
       MOZ_ASSERT(mRefCount > 0, "Release() during/after Finalize()/dtor.");
       // Read mRecycleCallback early so that it does not get set to
       // deleted memory, if the object is goes away.  See bug 994903.
       // This saves us in the case where there is no callback, so that
       // we can do the "else if" below.
@@ -199,15 +201,17 @@ private:
     Atomic<int> mRefCount;
     MessageLoop *mMessageLoopToPostDestructionTo;
 #ifdef DEBUG
 public:
     bool mSpew;
 private:
     Atomic<uint32_t> mManualAddRefs;
     Atomic<uint32_t> mManualReleases;
+#endif
+#ifdef NS_BUILD_REFCNT_LOGGING
     const char* mName;
 #endif
 };
 
 } // namespace mozilla
 
 #endif
--- a/ipc/moz.build
+++ b/ipc/moz.build
@@ -24,9 +24,12 @@ if CONFIG['MOZ_B2G_RIL'] or CONFIG['MOZ_
     DIRS += ['unixfd', 'unixsocket']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     DIRS += ['hal', 'keystore', 'netd']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
     DIRS += ['contentproc']
 
+if CONFIG['OS_ARCH'] == 'WINNT':
+    DIRS += ['mscom']
+
 DIRS += ['app']
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/COMApartmentRegion.h
@@ -0,0 +1,62 @@
+/* -*- 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_mscom_COMApartmentRegion_h
+#define mozilla_mscom_COMApartmentRegion_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+
+#include <objbase.h>
+
+namespace mozilla {
+namespace mscom {
+
+template<COINIT T>
+class MOZ_RAII COMApartmentRegion
+{
+public:
+  COMApartmentRegion()
+    : mInitResult(::CoInitializeEx(nullptr, T))
+  {
+    // If this fires then we're probably mixing apartments on the same thread
+    MOZ_ASSERT(IsValid());
+  }
+
+  ~COMApartmentRegion()
+  {
+    if (IsValid()) {
+      ::CoUninitialize();
+    }
+  }
+
+  bool IsValidOutermost() const
+  {
+    return mInitResult == S_OK;
+  }
+
+  bool IsValid() const
+  {
+    return SUCCEEDED(mInitResult);
+  }
+
+private:
+  COMApartmentRegion(const COMApartmentRegion&) = delete;
+  COMApartmentRegion& operator=(const COMApartmentRegion&) = delete;
+  COMApartmentRegion(COMApartmentRegion&&) = delete;
+  COMApartmentRegion& operator=(COMApartmentRegion&&) = delete;
+
+  HRESULT mInitResult;
+};
+
+typedef COMApartmentRegion<COINIT_APARTMENTTHREADED> STARegion;
+typedef COMApartmentRegion<COINIT_MULTITHREADED> MTARegion;
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_COMApartmentRegion_h
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/DynamicallyLinkedFunctionPtr.h
@@ -0,0 +1,68 @@
+/* -*- 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_mscom_DynamicallyLinkedFunctionPtr_h
+#define mozilla_mscom_DynamicallyLinkedFunctionPtr_h
+
+#include "mozilla/Move.h"
+#include <windows.h>
+
+namespace mozilla {
+namespace mscom {
+
+template <typename T>
+class DynamicallyLinkedFunctionPtr;
+
+template <typename R, typename... Args>
+class DynamicallyLinkedFunctionPtr<R (__stdcall*)(Args...)>
+{
+  typedef R (__stdcall* FunctionPtrT)(Args...);
+
+public:
+  DynamicallyLinkedFunctionPtr(const wchar_t* aLibName, const char* aFuncName)
+    : mModule(NULL)
+    , mFunction(nullptr)
+  {
+    mModule = ::LoadLibraryW(aLibName);
+    if (mModule) {
+      mFunction = reinterpret_cast<FunctionPtrT>(
+                    ::GetProcAddress(mModule, aFuncName));
+    }
+  }
+
+  DynamicallyLinkedFunctionPtr(const DynamicallyLinkedFunctionPtr&) = delete;
+  DynamicallyLinkedFunctionPtr& operator=(const DynamicallyLinkedFunctionPtr&) = delete;
+
+  DynamicallyLinkedFunctionPtr(DynamicallyLinkedFunctionPtr&&) = delete;
+  DynamicallyLinkedFunctionPtr& operator=(DynamicallyLinkedFunctionPtr&&) = delete;
+
+  ~DynamicallyLinkedFunctionPtr()
+  {
+    if (mModule) {
+      ::FreeLibrary(mModule);
+    }
+  }
+
+  R operator()(Args... args)
+  {
+    return mFunction(mozilla::Forward<Args>(args)...);
+  }
+
+  explicit operator bool() const
+  {
+    return !!mFunction;
+  }
+
+private:
+  HMODULE       mModule;
+  FunctionPtrT  mFunction;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_DynamicallyLinkedFunctionPtr_h
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/Utils.cpp
@@ -0,0 +1,34 @@
+/* -*- 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/mscom/Utils.h"
+#include "mozilla/RefPtr.h"
+
+#include <objidl.h>
+
+namespace mozilla {
+namespace mscom {
+
+bool
+IsProxy(IUnknown* aUnknown)
+{
+  if (!aUnknown) {
+    return false;
+  }
+
+  // Only proxies implement this interface, so if it is present then we must
+  // be dealing with a proxied object.
+  RefPtr<IClientSecurity> clientSecurity;
+  HRESULT hr = aUnknown->QueryInterface(IID_IClientSecurity,
+                                        (void**)getter_AddRefs(clientSecurity));
+  if (SUCCEEDED(hr) || hr == RPC_E_WRONG_THREAD) {
+    return true;
+  }
+  return false;
+}
+
+} // namespace mscom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/Utils.h
@@ -0,0 +1,21 @@
+/* -*- 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_mscom_Utils_h
+#define mozilla_mscom_Utils_h
+
+struct IUnknown;
+
+namespace mozilla {
+namespace mscom {
+
+bool IsProxy(IUnknown* aUnknown);
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_Utils_h
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.mscom += [
+    'COMApartmentRegion.h',
+    'Utils.h',
+]
+
+UNIFIED_SOURCES += [
+    'Utils.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -1203,17 +1203,17 @@ RestyleManager::AttributeWillChange(Elem
   nsRestyleHint rshint =
     StyleSet()->HasAttributeDependentStyle(aElement,
                                            aNameSpaceID,
                                            aAttribute,
                                            aModType,
                                            false,
                                            aNewValue,
                                            rsdata);
-  PostRestyleEvent(aElement, rshint, NS_STYLE_HINT_NONE, &rsdata);
+  PostRestyleEvent(aElement, rshint, nsChangeHint(0), &rsdata);
 }
 
 // Forwarded nsIMutationObserver method, to handle restyling (and
 // passing the notification to the frame).
 void
 RestyleManager::AttributeChanged(Element* aElement,
                                  int32_t aNameSpaceID,
                                  nsIAtom* aAttribute,
@@ -1317,17 +1317,17 @@ RestyleManager::RestyleForEmptyChange(El
   // In some cases (:empty + E, :empty ~ E), a change in the content of
   // an element requires restyling its parent's siblings.
   nsRestyleHint hint = eRestyle_Subtree;
   nsIContent* grandparent = aContainer->GetParent();
   if (grandparent &&
       (grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) {
     hint = nsRestyleHint(hint | eRestyle_LaterSiblings);
   }
-  PostRestyleEvent(aContainer, hint, NS_STYLE_HINT_NONE);
+  PostRestyleEvent(aContainer, hint, nsChangeHint(0));
 }
 
 void
 RestyleManager::RestyleForAppend(Element* aContainer,
                                  nsIContent* aFirstNewContent)
 {
   NS_ASSERTION(aContainer, "must have container for append");
 #ifdef DEBUG
@@ -1361,28 +1361,28 @@ RestyleManager::RestyleForAppend(Element
     }
     if (wasEmpty) {
       RestyleForEmptyChange(aContainer);
       return;
     }
   }
 
   if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
-    PostRestyleEvent(aContainer, eRestyle_Subtree, NS_STYLE_HINT_NONE);
+    PostRestyleEvent(aContainer, eRestyle_Subtree, nsChangeHint(0));
     // Restyling the container is the most we can do here, so we're done.
     return;
   }
 
   if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
     // restyle the last element child before this node
     for (nsIContent* cur = aFirstNewContent->GetPreviousSibling();
          cur;
          cur = cur->GetPreviousSibling()) {
       if (cur->IsElement()) {
-        PostRestyleEvent(cur->AsElement(), eRestyle_Subtree, NS_STYLE_HINT_NONE);
+        PostRestyleEvent(cur->AsElement(), eRestyle_Subtree, nsChangeHint(0));
         break;
       }
     }
   }
 }
 
 // Needed since we can't use PostRestyleEvent on non-elements (with
 // eRestyle_LaterSiblings or nsRestyleHint(eRestyle_Subtree |
@@ -1392,17 +1392,17 @@ RestyleSiblingsStartingWith(RestyleManag
                             nsIContent* aStartingSibling /* may be null */)
 {
   for (nsIContent* sibling = aStartingSibling; sibling;
        sibling = sibling->GetNextSibling()) {
     if (sibling->IsElement()) {
       aRestyleManager->
         PostRestyleEvent(sibling->AsElement(),
                          nsRestyleHint(eRestyle_Subtree | eRestyle_LaterSiblings),
-                         NS_STYLE_HINT_NONE);
+                         nsChangeHint(0));
       break;
     }
   }
 }
 
 // Restyling for a ContentInserted or CharacterDataChanged notification.
 // This could be used for ContentRemoved as well if we got the
 // notification before the removal happened (and sometimes
@@ -1439,17 +1439,17 @@ RestyleManager::RestyleForInsertOrChange
     }
     if (wasEmpty) {
       RestyleForEmptyChange(aContainer);
       return;
     }
   }
 
   if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
-    PostRestyleEvent(aContainer, eRestyle_Subtree, NS_STYLE_HINT_NONE);
+    PostRestyleEvent(aContainer, eRestyle_Subtree, nsChangeHint(0));
     // Restyling the container is the most we can do here, so we're done.
     return;
   }
 
   if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
     // Restyle all later siblings.
     RestyleSiblingsStartingWith(this, aChild->GetNextSibling());
   }
@@ -1462,34 +1462,34 @@ RestyleManager::RestyleForInsertOrChange
          content = content->GetNextSibling()) {
       if (content == aChild) {
         passedChild = true;
         continue;
       }
       if (content->IsElement()) {
         if (passedChild) {
           PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
-                           NS_STYLE_HINT_NONE);
+                           nsChangeHint(0));
         }
         break;
       }
     }
     // restyle the previously-last element child if it is before this node
     passedChild = false;
     for (nsIContent* content = aContainer->GetLastChild();
          content;
          content = content->GetPreviousSibling()) {
       if (content == aChild) {
         passedChild = true;
         continue;
       }
       if (content->IsElement()) {
         if (passedChild) {
           PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
-                           NS_STYLE_HINT_NONE);
+                           nsChangeHint(0));
         }
         break;
       }
     }
   }
 }
 
 void
@@ -1526,17 +1526,17 @@ RestyleManager::RestyleForRemove(Element
     }
     if (isEmpty) {
       RestyleForEmptyChange(aContainer);
       return;
     }
   }
 
   if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
-    PostRestyleEvent(aContainer, eRestyle_Subtree, NS_STYLE_HINT_NONE);
+    PostRestyleEvent(aContainer, eRestyle_Subtree, nsChangeHint(0));
     // Restyling the container is the most we can do here, so we're done.
     return;
   }
 
   if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
     // Restyle all later siblings.
     RestyleSiblingsStartingWith(this, aFollowingSibling);
   }
@@ -1549,29 +1549,29 @@ RestyleManager::RestyleForRemove(Element
          content = content->GetNextSibling()) {
       if (content == aFollowingSibling) {
         reachedFollowingSibling = true;
         // do NOT continue here; we might want to restyle this node
       }
       if (content->IsElement()) {
         if (reachedFollowingSibling) {
           PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
-                           NS_STYLE_HINT_NONE);
+                           nsChangeHint(0));
         }
         break;
       }
     }
     // restyle the now-last element child if it was before aOldChild
     reachedFollowingSibling = (aFollowingSibling == nullptr);
     for (nsIContent* content = aContainer->GetLastChild();
          content;
          content = content->GetPreviousSibling()) {
       if (content->IsElement()) {
         if (reachedFollowingSibling) {
-          PostRestyleEvent(content->AsElement(), eRestyle_Subtree, NS_STYLE_HINT_NONE);
+          PostRestyleEvent(content->AsElement(), eRestyle_Subtree, nsChangeHint(0));
         }
         break;
       }
       if (content == aFollowingSibling) {
         reachedFollowingSibling = true;
       }
     }
   }
@@ -3840,17 +3840,17 @@ ElementRestyler::RestyleSelf(nsIFrame* a
   if (!mIsRootOfRestyle) {
     canStopWithStyleChange = false;
   }
 
   // Look at the frame and its current style context for conditions
   // that would change our RestyleResult.
   ComputeRestyleResultFromFrame(aSelf, result, canStopWithStyleChange);
 
-  nsChangeHint assumeDifferenceHint = NS_STYLE_HINT_NONE;
+  nsChangeHint assumeDifferenceHint = nsChangeHint(0);
   RefPtr<nsStyleContext> oldContext = aSelf->StyleContext();
   nsStyleSet* styleSet = StyleSet();
 
 #ifdef ACCESSIBILITY
   mWasFrameVisible = nsIPresShell::IsAccessibilityActive() ?
     oldContext->StyleVisibility()->IsVisible() : false;
 #endif
 
@@ -5146,17 +5146,17 @@ RestyleManager::ChangeHintToString(nsCha
   }
   if (rest) {
     if (any) {
       result.AppendLiteral(" | ");
     }
     result.AppendPrintf("0x%0x", rest);
   } else {
     if (!any) {
-      result.AppendLiteral("NS_STYLE_HINT_NONE");
+      result.AppendLiteral("nsChangeHint(0)");
     }
   }
   return result;
 }
 
 /* static */ nsCString
 RestyleManager::StructNamesToString(uint32_t aSIDs)
 {
--- a/layout/base/RestyleManagerBase.cpp
+++ b/layout/base/RestyleManagerBase.cpp
@@ -32,17 +32,17 @@ RestyleManagerBase::ContentStateChangedI
                                                 nsRestyleHint* aOutRestyleHint)
 {
   MOZ_ASSERT(aOutChangeHint);
   MOZ_ASSERT(aOutRestyleHint);
 
   StyleSetHandle styleSet = PresContext()->StyleSet();
   NS_ASSERTION(styleSet, "couldn't get style set");
 
-  *aOutChangeHint = NS_STYLE_HINT_NONE;
+  *aOutChangeHint = nsChangeHint(0);
   // Any change to a content state that affects which frames we construct
   // must lead to a frame reconstruct here if we already have a frame.
   // Note that we never decide through non-CSS means to not create frames
   // based on content states, so if we already don't have a frame we don't
   // need to force a reframe -- if it's needed, the HasStateDependentStyle
   // call will handle things.
   nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
   CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo;
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -186,17 +186,17 @@ RestyleTracker::DoProcessRestyles()
                sibling;
                sibling = sibling->GetNextSibling()) {
             if (sibling->IsElement()) {
               LOG_RESTYLE("adding pending restyle for %s due to "
                           "eRestyle_LaterSiblings hint on %s",
                           FrameTagToString(sibling->AsElement()).get(),
                           FrameTagToString(element->AsElement()).get());
               if (AddPendingRestyle(sibling->AsElement(), eRestyle_Subtree,
-                                    NS_STYLE_HINT_NONE)) {
+                                    nsChangeHint(0))) {
                   // Nothing else to do here; we'll handle the following
                   // siblings when we get to |sibling| in laterSiblingArr.
                 break;
               }
             }
           }
         }
 
--- a/layout/base/RestyleTracker.h
+++ b/layout/base/RestyleTracker.h
@@ -294,17 +294,17 @@ public:
     nsRestyleHint mRestyleHint;        // What we want to restyle
     nsChangeHint mChangeHint;          // The minimal change hint for "self"
     RestyleHintData mRestyleHintData;  // Data associated with mRestyleHint
   };
 
   struct RestyleData : Hints {
     RestyleData() {
       mRestyleHint = nsRestyleHint(0);
-      mChangeHint = NS_STYLE_HINT_NONE;
+      mChangeHint = nsChangeHint(0);
     }
 
     RestyleData(nsRestyleHint aRestyleHint, nsChangeHint aChangeHint,
                 const RestyleHintData* aRestyleHintData) {
       mRestyleHint = aRestyleHint;
       mChangeHint = aChangeHint;
       if (aRestyleHintData) {
         mRestyleHintData = *aRestyleHintData;
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -135,26 +135,37 @@ ServoRestyleManager::RecreateStyleContex
     }
     aContent->UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
   }
 }
 
 void
 ServoRestyleManager::ProcessPendingRestyles()
 {
+  if (!HasPendingRestyles()) {
+    return;
+  }
   ServoStyleSet* styleSet = StyleSet();
 
   nsIDocument* doc = PresContext()->Document();
+
   Element* root = doc->GetRootElement();
-
   if (root) {
     styleSet->RestyleSubtree(root, /* aForce = */ false);
     RecreateStyleContexts(root, nullptr, styleSet);
   }
 
+  // NB: we restyle from the root element, but the document also gets the
+  // HAS_DIRTY_DESCENDANTS flag as part of the loop on PostRestyleEvent, and we
+  // use that to check we have pending restyles.
+  //
+  // Thus, they need to get cleared here.
+  MOZ_ASSERT(!doc->IsDirtyForServo());
+  doc->UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+
   IncrementRestyleGeneration();
 }
 
 void
 ServoRestyleManager::RestyleForInsertOrChange(Element* aContainer,
                                               nsIContent* aChild)
 {
   NS_ERROR("stylo: ServoRestyleManager::RestyleForInsertOrChange not implemented");
@@ -183,18 +194,17 @@ ServoRestyleManager::ContentStateChanged
     return NS_OK;
   }
 
   Element* aElement = aContent->AsElement();
   nsChangeHint changeHint;
   nsRestyleHint restyleHint;
   ContentStateChangedInternal(aElement, aStateMask, &changeHint, &restyleHint);
 
-  // TODO(emilio): Post a restyle here, and make it effective.
-  // PostRestyleEvent(aElement, restyleHint, changeHint);
+  PostRestyleEvent(aElement, restyleHint, changeHint);
   return NS_OK;
 }
 
 void
 ServoRestyleManager::AttributeWillChange(Element* aElement,
                                          int32_t aNameSpaceID,
                                          nsIAtom* aAttribute,
                                          int32_t aModType,
@@ -214,16 +224,9 @@ ServoRestyleManager::AttributeChanged(El
 }
 
 nsresult
 ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
 {
   MOZ_CRASH("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
 }
 
-bool
-ServoRestyleManager::HasPendingRestyles()
-{
-  NS_ERROR("stylo: ServoRestyleManager::HasPendingRestyles not implemented");
-  return false;
-}
-
 } // namespace mozilla
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -61,17 +61,24 @@ public:
                            int32_t aModType,
                            const nsAttrValue* aNewValue);
   void AttributeChanged(dom::Element* aElement,
                         int32_t aNameSpaceID,
                         nsIAtom* aAttribute,
                         int32_t aModType,
                         const nsAttrValue* aOldValue);
   nsresult ReparentStyleContext(nsIFrame* aFrame);
-  bool HasPendingRestyles();
+
+  bool HasPendingRestyles() {
+    if (MOZ_UNLIKELY(IsDisconnected())) {
+      return false;
+    }
+
+    return PresContext()->PresShell()->GetDocument()->HasDirtyDescendantsForServo();
+  }
 
 protected:
   ~ServoRestyleManager() {}
 
 private:
   /**
    * Traverses a tree of content that Servo has just restyled, recreating style
    * contexts for their frames with the new style data.
--- a/layout/base/nsChangeHint.h
+++ b/layout/base/nsChangeHint.h
@@ -151,17 +151,17 @@ enum nsChangeHint {
   /**
    * This will schedule an invalidating paint. This is useful if something
    * has changed which will be invalidated by DLBI.
    */
   nsChangeHint_SchedulePaint = 1 << 20,
 
   /**
    * A hint reflecting that style data changed with no change handling
-   * behavior.  We need to return this, rather than NS_STYLE_HINT_NONE,
+   * behavior.  We need to return this, rather than nsChangeHint(0),
    * so that certain optimizations that manipulate the style context tree are
    * correct.
    *
    * nsChangeHint_NeutralChange must be returned by CalcDifference on a given
    * style struct if the data in the style structs are meaningfully different
    * and if no other change hints are returned.  If any other change hints are
    * set, then nsChangeHint_NeutralChange need not also be included, but it is
    * safe to do so.  (An example of style structs having non-meaningfully
@@ -341,18 +341,16 @@ inline nsChangeHint NS_HintsNotHandledFo
   MOZ_ASSERT(NS_IsHintSubset(result,
                              nsChangeHint_Hints_NotHandledForDescendants),
              "something is inconsistent");
 
   return result;
 }
 
 // Redefine the old NS_STYLE_HINT constants in terms of the new hint structure
-#define NS_STYLE_HINT_NONE \
-  nsChangeHint(0)
 #define NS_STYLE_HINT_VISUAL \
   nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView | \
                nsChangeHint_SchedulePaint)
 #define nsChangeHint_AllReflowHints                     \
   nsChangeHint(nsChangeHint_NeedReflow |                \
                nsChangeHint_ReflowChangesSizeOrPosition|\
                nsChangeHint_ClearAncestorIntrinsics |   \
                nsChangeHint_ClearDescendantIntrinsics | \
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -2913,17 +2913,17 @@ nsIPresShell::PostRecreateFramesFor(Elem
 void
 nsIPresShell::RestyleForAnimation(Element* aElement, nsRestyleHint aHint)
 {
   // Now that we no longer have separate non-animation and animation
   // restyles, this method having a distinct identity is less important,
   // but it still seems useful to offer as a "more public" API and as a
   // chokepoint for these restyles to go through.
   mPresContext->RestyleManager()->PostRestyleEvent(aElement, aHint,
-                                                   NS_STYLE_HINT_NONE);
+                                                   nsChangeHint(0));
 }
 
 void
 nsIPresShell::SetForwardingContainer(const WeakPtr<nsDocShell> &aContainer)
 {
   mForwardingContainer = aContainer;
 }
 
@@ -4230,17 +4230,17 @@ PresShell::DocumentStatesChanged(nsIDocu
     return;
   }
 
   if (mDidInitialize &&
       styleSet->HasDocumentStateDependentStyle(mDocument->GetRootElement(),
                                                aStateMask)) {
     mPresContext->RestyleManager()->PostRestyleEvent(mDocument->GetRootElement(),
                                                      eRestyle_Subtree,
-                                                     NS_STYLE_HINT_NONE);
+                                                     nsChangeHint(0));
     VERIFY_STYLE_TREE;
   }
 
   if (aStateMask.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
     nsIFrame* root = mFrameConstructor->GetRootFrame();
     if (root) {
       root->SchedulePaint();
     }
@@ -4508,21 +4508,21 @@ nsIPresShell::RestyleForCSSRuleChanges()
   }
 
   RestyleManagerHandle restyleManager = mPresContext->RestyleManager();
   if (scopeRoots.IsEmpty()) {
     // If scopeRoots is empty, we know that mStylesHaveChanged was true at
     // the beginning of this function, and that we need to restyle the whole
     // document.
     restyleManager->PostRestyleEvent(root, eRestyle_Subtree,
-                                     NS_STYLE_HINT_NONE);
+                                     nsChangeHint(0));
   } else {
     for (Element* scopeRoot : scopeRoots) {
       restyleManager->PostRestyleEvent(scopeRoot, eRestyle_Subtree,
-                                       NS_STYLE_HINT_NONE);
+                                       nsChangeHint(0));
     }
   }
 }
 
 void
 PresShell::RecordStyleSheetChange(StyleSheetHandle aStyleSheet)
 {
   // too bad we can't check that the update is UPDATE_STYLE
--- a/layout/style/CSS.cpp
+++ b/layout/style/CSS.cpp
@@ -3,31 +3,33 @@
  * 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/. */
 
 /* DOM object holding utility CSS functions */
 
 #include "CSS.h"
 
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/ServoBindings.h"
 #include "nsCSSParser.h"
 #include "nsGlobalWindow.h"
 #include "nsIDocument.h"
 #include "nsIURI.h"
 #include "nsStyleUtil.h"
 #include "xpcpublic.h"
 
 namespace mozilla {
 namespace dom {
 
 struct SupportsParsingInfo
 {
   nsIURI* mDocURI;
   nsIURI* mBaseURI;
   nsIPrincipal* mPrincipal;
+  StyleBackendType mStyleBackendType;
 };
 
 static nsresult
 GetParsingInfo(const GlobalObject& aGlobal,
                SupportsParsingInfo& aInfo)
 {
   nsGlobalWindow* win = xpc::WindowOrNull(aGlobal.Get());
   if (!win) {
@@ -37,52 +39,67 @@ GetParsingInfo(const GlobalObject& aGlob
   nsCOMPtr<nsIDocument> doc = win->GetDoc();
   if (!doc) {
     return NS_ERROR_FAILURE;
   }
 
   aInfo.mDocURI = nsCOMPtr<nsIURI>(doc->GetDocumentURI()).get();
   aInfo.mBaseURI = nsCOMPtr<nsIURI>(doc->GetBaseURI()).get();
   aInfo.mPrincipal = win->GetPrincipal();
+  aInfo.mStyleBackendType = doc->GetStyleBackendType();
   return NS_OK;
 }
 
 /* static */ bool
 CSS::Supports(const GlobalObject& aGlobal,
               const nsAString& aProperty,
               const nsAString& aValue,
               ErrorResult& aRv)
 {
-  nsCSSParser parser;
   SupportsParsingInfo info;
 
   nsresult rv = GetParsingInfo(aGlobal, info);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return false;
   }
 
+  if (info.mStyleBackendType == StyleBackendType::Servo) {
+    NS_ConvertUTF16toUTF8 property(aProperty);
+    NS_ConvertUTF16toUTF8 value(aValue);
+
+    return Servo_CSSSupports(reinterpret_cast<const uint8_t*>(property.get()),
+                             property.Length(),
+                             reinterpret_cast<const uint8_t*>(value.get()),
+                             value.Length());
+  }
+
+  nsCSSParser parser;
   return parser.EvaluateSupportsDeclaration(aProperty, aValue, info.mDocURI,
                                             info.mBaseURI, info.mPrincipal);
 }
 
 /* static */ bool
 CSS::Supports(const GlobalObject& aGlobal,
               const nsAString& aCondition,
               ErrorResult& aRv)
 {
-  nsCSSParser parser;
   SupportsParsingInfo info;
 
   nsresult rv = GetParsingInfo(aGlobal, info);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return false;
   }
 
+  if (info.mStyleBackendType == StyleBackendType::Servo) {
+    MOZ_CRASH("stylo: CSS.supports() with arguments is not yet implemented");
+  }
+
+  nsCSSParser parser;
   return parser.EvaluateSupportsCondition(aCondition, info.mDocURI,
                                           info.mBaseURI, info.mPrincipal);
 }
 
 /* static */ void
 CSS::Escape(const GlobalObject& aGlobal,
             const nsAString& aIdent,
             nsAString& aReturn)
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -679,17 +679,17 @@ Servo_InitStyleSet()
 void
 Servo_DropStyleSet(RawServoStyleSet* set)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_DropStyleSet in a "
             "non-MOZ_STYLO build");
 }
 
 ServoDeclarationBlock*
-Servo_ParseStyleAttribute(const uint8_t* bytes, uint8_t length,
+Servo_ParseStyleAttribute(const uint8_t* bytes, uint32_t length,
                           nsHTMLCSSStyleSheet* cache)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_ParseStyleAttribute in a "
             "non-MOZ_STYLO build");
 }
 
 void
 Servo_DropDeclarationBlock(ServoDeclarationBlock* declarations)
@@ -714,16 +714,24 @@ Servo_SetDeclarationBlockImmutable(Servo
 
 void
 Servo_ClearDeclarationBlockCachePointer(ServoDeclarationBlock* declarations)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_ClearDeclarationBlockCachePointer in a "
             "non-MOZ_STYLO build");
 }
 
+bool
+Servo_CSSSupports(const uint8_t* name, uint32_t name_length,
+                  const uint8_t* value, uint32_t value_length)
+{
+  MOZ_CRASH("stylo: shouldn't be calling Servo_CSSSupports in a "
+            "non-MOZ_STYLO build");
+}
+
 ServoComputedValues*
 Servo_GetComputedValues(RawGeckoNode* node)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_GetComputedValues in a "
             "non-MOZ_STYLO build");
 }
 
 ServoComputedValues*
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -212,24 +212,28 @@ void Servo_InsertStyleSheetBefore(RawSer
                                   RawServoStyleSheet* reference,
                                   RawServoStyleSet* set);
 bool Servo_StyleSheetHasRules(RawServoStyleSheet* sheet);
 RawServoStyleSet* Servo_InitStyleSet();
 void Servo_DropStyleSet(RawServoStyleSet* set);
 
 // Style attributes.
 ServoDeclarationBlock* Servo_ParseStyleAttribute(const uint8_t* bytes,
-                                                 uint8_t length,
+                                                 uint32_t length,
                                                  nsHTMLCSSStyleSheet* cache);
 void Servo_DropDeclarationBlock(ServoDeclarationBlock* declarations);
 nsHTMLCSSStyleSheet* Servo_GetDeclarationBlockCache(
     ServoDeclarationBlock* declarations);
 void Servo_SetDeclarationBlockImmutable(ServoDeclarationBlock* declarations);
 void Servo_ClearDeclarationBlockCachePointer(ServoDeclarationBlock* declarations);
 
+// CSS supports().
+bool Servo_CSSSupports(const uint8_t* name, uint32_t name_length,
+                       const uint8_t* value, uint32_t value_length);
+
 // Computed style data.
 ServoComputedValues* Servo_GetComputedValues(RawGeckoNode* node);
 ServoComputedValues* Servo_GetComputedValuesForAnonymousBox(ServoComputedValues* parentStyleOrNull,
                                                             nsIAtom* pseudoTag,
                                                             RawServoStyleSet* set);
 ServoComputedValues* Servo_GetComputedValuesForPseudoElement(ServoComputedValues* parent_style,
                                                              RawGeckoElement* match_element,
                                                              nsIAtom* pseudo_tag,
--- a/layout/style/nsHTMLStyleSheet.cpp
+++ b/layout/style/nsHTMLStyleSheet.cpp
@@ -461,17 +461,17 @@ nsHTMLStyleSheet::ImplLinkColorSetter(Re
 
   aRule->mColor = aColor;
   // Now make sure we restyle any links that might need it.  This
   // shouldn't happen often, so just rebuilding everything is ok.
   if (mDocument && mDocument->GetShell()) {
     Element* root = mDocument->GetRootElement();
     if (root) {
       mDocument->GetShell()->GetPresContext()->RestyleManager()->
-        PostRestyleEvent(root, eRestyle_Subtree, NS_STYLE_HINT_NONE);
+        PostRestyleEvent(root, eRestyle_Subtree, nsChangeHint(0));
     }
   }
   return NS_OK;
 }
 
 nsresult
 nsHTMLStyleSheet::SetLinkColor(nscolor aColor)
 {
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -904,17 +904,17 @@ nsStyleContext::CalcStyleDifference(nsSt
                              nsChangeHint_Hints_NotHandledForDescendants),
              "caller is passing inherited hints, but shouldn't be");
 
   static_assert(nsStyleStructID_Length <= 32,
                 "aEqualStructs is not big enough");
 
   *aEqualStructs = 0;
 
-  nsChangeHint hint = NS_STYLE_HINT_NONE;
+  nsChangeHint hint = nsChangeHint(0);
   NS_ENSURE_TRUE(aNewContext, hint);
   // We must always ensure that we populate the structs on the new style
   // context that are filled in on the old context, so that if we get
   // two style changes in succession, the second of which causes a real
   // style change, the PeekStyleData doesn't return null (implying that
   // nobody ever looked at that struct's data).  In other words, we
   // can't skip later structs if we get a big change up front, because
   // we could later get a small change in one of those structs that we
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -202,17 +202,17 @@ nsStyleFont::CalcDifference(const nsStyl
   if (mGenericID != aNewData.mGenericID ||
       mScriptLevel != aNewData.mScriptLevel ||
       mScriptUnconstrainedSize != aNewData.mScriptUnconstrainedSize ||
       mScriptMinSize != aNewData.mScriptMinSize ||
       mScriptSizeMultiplier != aNewData.mScriptSizeMultiplier) {
     return nsChangeHint_NeutralChange;
   }
 
-  return NS_STYLE_HINT_NONE;
+  return nsChangeHint(0);
 }
 
 /* static */ nscoord
 nsStyleFont::ZoomText(StyleStructContext aContext, nscoord aSize)
 {
   // aSize can be negative (e.g.: calc(-1px)) so we can't assert that here.
   // The caller is expected deal with that.
   return NSToCoordTruncClamped(float(aSize) * aContext.TextZoom());
@@ -278,17 +278,17 @@ nsStyleMargin::Destroy(nsPresContext* aC
   aContext->PresShell()->
     FreeByObjectID(eArenaObjectID_nsStyleMargin, this);
 }
 
 nsChangeHint
 nsStyleMargin::CalcDifference(const nsStyleMargin& aNewData) const
 {
   if (mMargin == aNewData.mMargin) {
-    return NS_STYLE_HINT_NONE;
+    return nsChangeHint(0);
   }
   // Margin differences can't affect descendant intrinsic sizes and
   // don't need to force children to reflow.
   return nsChangeHint_NeedReflow |
          nsChangeHint_ReflowChangesSizeOrPosition |
          nsChangeHint_ClearAncestorIntrinsics;
 }
 
@@ -313,17 +313,17 @@ nsStylePadding::Destroy(nsPresContext* a
   aContext->PresShell()->
     FreeByObjectID(eArenaObjectID_nsStylePadding, this);
 }
 
 nsChangeHint
 nsStylePadding::CalcDifference(const nsStylePadding& aNewData) const
 {
   if (mPadding == aNewData.mPadding) {
-    return NS_STYLE_HINT_NONE;
+    return nsChangeHint(0);
   }
   // Padding differences can't affect descendant intrinsic sizes, but do need
   // to force children to reflow so that we can reposition them, since their
   // offsets are from our frame bounds but our content rect's position within
   // those bounds is moving.
   return NS_STYLE_HINT_REFLOW & ~nsChangeHint_ClearDescendantIntrinsics;
 }
 
@@ -522,17 +522,17 @@ nsStyleBorder::CalcDifference(const nsSt
 
   // mBorder is the specified border value.  Changes to this don't
   // need any change processing, since we operate on the computed
   // border values instead.
   if (mBorder != aNewData.mBorder) {
     return nsChangeHint_NeutralChange;
   }
 
-  return NS_STYLE_HINT_NONE;
+  return nsChangeHint(0);
 }
 
 nsStyleOutline::nsStyleOutline(StyleStructContext aContext)
   : mOutlineWidth(NS_STYLE_BORDER_WIDTH_MEDIUM, eStyleUnit_Enumerated)
   , mOutlineOffset(0)
   , mActualOutlineWidth(0)
   , mOutlineColor(NS_RGB(0, 0, 0))
   , mOutlineStyle(NS_STYLE_BORDER_STYLE_NONE)
@@ -597,17 +597,17 @@ nsStyleOutline::CalcDifference(const nsS
   }
 
   if (mOutlineWidth != aNewData.mOutlineWidth ||
       mOutlineOffset != aNewData.mOutlineOffset ||
       mTwipsPerPixel != aNewData.mTwipsPerPixel) {
     return nsChangeHint_NeutralChange;
   }
 
-  return NS_STYLE_HINT_NONE;
+  return nsChangeHint(0);
 }
 
 // --------------------
 // nsStyleList
 //
 nsStyleList::nsStyleList(StyleStructContext aContext) 
   : mListStylePosition(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE)
   , mCounterStyle(aContext.BuildCounterStyle(NS_LITERAL_STRING("disc")))
@@ -693,17 +693,17 @@ nsStyleList::CalcDifference(const nsStyl
     return nsChangeHint_ReconstructFrame;
   }
   if (mListStylePosition != aNewData.mListStylePosition) {
     return nsChangeHint_ReconstructFrame;
   }
   if (EqualImages(mListStyleImage, aNewData.mListStyleImage) &&
       mCounterStyle == aNewData.mCounterStyle) {
     if (mImageRegion.IsEqualInterior(aNewData.mImageRegion)) {
-      return NS_STYLE_HINT_NONE;
+      return nsChangeHint(0);
     }
     if (mImageRegion.width == aNewData.mImageRegion.width &&
         mImageRegion.height == aNewData.mImageRegion.height) {
       return NS_STYLE_HINT_VISUAL;
     }
   }
   return NS_STYLE_HINT_REFLOW;
 }
@@ -752,17 +752,17 @@ nsStyleXUL::CalcDifference(const nsStyle
 {
   if (mBoxAlign == aNewData.mBoxAlign &&
       mBoxDirection == aNewData.mBoxDirection &&
       mBoxFlex == aNewData.mBoxFlex &&
       mBoxOrient == aNewData.mBoxOrient &&
       mBoxPack == aNewData.mBoxPack &&
       mBoxOrdinal == aNewData.mBoxOrdinal &&
       mStretchStack == aNewData.mStretchStack) {
-    return NS_STYLE_HINT_NONE;
+    return nsChangeHint(0);
   }
   if (mBoxOrdinal != aNewData.mBoxOrdinal) {
     return nsChangeHint_ReconstructFrame;
   }
   return NS_STYLE_HINT_REFLOW;
 }
 
 // --------------------
@@ -831,17 +831,17 @@ nsStyleColumn::CalcDifference(const nsSt
 
   // XXX Is it right that we never check mTwipsPerPixel to return a
   // non-nsChangeHint_NeutralChange hint?
   if (mColumnRuleWidth != aNewData.mColumnRuleWidth ||
       mTwipsPerPixel != aNewData.mTwipsPerPixel) {
     return nsChangeHint_NeutralChange;
   }
 
-  return NS_STYLE_HINT_NONE;
+  return nsChangeHint(0);
 }
 
 // --------------------
 // nsStyleSVG
 //
 nsStyleSVG::nsStyleSVG(StyleStructContext aContext)
   : mFill(eStyleSVGPaintType_Color) // Will be initialized to NS_RGB(0, 0, 0)
   , mStroke(eStyleSVGPaintType_None)
@@ -1751,17 +1751,17 @@ nsStyleTable::nsStyleTable(const nsStyle
 
 nsChangeHint
 nsStyleTable::CalcDifference(const nsStyleTable& aNewData) const
 {
   if (mSpan != aNewData.mSpan ||
       mLayoutStrategy != aNewData.mLayoutStrategy) {
     return nsChangeHint_ReconstructFrame;
   }
-  return NS_STYLE_HINT_NONE;
+  return nsChangeHint(0);
 }
 
 // -----------------------
 // nsStyleTableBorder
 
 nsStyleTableBorder::nsStyleTableBorder(StyleStructContext aContext)
   : mBorderSpacingCol(0)
   , mBorderSpacingRow(0)
@@ -1797,17 +1797,17 @@ nsStyleTableBorder::CalcDifference(const
   if (mBorderCollapse != aNewData.mBorderCollapse) {
     return nsChangeHint_ReconstructFrame;
   }
 
   if ((mCaptionSide == aNewData.mCaptionSide) &&
       (mBorderSpacingCol == aNewData.mBorderSpacingCol) &&
       (mBorderSpacingRow == aNewData.mBorderSpacingRow)) {
     if (mEmptyCells == aNewData.mEmptyCells) {
-      return NS_STYLE_HINT_NONE;
+      return nsChangeHint(0);
     }
     return NS_STYLE_HINT_VISUAL;
   } else {
     return NS_STYLE_HINT_REFLOW;
   }
 }
 
 // --------------------
@@ -1825,17 +1825,17 @@ nsStyleColor::nsStyleColor(const nsStyle
 {
   MOZ_COUNT_CTOR(nsStyleColor);
 }
 
 nsChangeHint
 nsStyleColor::CalcDifference(const nsStyleColor& aNewData) const
 {
   if (mColor == aNewData.mColor) {
-    return NS_STYLE_HINT_NONE;
+    return nsChangeHint(0);
   }
   return nsChangeHint_RepaintFrame;
 }
 
 // --------------------
 // nsStyleGradient
 //
 bool
@@ -3517,17 +3517,17 @@ nsStyleContent::CalcDifference(const nsS
     if ((mResets[ix].mValue != aNewData.mResets[ix].mValue) ||
         (mResets[ix].mCounter != aNewData.mResets[ix].mCounter)) {
       return nsChangeHint_ReconstructFrame;
     }
   }
   if (mMarkerOffset != aNewData.mMarkerOffset) {
     return NS_STYLE_HINT_REFLOW;
   }
-  return NS_STYLE_HINT_NONE;
+  return nsChangeHint(0);
 }
 
 nsresult
 nsStyleContent::AllocateContents(uint32_t aCount)
 {
   // We need to run the destructors of the elements of mContents, so we
   // delete and reallocate even if aCount == mContentCount.  (If
   // nsStyleContentData had its members private and managed their
@@ -3597,17 +3597,17 @@ nsStyleTextReset::CalcDifference(const n
   if (isFG != otherIsFG || (!isFG && decColor != otherDecColor)) {
     return nsChangeHint_RepaintFrame;
   }
 
   if (mTextOverflow != aNewData.mTextOverflow) {
     return nsChangeHint_RepaintFrame;
   }
 
-  return NS_STYLE_HINT_NONE;
+  return nsChangeHint(0);
 }
 
 // Returns true if the given shadow-arrays are equal.
 static bool
 AreShadowArraysEqual(nsCSSShadowArray* lhs,
                      nsCSSShadowArray* rhs)
 {
   if (lhs == rhs) {
@@ -3792,17 +3792,17 @@ nsStyleText::CalcDifference(const nsStyl
   if (hint) {
     return hint;
   }
 
   if (mTextEmphasisPosition != aNewData.mTextEmphasisPosition) {
     return nsChangeHint_NeutralChange;
   }
 
-  return NS_STYLE_HINT_NONE;
+  return nsChangeHint(0);
 }
 
 LogicalSide
 nsStyleText::TextEmphasisSide(WritingMode aWM) const
 {
   MOZ_ASSERT(
     (!(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT) !=
      !(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT)) &&
@@ -3987,17 +3987,17 @@ nsStyleUIReset::CalcDifference(const nsS
   if (mUserSelect != aNewData.mUserSelect) {
     return NS_STYLE_HINT_VISUAL;
   }
 
   if (mWindowDragging != aNewData.mWindowDragging) {
     return nsChangeHint_SchedulePaint;
   }
 
-  return NS_STYLE_HINT_NONE;
+  return nsChangeHint(0);
 }
 
 //-----------------------
 // nsStyleVariables
 //
 
 nsStyleVariables::nsStyleVariables(StyleStructContext aContext)
 {
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyCencParser.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ClearKeyCencParser.h"
+
+#include "mozilla/Assertions.h"
+#include "ArrayUtils.h"
+#include "BigEndian.h"
+#include <memory.h>
+#include <algorithm>
+#include <assert.h>
+#include <limits>
+
+// Stripped down version of mp4_demuxer::ByteReader, stripped down to make it
+// easier to link into ClearKey DLL and gtest.
+class ByteReader
+{
+public:
+  ByteReader(const uint8_t* aData, size_t aSize)
+    : mPtr(aData), mRemaining(aSize), mLength(aSize)
+  {
+  }
+
+  size_t Offset() const
+  {
+    return mLength - mRemaining;
+  }
+
+  size_t Remaining() const { return mRemaining; }
+
+  size_t Length() const { return mLength; }
+
+  bool CanRead8() const { return mRemaining >= 1; }
+
+  uint8_t ReadU8()
+  {
+    auto ptr = Read(1);
+    if (!ptr) {
+      MOZ_ASSERT(false);
+      return 0;
+    }
+    return *ptr;
+  }
+
+  bool CanRead32() const { return mRemaining >= 4; }
+
+  uint32_t ReadU32()
+  {
+    auto ptr = Read(4);
+    if (!ptr) {
+      MOZ_ASSERT(false);
+      return 0;
+    }
+    return mozilla::BigEndian::readUint32(ptr);
+  }
+
+  const uint8_t* Read(size_t aCount)
+  {
+    if (aCount > mRemaining) {
+      mRemaining = 0;
+      return nullptr;
+    }
+    mRemaining -= aCount;
+
+    const uint8_t* result = mPtr;
+    mPtr += aCount;
+
+    return result;
+  }
+
+  const uint8_t* Seek(size_t aOffset)
+  {
+    if (aOffset > mLength) {
+      MOZ_ASSERT(false);
+      return nullptr;
+    }
+
+    mPtr = mPtr - Offset() + aOffset;
+    mRemaining = mLength - aOffset;
+    return mPtr;
+  }
+
+private:
+  const uint8_t* mPtr;
+  size_t mRemaining;
+  const size_t mLength;
+};
+
+#define FOURCC(a,b,c,d) ((a << 24) + (b << 16) + (c << 8) + d)
+
+ // System ID identifying the cenc v2 pssh box format; specified at:
+ // https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
+const uint8_t kSystemID[] = {
+  0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
+  0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
+};
+
+void
+ParseCENCInitData(const uint8_t* aInitData,
+                  uint32_t aInitDataSize,
+                  std::vector<std::vector<uint8_t>>& aOutKeyIds)
+{
+  ByteReader reader(aInitData, aInitDataSize);
+  while (reader.CanRead32()) {
+    // Box size. For the common system Id, ignore this, as some useragents
+    // handle invalid box sizes.
+    const size_t start = reader.Offset();
+    const size_t size = reader.ReadU32();
+    if (size > std::numeric_limits<size_t>::max() - start) {
+      // Ensure 'start + size' calculation below can't overflow.
+      return;
+    }
+    const size_t end = std::min<size_t>(start + size, reader.Length());
+
+    // PSSH box type.
+    if (!reader.CanRead32()) {
+      return;
+    }
+    uint32_t box = reader.ReadU32();
+    if (box != FOURCC('p','s','s','h')) {
+      reader.Seek(std::max<size_t>(reader.Offset(), end));
+      continue;
+    }
+
+    // 1 byte version, 3 bytes flags.
+    if (!reader.CanRead32()) {
+      return;
+    }
+    uint8_t version = reader.ReadU8();
+    if (version != 1) {
+      // Ignore pssh boxes with wrong version.
+      reader.Seek(std::max<size_t>(reader.Offset(), end));
+      continue;
+    }
+    reader.Read(3); // skip flags.
+
+    // SystemID
+    const uint8_t* sid = reader.Read(sizeof(kSystemID));
+    if (!sid) {
+      // Insufficinet bytes to read SystemID.
+      return;
+    }
+    if (memcmp(kSystemID, sid, sizeof(kSystemID))) {
+      // Ignore pssh boxes with wrong system ID.
+      reader.Seek(std::max<size_t>(reader.Offset(), end));
+      continue;
+    }
+
+    if (!reader.CanRead32()) {
+      return;
+    }
+    uint32_t kidCount = reader.ReadU32();
+
+    for (uint32_t i = 0; i < kidCount; i++) {
+      if (reader.Remaining() < CLEARKEY_KEY_LEN) {
+        // Not enough remaining to read key.
+        return;
+      }
+      const uint8_t* kid = reader.Read(CLEARKEY_KEY_LEN);
+      aOutKeyIds.push_back(std::vector<uint8_t>(kid, kid + CLEARKEY_KEY_LEN));
+    }
+
+    // Size of extra data. EME CENC format spec says datasize should
+    // always be 0. We explicitly read the datasize, in case the box
+    // size was 0, so that we get to the end of the box.
+    if (!reader.CanRead32()) {
+      return;
+    }
+    reader.ReadU32();
+
+    // Jump forwards to the end of the box, skipping any padding.
+    if (size) {
+      reader.Seek(end);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyCencParser.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ClearKeyCencParser_h__
+#define __ClearKeyCencParser_h__
+
+#include <stdint.h>
+#include <vector>
+
+#define CLEARKEY_KEY_LEN ((size_t)16)
+
+void
+ParseCENCInitData(const uint8_t* aInitData,
+                  uint32_t aInitDataSize,
+                  std::vector<std::vector<uint8_t>>& aOutKeyIds);
+
+#endif
--- a/media/gmp-clearkey/0.1/ClearKeySession.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySession.cpp
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
 #include "BigEndian.h"
 #include "ClearKeyDecryptionManager.h"
 #include "ClearKeySession.h"
 #include "ClearKeyUtils.h"
 #include "ClearKeyStorage.h"
+#include "ClearKeyCencParser.h"
 #include "gmp-task-utils.h"
 #include "gmp-api/gmp-decryption.h"
 #include <assert.h>
 #include <string.h>
 
 using namespace mozilla;
 
 ClearKeySession::ClearKeySession(const std::string& aSessionId,
@@ -55,17 +56,17 @@ void
 ClearKeySession::Init(uint32_t aCreateSessionToken,
                       uint32_t aPromiseId,
                       const std::string& aInitDataType,
                       const uint8_t* aInitData, uint32_t aInitDataSize)
 {
   CK_LOGD("ClearKeySession::Init");
 
   if (aInitDataType == "cenc") {
-    ClearKeyUtils::ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
+    ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
   } else if (aInitDataType == "keyids") {
     std::string sessionType;
     ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds, sessionType);
     if (sessionType != ClearKeyUtils::SessionTypeToString(mSessionType)) {
       const char message[] = "Session type specified in keyids init data doesn't match session type.";
       mCallback->RejectPromise(aPromiseId, kGMPAbortError, message, strlen(message));
       return;
     }
--- a/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
@@ -25,25 +25,16 @@
 #include "ArrayUtils.h"
 #include <assert.h>
 #include <memory.h>
 #include "BigEndian.h"
 #include "openaes/oaes_lib.h"
 
 using namespace std;
 
-#define FOURCC(a,b,c,d) ((a << 24) + (b << 16) + (c << 8) + d)
-
-// System ID identifying the cenc v2 pssh box format; specified at:
-// https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
-const uint8_t kSystemID[] = {
-  0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
-  0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
-};
-
 void
 CK_Log(const char* aFmt, ...)
 {
   va_list ap;
 
   va_start(ap, aFmt);
   vprintf(aFmt, ap);
   va_end(ap);
@@ -127,74 +118,16 @@ EncodeBase64Web(vector<uint8_t> aBinary,
     assert(idx < MOZ_ARRAY_LENGTH(sAlphabet)); // out of bounds index for 'sAlphabet'
     out[i] = sAlphabet[idx];
   }
 
   return true;
 }
 
 /* static */ void
-ClearKeyUtils::ParseCENCInitData(const uint8_t* aInitData,
-                                 uint32_t aInitDataSize,
-                                 vector<KeyId>& aOutKeyIds)
-{
-  using mozilla::BigEndian;
-
-  uint32_t size = 0;
-  for (uint32_t offset = 0; offset + sizeof(uint32_t) < aInitDataSize; offset += size) {
-    const uint8_t* data = aInitData + offset;
-    size = BigEndian::readUint32(data); data += sizeof(uint32_t);
-
-    CK_LOGD("Looking for pssh at offset %u", offset);
-
-    if (size + offset > aInitDataSize) {
-      CK_LOGE("Box size %u overflows init data buffer", size);
-      return;
-    }
-
-    if (size < 36) {
-      // Too small to be a cenc2 pssh box
-      continue;
-    }
-
-    uint32_t box = BigEndian::readUint32(data); data += sizeof(uint32_t);
-    if (box != FOURCC('p','s','s','h')) {
-      CK_LOGE("ClearKey CDM passed non-pssh initData");
-      return;
-    }
-
-    uint32_t head = BigEndian::readUint32(data); data += sizeof(uint32_t);
-    CK_LOGD("Got version %u pssh box, length %u", head & 0xff, size);
-
-    if ((head >> 24) != 1) {
-      // Ignore pssh boxes with wrong version
-      CK_LOGD("Ignoring pssh box with wrong version");
-      continue;
-    }
-
-    if (memcmp(kSystemID, data, sizeof(kSystemID))) {
-      // Ignore pssh boxes with wrong system ID
-      continue;
-    }
-    data += sizeof(kSystemID);
-
-    uint32_t kidCount = BigEndian::readUint32(data); data += sizeof(uint32_t);
-    if (data + kidCount * CLEARKEY_KEY_LEN > aInitData + aInitDataSize) {
-      CK_LOGE("pssh key IDs overflow init data buffer");
-      return;
-    }
-
-    for (uint32_t i = 0; i < kidCount; i++) {
-      aOutKeyIds.push_back(KeyId(data, data + CLEARKEY_KEY_LEN));
-      data += CLEARKEY_KEY_LEN;
-    }
-  }
-}
-
-/* static */ void
 ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
                               string& aOutRequest,
                               GMPSessionType aSessionType)
 {
   assert(aKeyIDs.size() && aOutRequest.empty());
 
   aOutRequest.append("{\"kids\":[");
   for (size_t i = 0; i < aKeyIDs.size(); i++) {
--- a/media/gmp-clearkey/0.1/ClearKeyUtils.h
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.h
@@ -18,18 +18,16 @@
 #define __ClearKeyUtils_h__
 
 #include <stdint.h>
 #include <string>
 #include <vector>
 #include <assert.h>
 #include "gmp-api/gmp-decryption.h"
 
-#define CLEARKEY_KEY_LEN ((size_t)16)
-
 #if 0
 void CK_Log(const char* aFmt, ...);
 #define CK_LOGE(...) CK_Log(__VA_ARGS__)
 #define CK_LOGD(...) CK_Log(__VA_ARGS__)
 #define CK_LOGW(...) CK_Log(__VA_ARGS__)
 #else
 #define CK_LOGE(...)
 #define CK_LOGD(...)
@@ -49,20 +47,16 @@ struct KeyIdPair
 };
 
 class ClearKeyUtils
 {
 public:
   static void DecryptAES(const std::vector<uint8_t>& aKey,
                          std::vector<uint8_t>& aData, std::vector<uint8_t>& aIV);
 
-  static void ParseCENCInitData(const uint8_t* aInitData,
-                                uint32_t aInitDataSize,
-                                std::vector<Key>& aOutKeyIds);
-
   static bool ParseKeyIdsInitData(const uint8_t* aInitData,
                                   uint32_t aInitDataSize,
                                   std::vector<KeyId>& aOutKeyIds,
                                   std::string& aOutSessionType);
 
   static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
                              std::string& aOutRequest,
                              GMPSessionType aSessionType);
--- a/media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
+++ b/media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
@@ -5,16 +5,17 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 #include <algorithm>
 #include <stdint.h>
 #include <vector>
 
 #include "../ClearKeyBase64.cpp"
+#include "../ClearKeyCencParser.cpp"
 #include "../ArrayUtils.h"
 
 
 using namespace std;
 
 struct B64Test {
   const char* b64;
   uint8_t raw[16];
@@ -68,8 +69,168 @@ TEST(ClearKey, DecodeBase64KeyOrId) {
     if (test.shouldPass) {
       EXPECT_EQ(v.size(), 16u);
       for (size_t k = 0; k < 16; k++) {
         EXPECT_EQ(v[k], test.raw[k]);
       }
     }
   }
 }
+
+// This is the CENC initData from Google's web-platform tests.
+// https://github.com/w3c/web-platform-tests/blob/master/encrypted-media/Google/encrypted-media-utils.js#L50
+const uint8_t gGoogleWPTCencInitData[] = {
+  0x00, 0x00, 0x00, 0x00,                          // size = 0
+  0x70, 0x73, 0x73, 0x68,                          // 'pssh'
+  0x01,                                            // version = 1
+  0x00, 0x00, 0x00,                                // flags
+  0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02,  // Common SystemID
+  0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
+  0x00, 0x00, 0x00, 0x01,                          // key count
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,  // key
+  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+  0x00, 0x00, 0x00, 0x00                           // datasize
+};
+
+// Example CENC initData from the EME spec format registry:
+// https://w3c.github.io/encrypted-media/format-registry/initdata/cenc.html
+const uint8_t gW3SpecExampleCencInitData[] = {
+  0x00, 0x00, 0x00, 0x4c, 0x70, 0x73, 0x73, 0x68, // BMFF box header (76 bytes, 'pssh')
+  0x01, 0x00, 0x00, 0x00,                         // Full box header (version = 1, flags = 0)
+  0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // SystemID
+  0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+  0x00, 0x00, 0x00, 0x02,                         // KID_count (2)
+  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // First KID ("0123456789012345")
+  0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+  0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, // Second KID ("ABCDEFGHIJKLMNOP")
+  0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
+  0x00, 0x00, 0x00, 0x00                         // Size of Data (0)
+};
+
+// Invalid box size, would overflow if used.
+const uint8_t gOverflowBoxSize[] = {
+  0xff, 0xff, 0xff, 0xff,                          // size = UINT32_MAX
+};
+
+// Invalid box size, but retrievable data.
+const uint8_t gMalformedCencInitData[] = {
+  0x00, 0x00, 0xff, 0xff,                          // size = too big a number
+  0x70, 0x73, 0x73, 0x68,                          // 'pssh'
+  0x01,                                            // version = 1
+  0xff, 0xff, 0xff,                                // flags
+  0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02,  // Common SystemID
+  0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
+  0xff, 0xff, 0xff, 0xff,                          // key count = UINT32_MAX
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // key
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff                           // datasize
+};
+
+// Non PSSH box, followed by Non common SystemID PSSH, followed by common SystemID PSSH box.
+const uint8_t gLeadingNonCommonCencInitData[] = {
+  0x00, 0x00, 0x00, 0x09,                          // size = 9
+  0xff, 0xff, 0xff, 0xff,                          // something other than 'pssh'
+  0xff,                                            // odd number of bytes of garbage to throw off the parser
+
+  0x00, 0x00, 0x00, 0x5c,                          // size = 92
+  0x70, 0x73, 0x73, 0x68,                          // 'pssh'
+  0x01,                                            // version = 1
+  0x00, 0x00, 0x00,                                // flags
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // Invalid SystemID
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // Some data to pad out the box.
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+
+  // gW3SpecExampleCencInitData
+  0x00, 0x00, 0x00, 0x4c, 0x70, 0x73, 0x73, 0x68, // BMFF box header (76 bytes, 'pssh')
+  0x01, 0x00, 0x00, 0x00,                         // Full box header (version = 1, flags = 0)
+  0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // SystemID
+  0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+  0x00, 0x00, 0x00, 0x02,                         // KID_count (2)
+  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // First KID ("0123456789012345")
+  0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+  0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, // Second KID ("ABCDEFGHIJKLMNOP")
+  0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
+  0x00, 0x00, 0x00, 0x00                         // Size of Data (0)
+};
+
+const uint8_t gNonPSSHBoxZeroSize[] = {
+  0x00, 0x00, 0x00, 0x00,                          // size = 0
+  0xff, 0xff, 0xff, 0xff,                          // something other than 'pssh'
+};
+
+// Two lots of the google init data. To ensure we handle
+// multiple boxes with size 0.
+const uint8_t g2xGoogleWPTCencInitData[] = {
+  0x00, 0x00, 0x00, 0x00,                          // size = 0
+  0x70, 0x73, 0x73, 0x68,                          // 'pssh'
+  0x01,                                            // version = 1
+  0x00, 0x00, 0x00,                                // flags
+  0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02,  // Common SystemID
+  0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
+  0x00, 0x00, 0x00, 0x01,                          // key count
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,  // key
+  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+  0x00, 0x00, 0x00, 0x00,                          // datasize
+
+  0x00, 0x00, 0x00, 0x00,                          // size = 0
+  0x70, 0x73, 0x73, 0x68,                          // 'pssh'
+  0x01,                                            // version = 1
+  0x00, 0x00, 0x00,                                // flags
+  0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02,  // Common SystemID
+  0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
+  0x00, 0x00, 0x00, 0x01,                          // key count
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,  // key
+  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+  0x00, 0x00, 0x00, 0x00                           // datasize  
+};
+
+TEST(ClearKey, ParseCencInitData) {
+  std::vector<std::vector<uint8_t>> keyIds;
+
+  ParseCENCInitData(gGoogleWPTCencInitData, MOZ_ARRAY_LENGTH(gGoogleWPTCencInitData), keyIds);
+  EXPECT_EQ(keyIds.size(), 1u);
+  EXPECT_EQ(keyIds[0].size(), 16u);
+  EXPECT_EQ(memcmp(&keyIds[0].front(), &gGoogleWPTCencInitData[32], 16), 0);
+
+  keyIds.clear();
+  ParseCENCInitData(gW3SpecExampleCencInitData, MOZ_ARRAY_LENGTH(gW3SpecExampleCencInitData), keyIds);
+  EXPECT_EQ(keyIds.size(), 2u);
+  EXPECT_EQ(keyIds[0].size(), 16u);
+  EXPECT_EQ(memcmp(&keyIds[0].front(), &gW3SpecExampleCencInitData[32], 16), 0);
+  EXPECT_EQ(memcmp(&keyIds[1].front(), &gW3SpecExampleCencInitData[48], 16), 0);
+
+  keyIds.clear();
+  ParseCENCInitData(gOverflowBoxSize, MOZ_ARRAY_LENGTH(gOverflowBoxSize), keyIds);
+  EXPECT_EQ(keyIds.size(), 0u);
+
+  keyIds.clear();
+  ParseCENCInitData(gMalformedCencInitData, MOZ_ARRAY_LENGTH(gMalformedCencInitData), keyIds);
+  EXPECT_EQ(keyIds.size(), 1u);
+  EXPECT_EQ(keyIds[0].size(), 16u);
+  EXPECT_EQ(memcmp(&keyIds[0].front(), &gMalformedCencInitData[32], 16), 0);
+
+  keyIds.clear();
+  ParseCENCInitData(gLeadingNonCommonCencInitData, MOZ_ARRAY_LENGTH(gLeadingNonCommonCencInitData), keyIds);
+  EXPECT_EQ(keyIds.size(), 2u);
+  EXPECT_EQ(keyIds[0].size(), 16u);
+  EXPECT_EQ(memcmp(&keyIds[0].front(), &gW3SpecExampleCencInitData[32], 16), 0);
+  EXPECT_EQ(memcmp(&keyIds[1].front(), &gW3SpecExampleCencInitData[48], 16), 0);
+
+  keyIds.clear();
+  ParseCENCInitData(gNonPSSHBoxZeroSize, MOZ_ARRAY_LENGTH(gNonPSSHBoxZeroSize), keyIds);
+  EXPECT_EQ(keyIds.size(), 0u);
+
+  keyIds.clear();
+  ParseCENCInitData(g2xGoogleWPTCencInitData, MOZ_ARRAY_LENGTH(g2xGoogleWPTCencInitData), keyIds);
+  EXPECT_EQ(keyIds.size(), 2u);
+  EXPECT_EQ(keyIds[0].size(), 16u);
+  EXPECT_EQ(keyIds[1].size(), 16u);
+  EXPECT_EQ(memcmp(&keyIds[0].front(), &g2xGoogleWPTCencInitData[32], 16), 0);
+  EXPECT_EQ(memcmp(&keyIds[1].front(), &g2xGoogleWPTCencInitData[84], 16), 0);
+
+}
--- a/media/gmp-clearkey/0.1/moz.build
+++ b/media/gmp-clearkey/0.1/moz.build
@@ -8,16 +8,17 @@ SharedLibrary('clearkey')
 
 FINAL_TARGET = 'dist/bin/gmp-clearkey/0.1'
 
 FINAL_TARGET_PP_FILES += ['clearkey.info.in']
 
 UNIFIED_SOURCES += [
     'ClearKeyAsyncShutdown.cpp',
     'ClearKeyBase64.cpp',
+    'ClearKeyCencParser.cpp',
     'ClearKeyDecryptionManager.cpp',
     'ClearKeyPersistence.cpp',
     'ClearKeySession.cpp',
     'ClearKeySessionManager.cpp',
     'ClearKeyStorage.cpp',
     'ClearKeyUtils.cpp',
     'gmp-clearkey.cpp',
 ]
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -52,16 +52,17 @@
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
 #include "MediaStreamList.h"
 #include "nsIScriptGlobalObject.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/RTCStatsReportBinding.h"
 #include "MediaStreamTrack.h"
 #include "VideoStreamTrack.h"
+#include "MediaStreamError.h"
 #endif
 
 
 
 namespace mozilla {
 using namespace dom;
 
 static const char* logTag = "PeerConnectionMedia";
@@ -1631,9 +1632,23 @@ LocalSourceStreamInfo::ForgetPipelineByT
       mPipelines.erase(trackId);
       return pipeline.forget();
     }
   }
 
   return nullptr;
 }
 
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+auto
+RemoteTrackSource::ApplyConstraints(
+    nsPIDOMWindowInner* aWindow,
+    const dom::MediaTrackConstraints& aConstraints) -> already_AddRefed<PledgeVoid>
+{
+  RefPtr<PledgeVoid> p = new PledgeVoid();
+  p->Reject(new dom::MediaStreamError(aWindow,
+                                      NS_LITERAL_STRING("OverconstrainedError"),
+                                      NS_LITERAL_STRING("")));
+  return p.forget();
+}
+#endif
+
 } // namespace mozilla
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -163,24 +163,19 @@ public:
   explicit RemoteTrackSource(nsIPrincipal* aPrincipal, const nsString& aLabel)
     : dom::MediaStreamTrackSource(aPrincipal, true, aLabel) {}
 
   dom::MediaSourceEnum GetMediaSource() const override
   {
     return dom::MediaSourceEnum::Other;
   }
 
-  already_AddRefed<dom::Promise>
+  already_AddRefed<PledgeVoid>
   ApplyConstraints(nsPIDOMWindowInner* aWindow,
-                   const dom::MediaTrackConstraints& aConstraints,
-                   ErrorResult &aRv) override
-  {
-    NS_ERROR("Can't ApplyConstraints() a remote source!");
-    return nullptr;
-  }
+                   const dom::MediaTrackConstraints& aConstraints) override;
 
   void Stop() override { NS_ERROR("Can't stop a remote source!"); }
 
   void SetPrincipal(nsIPrincipal* aPrincipal)
   {
     mPrincipal = aPrincipal;
     PrincipalChanged();
   }
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -811,30 +811,31 @@ public class BrowserApp extends GeckoApp
         // those before upload can occur in Adjust.onResume.
         final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
         final boolean enabled = !isInAutomation &&
                 prefs.getBoolean(GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true);
         adjustHelper.setEnabled(enabled);
     }
 
     private void showUpdaterPermissionSnackbar() {
-        SnackbarHelper.SnackbarCallback allowCallback = new SnackbarHelper.SnackbarCallback() {
+        SnackbarBuilder.SnackbarCallback allowCallback = new SnackbarBuilder.SnackbarCallback() {
             @Override
             public void onClick(View v) {
                 Permissions.from(BrowserApp.this)
                         .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                         .run();
             }
         };
 
-        SnackbarHelper.showSnackbarWithAction(this,
-                getString(R.string.updater_permission_text),
-                Snackbar.LENGTH_INDEFINITE,
-                getString(R.string.updater_permission_allow),
-                allowCallback);
+        SnackbarBuilder.builder(this)
+                .message(R.string.updater_permission_text)
+                .duration(Snackbar.LENGTH_INDEFINITE)
+                .action(R.string.updater_permission_allow)
+                .callback(allowCallback)
+                .buildAndShow();
     }
 
     private void conditionallyNotifyEOL() {
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
         try {
             final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
             if (!prefs.contains(EOL_NOTIFIED)) {
 
@@ -2902,17 +2903,17 @@ public class BrowserApp extends GeckoApp
      */
     private class HideOnTouchListener implements TouchEventInterceptor {
         private boolean mIsHidingTabs;
         private final Rect mTempRect = new Rect();
 
         @Override
         public boolean onInterceptTouchEvent(View view, MotionEvent event) {
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                SnackbarHelper.dismissCurrentSnackbar();
+                SnackbarBuilder.dismissCurrentSnackbar();
             }
 
 
 
             // We need to account for scroll state for the touched view otherwise
             // tapping on an "empty" part of the view will still be considered a
             // valid touch event.
             if (view.getScrollX() != 0 || view.getScrollY() != 0) {
@@ -3911,31 +3912,36 @@ public class BrowserApp extends GeckoApp
         final Tab newTab = Tabs.getInstance().loadUrl(pageURL, loadFlags);
 
         // We switch to the desired tab by unique ID, which closes any window
         // for a race between opening the tab and closing it, and switching to
         // it. We could also switch to the Tab explicitly, but we don't want to
         // hold a reference to the Tab itself in the anonymous listener class.
         final int newTabId = newTab.getId();
 
-        final SnackbarHelper.SnackbarCallback callback = new SnackbarHelper.SnackbarCallback() {
+        final SnackbarBuilder.SnackbarCallback callback = new SnackbarBuilder.SnackbarCallback() {
             @Override
             public void onClick(View v) {
                 Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.TOAST, "switchtab");
 
                 maybeSwitchToTab(newTabId);
             }
         };
 
         final String message = isPrivate ?
                 getResources().getString(R.string.new_private_tab_opened) :
                 getResources().getString(R.string.new_tab_opened);
         final String buttonMessage = getResources().getString(R.string.switch_button_message);
 
-        SnackbarHelper.showSnackbarWithAction(this, message, Snackbar.LENGTH_LONG, buttonMessage, callback);
+        SnackbarBuilder.builder(this)
+                .message(message)
+                .duration(Snackbar.LENGTH_LONG)
+                .action(buttonMessage)
+                .callback(callback)
+                .buildAndShow();
     }
 
     // BrowserSearch.OnSearchListener
     @Override
     public void onSearch(SearchEngine engine, final String text, final TelemetryContract.Method method) {
         // Don't store searches that happen in private tabs. This assumes the user can only
         // perform a search inside the currently selected tab, which is true for searches
         // that come from SearchEngineRow.
--- a/mobile/android/base/java/org/mozilla/gecko/EditBookmarkDialog.java
+++ b/mobile/android/base/java/org/mozilla/gecko/EditBookmarkDialog.java
@@ -212,19 +212,20 @@ public class EditBookmarkDialog {
                         String newKeyword = keywordText.getText().toString().trim();
 
                         db.updateBookmark(context.getContentResolver(), id, newUrl, nameText.getText().toString(), newKeyword);
                         return null;
                     }
 
                     @Override
                     public void onPostExecute(Void result) {
-                        SnackbarHelper.showSnackbar((Activity) context,
-                                context.getString(R.string.bookmark_updated),
-                                Snackbar.LENGTH_LONG);
+                        SnackbarBuilder.builder((Activity) context)
+                                .message(R.string.bookmark_updated)
+                                .duration(Snackbar.LENGTH_LONG)
+                                .buildAndShow();
                     }
                 }).execute();
             }
         });
 
         editPrompt.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int whichButton) {
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -631,17 +631,20 @@ public abstract class GeckoApp
             ThreadUtils.postToBackgroundThread(new Runnable() {
                 @Override
                 public void run() {
                     final boolean bookmarkAdded = db.addBookmark(getContentResolver(), title, url);
                     final int resId = bookmarkAdded ? R.string.bookmark_added : R.string.bookmark_already_added;
                     ThreadUtils.postToUiThread(new Runnable() {
                         @Override
                         public void run() {
-                            SnackbarHelper.showSnackbar(GeckoApp.this, getString(resId), Snackbar.LENGTH_LONG);
+                            SnackbarBuilder.builder(GeckoApp.this)
+                                    .message(resId)
+                                    .duration(Snackbar.LENGTH_LONG)
+                                    .buildAndShow();
                         }
                     });
                 }
             });
 
         } else if ("Contact:Add".equals(event)) {
             final String email = message.optString("email", null);
             final String phone = message.optString("phone", null);
@@ -701,17 +704,21 @@ public abstract class GeckoApp
                 title = tab.getDisplayTitle();
             }
             IntentHelper.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, title, false);
 
             // Context: Sharing via chrome list (no explicit session is active)
             Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "text");
 
         } else if ("Snackbar:Show".equals(event)) {
-            SnackbarHelper.showSnackbar(this, message, callback);
+            SnackbarBuilder.builder(this)
+                    .fromEvent(message)
+                    .callback(callback)
+                    .buildAndShow();
+
         } else if ("SystemUI:Visibility".equals(event)) {
             setSystemUiVisible(message.getBoolean("visible"));
 
         } else if ("ToggleChrome:Focus".equals(event)) {
             focusChrome();
 
         } else if ("ToggleChrome:Hide".equals(event)) {
             toggleChrome(false);
@@ -1011,39 +1018,48 @@ public abstract class GeckoApp
                 byte[] imgBuffer = os.toByteArray();
                 image = BitmapUtils.decodeByteArray(imgBuffer);
             }
             if (image != null) {
                 // Some devices don't have a DCIM folder and the Media.insertImage call will fail.
                 File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
 
                 if (!dcimDir.mkdirs() && !dcimDir.isDirectory()) {
-                    SnackbarHelper.showSnackbar(this, getString(R.string.set_image_path_fail), Snackbar.LENGTH_LONG);
+                    SnackbarBuilder.builder(this)
+                            .message(R.string.set_image_path_fail)
+                            .duration(Snackbar.LENGTH_LONG)
+                            .buildAndShow();
                     return;
                 }
                 String path = Media.insertImage(getContentResolver(), image, null, null);
                 if (path == null) {
-                    SnackbarHelper.showSnackbar(this, getString(R.string.set_image_path_fail), Snackbar.LENGTH_LONG);
+                    SnackbarBuilder.builder(this)
+                            .message(R.string.set_image_path_fail)
+                            .duration(Snackbar.LENGTH_LONG)
+                            .buildAndShow();
                     return;
                 }
                 final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
                 intent.addCategory(Intent.CATEGORY_DEFAULT);
                 intent.setData(Uri.parse(path));
 
                 // Removes the image from storage once the chooser activity ends.
                 Intent chooser = Intent.createChooser(intent, getString(R.string.set_image_chooser_title));
                 ActivityResultHandler handler = new ActivityResultHandler() {
                     @Override
                     public void onActivityResult (int resultCode, Intent data) {
                         getContentResolver().delete(intent.getData(), null, null);
                     }
                 };
                 ActivityHandlerHelper.startIntentForActivity(this, chooser, handler);
             } else {
-                SnackbarHelper.showSnackbar(this, getString(R.string.set_image_fail), Snackbar.LENGTH_LONG);
+                SnackbarBuilder.builder(this)
+                        .message(R.string.set_image_fail)
+                        .duration(Snackbar.LENGTH_LONG)
+                        .buildAndShow();
             }
         } catch (OutOfMemoryError ome) {
             Log.e(LOGTAG, "Out of Memory when converting to byte array", ome);
         } catch (IOException ioe) {
             Log.e(LOGTAG, "I/O Exception while setting wallpaper", ioe);
         } finally {
             if (is != null) {
                 try {
rename from mobile/android/base/java/org/mozilla/gecko/SnackbarHelper.java
rename to mobile/android/base/java/org/mozilla/gecko/SnackbarBuilder.java
--- a/mobile/android/base/java/org/mozilla/gecko/SnackbarHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/SnackbarBuilder.java
@@ -7,37 +7,38 @@ package org.mozilla.gecko;
 
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.NativeJSObject;
 
 import android.app.Activity;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
+import android.support.annotation.StringRes;
 import android.support.design.widget.Snackbar;
 import android.support.v4.content.ContextCompat;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.View;
 import android.widget.TextView;
 
 import java.lang.ref.WeakReference;
 
 /**
  * Helper class for creating and dismissing snackbars. Use this class to guarantee a consistent style and behavior
  * across the app.
  */
-public class SnackbarHelper {
+public class SnackbarBuilder {
     /**
      * Combined interface for handling all callbacks from a snackbar because anonymous classes can only extend one
      * interface or class.
      */
     public static abstract class SnackbarCallback extends Snackbar.Callback implements View.OnClickListener {}
-    public static final String LOGTAG = "GeckoSnackbarHelper";
+    public static final String LOGTAG = "GeckoSnackbarBuilder";
 
     /**
      * SnackbarCallback implementation for delegating snackbar events to an EventCallback.
      */
     private static class SnackbarEventCallback extends SnackbarCallback {
         private EventCallback callback;
 
         public SnackbarEventCallback(EventCallback callback) {
@@ -63,87 +64,147 @@ public class SnackbarHelper {
             callback.sendError(null);
             callback = null; // Releasing reference. We only want to execute the callback once.
         }
     }
 
     private static final Object currentSnackbarLock = new Object();
     private static WeakReference<Snackbar> currentSnackbar = new WeakReference<>(null); // Guarded by 'currentSnackbarLock'
 
+    private final Activity activity;
+    private String message;
+    private int duration;
+    private String action;
+    private SnackbarCallback callback;
+    private Drawable icon;
+    private Integer backgroundColor;
+    private Integer actionColor;
+
     /**
-     * Show a snackbar to display a message.
-     *
      * @param activity Activity to show the snackbar in.
+     */
+    private SnackbarBuilder(final Activity activity) {
+        this.activity = activity;
+    }
+
+    public static SnackbarBuilder builder(final Activity activity) {
+        return new SnackbarBuilder(activity);
+    }
+
+    /**
      * @param message The text to show. Can be formatted text.
+     */
+    public SnackbarBuilder message(final String message) {
+        this.message = message;
+        return this;
+    }
+
+    /**
+     * @param id The id of the string resource to show. Can be formatted text.
+     */
+    public SnackbarBuilder message(@StringRes final int id) {
+        message = activity.getResources().getString(id);
+        return this;
+    }
+
+    /**
      * @param duration How long to display the message.
      */
-    public static void showSnackbar(Activity activity, String message, int duration) {
-        showSnackbarWithAction(activity, message, duration, null, null);
+    public SnackbarBuilder duration(final int duration) {
+        this.duration = duration;
+        return this;
+    }
+
+    /**
+     * @param action Action text to display.
+     */
+    public SnackbarBuilder action(final String action) {
+        this.action = action;
+        return this;
     }
 
     /**
-     * Build and show a snackbar from a Gecko Snackbar:Show event.
+     * @param id The id of the string resource for the action text to display.
+     */
+    public SnackbarBuilder action(@StringRes final int id) {
+        action = activity.getResources().getString(id);
+        return this;
+    }
+
+    /**
+     * @param callback Callback to be invoked when the action is clicked or the snackbar is dismissed.
+     */
+    public SnackbarBuilder callback(final SnackbarCallback callback) {
+        this.callback = callback;
+        return this;
+    }
+
+    /**
+     * @param callback Callback to be invoked when the action is clicked or the snackbar is dismissed.
+     */
+    public SnackbarBuilder callback(final EventCallback callback) {
+        this.callback = new SnackbarEventCallback(callback);
+        return this;
+    }
+
+    /**
+     * @param icon Icon to be displayed with the snackbar text.
      */
-    public static void showSnackbar(Activity activity, final NativeJSObject object, final EventCallback callback) {
-        final String message = object.getString("message");
-        final int duration = object.getInt("duration");
+    public SnackbarBuilder icon(final Drawable icon) {
+        this.icon = icon;
+        return this;
+    }
+
+    /**
+     * @param backgroundColor Snackbar background color.
+     */
+    public SnackbarBuilder backgroundColor(final Integer backgroundColor) {
+        this.backgroundColor = backgroundColor;
+        return this;
+    }
 
-        Integer backgroundColor = null;
+    /**
+     * @param actionColor Action text color.
+     */
+    public SnackbarBuilder actionColor(final Integer actionColor) {
+        this.actionColor = actionColor;
+        return this;
+    }
+
+    /**
+     * @param object Populate the builder with data from a Gecko Snackbar:Show event.
+     */
+    public SnackbarBuilder fromEvent(final NativeJSObject object) {
+        message = object.getString("message");
+        duration = object.getInt("duration");
 
         if (object.has("backgroundColor")) {
             final String providedColor = object.getString("backgroundColor");
             try {
                 backgroundColor = Color.parseColor(providedColor);
             } catch (IllegalArgumentException e) {
                 Log.w(LOGTAG, "Failed to parse color string: " + providedColor);
             }
         }
 
-        NativeJSObject action = object.optObject("action", null);
-
-        showSnackbarWithActionAndColors(activity,
-                message,
-                duration,
-                action != null ? action.optString("label", null) : null,
-                new SnackbarHelper.SnackbarEventCallback(callback),
-                null,
-                backgroundColor,
-                null
-        );
+        NativeJSObject actionObject = object.optObject("action", null);
+        if (actionObject != null) {
+            action = actionObject.optString("label", null);
+        }
+        return this;
     }
 
-    /**
-     * Show a snackbar to display a message and an action.
-     *
-     * @param activity Activity to show the snackbar in.
-     * @param message The text to show. Can be formatted text.
-     * @param duration How long to display the message.
-     * @param action Action text to display.
-     * @param callback Callback to be invoked when the action is clicked or the snackbar is dismissed.
-     */
-    public static void showSnackbarWithAction(Activity activity, String message, int duration, String action, SnackbarCallback callback) {
-        showSnackbarWithActionAndColors(activity, message, duration, action, callback, null, null, null);
-    }
-
-
-    public static void showSnackbarWithActionAndColors(Activity activity,
-                                                       String message,
-                                                       int duration,
-                                                       String action,
-                                                       SnackbarCallback callback,
-                                                       Drawable icon,
-                                                       Integer backgroundColor,
-                                                       Integer actionColor) {
+    public void buildAndShow() {
         final View parentView = findBestParentView(activity);
         final Snackbar snackbar = Snackbar.make(parentView, message, duration);
 
         if (callback != null && !TextUtils.isEmpty(action)) {
             snackbar.setAction(action, callback);
             if (actionColor == null) {
-                ContextCompat.getColor(activity, R.color.fennec_ui_orange);
+                snackbar.setActionTextColor(ContextCompat.getColor(activity, R.color.fennec_ui_orange));
             } else {
                 snackbar.setActionTextColor(actionColor);
             }
             snackbar.setCallback(callback);
         }
 
         if (icon != null) {
             int leftPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, activity.getResources().getDisplayMetrics());
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java
@@ -20,17 +20,17 @@ import android.widget.ListView;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.EditBookmarkDialog;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarHelper;
+import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.home.HomeConfig;
 import org.mozilla.gecko.promotion.SimpleHelperUI;
 import org.mozilla.gecko.prompts.Prompt;
 import org.mozilla.gecko.prompts.PromptListItem;
@@ -125,38 +125,42 @@ public class BookmarkStateChangeDelegate
     private void showBookmarkAddedSnackbar() {
         final BrowserApp browserApp = getBrowserApp();
         if (browserApp == null) {
             return;
         }
 
         // This flow is from the option menu which has check to see if a bookmark was already added.
         // So, it is safe here to show the snackbar that bookmark_added without any checks.
-        final SnackbarHelper.SnackbarCallback callback = new SnackbarHelper.SnackbarCallback() {
+        final SnackbarBuilder.SnackbarCallback callback = new SnackbarBuilder.SnackbarCallback() {
             @Override
             public void onClick(View v) {
                 Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.TOAST, "bookmark_options");
                 showBookmarkDialog(browserApp);
             }
         };
 
-        SnackbarHelper.showSnackbarWithAction(browserApp,
-                browserApp.getResources().getString(R.string.bookmark_added),
-                Snackbar.LENGTH_LONG,
-                browserApp.getResources().getString(R.string.bookmark_options),
-                callback);
+        SnackbarBuilder.builder(browserApp)
+                .message(R.string.bookmark_added)
+                .duration(Snackbar.LENGTH_LONG)
+                .action(R.string.bookmark_options)
+                .callback(callback)
+                .buildAndShow();
     }
 
     private void showBookmarkRemovedSnackbar() {
         final BrowserApp browserApp = getBrowserApp();
         if (browserApp == null) {
             return;
         }
 
-        SnackbarHelper.showSnackbar(browserApp, browserApp.getResources().getString(R.string.bookmark_removed), Snackbar.LENGTH_LONG);
+        SnackbarBuilder.builder(browserApp)
+                .message(R.string.bookmark_removed)
+                .duration(Snackbar.LENGTH_LONG)
+                .buildAndShow();
     }
 
     private static void showBookmarkDialog(final BrowserApp browserApp) {
         final Resources res = browserApp.getResources();
         final Tab tab = Tabs.getInstance().getSelectedTab();
 
         final Prompt ps = new Prompt(browserApp, new Prompt.PromptCallback() {
             @Override
@@ -208,25 +212,26 @@ public class BookmarkStateChangeDelegate
     private void showReaderModeBookmarkAddedSnackbar() {
         final BrowserApp browserApp = getBrowserApp();
         if (browserApp == null) {
             return;
         }
 
         final Drawable iconDownloaded = DrawableUtil.tintDrawable(browserApp, R.drawable.status_icon_readercache, Color.WHITE);
 
-        final SnackbarHelper.SnackbarCallback callback = new SnackbarHelper.SnackbarCallback() {
+        final SnackbarBuilder.SnackbarCallback callback = new SnackbarBuilder.SnackbarCallback() {
             @Override
             public void onClick(View v) {
                 browserApp.openUrlAndStopEditing("about:home?panel=" + HomeConfig.getIdForBuiltinPanelType(HomeConfig.PanelType.BOOKMARKS));
             }
         };
 
-        SnackbarHelper.showSnackbarWithActionAndColors(browserApp,
-                browserApp.getResources().getString(R.string.reader_saved_offline),
-                Snackbar.LENGTH_LONG,
-                browserApp.getResources().getString(R.string.reader_switch_to_bookmarks),
-                callback,
-                iconDownloaded,
-                ContextCompat.getColor(browserApp, R.color.link_blue),
-                Color.WHITE);
+        SnackbarBuilder.builder(browserApp)
+                .message(R.string.reader_saved_offline)
+                .duration(Snackbar.LENGTH_LONG)
+                .action(R.string.reader_switch_to_bookmarks)
+                .callback(callback)
+                .icon(iconDownloaded)
+                .backgroundColor(ContextCompat.getColor(browserApp, R.color.link_blue))
+                .actionColor(Color.WHITE)
+                .buildAndShow();
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/OfflineTabStatusDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/delegates/OfflineTabStatusDelegate.java
@@ -9,17 +9,17 @@ import android.app.Activity;
 import android.os.Bundle;
 import android.support.annotation.CallSuper;
 import android.support.design.widget.Snackbar;
 import android.support.v4.content.ContextCompat;
 
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarHelper;
+import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.WeakHashMap;
@@ -95,18 +95,15 @@ public class OfflineTabStatusDelegate ex
      */
     private static void showLoadedOfflineSnackbar(final Activity activity) {
         if (activity == null) {
             return;
         }
 
         Telemetry.sendUIEvent(TelemetryContract.Event.NETERROR, TelemetryContract.Method.TOAST, "usecache");
 
-        SnackbarHelper.showSnackbarWithActionAndColors(
-                activity,
-                activity.getResources().getString(R.string.tab_offline_version),
-                Snackbar.LENGTH_INDEFINITE,
-                null, null, null,
-                ContextCompat.getColor(activity, R.color.link_blue),
-                null
-        );
+        SnackbarBuilder.builder(activity)
+                .message(R.string.tab_offline_version)
+                .duration(Snackbar.LENGTH_INDEFINITE)
+                .backgroundColor(ContextCompat.getColor(activity, R.color.link_blue))
+                .buildAndShow();
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/ScreenshotDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/delegates/ScreenshotDelegate.java
@@ -10,17 +10,17 @@ import android.os.Bundle;
 import android.support.design.widget.Snackbar;
 import android.util.Log;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.ScreenshotObserver;
-import org.mozilla.gecko.SnackbarHelper;
+import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 
 import java.lang.ref.WeakReference;
 
 /**
@@ -56,18 +56,20 @@ public class ScreenshotDelegate extends 
         final Activity activity = getBrowserApp();
         if (activity == null) {
             return;
         }
 
         GeckoProfile.get(activity).getDB().getUrlAnnotations().insertScreenshot(
                 activity.getContentResolver(), selectedTab.getURL(), screenshotPath);
 
-        SnackbarHelper.showSnackbar(activity,
-                activity.getResources().getString(R.string.screenshot_added_to_bookmarks), Snackbar.LENGTH_SHORT);
+        SnackbarBuilder.builder(activity)
+                .message(R.string.screenshot_added_to_bookmarks)
+                .duration(Snackbar.LENGTH_SHORT)
+                .buildAndShow();
     }
 
     @Override
     public void onResume(BrowserApp browserApp) {
         mScreenshotObserver.start();
     }
 
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
@@ -8,17 +8,17 @@ package org.mozilla.gecko.home;
 import java.util.EnumSet;
 
 import org.mozilla.gecko.EditBookmarkDialog;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.IntentHelper;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarHelper;
+import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
 import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
@@ -431,14 +431,15 @@ public abstract class HomeFragment exten
                     Log.e(LOGTAG, "Can't remove item type " + mType.toString());
                     break;
             }
             return null;
         }
 
         @Override
         public void onPostExecute(Void result) {
-            SnackbarHelper.showSnackbar((Activity) mContext,
-                    mContext.getString(R.string.page_removed),
-                    Snackbar.LENGTH_LONG);
+            SnackbarBuilder.builder((Activity) mContext)
+                    .message(R.string.page_removed)
+                    .duration(Snackbar.LENGTH_LONG)
+                    .buildAndShow();
         }
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
@@ -19,17 +19,17 @@ import org.mozilla.gecko.GeckoActivitySt
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.LocaleManager;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarHelper;
+import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.TelemetryContract.Method;
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
 import org.mozilla.gecko.feeds.FeedService;
 import org.mozilla.gecko.feeds.action.CheckForUpdatesAction;
 import org.mozilla.gecko.permissions.Permissions;
@@ -618,29 +618,33 @@ OnSharedPreferenceChangeListener
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("Sanitize:Finished")) {
                 boolean success = message.getBoolean("success");
                 final int stringRes = success ? R.string.private_data_success : R.string.private_data_fail;
 
-                SnackbarHelper.showSnackbar(GeckoPreferences.this,
-                        getString(stringRes),
-                        Snackbar.LENGTH_LONG);
+                SnackbarBuilder.builder(GeckoPreferences.this)
+                        .message(stringRes)
+                        .duration(Snackbar.LENGTH_LONG)
+                        .buildAndShow();
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     @Override
     public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
         if ("Snackbar:Show".equals(event)) {
-            SnackbarHelper.showSnackbar(this, message, callback);
+            SnackbarBuilder.builder(this)
+                    .fromEvent(message)
+                    .callback(callback)
+                    .buildAndShow();
         }
     }
 
     /**
       * Initialize all of the preferences (native of Gecko ones) for this screen.
       *
       * @param prefs The android.preference.PreferenceGroup to initialize
       * @return The integer id for the PrefsHelper.PrefHandlerBase listener added
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/SearchEnginePreference.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/SearchEnginePreference.java
@@ -2,17 +2,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/. */
 
 package org.mozilla.gecko.preferences;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarHelper;
+import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.FaviconView;
 
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -87,19 +87,20 @@ public class SearchEnginePreference exte
 
     @Override
     public void showDialog() {
         // If this is the last engine, then we are the default, and none of the options
         // on this menu can do anything.
         if (mParentCategory.getPreferenceCount() == 1) {
             Activity activity = (Activity) getContext();
 
-            SnackbarHelper.showSnackbar(activity,
-                    activity.getString(R.string.pref_search_last_toast),
-                    Snackbar.LENGTH_LONG);
+            SnackbarBuilder.builder(activity)
+                    .message(R.string.pref_search_last_toast)
+                    .duration(Snackbar.LENGTH_LONG)
+                    .buildAndShow();
 
             return;
         }
 
         super.showDialog();
     }
 
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java
@@ -21,17 +21,17 @@ import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.SiteIdentity;
 import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.SiteIdentity.MixedMode;
 import org.mozilla.gecko.SiteIdentity.TrackingMode;
-import org.mozilla.gecko.SnackbarHelper;
+import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.AnchoredPopup;
 import org.mozilla.gecko.widget.DoorHanger;
 import org.mozilla.gecko.widget.DoorHanger.OnButtonClickListener;
 import org.json.JSONObject;
@@ -226,22 +226,28 @@ public class SiteIdentityPopup extends A
                         } else {
                             password = login.getString("password");
                         }
                         if (AppConstants.Versions.feature11Plus) {
                             manager.setPrimaryClip(ClipData.newPlainText("password", password));
                         } else {
                             manager.setText(password);
                         }
-                        SnackbarHelper.showSnackbar(activity, activity.getString(R.string.doorhanger_login_select_toast_copy), Snackbar.LENGTH_SHORT);
+                        SnackbarBuilder.builder(activity)
+                                .message(R.string.doorhanger_login_select_toast_copy)
+                                .duration(Snackbar.LENGTH_SHORT)
+                                .buildAndShow();
                     }
                     dismiss();
                 } catch (JSONException e) {
                     Log.e(LOGTAG, "Error handling Select login button click", e);
-                    SnackbarHelper.showSnackbar(activity, activity.getString(R.string.doorhanger_login_select_toast_copy_error), Snackbar.LENGTH_SHORT);
+                    SnackbarBuilder.builder(activity)
+                            .message(R.string.doorhanger_login_select_toast_copy_error)
+                            .duration(Snackbar.LENGTH_SHORT)
+                            .buildAndShow();
                 }
             }
         };
 
         final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.LOGIN, buttonClickListener);
 
         // Set buttons.
         config.setButton(mContext.getString(R.string.button_cancel), ButtonType.CANCEL.ordinal(), false);
--- a/mobile/android/base/java/org/mozilla/gecko/widget/GeckoActionProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/GeckoActionProvider.java
@@ -8,17 +8,17 @@ package org.mozilla.gecko.widget;
 import android.app.Activity;
 import android.net.Uri;
 import android.support.design.widget.Snackbar;
 import android.util.Base64;
 import android.view.Menu;
 
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarHelper;
+import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.overlays.ui.ShareDialog;
 import org.mozilla.gecko.menu.MenuItemSwitcherLayout;
 import org.mozilla.gecko.util.IOUtils;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
@@ -291,19 +291,20 @@ public class GeckoActionProvider {
     public void downloadImageForIntent(final Intent intent) {
         final String src = IntentUtils.getStringExtraSafe(intent, Intent.EXTRA_TEXT);
         final File dir = GeckoApp.getTempDirectory();
 
         if (src == null || dir == null) {
             // We should be, but currently aren't, statically guaranteed an Activity context.
             // Try our best.
             if (mContext instanceof Activity) {
-                SnackbarHelper.showSnackbar((Activity) mContext,
-                        mContext.getApplicationContext().getString(R.string.share_image_failed),
-                        Snackbar.LENGTH_LONG);
+                SnackbarBuilder.builder((Activity) mContext)
+                        .message(mContext.getApplicationContext().getString(R.string.share_image_failed))
+                        .duration(Snackbar.LENGTH_LONG)
+                        .buildAndShow();
             }
             return;
         }
 
         GeckoApp.deleteTempFiles();
 
         String type = intent.getType();
         OutputStream os = null;
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -560,17 +560,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'ScreenshotObserver.java',
     'search/SearchEngine.java',
     'search/SearchEngineManager.java',
     'ServiceNotificationClient.java',
     'SessionParser.java',
     'SharedPreferencesHelper.java',
     'SiteIdentity.java',
     'SmsManager.java',
-    'SnackbarHelper.java',
+    'SnackbarBuilder.java',
     'sqlite/ByteBufferInputStream.java',
     'sqlite/MatrixBlobCursor.java',
     'sqlite/SQLiteBridge.java',
     'sqlite/SQLiteBridgeException.java',
     'SuggestClient.java',
     'SurfaceBits.java',
     'Tab.java',
     'tabqueue/TabQueueHelper.java',
--- a/python/mozboot/mozboot/debian.py
+++ b/python/mozboot/mozboot/debian.py
@@ -49,26 +49,27 @@ class DebianBootstrapper(BaseBootstrappe
     # These are common packages for building Firefox for Desktop
     # (browser) for all Debian-derived distros (such as Ubuntu).
     BROWSER_COMMON_PACKAGES = [
         'libasound2-dev',
         'libcurl4-openssl-dev',
         'libdbus-1-dev',
         'libdbus-glib-1-dev',
         'libgconf2-dev',
+        'libgtk-3-dev',
         'libgtk2.0-dev',
-        'libgtk-3-dev',
         'libi