Merge inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 19 Dec 2014 21:57:29 -0500
changeset 220720 490f124d7dead47908e22cc50b57b75f324ec6fa
parent 220678 b894d06713da46a914ed06ff3b3b299ea490f614 (current diff)
parent 220719 2e09c000bb8babb3f39170152f37594b1fcd1ecf (diff)
child 220721 4a0daf43d1de211aeb630e557e127f4609f3d7c7
child 220724 52326a6d6a65157a9d362707869f52dec0787cac
child 220735 c5d306ab9407de1fe6d35de60591d81bfc61c2ff
child 220746 5ff0c91afef0f57a760dfab16daecdd5d3d67401
push id27994
push userryanvm@gmail.com
push dateSat, 20 Dec 2014 03:00:19 +0000
treeherdermozilla-central@490f124d7dea [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone37.0a1
first release with
nightly linux32
490f124d7dea / 37.0a1 / 20141220030202 / files
nightly linux64
490f124d7dea / 37.0a1 / 20141220030202 / files
nightly mac
490f124d7dea / 37.0a1 / 20141220030202 / files
nightly win32
490f124d7dea / 37.0a1 / 20141220030202 / files
nightly win64
490f124d7dea / 37.0a1 / 20141220030202 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c. a=merge CLOSED TREE
--- a/build/autoconf/clang-plugin.m4
+++ b/build/autoconf/clang-plugin.m4
@@ -10,21 +10,21 @@ MOZ_ARG_ENABLE_BOOL(clang-plugin,
    ENABLE_CLANG_PLUGIN= )
 if test -n "$ENABLE_CLANG_PLUGIN"; then
     if test -z "$CLANG_CC"; then
         AC_MSG_ERROR([Can't use clang plugin without clang.])
     fi
 
     AC_MSG_CHECKING([for llvm-config])
     if test -z "$LLVMCONFIG"; then
-      LLVMCONFIG=`which llvm-config`
+      LLVMCONFIG=`$CXX -print-prog-name=llvm-config`
     fi
 
     if test -z "$LLVMCONFIG"; then
-      LLVMCONFIG=`$CXX -print-prog-name=llvm-config`
+      LLVMCONFIG=`which llvm-config`
     fi
 
     if test ! -x "$LLVMCONFIG"; then
       AC_MSG_RESULT([not found])
       AC_MSG_ERROR([Cannot find an llvm-config binary for building a clang plugin])
     fi
 
     AC_MSG_RESULT([$LLVMCONFIG])
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -46,18 +46,24 @@ private:
   };
 
   class NonHeapClassChecker : public MatchFinder::MatchCallback {
   public:
     virtual void run(const MatchFinder::MatchResult &Result);
     void noteInferred(QualType T, DiagnosticsEngine &Diag);
   };
 
+  class ArithmeticArgChecker : public MatchFinder::MatchCallback {
+  public:
+    virtual void run(const MatchFinder::MatchResult &Result);
+  };
+
   StackClassChecker stackClassChecker;
   NonHeapClassChecker nonheapClassChecker;
+  ArithmeticArgChecker arithmeticArgChecker;
   MatchFinder astMatcher;
 };
 
 namespace {
 
 bool isInIgnoredNamespace(const Decl *decl) {
   const DeclContext *DC = decl->getDeclContext()->getEnclosingNamespaceContext();
   const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC);
@@ -324,16 +330,59 @@ AST_MATCHER(QualType, nonheapClassAggreg
   return getClassAttrs(Node) == NonHeapClass;
 }
 
 /// This matcher will match any function declaration that is declared as a heap
 /// allocator.
 AST_MATCHER(FunctionDecl, heapAllocator) {
   return MozChecker::hasCustomAnnotation(&Node, "moz_heap_allocator");
 }
+
+/// This matcher will match any declaration that is marked as not accepting
+/// arithmetic expressions in its arguments.
+AST_MATCHER(Decl, noArithmeticExprInArgs) {
+  return MozChecker::hasCustomAnnotation(&Node, "moz_no_arith_expr_in_arg");
+}
+
+/// This matcher will match all arithmetic binary operators.
+AST_MATCHER(BinaryOperator, binaryArithmeticOperator) {
+  BinaryOperatorKind opcode = Node.getOpcode();
+  return opcode == BO_Mul ||
+         opcode == BO_Div ||
+         opcode == BO_Rem ||
+         opcode == BO_Add ||
+         opcode == BO_Sub ||
+         opcode == BO_Shl ||
+         opcode == BO_Shr ||
+         opcode == BO_And ||
+         opcode == BO_Xor ||
+         opcode == BO_Or ||
+         opcode == BO_MulAssign ||
+         opcode == BO_DivAssign ||
+         opcode == BO_RemAssign ||
+         opcode == BO_AddAssign ||
+         opcode == BO_SubAssign ||
+         opcode == BO_ShlAssign ||
+         opcode == BO_ShrAssign ||
+         opcode == BO_AndAssign ||
+         opcode == BO_XorAssign ||
+         opcode == BO_OrAssign;
+}
+
+/// This matcher will match all arithmetic unary operators.
+AST_MATCHER(UnaryOperator, unaryArithmeticOperator) {
+  UnaryOperatorKind opcode = Node.getOpcode();
+  return opcode == UO_PostInc ||
+         opcode == UO_PostDec ||
+         opcode == UO_PreInc ||
+         opcode == UO_PreDec ||
+         opcode == UO_Plus ||
+         opcode == UO_Minus ||
+         opcode == UO_Not;
+}
 }
 }
 
 namespace {
 
 bool isPlacementNew(const CXXNewExpr *expr) {
   // Regular new expressions aren't placement new
   if (expr->getNumPlacementArgs() == 0)
@@ -362,16 +411,43 @@ DiagnosticsMatcher::DiagnosticsMatcher()
   // Any heap allocation function that returns a non-heap or a stack class is
   // definitely doing something wrong
   astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(),
       returns(pointerType(pointee(nonheapClassAggregate()))))))).bind("node"),
     &nonheapClassChecker);
   astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(),
       returns(pointerType(pointee(stackClassAggregate()))))))).bind("node"),
     &stackClassChecker);
+
+  astMatcher.addMatcher(callExpr(allOf(hasDeclaration(noArithmeticExprInArgs()),
+          anyOf(
+              hasDescendant(binaryOperator(allOf(binaryArithmeticOperator(),
+                  hasLHS(hasDescendant(declRefExpr())),
+                  hasRHS(hasDescendant(declRefExpr()))
+              )).bind("node")),
+              hasDescendant(unaryOperator(allOf(unaryArithmeticOperator(),
+                  hasUnaryOperand(allOf(hasType(builtinType()),
+                                        anyOf(hasDescendant(declRefExpr()), declRefExpr())))
+              )).bind("node"))
+          )
+      )).bind("call"),
+    &arithmeticArgChecker);
+  astMatcher.addMatcher(constructExpr(allOf(hasDeclaration(noArithmeticExprInArgs()),
+          anyOf(
+              hasDescendant(binaryOperator(allOf(binaryArithmeticOperator(),
+                  hasLHS(hasDescendant(declRefExpr())),
+                  hasRHS(hasDescendant(declRefExpr()))
+              )).bind("node")),
+              hasDescendant(unaryOperator(allOf(unaryArithmeticOperator(),
+                  hasUnaryOperand(allOf(hasType(builtinType()),
+                                        anyOf(hasDescendant(declRefExpr()), declRefExpr())))
+              )).bind("node"))
+          )
+      )).bind("call"),
+    &arithmeticArgChecker);
 }
 
 void DiagnosticsMatcher::StackClassChecker::run(
     const MatchFinder::MatchResult &Result) {
   DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
   unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID(
     DiagnosticIDs::Error, "variable of type %0 only valid on the stack");
   if (const VarDecl *d = Result.Nodes.getNodeAs<VarDecl>("node")) {
@@ -467,16 +543,29 @@ void DiagnosticsMatcher::NonHeapClassChe
   } else if (const FieldDecl *FD = dyn_cast<FieldDecl>(cause)) {
     Diag.Report(FD->getLocation(), memberID) << T << FD << FD->getType();
   }
   
   // Recursively follow this back.
   noteInferred(cast<ValueDecl>(cause)->getType(), Diag);
 }
 
+void DiagnosticsMatcher::ArithmeticArgChecker::run(
+    const MatchFinder::MatchResult &Result) {
+  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
+  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Error, "cannot pass an arithmetic expression of built-in types to %0");
+  const Expr *expr = Result.Nodes.getNodeAs<Expr>("node");
+  if (const CallExpr *call = Result.Nodes.getNodeAs<CallExpr>("call")) {
+    Diag.Report(expr->getLocStart(), errorID) << call->getDirectCallee();
+  } else if (const CXXConstructExpr *ctr = Result.Nodes.getNodeAs<CXXConstructExpr>("call")) {
+    Diag.Report(expr->getLocStart(), errorID) << ctr->getConstructor();
+  }
+}
+
 class MozCheckAction : public PluginASTAction {
 public:
   ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI, StringRef fileName) override {
 #if CLANG_VERSION_FULL >= 306
     std::unique_ptr<MozChecker> checker(make_unique<MozChecker>(CI));
 
     std::vector<std::unique_ptr<ASTConsumer>> consumers;
     consumers.push_back(std::move(checker));
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestNoArithmeticExprInArgument.cpp
@@ -0,0 +1,32 @@
+#define MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT __attribute__((annotate("moz_no_arith_expr_in_arg")))
+
+struct X {
+  explicit X(int) MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT;
+  void baz(int) MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT;
+};
+
+int operator+(int, X);
+int operator+(X, int);
+int operator++(X);
+
+void badArithmeticsInArgs() {
+  int a;
+  typedef int myint;
+  myint b;
+  X goodObj1(a);
+  goodObj1.baz(b);
+  X badObj1(a + b); // expected-error{{cannot pass an arithmetic expression of built-in types to 'X'}}
+  X badObj2 = X(a ? 0 : ++a); // expected-error{{cannot pass an arithmetic expression of built-in types to 'X'}}
+  X badObj3(~a); // expected-error{{cannot pass an arithmetic expression of built-in types to 'X'}}
+  badObj1.baz(a - 1 - b); // expected-error{{cannot pass an arithmetic expression of built-in types to 'baz'}}
+  badObj1.baz(++a); // expected-error{{cannot pass an arithmetic expression of built-in types to 'baz'}}
+  badObj1.baz(a++); // expected-error{{cannot pass an arithmetic expression of built-in types to 'baz'}}
+  badObj1.baz(a || b);
+  badObj1.baz(a + goodObj1);
+  badObj1.baz(goodObj1 + a);
+  badObj1.baz(++goodObj1);
+  badObj1.baz(-1);
+  badObj1.baz(-1.0);
+  badObj1.baz(1 + 2);
+  badObj1.baz(1 << (sizeof(int)/2));
+}
--- a/build/clang-plugin/tests/moz.build
+++ b/build/clang-plugin/tests/moz.build
@@ -3,14 +3,15 @@
 # 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/.
 
 SOURCES += [
     'TestBadImplicitConversionCtor.cpp',
     'TestCustomHeap.cpp',
     'TestMustOverride.cpp',
+    'TestNoArithmeticExprInArgument.cpp',
     'TestNonHeapClass.cpp',
     'TestStackClass.cpp',
 ]
 
 DISABLE_STL_WRAPPING = True
 NO_VISIBILITY_FLAGS = True
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -119,16 +119,18 @@ skip-if = (buildapp == 'b2g' && toolkit 
 # This test can only run in the main process.
 skip-if = buildapp == 'b2g' || buildapp == 'mulet' || e10s
 [test_blob_simple.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_blob_worker_crash.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_blob_worker_xhr_post.html]
 skip-if = ((buildapp == 'b2g' && toolkit != 'gonk') || (e10s && toolkit == 'windows')) # Bug 931116
+[test_blob_worker_xhr_read.html]
+skip-if = ((buildapp == 'b2g' && toolkit != 'gonk') || (e10s && toolkit == 'windows')) # Bug 931116
 [test_blocked_order.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_bug937006.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_clear.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_complex_keyPaths.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_blob_worker_xhr_read.html
@@ -0,0 +1,114 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Blob Read From Worker</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  /**
+   * Create an IndexedDB-backed Blob, send it to the worker, try and read the
+   * contents of the Blob from the worker using an XHR.  Ideally, we don't
+   * deadlock the main thread.
+   */
+  function testSteps()
+  {
+    const BLOB_DATA = ["Green"];
+    const BLOB_TYPE = "text/plain";
+    const BLOB_SIZE = BLOB_DATA.join("").length;
+
+    info("Setting up");
+
+    let request = indexedDB.open(window.location.pathname, 1);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+    let event = yield undefined;
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    ok(db, "Created database");
+
+    info("Creating objectStore");
+
+    let objectStore = db.createObjectStore("foo", { autoIncrement: true });
+
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield undefined;
+
+    ok(true, "Opened database");
+
+    let blob = new Blob(BLOB_DATA, { type: BLOB_TYPE });
+
+    info("Adding blob to database");
+
+    objectStore = db.transaction("foo", "readwrite").objectStore("foo");
+    objectStore.add(blob).onsuccess = grabEventAndContinueHandler;
+    event = yield undefined;
+
+    let blobKey = event.target.result;
+    ok(blobKey, "Got a key for the blob");
+
+    info("Getting blob from the database");
+
+    objectStore = db.transaction("foo").objectStore("foo");
+    objectStore.get(blobKey).onsuccess = grabEventAndContinueHandler;
+    event = yield undefined;
+
+    blob = event.target.result;
+
+    ok(blob instanceof Blob, "Got a blob");
+    is(blob.size, BLOB_SIZE, "Correct size");
+    is(blob.type, BLOB_TYPE, "Correct type");
+
+    info("Sending blob to a worker");
+
+    function workerScript() {
+      onmessage = function(event) {
+        var blob = event.data;
+        var blobUrl = URL.createObjectURL(blob);
+        var xhr = new XMLHttpRequest();
+        xhr.open('GET', blobUrl, true);
+        xhr.responseType = 'text';
+        xhr.onload = function() {
+          postMessage({ data: xhr.response });
+          URL.revokeObjectURL(blobUrl);
+        };
+        xhr.onerror = function() {
+          postMessage({ data: null });
+          URL.revokeObjectURL(blobUrl);
+        }
+        xhr.send();
+      }
+    }
+
+    let workerScriptUrl =
+      URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
+
+    let xhrWorker = new Worker(workerScriptUrl);
+    xhrWorker.postMessage(blob);
+    xhrWorker.onmessage = grabEventAndContinueHandler;
+    event = yield undefined;
+
+    is(event.data.data, "Green", "XHR returned expected payload.");
+    xhrWorker.terminate();
+
+    URL.revokeObjectURL(workerScriptUrl);
+
+    finishTest();
+    yield undefined;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -2439,16 +2439,18 @@ CreateStreamHelper::GetStream(nsIInputSt
 
   nsRefPtr<RemoteBlobImpl> baseRemoteBlobImpl =
     mRemoteBlobImpl->BaseRemoteBlobImpl();
   MOZ_ASSERT(baseRemoteBlobImpl);
 
   if (EventTargetIsOnCurrentThread(baseRemoteBlobImpl->GetActorEventTarget())) {
     RunInternal(baseRemoteBlobImpl, false);
   } else {
+    MOZ_ASSERT(!NS_IsMainThread());
+
     nsCOMPtr<nsIEventTarget> target = baseRemoteBlobImpl->GetActorEventTarget();
     if (!target) {
       target = do_GetMainThread();
     }
 
     MOZ_ASSERT(target);
 
     nsresult rv = target->Dispatch(this, NS_DISPATCH_NORMAL);
--- a/dom/media/gmp/GMPLoader.cpp
+++ b/dom/media/gmp/GMPLoader.cpp
@@ -158,24 +158,22 @@ GMPLoaderImpl::Load(const char* aLibPath
     assert(top >= bottom);
     SecureZeroMemory(bottom, (top - bottom));
   } else
 #endif
   {
     nodeId = std::string(aOriginSalt, aOriginSalt + aOriginSaltLen);
   }
 
-#if defined(MOZ_GMP_SANDBOX)
   // Start the sandbox now that we've generated the device bound node id.
   // This must happen after the node id is bound to the device id, as
   // generating the device id requires privileges.
   if (mSandboxStarter) {
     mSandboxStarter->Start(aLibPath);
   }
-#endif
 
   // Load the GMP.
   PRLibSpec libSpec;
   libSpec.value.pathname = aLibPath;
   libSpec.type = PR_LibSpec_Pathname;
   mLib = PR_LoadLibraryWithFlags(libSpec, 0);
   if (!mLib) {
     return false;
--- a/dom/media/ogg/OggCodecState.cpp
+++ b/dom/media/ogg/OggCodecState.cpp
@@ -931,17 +931,17 @@ int64_t OpusState::Time(int64_t aGranule
 }
 
 int64_t OpusState::Time(int aPreSkip, int64_t aGranulepos)
 {
   if (aGranulepos < 0)
     return -1;
 
   // Ogg Opus always runs at a granule rate of 48 kHz.
-  CheckedInt64 t = CheckedInt64(aGranulepos - aPreSkip) * USECS_PER_S;
+  CheckedInt64 t = (CheckedInt64(aGranulepos) - aPreSkip) * USECS_PER_S;
   return t.isValid() ? t.value() / 48000 : -1;
 }
 
 bool OpusState::IsHeader(ogg_packet* aPacket)
 {
   return aPacket->bytes >= 16 &&
          (!memcmp(aPacket->packet, "OpusHead", 8) ||
           !memcmp(aPacket->packet, "OpusTags", 8));
@@ -1192,25 +1192,27 @@ bool SkeletonState::DecodeIndex(ogg_pack
   int64_t timeDenom = LittleEndian::readInt64(aPacket->packet + INDEX_TIME_DENOM_OFFSET);
   if (timeDenom == 0) {
     LOG(PR_LOG_DEBUG, ("Ogg Skeleton Index packet for stream %u has 0 "
                        "timestamp denominator.", serialno));
     return (mActive = false);
   }
 
   // Extract the start time.
-  CheckedInt64 t = CheckedInt64(LittleEndian::readInt64(p + INDEX_FIRST_NUMER_OFFSET)) * USECS_PER_S;
+  int64_t timeRawInt = LittleEndian::readInt64(p + INDEX_FIRST_NUMER_OFFSET);
+  CheckedInt64 t = CheckedInt64(timeRawInt) * USECS_PER_S;
   if (!t.isValid()) {
     return (mActive = false);
   } else {
     startTime = t.value() / timeDenom;
   }
 
   // Extract the end time.
-  t = LittleEndian::readInt64(p + INDEX_LAST_NUMER_OFFSET) * USECS_PER_S;
+  timeRawInt = LittleEndian::readInt64(p + INDEX_LAST_NUMER_OFFSET);
+  t = CheckedInt64(timeRawInt) * USECS_PER_S;
   if (!t.isValid()) {
     return (mActive = false);
   } else {
     endTime = t.value() / timeDenom;
   }
 
   // Check the numKeyPoints value read, ensure we're not going to run out of
   // memory while trying to decode the index packet.
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -11,16 +11,18 @@ support-files =
   NetworkPreparationChromeScript.js
   blacksilence.js
   turnConfig.js
 
 [test_dataChannel_basicAudio.html]
 skip-if = toolkit == 'gonk' # Bug 962984 for debug, bug 963244 for opt
 [test_dataChannel_basicAudioVideo.html]
 skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+[test_dataChannel_basicAudioVideoNoBundle.html]
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_dataChannel_basicAudioVideoCombined.html]
 skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_dataChannel_basicDataOnly.html]
 skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
 [test_dataChannel_basicVideo.html]
 skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_dataChannel_bug1013809.html]
 skip-if = toolkit == 'gonk' # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
@@ -58,16 +60,18 @@ skip-if = toolkit == 'gonk' # b2g(Bug 10
 [test_peerConnection_addCandidateInHaveLocalOffer.html]
 skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
 [test_peerConnection_basicAudio.html]
 skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
 [test_peerConnection_basicAudioVideo.html]
 skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_basicAudioVideoCombined.html]
 skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+[test_peerConnection_basicAudioVideoNoBundle.html]
+skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
 [test_peerConnection_basicVideo.html]
 skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_basicScreenshare.html]
 # no screenshare on b2g/android
 # frequent timeouts/crashes on e10s (bug 1048455)
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s
 [test_peerConnection_basicWindowshare.html]
 # no screenshare on b2g/android
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -171,30 +171,36 @@ var commandsPeerConnection = [
     }
   ],
   [
     'PC_LOCAL_CREATE_OFFER',
     function (test) {
       test.createOffer(test.pcLocal, function (offer) {
         is(test.pcLocal.signalingState, STABLE,
            "Local create offer does not change signaling state");
-        if (test.steeplechase) {
-          send_message({"type": "offer",
-                        "offer": test.originalOffer,
-                        "offer_constraints": test.pcLocal.constraints,
-                        "offer_options": test.pcLocal.offerOptions});
-          test._local_offer = test.originalOffer;
-          test._offer_constraints = test.pcLocal.constraints;
-          test._offer_options = test.pcLocal.offerOptions;
-        }
         test.next();
       });
     }
   ],
   [
+    'PC_LOCAL_STEEPLECHASE_SIGNAL_OFFER',
+    function (test) {
+      if (test.steeplechase) {
+        send_message({"type": "offer",
+          "offer": test.originalOffer,
+          "offer_constraints": test.pcLocal.constraints,
+          "offer_options": test.pcLocal.offerOptions});
+        test._local_offer = test.originalOffer;
+        test._offer_constraints = test.pcLocal.constraints;
+        test._offer_options = test.pcLocal.offerOptions;
+      }
+      test.next();
+    }
+  ],
+  [
     'PC_LOCAL_SET_LOCAL_DESCRIPTION',
     function (test) {
       test.setLocalDescription(test.pcLocal, test.originalOffer, HAVE_LOCAL_OFFER, function () {
         is(test.pcLocal.signalingState, HAVE_LOCAL_OFFER,
            "signalingState after local setLocalDescription is 'have-local-offer'");
         test.next();
       });
     }
@@ -830,30 +836,36 @@ var commandsDataChannel = [
   [
     'PC_LOCAL_CREATE_OFFER',
     function (test) {
       test.createOffer(test.pcLocal, function (offer) {
         is(test.pcLocal.signalingState, STABLE,
            "Local create offer does not change signaling state");
         ok(offer.sdp.contains("m=application"),
            "m=application is contained in the SDP");
-        if (test.steeplechase) {
-          send_message({"type": "offer",
-                        "offer": test.originalOffer,
-                        "offer_constraints": test.pcLocal.constraints,
-                        "offer_options": test.pcLocal.offerOptions});
-          test._local_offer = test.pcLocal._last_offer;
-          test._offer_constraints = test.pcLocal.constraints;
-          test._offer_options = test.pcLocal.offerOptions;
-        }
         test.next();
       });
     }
   ],
   [
+    'PC_LOCAL_STEEPLECHASE_SIGNAL_OFFER',
+    function (test) {
+      if (test.steeplechase) {
+        send_message({"type": "offer",
+          "offer": test.originalOffer,
+          "offer_constraints": test.pcLocal.constraints,
+          "offer_options": test.pcLocal.offerOptions});
+        test._local_offer = test.originalOffer;
+        test._offer_constraints = test.pcLocal.constraints;
+        test._offer_options = test.pcLocal.offerOptions;
+      }
+      test.next();
+    }
+  ],
+  [
     'PC_LOCAL_SET_LOCAL_DESCRIPTION',
     function (test) {
       test.setLocalDescription(test.pcLocal, test.originalOffer, HAVE_LOCAL_OFFER,
         function () {
         is(test.pcLocal.signalingState, HAVE_LOCAL_OFFER,
            "signalingState after local setLocalDescription is 'have-local-offer'");
         test.next();
       });
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_dataChannel_basicAudioVideoNoBundle.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="pc.js"></script>
+  <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  SimpleTest.requestFlakyTimeout("untriaged");
+
+  createHTML({
+    bug: "1016476",
+    title: "Basic data channel audio/video connection without bundle"
+  });
+
+  var test;
+  runNetworkTest(function () {
+    test = new DataChannelTest();
+    test.setMediaConstraints([{audio: true}, {video: true}],
+                             [{audio: true}, {video: true}]);
+    test.chain.insertAfter("PC_LOCAL_CREATE_OFFER",
+      [[
+        'PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER',
+        function (test) {
+          // Just replace a=group:BUNDLE with something that will be ignored.
+          test.originalOffer.sdp = test.originalOffer.sdp.replace(
+            "a=group:BUNDLE",
+            "a=foo:");
+          test.next();
+        }
+      ]]
+      );
+    test.run();
+  });
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioVideoNoBundle.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+  <script type="application/javascript" src="pc.js"></script>
+  <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "1016476",
+    title: "Basic audio/video peer connection with no Bundle"
+  });
+
+  SimpleTest.requestFlakyTimeout("WebRTC is full of inherent timeouts");
+
+  var test;
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+    test.chain.insertAfter('PC_LOCAL_CREATE_OFFER',
+    [['PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER',
+      function (test) {
+        test.originalOffer.sdp = test.originalOffer.sdp.replace(
+          /a=group:BUNDLE .*\r\n/g,
+          ""
+        );
+        info("Updated no bundle offer: " + JSON.stringify(test.originalOffer));
+        test.next();
+      }
+    ]]);
+    test.setMediaConstraints([{audio: true}, {video: true}],
+                             [{audio: true}, {video: true}]);
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/dom/workers/URL.cpp
+++ b/dom/workers/URL.cpp
@@ -8,16 +8,19 @@
 #include "nsIDocument.h"
 #include "nsIIOService.h"
 #include "nsPIDOMWindow.h"
 
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/URL.h"
 #include "mozilla/dom/URLBinding.h"
 #include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/ipc/nsIRemoteBlob.h"
+#include "mozilla/ipc/BackgroundChild.h"
 #include "nsGlobalWindow.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsNetCID.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
@@ -78,18 +81,44 @@ public:
     mURL(aURL)
   {
     MOZ_ASSERT(aBlobImpl);
   }
 
   bool
   MainThreadRun()
   {
+    using namespace mozilla::ipc;
+
     AssertIsOnMainThread();
 
+    nsRefPtr<FileImpl> newBlobImplHolder;
+
+    if (nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryInterface(mBlobImpl)) {
+      if (BlobChild* blobChild = remoteBlob->GetBlobChild()) {
+        if (PBackgroundChild* blobManager = blobChild->GetBackgroundManager()) {
+          PBackgroundChild* backgroundManager =
+            BackgroundChild::GetForCurrentThread();
+          MOZ_ASSERT(backgroundManager);
+
+          if (blobManager != backgroundManager) {
+            // Always make sure we have a blob from an actor we can use on this
+            // thread.
+            blobChild = BlobChild::GetOrCreate(backgroundManager, mBlobImpl);
+            MOZ_ASSERT(blobChild);
+
+            newBlobImplHolder = blobChild->GetBlobImpl();
+            MOZ_ASSERT(newBlobImplHolder);
+
+            mBlobImpl = newBlobImplHolder;
+          }
+        }
+      }
+    }
+
     nsCOMPtr<nsIPrincipal> principal;
     nsIDocument* doc = nullptr;
 
     nsCOMPtr<nsPIDOMWindow> window = mWorkerPrivate->GetWindow();
     if (window) {
       doc = window->GetExtantDoc();
       if (!doc) {
         SetDOMStringToNull(mURL);
--- a/gfx/layers/client/ClientPaintedLayer.cpp
+++ b/gfx/layers/client/ClientPaintedLayer.cpp
@@ -157,17 +157,17 @@ ClientLayerManager::CreatePaintedLayer()
 already_AddRefed<PaintedLayer>
 ClientLayerManager::CreatePaintedLayerWithHint(PaintedLayerCreationHint aHint)
 {
   NS_ASSERTION(InConstruction(), "Only allowed in construction phase");
   if (
 #ifdef MOZ_B2G
       aHint == SCROLLABLE &&
 #endif
-      gfxPlatform::GetPlatform()->UseTiling()
+      gfxPrefs::LayersTilesEnabled()
 #ifndef MOZ_X11
       && (AsShadowForwarder()->GetCompositorBackendType() == LayersBackend::LAYERS_OPENGL ||
           AsShadowForwarder()->GetCompositorBackendType() == LayersBackend::LAYERS_D3D9 ||
           AsShadowForwarder()->GetCompositorBackendType() == LayersBackend::LAYERS_D3D11)
 #endif
   ) {
     nsRefPtr<ClientTiledPaintedLayer> layer = new ClientTiledPaintedLayer(this, aHint);
     CREATE_SHADOW(Painted);
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -200,30 +200,30 @@ CompositorVsyncObserver::CompositorVsync
   : mNeedsComposite(false)
   , mIsObservingVsync(false)
   , mCompositorParent(aCompositorParent)
   , mCurrentCompositeTaskMonitor("CurrentCompositeTaskMonitor")
   , mCurrentCompositeTask(nullptr)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWidget != nullptr);
-  mVsyncDispatcher = aWidget->GetVsyncDispatcher();
+  mCompositorVsyncDispatcher = aWidget->GetCompositorVsyncDispatcher();
 #ifdef MOZ_WIDGET_GONK
   GeckoTouchDispatcher::SetCompositorVsyncObserver(this);
 #endif
 }
 
 CompositorVsyncObserver::~CompositorVsyncObserver()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   MOZ_ASSERT(!mIsObservingVsync);
-  // The VsyncDispatcher is cleaned up before this in the nsBaseWidget, which stops vsync listeners
+  // The CompositorVsyncDispatcher is cleaned up before this in the nsBaseWidget, which stops vsync listeners
   CancelCurrentCompositeTask();
   mCompositorParent = nullptr;
-  mVsyncDispatcher = nullptr;
+  mCompositorVsyncDispatcher = nullptr;
   mNeedsComposite = false;
 }
 
 /**
  * TODO Potential performance heuristics:
  * If a composite takes 17 ms, do we composite ASAP or wait until next vsync?
  * If a layer transaction comes after vsync, do we composite ASAP or wait until
  * next vsync?
@@ -296,25 +296,25 @@ CompositorVsyncObserver::NeedsComposite(
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   return mNeedsComposite;
 }
 
 void
 CompositorVsyncObserver::ObserveVsync()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
-  mVsyncDispatcher->SetCompositorVsyncObserver(this);
+  mCompositorVsyncDispatcher->SetCompositorVsyncObserver(this);
   mIsObservingVsync = true;
 }
 
 void
 CompositorVsyncObserver::UnobserveVsync()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread() || NS_IsMainThread());
-  mVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
+  mCompositorVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
   mIsObservingVsync = false;
 }
 
 void
 CompositorVsyncObserver::DispatchTouchEvents(TimeStamp aVsyncTimestamp)
 {
 #ifdef MOZ_WIDGET_GONK
   GeckoTouchDispatcher::NotifyVsync(aVsyncTimestamp);
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -113,17 +113,17 @@ private:
   void NotifyCompositeTaskExecuted();
   void ObserveVsync();
   void UnobserveVsync();
   void DispatchTouchEvents(TimeStamp aVsyncTimestamp);
 
   bool mNeedsComposite;
   bool mIsObservingVsync;
   nsRefPtr<CompositorParent> mCompositorParent;
-  nsRefPtr<VsyncDispatcher> mVsyncDispatcher;
+  nsRefPtr<CompositorVsyncDispatcher> mCompositorVsyncDispatcher;
 
   mozilla::Monitor mCurrentCompositeTaskMonitor;
   CancelableTask* mCurrentCompositeTask;
 };
 
 class CompositorParent MOZ_FINAL : public PCompositorParent,
                                    public ShadowLayersManager
 {
--- a/gfx/thebes/VsyncSource.cpp
+++ b/gfx/thebes/VsyncSource.cpp
@@ -7,60 +7,60 @@
 #include "gfxPlatform.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/VsyncDispatcher.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 void
-VsyncSource::AddVsyncDispatcher(VsyncDispatcher* aVsyncDispatcher)
+VsyncSource::AddCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  GetGlobalDisplay().AddVsyncDispatcher(aVsyncDispatcher);
+  GetGlobalDisplay().AddCompositorVsyncDispatcher(aCompositorVsyncDispatcher);
 }
 
 void
-VsyncSource::RemoveVsyncDispatcher(VsyncDispatcher* aVsyncDispatcher)
+VsyncSource::RemoveCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  GetGlobalDisplay().RemoveVsyncDispatcher(aVsyncDispatcher);
+  GetGlobalDisplay().RemoveCompositorVsyncDispatcher(aCompositorVsyncDispatcher);
 }
 
 VsyncSource::Display&
-VsyncSource::FindDisplay(VsyncDispatcher* aVsyncDispatcher)
+VsyncSource::FindDisplay(CompositorVsyncDispatcher* aCompositorVsyncDispatcher)
 {
   return GetGlobalDisplay();
 }
 
 void
 VsyncSource::Display::NotifyVsync(TimeStamp aVsyncTimestamp)
 {
   // Called on the hardware vsync thread
-  for (size_t i = 0; i < mVsyncDispatchers.Length(); i++) {
-    mVsyncDispatchers[i]->NotifyVsync(aVsyncTimestamp);
+  for (size_t i = 0; i < mCompositorVsyncDispatchers.Length(); i++) {
+    mCompositorVsyncDispatchers[i]->NotifyVsync(aVsyncTimestamp);
   }
 }
 
 VsyncSource::Display::Display()
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 VsyncSource::Display::~Display()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mVsyncDispatchers.Clear();
+  mCompositorVsyncDispatchers.Clear();
 }
 
 void
-VsyncSource::Display::AddVsyncDispatcher(VsyncDispatcher* aVsyncDispatcher)
+VsyncSource::Display::AddCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mVsyncDispatchers.AppendElement(aVsyncDispatcher);
+  mCompositorVsyncDispatchers.AppendElement(aCompositorVsyncDispatcher);
 }
 
 void
-VsyncSource::Display::RemoveVsyncDispatcher(VsyncDispatcher* aVsyncDispatcher)
+VsyncSource::Display::RemoveCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mVsyncDispatchers.RemoveElement(aVsyncDispatcher);
+  mCompositorVsyncDispatchers.RemoveElement(aCompositorVsyncDispatcher);
 }
--- a/gfx/thebes/VsyncSource.h
+++ b/gfx/thebes/VsyncSource.h
@@ -4,47 +4,47 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/RefPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "nsISupportsImpl.h"
 #include "nsTArray.h"
 
 namespace mozilla {
-class VsyncDispatcher;
+class CompositorVsyncDispatcher;
 
 namespace gfx {
 
 // Controls how and when to enable/disable vsync. Lives as long as the
 // gfxPlatform does on the parent process
 class VsyncSource
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncSource)
 public:
   // Controls vsync unique to each display and unique on each platform
   class Display {
     public:
       Display();
       virtual ~Display();
-      void AddVsyncDispatcher(mozilla::VsyncDispatcher* aVsyncDispatcher);
-      void RemoveVsyncDispatcher(mozilla::VsyncDispatcher* aVsyncDispatcher);
+      void AddCompositorVsyncDispatcher(mozilla::CompositorVsyncDispatcher* aCompositorVsyncDispatcher);
+      void RemoveCompositorVsyncDispatcher(mozilla::CompositorVsyncDispatcher* aCompositorVsyncDispatcher);
       // Notified when this display's vsync occurs, on the hardware vsync thread
       void NotifyVsync(mozilla::TimeStamp aVsyncTimestamp);
 
       // These should all only be called on the main thread
       virtual void EnableVsync() = 0;
       virtual void DisableVsync() = 0;
       virtual bool IsVsyncEnabled() = 0;
 
     private:
-      nsTArray<nsRefPtr<mozilla::VsyncDispatcher>> mVsyncDispatchers;
+      nsTArray<nsRefPtr<mozilla::CompositorVsyncDispatcher>> mCompositorVsyncDispatchers;
   }; // end Display
 
-  void AddVsyncDispatcher(mozilla::VsyncDispatcher* aVsyncDispatcher);
-  void RemoveVsyncDispatcher(mozilla::VsyncDispatcher* aVsyncDispatcher);
+  void AddCompositorVsyncDispatcher(mozilla::CompositorVsyncDispatcher* aCompositorVsyncDispatcher);
+  void RemoveCompositorVsyncDispatcher(mozilla::CompositorVsyncDispatcher* aCompositorVsyncDispatcher);
 
 protected:
   virtual Display& GetGlobalDisplay() = 0; // Works across all displays
-  virtual Display& FindDisplay(mozilla::VsyncDispatcher* aVsyncDispatcher);
+  virtual Display& FindDisplay(mozilla::CompositorVsyncDispatcher* aCompositorVsyncDispatcher);
   virtual ~VsyncSource() {}
 }; // VsyncSource
 } // gfx
 } // mozilla
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -269,17 +269,16 @@ public:
     /// non-accelerated canvas.
     virtual bool HaveChoiceOfHWAndSWCanvas();
 
     virtual bool UseAcceleratedSkiaCanvas();
     virtual void InitializeSkiaCacheLimits();
 
     /// These should be used instead of directly accessing the preference,
     /// as different platforms may override the behaviour.
-    virtual bool UseTiling() { return gfxPrefs::LayersTilesEnabledDoNotUseDirectly(); }
     virtual bool UseProgressivePaint() { return gfxPrefs::ProgressivePaintDoNotUseDirectly(); }
 
     void GetAzureBackendInfo(mozilla::widget::InfoObject &aObj) {
       aObj.DefineProperty("AzureCanvasBackend", GetBackendName(mPreferredCanvasBackend));
       aObj.DefineProperty("AzureSkiaAccelerated", UseAcceleratedSkiaCanvas());
       aObj.DefineProperty("AzureFallbackCanvasBackend", GetBackendName(mFallbackCanvasBackend));
       aObj.DefineProperty("AzureContentBackend", GetBackendName(mContentBackend));
     }
--- a/gfx/thebes/gfxPlatformMac.cpp
+++ b/gfx/thebes/gfxPlatformMac.cpp
@@ -15,17 +15,16 @@
 #include "gfxTextRun.h"
 #include "gfxUserFontSet.h"
 
 #include "nsTArray.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/VsyncDispatcher.h"
 #include "qcms.h"
 #include "gfx2DGlue.h"
-#include "gfxPrefs.h"
 
 #include <dlfcn.h>
 #include <CoreVideo/CoreVideo.h>
 
 #include "nsCocoaFeatures.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "VsyncSource.h"
 
@@ -401,26 +400,16 @@ gfxPlatformMac::ReadAntiAliasingThreshol
 bool
 gfxPlatformMac::UseAcceleratedCanvas()
 {
   // Lion or later is required
   return nsCocoaFeatures::OnLionOrLater() && Preferences::GetBool("gfx.canvas.azure.accelerated", false);
 }
 
 bool
-gfxPlatformMac::UseTiling()
-{
-  if (gfxPrefs::LayersTilesForceEnabled()) {
-    return true;
-  }
-  // Tiling seems to be slow on 10.6 so disable it until we figure it out
-  return nsCocoaFeatures::OnLionOrLater() && gfxPlatform::UseTiling();
-}
-
-bool
 gfxPlatformMac::UseProgressivePaint()
 {
   // Progressive painting requires cross-process mutexes, which don't work so
   // well on OS X 10.6 so we disable there.
   return nsCocoaFeatures::OnLionOrLater() && gfxPlatform::UseProgressivePaint();
 }
 
 // This is the renderer output callback function, called on the vsync thread
--- a/gfx/thebes/gfxPlatformMac.h
+++ b/gfx/thebes/gfxPlatformMac.h
@@ -65,17 +65,16 @@ public:
                                         nsTArray<const char*>& aFontList);
 
     virtual bool CanRenderContentToDataSurface() const MOZ_OVERRIDE {
       return true;
     }
 
     bool UseAcceleratedCanvas();
 
-    virtual bool UseTiling() MOZ_OVERRIDE;
     virtual bool UseProgressivePaint() MOZ_OVERRIDE;
     virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() MOZ_OVERRIDE;
 
     // lower threshold on font anti-aliasing
     uint32_t GetAntiAliasingThreshold() { return mFontAntiAliasingThreshold; }
 
 private:
     virtual void GetPlatformCMSOutputProfile(void* &mem, size_t &size);
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -273,18 +273,17 @@ private:
   DECL_GFX_PREF(Live, "layers.dump-texture",                   LayersDumpTexture, bool, false);
 
   // 0 is "no change" for contrast, positive values increase it, negative values
   // decrease it until we hit mid gray at -1 contrast, after that it gets weird.
   DECL_GFX_PREF(Live, "layers.effect.contrast",                LayersEffectContrast, float, 0.0f);
   DECL_GFX_PREF(Live, "layers.effect.grayscale",               LayersEffectGrayscale, bool, false);
   DECL_GFX_PREF(Live, "layers.effect.invert",                  LayersEffectInvert, bool, false);
 
-  DECL_GFX_PREF(Once, "layers.enable-tiles",                   LayersTilesEnabledDoNotUseDirectly, bool, false);
-  DECL_GFX_PREF(Once, "layers.force-enable-tiles",             LayersTilesForceEnabled, bool, false);
+  DECL_GFX_PREF(Once, "layers.enable-tiles",                   LayersTilesEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.tiled-drawtarget.enabled",       TiledDrawTargetEnabled, bool, false);
   // We allow for configurable and rectangular tile size to avoid wasting memory on devices whose
   // screen size does not align nicely to the default tile size. Although layers can be any size,
   // they are often the same size as the screen, especially for width.
   DECL_GFX_PREF(Once, "layers.tile-width",                     LayersTileWidth, int32_t, 256);
   DECL_GFX_PREF(Once, "layers.tile-height",                    LayersTileHeight, int32_t, 256);
   DECL_GFX_PREF(Once, "layers.tiles.adjust",                   LayersTilesAdjust, bool, true);
   DECL_GFX_PREF(Once, "layers.tile-max-pool-size",             LayersTileMaxPoolSize, uint32_t, (uint32_t)50);
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayArrayLiteral.js
@@ -0,0 +1,16 @@
+window.tests.set('globalArrayArrayLiteral', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = [[], ....]",
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+    makeGarbage: (N) => {
+        for (var i = 0; i < N; i++) {
+            garbage[garbageIndex++] = ['foo', 'bar', 'baz', 'baa'];
+            if (garbageIndex == garbage.length)
+                garbageIndex = 0;
+        }
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayLargeArray.js
@@ -0,0 +1,18 @@
+window.tests.set('globalArrayLargeArray', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = [[...], ....]",
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+    makeGarbage: (N) => {
+        var a = new Array(N);
+        for (var i = 0; i < N; i++) {
+            a[i] = N - i;
+        }
+        garbage[garbageIndex++] = a;
+        if (garbageIndex == garbage.length)
+            garbageIndex = 0;
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayNewObject.js
@@ -0,0 +1,16 @@
+window.tests.set('globalArrayNewObject', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = [new Object(), ....]",
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+    makeGarbage: (N) => {
+        for (var i = 0; i < N; i++) {
+            garbage[garbageIndex++] = new Object();
+            if (garbageIndex == garbage.length)
+                garbageIndex = 0;
+        }
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayObjectLiteral.js
@@ -0,0 +1,16 @@
+window.tests.set('globalArrayObjectLiteral', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = [{}, ....]",
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+    makeGarbage: (N) => {
+        for (var i = 0; i < N; i++) {
+            garbage[garbageIndex++] = {a: 'foo', b: 'bar', 0: 'foo', 1: 'bar'};
+            if (garbageIndex == garbage.length)
+                garbageIndex = 0;
+        }
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayReallocArray.js
@@ -0,0 +1,18 @@
+window.tests.set('globalArrayReallocArray', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = [[,,,], ....]",
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+    makeGarbage: (N) => {
+        var a = [];
+        for (var i = 0; i < N; i++) {
+            a[i] = N - i;
+        }
+        garbage[garbageIndex++] = a;
+        if (garbageIndex == garbage.length)
+            garbageIndex = 0;
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/noAllocation.js
@@ -0,0 +1,6 @@
+window.tests.set('noAllocation', {
+    description: "Do not generate any garbage.",
+    load: (N) => {},
+    unload: () => {},
+    makeGarbage: (N) => {}
+});
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/index.html
@@ -0,0 +1,429 @@
+<html>
+<head>
+  <title>GC uBench</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+</head>
+<body onload="onload()" onunload="onunload()">
+
+<!-- Include benchmark modules. -->
+<script>var tests = new Map();</script>
+<script src="benchmarks/noAllocation.js"></script>
+<script src="benchmarks/globalArrayNewObject.js"></script>
+<script src="benchmarks/globalArrayArrayLiteral.js"></script>
+<script src="benchmarks/globalArrayLargeArray.js"></script>
+<script src="benchmarks/globalArrayObjectLiteral.js"></script>
+<script src="benchmarks/globalArrayReallocArray.js"></script>
+
+<!-- Harness implementation. -->
+<script>
+// Per-frame time sampling infra. Also GC'd: hopefully will not perturb things too badly.
+var numSamples = 500;
+var delays = new Array(numSamples);
+var sampleIndex = 0;
+var sampleTime = 16; // ms
+var gHistogram = new Map(); // {ms: count}
+
+// Draw state.
+var stopped = 0;
+var start;
+var prev;
+var ctx;
+
+// Current test state.
+var garbagePerFrame = undefined;
+var garbageTotal = undefined;
+var activeTest = undefined;
+var testDuration = undefined; // ms
+var testState = 'idle';  // One of 'idle' or 'running'.
+var testStart = undefined; // ms
+var testQueue = [];
+
+function xpos(index)
+{
+    return index * 2;
+}
+
+function ypos(delay)
+{
+    var r = 525 - Math.log(delay) * 64;
+    if (r < 5) return 5;
+    return r;
+}
+
+function drawHBar(delay, color, label)
+{
+    ctx.fillStyle = color;
+    ctx.strokeStyle = color;
+    ctx.fillText(label, xpos(numSamples) + 4, ypos(delay) + 3);
+
+    ctx.beginPath();
+    ctx.moveTo(xpos(0), ypos(delay));
+    ctx.lineTo(xpos(numSamples), ypos(delay));
+    ctx.stroke();
+    ctx.strokeStyle = 'rgb(0,0,0)';
+    ctx.fillStyle = 'rgb(0,0,0)';
+}
+
+function drawScale(delay)
+{
+    drawHBar(delay, 'rgb(150,150,150)', `${delay}ms`);
+}
+
+function draw60fps() {
+    drawHBar(1000/60, '#00cf61', '60fps');
+}
+
+function draw30fps() {
+    drawHBar(1000/30, '#cf0061', '30fps');
+}
+
+function drawGraph()
+{
+    ctx.clearRect(0, 0, 1100, 550);
+
+    drawScale(10);
+    draw60fps();
+    drawScale(20);
+    drawScale(30);
+    draw30fps();
+    drawScale(50);
+    drawScale(100);
+    drawScale(200);
+    drawScale(400);
+    drawScale(800);
+
+    var worst = 0, worstpos = 0;
+    ctx.beginPath();
+    for (var i = 0; i < numSamples; i++) {
+        ctx.lineTo(xpos(i), ypos(delays[i]));
+        if (delays[i] >= worst) {
+            worst = delays[i];
+            worstpos = i;
+        }
+    }
+    ctx.stroke();
+
+    ctx.fillStyle = 'rgb(255,0,0)';
+    if (worst)
+        ctx.fillText(''+worst+'ms', xpos(worstpos) - 10, ypos(worst) - 14);
+
+    ctx.beginPath();
+    var where = sampleIndex % numSamples;
+    ctx.arc(xpos(where), ypos(delays[where]), 5, 0, Math.PI*2, true);
+    ctx.fill();
+    ctx.fillStyle = 'rgb(0,0,0)';
+
+    ctx.fillText('Time', 550, 420);
+    ctx.save();
+    ctx.rotate(Math.PI/2);
+    ctx.fillText('Pause between frames (log scale)', 150, -1060);
+    ctx.restore();
+}
+
+function stopstart()
+{
+    if (stopped) {
+        window.requestAnimationFrame(handler);
+        prev = performance.now();
+        start += prev - stopped;
+        document.getElementById('stop').value = 'Pause';
+        stopped = 0;
+    } else {
+        document.getElementById('stop').value = 'Resume';
+        stopped = performance.now();
+    }
+}
+
+function handler(timestamp)
+{
+    if (stopped)
+        return;
+
+    if (testState === 'running' && (timestamp - testStart) > testDuration)
+        end_test(timestamp);
+
+    activeTest.makeGarbage(garbagePerFrame);
+
+    var elt = document.getElementById('data');
+    var delay = timestamp - prev;
+    prev = timestamp;
+
+    // Take the histogram at 10us intervals so that we have enough resolution to capture.
+    // a 16.66[666] target with adequate accuracy.
+    update_histogram(gHistogram, Math.round(delay * 100));
+
+    var t = timestamp - start;
+    var newIndex = Math.round(t / sampleTime);
+    while (sampleIndex < newIndex) {
+        sampleIndex++;
+        delays[sampleIndex % numSamples] = delay;
+    }
+
+    drawGraph();
+    window.requestAnimationFrame(handler);
+}
+
+function update_histogram(histogram, delay)
+{
+    var current = histogram.has(delay) ? histogram.get(delay) : 0;
+    histogram.set(delay, ++current);
+}
+
+function reset_draw_state()
+{
+    for (var i = 0; i < numSamples; i++)
+        delays[i] = 0;
+    start = prev = performance.now();
+    sampleIndex = 0;
+}
+
+function onunload()
+{
+    activeTest.unload();
+    activeTest = undefined;
+}
+
+function onload()
+{
+    // Load initial test duration.
+    duration_changed();
+
+    // Load initial garbage size.
+    garbage_total_changed();
+    garbage_per_frame_changed();
+
+    // Populate the test selection dropdown.
+    var select = document.getElementById("test-selection");
+    for (var [name, test] of tests) {
+        test.name = name;
+        var option = document.createElement("option");
+        option.id = name;
+        option.text = name;
+        option.title = test.description;
+        select.add(option);
+    }
+
+    // Load the initial test.
+    activeTest = tests.get('noAllocation');
+    activeTest.load(garbageTotal);
+
+    // Polyfill rAF.
+    var requestAnimationFrame =
+        window.requestAnimationFrame || window.mozRequestAnimationFrame ||
+        window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
+    window.requestAnimationFrame = requestAnimationFrame;
+
+    // Acquire our canvas.
+    var canvas = document.getElementById('graph');
+    ctx = canvas.getContext('2d');
+
+    // Start drawing.
+    reset_draw_state();
+    window.requestAnimationFrame(handler);
+}
+
+function run_one_test()
+{
+    start_test_cycle([activeTest.name]);
+}
+
+function run_all_tests()
+{
+    start_test_cycle(tests.keys());
+}
+
+function start_test_cycle(tests_to_run)
+{
+    // Convert from an iterable to an array for pop.
+    testQueue = [];
+    for (var key of tests_to_run)
+        testQueue.push(key);
+    testState = 'running';
+    testStart = performance.now();
+    gHistogram.clear();
+
+    change_active_test(testQueue.pop());
+    console.log(`Running test: ${activeTest.name}`);
+    reset_draw_state();
+}
+
+function end_test(timestamp)
+{
+    report_test_result(activeTest, gHistogram);
+    gHistogram.clear();
+    console.log(`Ending test ${activeTest.name}`);
+    if (testQueue.length) {
+        change_active_test(testQueue.pop());
+        console.log(`Running test: ${activeTest.name}`);
+        testStart = timestamp;
+    } else {
+        testState = 'idle';
+        testStart = 0;
+    }
+    reset_draw_state();
+}
+
+function report_test_result(test, histogram)
+{
+    var resultList = document.getElementById('results-display');
+    var resultElem = document.createElement("div");
+    var score = compute_test_score(histogram);
+    var sparks = compute_test_spark_histogram(histogram);
+    resultElem.innerHTML = `${score} ms/s : ${sparks} : ${test.name} - ${test.description}`;
+    resultList.appendChild(resultElem);
+}
+
+// Compute a score based on the total ms we missed frames by per second.
+function compute_test_score(histogram)
+{
+    var score = 0;
+    for (var [delay, count] of histogram) {
+        delay = delay / 100;
+        score += Math.abs((delay - 16.66) * count);
+    }
+    score = score / (testDuration / 1000);
+    return Math.round(score * 1000) / 1000;
+}
+
+// Build a spark-lines histogram for the test results to show with the aggregate score.
+function compute_test_spark_histogram(histogram)
+{
+    var ranges = [
+        [-99999999, 16.6],
+        [16.6, 16.8],
+        [16.8, 25],
+        [25, 33.4],
+        [33.4, 60],
+        [60, 100],
+        [100, 300],
+        [300, 99999999],
+    ];
+    var rescaled = new Map();
+    for (var [delay, count] of histogram) {
+        delay = delay / 100;
+        for (var i = 0; i < ranges.length; ++i) {
+            var low = ranges[i][0];
+            var high = ranges[i][1];
+            if (low <= delay && delay < high) {
+                update_histogram(rescaled, i);
+                break;
+            }
+        }
+    }
+    var total = 0;
+    for (var [i, count] of rescaled)
+        total += count;
+    var sparks = "▁▂▃▄▅▆▇█";
+    var colors = ['#aaaa00', '#007700', '#dd0000', '#ff0000',
+                  '#ff0000', '#ff0000', '#ff0000', '#ff0000'];
+    var line = "";
+    for (var i = 0; i < ranges.length; ++i) {
+        var amt = rescaled.has(i) ? rescaled.get(i) : 0;
+        var spark = sparks.charAt(parseInt(amt/total*8));
+        line += `<span style="color:${colors[i]}">${spark}</span>`;
+    }
+    return line;
+}
+
+function reload_active_test()
+{
+    activeTest.unload();
+    activeTest.load(garbageTotal);
+}
+
+function change_active_test(new_test_name)
+{
+    activeTest.unload();
+    activeTest = tests.get(new_test_name);
+    activeTest.load(garbageTotal);
+}
+
+function duration_changed()
+{
+    var durationInput = document.getElementById('test-duration');
+    testDuration = parseInt(durationInput.value) * 1000;
+    console.log(`Updated test duration to: ${testDuration / 1000} seconds`);
+}
+
+function testchanged()
+{
+    var select = document.getElementById("test-selection");
+    console.log(`Switching to test: ${select.value}`);
+    change_active_test(select.value);
+    gHistogram.clear();
+    reset_draw_state();
+}
+
+function garbage_total_changed()
+{
+    var mult = parseInt(document.getElementById('garbage-total-unit').value);
+    var base = parseInt(document.getElementById('garbage-total-size').value);
+    if (isNaN(mult) || isNaN(base))
+        return;
+    garbageTotal = base * mult;
+    console.log(`Updated garbage-total to ${garbageTotal} items`);
+    if (activeTest)
+        reload_active_test();
+    gHistogram.clear();
+    reset_draw_state();
+}
+
+function garbage_per_frame_changed()
+{
+    var mult = parseInt(document.getElementById('garbage-per-frame-unit').value);
+    var base = parseInt(document.getElementById('garbage-per-frame-size').value);
+    if (isNaN(mult) || isNaN(base))
+        return;
+    garbagePerFrame = base * mult;
+    console.log(`Updated garbage-per-frame to ${garbagePerFrame} items`);
+}
+</script>
+
+<script>
+</script>
+
+<canvas id="graph" width="1080" height="550" style="padding-left:10px"></canvas>
+
+<div>
+    <input type="button" id="stop" value="Pause" onclick="stopstart()"></input>
+</div>
+
+<div>
+    Duration: <input type="text" id="test-duration" size=3 value="8" oninput="duration_changed()"></input>s
+    <input type="button" id="test-one" value="Run Test" onclick="run_one_test()"></input>
+    <input type="button" id="test-all" value="Run All Tests" onclick="run_all_tests()"></input>
+</div>
+
+<div>
+    Currently running test:
+    <select id="test-selection" required onchange="testchanged()"></select>
+</div>
+
+<div>
+    Garbage items per frame:
+    <input type="text" id="garbage-per-frame-size" size=5 value="8"
+           oninput="garbage_per_frame_changed()"></input>
+    <select id="garbage-per-frame-unit" required>
+        <option value="1"></option>
+        <option value="1000" selected="selected">K</option>
+        <option value="1000000">M</option>
+    </select>
+</div>
+<div>
+    Garbage heap size in items:
+    <input type="text" id="garbage-total-size" size=5 value="8"
+           oninput="garbage_total_changed()"></input>
+    <select id="garbage-total-unit" required>
+        <option value="1"></option>
+        <option value="1000">K</option>
+        <option value="1000000" selected="selected">M</option>
+    </select>
+</div>
+
+<div id="results-Area">
+    Test Results:
+    <div id="results-display" style="padding-left: 10px; border: 1px solid black;"></div>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/instanceof-mutate-proto.js
@@ -0,0 +1,13 @@
+function g(i) {
+    with(this) {}; // Don't inline.
+    if (i === 1500)
+	String.prototype.__proto__ = Array.prototype;
+}
+function f() {
+    var arr = new String("A");
+    for (var i=0; i<2000; i++) {
+	g(i);
+	assertEq(arr instanceof Array, i >= 1500);
+    }
+}
+f();
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -11159,16 +11159,105 @@ IonBuilder::jsop_in_dense()
     MInArray *ins = MInArray::New(alloc(), elements, id, initLength, obj, needsHoleCheck);
 
     current->add(ins);
     current->push(ins);
 
     return true;
 }
 
+static bool
+HasOnProtoChain(types::CompilerConstraintList *constraints, types::TypeObjectKey *object,
+                JSObject *protoObject, bool *hasOnProto)
+{
+    MOZ_ASSERT(protoObject);
+
+    while (true) {
+        if (object->unknownProperties() ||
+            !object->clasp()->isNative() ||
+            !object->hasTenuredProto())
+        {
+            return false;
+        }
+
+        // Guard against mutating __proto__.
+        object->hasFlags(constraints, types::OBJECT_FLAG_UNKNOWN_PROPERTIES);
+
+        JSObject *proto = object->proto().toObjectOrNull();
+        if (!proto) {
+            *hasOnProto = false;
+            return true;
+        }
+
+        if (proto == protoObject) {
+            *hasOnProto = true;
+            return true;
+        }
+
+        object = types::TypeObjectKey::get(proto);
+    }
+
+    MOZ_CRASH("Unreachable");
+}
+
+bool
+IonBuilder::tryFoldInstanceOf(MDefinition *lhs, JSObject *protoObject)
+{
+    // Try to fold the js::IsDelegate part of the instanceof operation.
+
+    if (!lhs->mightBeType(MIRType_Object)) {
+        // If the lhs is a primitive, the result is false.
+        lhs->setImplicitlyUsedUnchecked();
+        pushConstant(BooleanValue(false));
+        return true;
+    }
+
+    types::TemporaryTypeSet *lhsTypes = lhs->resultTypeSet();
+    if (!lhsTypes || lhsTypes->unknownObject())
+        return false;
+
+    // We can fold if either all objects have protoObject on their proto chain
+    // or none have.
+    bool isFirst = true;
+    bool knownIsInstance = false;
+
+    for (unsigned i = 0; i < lhsTypes->getObjectCount(); i++) {
+        types::TypeObjectKey *object = lhsTypes->getObject(i);
+        if (!object)
+            continue;
+
+        bool isInstance;
+        if (!HasOnProtoChain(constraints(), object, protoObject, &isInstance))
+            return false;
+
+        if (isFirst) {
+            knownIsInstance = isInstance;
+            isFirst = false;
+        } else if (knownIsInstance != isInstance) {
+            // Some of the objects have protoObject on their proto chain and
+            // others don't, so we can't optimize this.
+            return false;
+        }
+    }
+
+    if (knownIsInstance && lhsTypes->getKnownMIRType() != MIRType_Object) {
+        // The result is true for all objects, but the lhs might be a primitive.
+        // We can't fold this completely but we can use a much faster IsObject
+        // test.
+        MIsObject *isObject = MIsObject::New(alloc(), lhs);
+        current->add(isObject);
+        current->push(isObject);
+        return true;
+    }
+
+    lhs->setImplicitlyUsedUnchecked();
+    pushConstant(BooleanValue(knownIsInstance));
+    return true;
+}
+
 bool
 IonBuilder::jsop_instanceof()
 {
     MDefinition *rhs = current->pop();
     MDefinition *obj = current->pop();
 
     // If this is an 'x instanceof function' operation and we can determine the
     // exact function and prototype object being tested for, use a typed path.
@@ -11185,16 +11274,19 @@ IonBuilder::jsop_instanceof()
         types::HeapTypeSetKey protoProperty =
             rhsType->property(NameToId(names().prototype));
         JSObject *protoObject = protoProperty.singleton(constraints());
         if (!protoObject)
             break;
 
         rhs->setImplicitlyUsedUnchecked();
 
+        if (tryFoldInstanceOf(obj, protoObject))
+            return true;
+
         MInstanceOf *ins = MInstanceOf::New(alloc(), obj, protoObject);
 
         current->add(ins);
         current->push(ins);
 
         return resumeAfter(ins);
     } while (false);
 
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -593,16 +593,18 @@ class IonBuilder
         return length;
     }
 
 
     MDefinition *getCallee();
     MDefinition *getAliasedVar(ScopeCoordinate sc);
     MDefinition *addLexicalCheck(MDefinition *input);
 
+    bool tryFoldInstanceOf(MDefinition *lhs, JSObject *protoObject);
+
     bool jsop_add(MDefinition *left, MDefinition *right);
     bool jsop_bitnot();
     bool jsop_bitop(JSOp op);
     bool jsop_binary(JSOp op);
     bool jsop_binary(JSOp op, MDefinition *left, MDefinition *right);
     bool jsop_pos();
     bool jsop_neg();
     bool jsop_setarg(uint32_t arg);
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1891,17 +1891,17 @@ ScrollFrameHelper::ScrollFrameHelper(nsC
 {
   if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
     mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter));
   }
 
   EnsureImageVisPrefsCached();
 
   if (IsAlwaysActive() &&
-      gfxPlatform::GetPlatform()->UseTiling() &&
+      gfxPrefs::LayersTilesEnabled() &&
       !nsLayoutUtils::UsesAsyncScrolling() &&
       mOuter->GetContent()) {
     // If we have tiling but no APZ, then set a 0-margin display port on
     // active scroll containers so that we paint by whole tile increments
     // when scrolling.
     nsLayoutUtils::SetDisplayPortMargins(mOuter->GetContent(),
                                          mOuter->PresContext()->PresShell(),
                                          ScreenMargin(),
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -210,17 +210,17 @@ pref(svg.marker-improvements.enabled,tru
 == mask-transformed-01.svg mask-transformed-01-ref.svg
 == mask-transformed-02.svg pass.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-01.svg mask-type-01-ref.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-02.svg mask-type-01-ref.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-03.svg mask-type-01-ref.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-04.svg mask-type-01-ref.svg
 == nested-viewBox-01.svg pass.svg
 == nesting-invalid-01.svg nesting-invalid-01-ref.svg
-fuzzy-if(d2d&&/^Windows\x20NT\x206\.1/.test(http.oscpu),1,168) fuzzy-if(azureQuartz&&OSX=="10.8",1,122) == non-scaling-stroke-01.svg non-scaling-stroke-01-ref.svg # bug 1074161 for Win7 and OSX 10.8
+fuzzy-if(d2d&&/^Windows\x20NT\x206\.1/.test(http.oscpu),1,168) fuzzy-if(azureQuartz,1,122) == non-scaling-stroke-01.svg non-scaling-stroke-01-ref.svg # bug 1074161 for Win7 and OSX 10.8
 fuzzy-if(Android||B2G,1,99) fuzzy-if(!contentSameGfxBackendAsCanvas,9,99) == non-scaling-stroke-02.svg non-scaling-stroke-02-ref.svg
 == non-scaling-stroke-03.svg non-scaling-stroke-03-ref.svg
 == objectBoundingBox-and-clipPath.svg pass.svg
 # Bug 588684
 random-if(gtk2Widget) == objectBoundingBox-and-fePointLight-01.svg objectBoundingBox-and-fePointLight-01-ref.svg
 random-if(gtk2Widget) == objectBoundingBox-and-fePointLight-02.svg objectBoundingBox-and-fePointLight-02-ref.svg
 == objectBoundingBox-and-mask.svg pass.svg
 == objectBoundingBox-and-mask-02.svg pass.svg
@@ -270,17 +270,17 @@ pref(svg.paint-order.enabled,true) == pa
 == polyline-points-invalid-01.svg pass.svg
 == pseudo-classes-01.svg pass.svg
 # This test depends on :visited styles (which are asynchronous), so we run
 # it in layout/style/test/test_visited_reftests.html instead of using the
 # reftest harness.
 # == pseudo-classes-02.svg pseudo-classes-02-ref.svg
 == radialGradient-basic-01.svg pass.svg
 == radialGradient-basic-02.svg pass.svg
-fuzzy-if(OSX==10.6&&azureQuartz,4,24) fuzzy-if(OSX==10.8&&azureQuartz,4,15038) fuzzy-if(winWidget,4,92) == radialGradient-basic-03.svg radialGradient-basic-03-ref.svg
+fuzzy-if(azureQuartz,4,15982) fuzzy-if(winWidget,4,92) == radialGradient-basic-03.svg radialGradient-basic-03-ref.svg
 == radialGradient-basic-04.svg pass.svg
 == rect-01.svg pass.svg
 == rect-02.svg pass.svg
 == rect-03.svg pass.svg
 == rect-04.svg pass.svg
 == rect-with-rx-and-ry-01.svg pass.svg
 == rect-with-rx-or-ry-01.svg rect-with-rx-or-ry-01-ref.svg
 == rootElement-null-01.svg pass.svg
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -3605,19 +3605,19 @@ StyleAnimationValue::operator=(const Sty
       NS_ABORT_IF_FALSE(aOther.mValue.mCSSValueTriplet,
                         "value triplets may not be null");
       mValue.mCSSValueTriplet = new nsCSSValueTriplet(*aOther.mValue.mCSSValueTriplet);
       break;
     case eUnit_CSSRect:
       NS_ABORT_IF_FALSE(aOther.mValue.mCSSRect, "rects may not be null");
       mValue.mCSSRect = new nsCSSRect(*aOther.mValue.mCSSRect);
       break;
-    case eUnit_Filter:
     case eUnit_Dasharray:
     case eUnit_Shadow:
+    case eUnit_Filter:
     case eUnit_BackgroundPosition:
       NS_ABORT_IF_FALSE(mUnit == eUnit_Shadow || mUnit == eUnit_Filter ||
                         aOther.mValue.mCSSValueList,
                         "value lists other than shadows and filters may not be null");
       if (aOther.mValue.mCSSValueList) {
         mValue.mCSSValueList = aOther.mValue.mCSSValueList->Clone();
       } else {
         mValue.mCSSValueList = nullptr;
@@ -3843,18 +3843,18 @@ StyleAnimationValue::operator==(const St
       return *mValue.mCSSValue == *aOther.mValue.mCSSValue;
     case eUnit_CSSValuePair:
       return *mValue.mCSSValuePair == *aOther.mValue.mCSSValuePair;
     case eUnit_CSSValueTriplet:
       return *mValue.mCSSValueTriplet == *aOther.mValue.mCSSValueTriplet;
     case eUnit_CSSRect:
       return *mValue.mCSSRect == *aOther.mValue.mCSSRect;
     case eUnit_Dasharray:
+    case eUnit_Shadow:
     case eUnit_Filter:
-    case eUnit_Shadow:
     case eUnit_BackgroundPosition:
       return nsCSSValueList::Equal(mValue.mCSSValueList,
                                    aOther.mValue.mCSSValueList);
     case eUnit_Transform:
       return *mValue.mCSSValueSharedList == *aOther.mValue.mCSSValueSharedList;
     case eUnit_CSSValuePairList:
       return nsCSSValuePairList::Equal(mValue.mCSSValuePairList,
                                        aOther.mValue.mCSSValuePairList);
--- a/layout/style/StyleAnimationValue.h
+++ b/layout/style/StyleAnimationValue.h
@@ -215,18 +215,18 @@ public:
     eUnit_Calc, // nsCSSValue* (never null), always with a single
                 // calc() expression that's either length or length+percent
     eUnit_ObjectPosition, // nsCSSValue* (never null), always with a
                           // 4-entry nsCSSValue::Array
     eUnit_CSSValuePair, // nsCSSValuePair* (never null)
     eUnit_CSSValueTriplet, // nsCSSValueTriplet* (never null)
     eUnit_CSSRect, // nsCSSRect* (never null)
     eUnit_Dasharray, // nsCSSValueList* (never null)
+    eUnit_Shadow, // nsCSSValueList* (may be null)
     eUnit_Filter, // nsCSSValueList* (may be null)
-    eUnit_Shadow, // nsCSSValueList* (may be null)
     eUnit_Transform, // nsCSSValueList* (never null)
     eUnit_BackgroundPosition, // nsCSSValueList* (never null)
     eUnit_CSSValuePairList, // nsCSSValuePairList* (never null)
     eUnit_UnparsedString // nsStringBuffer* (never null)
   };
 
 private:
   Unit mUnit;
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2884,21 +2884,21 @@ public:
     NS_FOR_CSS_HALF_CORNERS(corner) {
       if (mRadius.Get(corner) != zero) {
         return true;
       }
     }
     return false;
   }
   nsStyleCorners& GetRadius() {
-    NS_ASSERTION(mType == eInset, "expected circle or ellipse");
+    NS_ASSERTION(mType == eInset, "expected inset");
     return mRadius;
   }
   const nsStyleCorners& GetRadius() const {
-    NS_ASSERTION(mType == eInset, "expected circle or ellipse");
+    NS_ASSERTION(mType == eInset, "expected inset");
     return mRadius;
   }
 
   // mCoordinates has coordinates for polygon or radii for
   // ellipse and circle.
   nsTArray<nsStyleCoord>& Coordinates()
   {
     return mCoordinates;
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -1,16 +1,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/. */
 
 #include "logging.h"
 
 #include "signaling/src/jsep/JsepSessionImpl.h"
 #include <string>
+#include <set>
 #include <stdlib.h>
 
 #include "nspr.h"
 #include "nss.h"
 #include "pk11pub.h"
 #include "nsDebug.h"
 
 #include <mozilla/Move.h>
@@ -60,28 +61,48 @@ JsepSessionImpl::Init()
     return NS_ERROR_FAILURE;
   }
 
   if (!mUuidGen->Generate(&mDefaultRemoteStreamId)) {
     JSEP_SET_ERROR("Failed to generate default uuid for streams");
     return NS_ERROR_FAILURE;
   }
 
+  if (!mUuidGen->Generate(&mCNAME)) {
+    JSEP_SET_ERROR("Failed to generate CNAME");
+    return NS_ERROR_FAILURE;
+  }
+
   SetupDefaultCodecs();
   SetupDefaultRtpExtensions();
 
   return NS_OK;
 }
 
 nsresult
 JsepSessionImpl::AddTrack(const RefPtr<JsepTrack>& track)
 {
   mLastError.clear();
   MOZ_ASSERT(track->GetDirection() == JsepTrack::kJsepTrackSending);
 
+  if (track->GetMediaType() != SdpMediaSection::kApplication) {
+    track->SetCNAME(mCNAME);
+
+    if (track->GetSsrcs().empty()) {
+      uint32_t ssrc;
+      SECStatus rv = PK11_GenerateRandom(
+          reinterpret_cast<unsigned char*>(&ssrc), sizeof(ssrc));
+      if (rv != SECSuccess) {
+        JSEP_SET_ERROR("Failed to generate SSRC, error=" << rv);
+        return NS_ERROR_FAILURE;
+      }
+      track->AddSsrc(ssrc);
+    }
+  }
+
   JsepSendingTrack strack;
   strack.mTrack = track;
 
   mLocalTracks.push_back(strack);
 
   return NS_OK;
 }
 
@@ -207,29 +228,51 @@ JsepSessionImpl::AddOfferMSectionsByType
       dir = SdpDirectionAttribute::kSendonly;
     }
 
     nsresult rv = CreateOfferMSection(mediatype, dir, proto, sdp);
 
     NS_ENSURE_SUCCESS(rv, rv);
 
     track->mAssignedMLine = Some(sdp->GetMediaSectionCount() - 1);
+
+    AddLocalSsrcs(*track->mTrack,
+                  &sdp->GetMediaSection(*track->mAssignedMLine));
   }
 
   while (offerToReceive.isSome() && added < *offerToReceive) {
     nsresult rv = CreateOfferMSection(
         mediatype, SdpDirectionAttribute::kRecvonly, proto, sdp);
 
     NS_ENSURE_SUCCESS(rv, rv);
     ++added;
   }
 
   return NS_OK;
 }
 
+void
+JsepSessionImpl::SetupBundle(Sdp* sdp) const
+{
+  std::vector<std::string> mids;
+
+  for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
+    auto& attrs = sdp->GetMediaSection(i).GetAttributeList();
+    if (attrs.HasAttribute(SdpAttribute::kMidAttribute)) {
+      mids.push_back(attrs.GetMid());
+    }
+  }
+
+  if (!mids.empty()) {
+    UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
+    groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids);
+    sdp->GetAttributeList().SetAttribute(groupAttr.release());
+  }
+}
+
 nsresult
 JsepSessionImpl::CreateOffer(const JsepOfferOptions& options,
                              std::string* offer)
 {
   mLastError.clear();
 
   switch (mState) {
     case kJsepStateStable:
@@ -269,16 +312,18 @@ JsepSessionImpl::CreateOffer(const JsepO
   }
 
   if (!sdp->GetMediaSectionCount()) {
     JSEP_SET_ERROR("Cannot create an offer with no local tracks, "
                    "no offerToReceiveAudio/Video, and no DataChannel.");
     return NS_ERROR_INVALID_ARG;
   }
 
+  SetupBundle(sdp.get());
+
   *offer = sdp->ToString();
   mGeneratedLocalDescription = Move(sdp);
 
   return NS_OK;
 }
 
 std::string
 JsepSessionImpl::GetLocalDescription() const
@@ -319,16 +364,42 @@ JsepSessionImpl::AddExtmap(SdpMediaSecti
 
   if (extensions && !extensions->empty()) {
     SdpExtmapAttributeList* extmap = new SdpExtmapAttributeList;
     extmap->mExtmaps = *extensions;
     msection->GetAttributeList().SetAttribute(extmap);
   }
 }
 
+void
+JsepSessionImpl::AddMid(const std::string& mid,
+                        SdpMediaSection* msection) const
+{
+  msection->GetAttributeList().SetAttribute(new SdpStringAttribute(
+        SdpAttribute::kMidAttribute, mid));
+}
+
+void
+JsepSessionImpl::AddLocalSsrcs(const JsepTrack& track,
+                               SdpMediaSection* msection) const
+{
+  UniquePtr<SdpSsrcAttributeList> ssrcs(new SdpSsrcAttributeList);
+  for (auto i = track.GetSsrcs().begin(); i != track.GetSsrcs().end(); ++i) {
+    // When using ssrc attributes, we are required to at least have a cname.
+    // (See https://tools.ietf.org/html/rfc5576#section-6.1)
+    std::string cnameAttr("cname:");
+    cnameAttr += track.GetCNAME();
+    ssrcs->PushEntry(*i, cnameAttr);
+  }
+
+  if (!ssrcs->mSsrcs.empty()) {
+    msection->GetAttributeList().SetAttribute(ssrcs.release());
+  }
+}
+
 JsepCodecDescription*
 JsepSessionImpl::FindMatchingCodec(const std::string& fmt,
                                    const SdpMediaSection& msection) const
 {
   for (auto c = mCodecs.begin(); c != mCodecs.end(); ++c) {
     auto codec = *c;
     if (codec->mEnabled && codec->Matches(fmt, msection)) {
       return codec;
@@ -433,16 +504,24 @@ JsepSessionImpl::CreateAnswer(const Jsep
   UniquePtr<Sdp> sdp;
 
   // Make the basic SDP that is common to offer/answer.
   nsresult rv = CreateGenericSDP(&sdp);
   NS_ENSURE_SUCCESS(rv, rv);
 
   const Sdp& offer = *mPendingRemoteDescription;
 
+  auto* group = FindBundleGroup(offer);
+  if (group) {
+    // Copy the bundle group into our answer
+    UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
+    groupAttr->mGroups.push_back(*group);
+    sdp->GetAttributeList().SetAttribute(groupAttr.release());
+  }
+
   size_t numMsections = offer.GetMediaSectionCount();
 
   for (size_t i = 0; i < numMsections; ++i) {
     const SdpMediaSection& remoteMsection = offer.GetMediaSection(i);
     SdpMediaSection& msection =
         sdp->AddMediaSection(remoteMsection.GetMediaType(),
                              SdpDirectionAttribute::kSendrecv,
                              9,
@@ -531,26 +610,37 @@ JsepSessionImpl::CreateOfferMSection(Sdp
 
   rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
   NS_ENSURE_SUCCESS(rv, rv);
 
   AddCodecs(msection);
 
   AddExtmap(msection);
 
+  std::ostringstream osMid;
+  osMid << "sdparta_" << msection->GetLevel();
+  AddMid(osMid.str(), msection);
+
   return NS_OK;
 }
 
 nsresult
 JsepSessionImpl::CreateAnswerMSection(const JsepAnswerOptions& options,
                                       size_t mlineIndex,
                                       const SdpMediaSection& remoteMsection,
                                       SdpMediaSection* msection,
                                       Sdp* sdp)
 {
+  if (remoteMsection.GetPort() == 0 &&
+      !remoteMsection.GetAttributeList().HasAttribute(
+        SdpAttribute::kBundleOnlyAttribute)) {
+    DisableMsection(*sdp, *msection);
+    return NS_OK;
+  }
+
   SdpSetupAttribute::Role role;
   nsresult rv = DetermineAnswererSetupRole(remoteMsection, &role);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = AddTransportAttributes(msection, role);
   NS_ENSURE_SUCCESS(rv, rv);
 
   SdpDirectionAttribute::Direction remoteDirection =
@@ -566,16 +656,18 @@ JsepSessionImpl::CreateAnswerMSection(co
          ++track) {
       if (track->mAssignedMLine.isSome())
         continue;
 
       // TODO(bug 1095218): Pay attention to msid
       if (track->mTrack->GetMediaType() != remoteMsection.GetMediaType())
         continue;
 
+      AddLocalSsrcs(*track->mTrack, msection);
+
       localDirection = SdpDirectionAttribute::kSendonly;
       track->mAssignedMLine = Some(mlineIndex);
       found = true;
       break;
     }
 
     if (!found &&
         remoteMsection.GetMediaType() == SdpMediaSection::kApplication) {
@@ -597,47 +689,42 @@ JsepSessionImpl::CreateAnswerMSection(co
     }
   }
 
   if (remoteDirection & SdpDirectionAttribute::kSendFlag) {
     localDirection = static_cast<SdpDirectionAttribute::Direction>(
         localDirection | SdpDirectionAttribute::kRecvFlag);
   }
 
-  msection->GetAttributeList().SetAttribute(
-      new SdpDirectionAttribute(localDirection));
+  auto& remoteAttrs = remoteMsection.GetAttributeList();
+  auto& localAttrs = msection->GetAttributeList();
+
+  localAttrs.SetAttribute(new SdpDirectionAttribute(localDirection));
 
-  if (remoteMsection.GetAttributeList().HasAttribute(
-          SdpAttribute::kRtcpMuxAttribute)) {
+  if (remoteAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
     // If we aren't using a protocol with RTCP, just smile and nod.
-    msection->GetAttributeList().SetAttribute(
+    localAttrs.SetAttribute(
         new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
   }
 
+  // Reflect mid
+  if (remoteAttrs.HasAttribute(SdpAttribute::kMidAttribute)) {
+    localAttrs.SetAttribute(new SdpStringAttribute(SdpAttribute::kMidAttribute,
+                                                   remoteAttrs.GetMid()));
+  }
+
   // Now add the codecs.
   AddCommonCodecs(remoteMsection, msection);
 
   // Add extmap attributes.
   AddCommonExtmaps(remoteMsection, msection);
 
   if (msection->GetFormats().empty()) {
     // Could not negotiate anything. Disable m-section.
-
-    // Clear out attributes.
-    msection->GetAttributeList().Clear();
-
-    // We need to have something here to fit the grammar
-    // TODO(bcampen@mozilla.com): What's the accepted way of doing this? Just
-    // add the codecs we do support? Does it matter?
-    msection->AddCodec("111", "NULL", 0, 0);
-
-    auto* direction =
-        new SdpDirectionAttribute(SdpDirectionAttribute::kInactive);
-    msection->GetAttributeList().SetAttribute(direction);
-    msection->SetPort(0);
+    DisableMsection(*sdp, *msection);
   }
 
   return NS_OK;
 }
 
 nsresult
 JsepSessionImpl::DetermineAnswererSetupRole(
     const SdpMediaSection& remoteMsection,
@@ -730,20 +817,21 @@ JsepSessionImpl::SetLocalDescription(Jse
 
   // Check that content hasn't done anything unsupported with the SDP
   rv = ValidateLocalDescription(*parsed);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Create transport objects.
   size_t numMsections = parsed->GetMediaSectionCount();
   for (size_t t = 0; t < numMsections; ++t) {
-    if (t < mTransports.size())
+    if (t < mTransports.size()) {
+      mTransports[t]->mState = JsepTransport::kJsepTransportOffered;
       continue; // This transport already exists (assume we are renegotiating).
+    }
 
-    // TODO(bug 1016476): Deal with bundle-only and the like.
     RefPtr<JsepTransport> transport;
     nsresult rv = CreateTransport(parsed->GetMediaSection(t), &transport);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mTransports.push_back(transport);
   }
 
   switch (type) {
@@ -866,84 +954,103 @@ FindTrackForMSection(const SdpMediaSecti
 
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 nsresult
 JsepSessionImpl::HandleNegotiatedSession(const UniquePtr<Sdp>& local,
                                          const UniquePtr<Sdp>& remote)
 {
+  if (local->GetMediaSectionCount() != remote->GetMediaSectionCount()) {
+    JSEP_SET_ERROR("Local and remote SDP have different number of m-lines "
+                   << "(" << local->GetMediaSectionCount() << " vs "
+                   << remote->GetMediaSectionCount() << ")");
+    return NS_ERROR_INVALID_ARG;
+  }
+
   bool remoteIceLite =
       remote->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);
 
   mIceControlling = remoteIceLite || mIsOfferer;
 
-  if (local->GetMediaSectionCount() != remote->GetMediaSectionCount()) {
-    JSEP_SET_ERROR("Local and remote SDP have different number of m-lines "
-                   << "(" << local->GetMediaSectionCount() << " vs "
-                   << remote->GetMediaSectionCount() << ")");
-    return NS_ERROR_INVALID_ARG;
+  const Sdp& offer = mIsOfferer ? *local : *remote;
+  const Sdp& answer = mIsOfferer ? *remote : *local;
+
+  std::set<std::string> bundleMids;
+  const SdpMediaSection* bundleMsection = nullptr;
+
+  // TODO(bug 1112692): Support more than one bundle group
+  auto* group = FindBundleGroup(answer);
+  if (group && !group->tags.empty()) {
+    bundleMids.insert(group->tags.begin(), group->tags.end());
+    bundleMsection = FindMsectionByMid(answer, group->tags[0]);
+
+    if (!bundleMsection) {
+      JSEP_SET_ERROR("mid specified for bundle transport in group attribute"
+          " does not exist in the SDP. (mid="
+          << group->tags[0] << ")");
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    if (!bundleMsection->GetPort()) {
+      JSEP_SET_ERROR("mid specified for bundle transport in group attribute"
+          " points at a disabled m-section. (mid="
+          << group->tags[0] << ")");
+      return NS_ERROR_INVALID_ARG;
+    }
   }
 
   std::vector<JsepTrackPair> trackPairs;
 
   // Now walk through the m-sections, make sure they match, and create
   // track pairs that describe the media to be set up.
   for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) {
     const SdpMediaSection& lm = local->GetMediaSection(i);
     const SdpMediaSection& rm = remote->GetMediaSection(i);
-    const SdpMediaSection& offer = mIsOfferer ? lm : rm;
-    const SdpMediaSection& answer = mIsOfferer ? rm : lm;
+    const SdpMediaSection& offerMsection = offer.GetMediaSection(i);
+    const SdpMediaSection& answerMsection = answer.GetMediaSection(i);
 
     if (lm.GetMediaType() != rm.GetMediaType()) {
-      JSEP_SET_ERROR("Answer and offer have different media types at m-line "
-                     << i);
+      JSEP_SET_ERROR(
+          "Answer and offerMsection have different media types at m-line "
+          << i);
       return NS_ERROR_INVALID_ARG;
     }
 
-    RefPtr<JsepTransport> transport;
-
-    // Transports are created in SetLocal.
-    // TODO(bug 1016476): This will need to be changed for bundle.
-    MOZ_ASSERT(mTransports.size() > i);
-    if (mTransports.size() < i) {
-      JSEP_SET_ERROR("Fewer transports set up than m-lines");
-      return NS_ERROR_FAILURE;
-    }
-    transport = mTransports[i];
-
-    // If the answer says it's inactive we're not doing anything with it.
+    // Skip disabled m-sections.
     // TODO(bug 1017888): Need to handle renegotiation somehow.
-    // Note: the SDP engine guarantees the presence of an SDP direction
-    // attribute (with sendrecv as the default if one isn't in the SDP).
-    if (answer.GetDirectionAttribute().mValue ==
-            SdpDirectionAttribute::kInactive &&
-        answer.GetPort() == 0) {
-      transport->mState = JsepTransport::kJsepTransportClosed;
+    if (answerMsection.GetPort() == 0) {
+      // Transports start out in closed, so we don't need to do anything here.
       continue;
     }
 
     bool sending;
     bool receiving;
 
     nsresult rv = DetermineSendingDirection(
-        offer.GetDirectionAttribute().mValue,
-        answer.GetDirectionAttribute().mValue, &sending, &receiving);
+        offerMsection.GetDirectionAttribute().mValue,
+        answerMsection.GetDirectionAttribute().mValue, &sending, &receiving);
     NS_ENSURE_SUCCESS(rv, rv);
 
     MOZ_MTLOG(ML_DEBUG, "Negotiated m= line"
                             << " index=" << i << " type=" << lm.GetMediaType()
                             << " sending=" << sending
                             << " receiving=" << receiving);
 
     JsepTrackPair jpair;
 
-    // TODO(bug 1016476): Set the bundle level.
     jpair.mLevel = i;
 
+    if (answerMsection.GetAttributeList().HasAttribute(
+          SdpAttribute::kMidAttribute)) {
+      if (bundleMids.count(answerMsection.GetAttributeList().GetMid())) {
+        jpair.mBundleLevel = Some(bundleMsection->GetLevel());
+      }
+    }
+
     RefPtr<JsepTrack> track;
     if (sending) {
       rv = FindTrackForMSection(lm, mLocalTracks, i, &track);
       if (NS_FAILED(rv)) {
         JSEP_SET_ERROR("Failed to find local track for level " << i
                        << " in local SDP. This should never happen.");
         NS_ASSERTION(false, "Failed to find local track for level");
         return NS_ERROR_FAILURE;
@@ -962,49 +1069,95 @@ JsepSessionImpl::HandleNegotiatedSession
                        << " in remote SDP. This should never happen.");
         NS_ASSERTION(false, "Failed to find remote track for level");
         return NS_ERROR_FAILURE;
       }
 
       rv = NegotiateTrack(rm, lm, JsepTrack::kJsepTrackReceiving, &track);
       NS_ENSURE_SUCCESS(rv, rv);
 
+      if (rm.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
+        auto& ssrcs = rm.GetAttributeList().GetSsrc().mSsrcs;
+        for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) {
+          track->AddSsrc(i->ssrc);
+        }
+      }
+
+      if (jpair.mBundleLevel.isSome() &&
+          track->GetSsrcs().empty() &&
+          track->GetMediaType() != SdpMediaSection::kApplication) {
+        MOZ_MTLOG(ML_ERROR, "Bundled m-section has no ssrc attributes. "
+                            "This may cause media packets to be dropped.");
+      }
+
       jpair.mReceiving = track;
     }
 
+    RefPtr<JsepTransport> transport;
+
+    // The transport details are not necessarily on the m-section we're
+    // currently processing.
+    size_t transportLevel = i;
+    if (jpair.mBundleLevel.isSome()) {
+      transportLevel = *jpair.mBundleLevel;
+    }
+
+    // Transports are created in SetLocal.
+    MOZ_ASSERT(mTransports.size() > transportLevel);
+    if (mTransports.size() < transportLevel) {
+      JSEP_SET_ERROR("Fewer transports set up than m-lines");
+      return NS_ERROR_FAILURE;
+    }
+    transport = mTransports[transportLevel];
+
+    // If doing bundle, we need to grab all of the transport specifics from the
+    // bundle m-section, not the m-section we're currently processing.
+    auto& remoteTransportAttrs =
+      remote->GetMediaSection(transportLevel).GetAttributeList();
+
+    auto& answerTransportAttrs =
+      answer.GetMediaSection(transportLevel).GetAttributeList();
+
+    auto& offerTransportAttrs =
+      offer.GetMediaSection(transportLevel).GetAttributeList();
+
     rv = SetupTransport(
-        rm.GetAttributeList(), answer.GetAttributeList(), transport);
+        remoteTransportAttrs, answerTransportAttrs, transport);
     NS_ENSURE_SUCCESS(rv, rv);
     jpair.mRtpTransport = transport;
 
     if (HasRtcp(lm.GetProtocol())) {
       // RTCP MUX or not.
       // TODO(bug 1095743): verify that the PTs are consistent with mux.
-      if (offer.GetAttributeList().HasAttribute(
-              SdpAttribute::kRtcpMuxAttribute) &&
-          answer.GetAttributeList().HasAttribute(
-              SdpAttribute::kRtcpMuxAttribute)) {
+      if (offerTransportAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute) &&
+          answerTransportAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
         jpair.mRtcpTransport = nullptr; // We agree on mux.
         MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is on");
       } else {
         MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is off");
-        rv = SetupTransport(
-            rm.GetAttributeList(), answer.GetAttributeList(), transport);
-        NS_ENSURE_SUCCESS(rv, rv);
-
         jpair.mRtcpTransport = transport;
       }
     }
 
     trackPairs.push_back(jpair);
   }
 
+  nsresult rv = SetUniquePayloadTypes();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // Ouch, this probably needs some dirty bit instead of just clearing
   // stuff for renegotiation.
   mNegotiatedTrackPairs = trackPairs;
+
+  // Mark transports that weren't accepted as closed
+  for (auto i = mTransports.begin(); i != mTransports.end(); ++i) {
+    if ((*i)->mState != JsepTransport::kJsepTransportAccepted) {
+      (*i)->mState = JsepTransport::kJsepTransportClosed;
+    }
+  }
   return NS_OK;
 }
 
 nsresult
 JsepSessionImpl::NegotiateTrack(const SdpMediaSection& remoteMsection,
                                 const SdpMediaSection& localMsection,
                                 JsepTrack::Direction direction,
                                 RefPtr<JsepTrack>* track)
@@ -1031,16 +1184,28 @@ JsepSessionImpl::NegotiateTrack(const Sd
     // in order to negotiate.
     JsepCodecDescription* negotiated =
         codec->MakeNegotiatedCodec(remoteMsection, *fmt, sending);
 
     if (!negotiated) {
       continue;
     }
 
+    if (remoteMsection.GetMediaType() == SdpMediaSection::kAudio ||
+        remoteMsection.GetMediaType() == SdpMediaSection::kVideo) {
+      // Sanity-check that payload type can work with RTP
+      uint16_t payloadType;
+      if (!negotiated->GetPtAsInt(&payloadType) ||
+          payloadType > UINT8_MAX) {
+        JSEP_SET_ERROR("audio/video payload type is not an 8 bit unsigned int: "
+                       << *fmt);
+        return NS_ERROR_INVALID_ARG;
+      }
+    }
+
     negotiatedDetails->mCodecs.push_back(negotiated);
     break;
   }
 
   if (negotiatedDetails->mCodecs.empty()) {
     JSEP_SET_ERROR("Failed to negotiate codec details for all codecs");
     return NS_ERROR_INVALID_ARG;
   }
@@ -1118,19 +1283,16 @@ JsepSessionImpl::SetupTransport(const Sd
                         ? JsepDtlsTransport::kJsepDtlsClient
                         : JsepDtlsTransport::kJsepDtlsServer;
     }
   }
 
   transport->mIce = Move(ice);
   transport->mDtls = Move(dtls);
 
-  // TODO(bug 1016476): If we are doing bundle, and this is not the bundle
-  // level, we should be marking this Closed, right?
-
   if (answer.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
     transport->mComponents = 1;
   }
 
   transport->mState = JsepTransport::kJsepTransportAccepted;
 
   return NS_OK;
 }
@@ -1370,16 +1532,19 @@ JsepSessionImpl::CreateReceivingTrack(si
     JSEP_SET_ERROR("Failed to generate UUID for JsepTrack");
     return NS_ERROR_FAILURE;
   }
 
   JsepTrack* remote = new JsepTrack(msection.GetMediaType(),
                                     mDefaultRemoteStreamId,
                                     trackId,
                                     JsepTrack::kJsepTrackReceiving);
+
+  remote->SetCNAME(GetCNAME(msection));
+
   JsepReceivingTrack rtrack;
   rtrack.mTrack = remote;
   rtrack.mAssignedMLine = Some(mline);
   mRemoteTracks.push_back(rtrack);
 
   return NS_OK;
 }
 
@@ -1643,16 +1808,177 @@ JsepSessionImpl::EndOfLocalCandidates(co
     if (!mIsOfferer) {
       attrs.RemoveAttribute(SdpAttribute::kIceOptionsAttribute);
     }
   }
 
   return NS_OK;
 }
 
+const SdpMediaSection*
+JsepSessionImpl::FindMsectionByMid(const Sdp& sdp,
+                                   const std::string& mid) const
+{
+  for (size_t i = 0; i < sdp.GetMediaSectionCount(); ++i) {
+    auto& attrs = sdp.GetMediaSection(i).GetAttributeList();
+    if (attrs.HasAttribute(SdpAttribute::kMidAttribute) &&
+        attrs.GetMid() == mid) {
+      return &sdp.GetMediaSection(i);
+    }
+  }
+  return nullptr;
+}
+
+const SdpGroupAttributeList::Group*
+JsepSessionImpl::FindBundleGroup(const Sdp& sdp) const
+{
+  if (sdp.GetAttributeList().HasAttribute(SdpAttribute::kGroupAttribute)) {
+    auto& groups = sdp.GetAttributeList().GetGroup().mGroups;
+    for (auto i = groups.begin(); i != groups.end(); ++i) {
+      if (i->semantics == SdpGroupAttributeList::kBundle) {
+        return &(*i);
+      }
+    }
+  }
+
+  return nullptr;
+}
+
+void
+JsepSessionImpl::DisableMsection(Sdp& sdp, SdpMediaSection& msection) const
+{
+  // Make sure to remove the mid from any group attributes
+  if (msection.GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) {
+    std::string mid = msection.GetAttributeList().GetMid();
+    if (sdp.GetAttributeList().HasAttribute(SdpAttribute::kGroupAttribute)) {
+      UniquePtr<SdpGroupAttributeList> newGroupAttr(new SdpGroupAttributeList(
+            sdp.GetAttributeList().GetGroup()));
+      newGroupAttr->RemoveMid(mid);
+      sdp.GetAttributeList().SetAttribute(newGroupAttr.release());
+    }
+  }
+
+  // Clear out attributes.
+  msection.GetAttributeList().Clear();
+
+  // We need to have something here to fit the grammar
+  // TODO(bcampen@mozilla.com): What's the accepted way of doing this? Just
+  // add the codecs we do support? Does it matter?
+  msection.AddCodec("111", "NULL", 0, 0);
+
+  auto* direction =
+    new SdpDirectionAttribute(SdpDirectionAttribute::kInactive);
+  msection.GetAttributeList().SetAttribute(direction);
+  msection.SetPort(0);
+}
+
+nsresult
+JsepSessionImpl::GetAllPayloadTypes(
+    const JsepTrackNegotiatedDetails& trackDetails,
+    std::vector<uint8_t>* payloadTypesOut)
+{
+  for (size_t j = 0; j < trackDetails.GetCodecCount(); ++j) {
+    const JsepCodecDescription* codec;
+    nsresult rv = trackDetails.GetCodec(j, &codec);
+    if (NS_FAILED(rv)) {
+      JSEP_SET_ERROR("GetCodec failed in GetAllPayloadTypes. rv="
+                     << static_cast<uint32_t>(rv));
+      MOZ_ASSERT(false);
+      return NS_ERROR_FAILURE;
+    }
+
+    uint16_t payloadType;
+    if (!codec->GetPtAsInt(&payloadType) || payloadType > UINT8_MAX) {
+      JSEP_SET_ERROR("Non-UINT8 payload type in GetAllPayloadTypes ("
+                     << codec->mType
+                     << "), this should have been caught sooner.");
+      MOZ_ASSERT(false);
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    payloadTypesOut->push_back(payloadType);
+  }
+
+  return NS_OK;
+}
+
+// When doing bundle, if all else fails we can try to figure out which m-line a
+// given RTP packet belongs to by looking at the payload type field. This only
+// works, however, if that payload type appeared in only one m-section.
+// We figure that out here.
+nsresult
+JsepSessionImpl::SetUniquePayloadTypes()
+{
+  // Maps to track details if no other track contains the payload type,
+  // otherwise maps to nullptr.
+  std::map<uint8_t, JsepTrackNegotiatedDetails*> payloadTypeToDetailsMap;
+
+  for (size_t i = 0; i < mRemoteTracks.size(); ++i) {
+    auto track = mRemoteTracks[i].mTrack;
+
+    if (track->GetMediaType() == SdpMediaSection::kApplication) {
+      continue;
+    }
+
+    auto* details = track->GetNegotiatedDetails();
+    if (!details) {
+      // Can happen if negotiation fails on a track
+      continue;
+    }
+
+    // Renegotiation might cause a PT to no longer be unique
+    details->ClearUniquePayloadTypes();
+
+    std::vector<uint8_t> payloadTypesForTrack;
+    nsresult rv = GetAllPayloadTypes(*details, &payloadTypesForTrack);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    for (auto j = payloadTypesForTrack.begin();
+         j != payloadTypesForTrack.end();
+         ++j) {
+
+      if (payloadTypeToDetailsMap.count(*j)) {
+        // Found in more than one track, not unique
+        payloadTypeToDetailsMap[*j] = nullptr;
+      } else {
+        payloadTypeToDetailsMap[*j] = details;
+      }
+    }
+  }
+
+  for (auto i = payloadTypeToDetailsMap.begin();
+       i != payloadTypeToDetailsMap.end();
+       ++i) {
+    uint8_t uniquePt = i->first;
+    auto trackDetails = i->second;
+
+    if (!trackDetails) {
+      continue;
+    }
+
+    trackDetails->AddUniquePayloadType(uniquePt);
+  }
+
+  return NS_OK;
+}
+
+std::string
+JsepSessionImpl::GetCNAME(const SdpMediaSection& msection) const
+{
+  if (msection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
+    auto& ssrcs = msection.GetAttributeList().GetSsrc().mSsrcs;
+    for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) {
+      if (i->attribute.find("cname:") == 0) {
+        return i->attribute.substr(6);
+      }
+    }
+  }
+  return "";
+}
+
 nsresult
 JsepSessionImpl::Close()
 {
   mLastError.clear();
   SetState(kJsepStateClosed);
   return NS_OK;
 }
 
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -203,16 +203,18 @@ private:
     RefPtr<JsepTrack> mTrack;
     Maybe<size_t> mAssignedMLine;
   };
 
   // Non-const so it can set mLastError
   nsresult CreateGenericSDP(UniquePtr<Sdp>* sdp);
   void AddCodecs(SdpMediaSection* msection) const;
   void AddExtmap(SdpMediaSection* msection) const;
+  void AddMid(const std::string& mid, SdpMediaSection* msection) const;
+  void AddLocalSsrcs(const JsepTrack& track, SdpMediaSection* msection) const;
   JsepCodecDescription* FindMatchingCodec(
       const std::string& pt,
       const SdpMediaSection& msection) const;
   const std::vector<SdpExtmapAttributeList::Extmap>* GetRtpExtensions(
       SdpMediaSection::MediaType type) const;
   void AddCommonCodecs(const SdpMediaSection& remoteMsection,
                        SdpMediaSection* msection);
   void AddCommonExtmaps(const SdpMediaSection& remoteMsection,
@@ -236,16 +238,17 @@ private:
                                      SdpDirectionAttribute::Direction answer,
                                      bool* sending, bool* receiving);
   nsresult AddTransportAttributes(SdpMediaSection* msection,
                                   SdpSetupAttribute::Role dtlsRole);
   // Non-const so it can assign m-line index to tracks
   nsresult AddOfferMSectionsByType(SdpMediaSection::MediaType type,
                                    Maybe<size_t> offerToReceive,
                                    Sdp* sdp);
+  void SetupBundle(Sdp* sdp) const;
   nsresult CreateOfferMSection(SdpMediaSection::MediaType type,
                                SdpDirectionAttribute::Direction direction,
                                SdpMediaSection::Protocol proto,
                                Sdp* sdp);
   nsresult CreateAnswerMSection(const JsepAnswerOptions& options,
                                 size_t mlineIndex,
                                 const SdpMediaSection& remoteMsection,
                                 SdpMediaSection* msection,
@@ -264,16 +267,28 @@ private:
                           const SdpAttributeList& answer,
                           const RefPtr<JsepTransport>& transport);
 
   nsresult AddCandidateToSdp(Sdp* sdp,
                              const std::string& candidate,
                              const std::string& mid,
                              uint16_t level);
 
+  const SdpMediaSection* FindMsectionByMid(const Sdp& sdp,
+                                           const std::string& mid) const;
+
+  const SdpGroupAttributeList::Group* FindBundleGroup(const Sdp& sdp) const;
+
+  void DisableMsection(Sdp& sdp, SdpMediaSection& msection) const;
+
+  nsresult SetUniquePayloadTypes();
+  nsresult GetAllPayloadTypes(const JsepTrackNegotiatedDetails& trackDetails,
+                              std::vector<uint8_t>* payloadTypesOut);
+  std::string GetCNAME(const SdpMediaSection& msection) const;
+
   std::vector<JsepSendingTrack> mLocalTracks;
   std::vector<JsepReceivingTrack> mRemoteTracks;
   std::vector<RefPtr<JsepTransport> > mTransports;
   std::vector<JsepTrackPair> mNegotiatedTrackPairs;
 
   bool mIsOfferer;
   bool mIceControlling;
   std::string mIceUfrag;
@@ -282,16 +297,17 @@ private:
   std::vector<std::string> mIceOptions;
   std::vector<JsepDtlsFingerprint> mDtlsFingerprints;
   uint64_t mSessionId;
   uint64_t mSessionVersion;
   std::vector<SdpExtmapAttributeList::Extmap> mAudioRtpExtensions;
   std::vector<SdpExtmapAttributeList::Extmap> mVideoRtpExtensions;
   UniquePtr<JsepUuidGenerator> mUuidGen;
   std::string mDefaultRemoteStreamId;
+  std::string mCNAME;
   UniquePtr<Sdp> mGeneratedLocalDescription; // Created but not set.
   UniquePtr<Sdp> mCurrentLocalDescription;
   UniquePtr<Sdp> mCurrentRemoteDescription;
   UniquePtr<Sdp> mPendingLocalDescription;
   UniquePtr<Sdp> mPendingRemoteDescription;
   std::vector<JsepCodecDescription*> mCodecs;
   std::string mLastError;
   SipccSdpParser mParser;
--- a/media/webrtc/signaling/src/jsep/JsepTrack.h
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.h
@@ -29,16 +29,20 @@ public:
 
   virtual mozilla::SdpMediaSection::Protocol GetProtocol() const = 0;
   virtual Maybe<std::string> GetBandwidth(const std::string& type) const = 0;
   virtual size_t GetCodecCount() const = 0;
   virtual nsresult GetCodec(size_t index,
                             const JsepCodecDescription** config) const = 0;
   virtual const SdpExtmapAttributeList::Extmap* GetExt(
       const std::string& ext_name) const = 0;
+  virtual std::vector<uint8_t> GetUniquePayloadTypes() const = 0;
+
+  virtual void AddUniquePayloadType(uint8_t pt) = 0;
+  virtual void ClearUniquePayloadTypes() = 0;
 };
 
 class JsepTrack
 {
 public:
   enum Direction { kJsepTrackSending, kJsepTrackReceiving };
 
   JsepTrack(mozilla::SdpMediaSection::MediaType type,
@@ -65,55 +69,92 @@ public:
   }
 
   virtual const std::string&
   GetTrackId() const
   {
     return mTrackId;
   }
 
+  virtual const std::string&
+  GetCNAME() const
+  {
+    return mCNAME;
+  }
+
+  virtual void
+  SetCNAME(const std::string& cname)
+  {
+    mCNAME = cname;
+  }
+
   virtual Direction
   GetDirection() const
   {
     return mDirection;
   }
 
+  virtual const std::vector<uint32_t>&
+  GetSsrcs() const
+  {
+    return mSsrcs;
+  }
+
+  virtual void
+  AddSsrc(uint32_t ssrc)
+  {
+    mSsrcs.push_back(ssrc);
+  }
+
   // This will be set when negotiation is carried out.
   virtual const JsepTrackNegotiatedDetails*
   GetNegotiatedDetails() const
   {
     if (mNegotiatedDetails) {
       return mNegotiatedDetails.get();
     }
     return nullptr;
   }
 
+  virtual JsepTrackNegotiatedDetails*
+  GetNegotiatedDetails()
+  {
+    if (mNegotiatedDetails) {
+      return mNegotiatedDetails.get();
+    }
+    return nullptr;
+  }
+
   // This is for JsepSession's use.
   virtual void
   SetNegotiatedDetails(UniquePtr<JsepTrackNegotiatedDetails> details)
   {
     mNegotiatedDetails = Move(details);
   }
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTrack);
 
 protected:
   virtual ~JsepTrack() {}
 
 private:
   const mozilla::SdpMediaSection::MediaType mType;
   const std::string mStreamId;
   const std::string mTrackId;
+  std::string mCNAME;
   const Direction mDirection;
   UniquePtr<JsepTrackNegotiatedDetails> mNegotiatedDetails;
+  std::vector<uint32_t> mSsrcs;
 };
 
 // Need a better name for this.
 struct JsepTrackPair {
   size_t mLevel;
+  // Is this track pair sharing a transport with another?
+  Maybe<size_t> mBundleLevel;
   RefPtr<JsepTrack> mSending;
   RefPtr<JsepTrack> mReceiving;
   RefPtr<JsepTransport> mRtpTransport;
   RefPtr<JsepTransport> mRtcpTransport;
 };
 
 } // namespace mozilla
 
--- a/media/webrtc/signaling/src/jsep/JsepTrackImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepTrackImpl.h
@@ -58,22 +58,38 @@ public:
   {
     auto it = mExtmap.find(ext_name);
     if (it != mExtmap.end()) {
       return &it->second;
     }
     return nullptr;
   }
 
+  virtual std::vector<uint8_t> GetUniquePayloadTypes() const
+  {
+    return mUniquePayloadTypes;
+  }
+
+  virtual void AddUniquePayloadType(uint8_t pt) MOZ_OVERRIDE
+  {
+    mUniquePayloadTypes.push_back(pt);
+  }
+
+  virtual void ClearUniquePayloadTypes() MOZ_OVERRIDE
+  {
+    mUniquePayloadTypes.clear();
+  }
+
 private:
   // Make these friends to JsepSessionImpl to avoid having to
   // write setters.
   friend class JsepSessionImpl;
 
   mozilla::SdpMediaSection::Protocol mProtocol;
   Maybe<std::string> mBandwidth;
   std::vector<JsepCodecDescription*> mCodecs;
   std::map<std::string, SdpExtmapAttributeList::Extmap> mExtmap;
+  std::vector<uint8_t> mUniquePayloadTypes;
 };
 
 } // namespace mozilla
 
 #endif
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
@@ -121,24 +121,37 @@ WebrtcAudioConduit::~WebrtcAudioConduit(
     // only one opener can call Delete.  Have it be the last to close.
     if(mVoiceEngine)
     {
       webrtc::VoiceEngine::Delete(mVoiceEngine);
     }
   }
 }
 
+bool WebrtcAudioConduit::SetLocalSSRC(unsigned int ssrc)
+{
+  return !mPtrRTP->SetLocalSSRC(mChannel, ssrc);
+}
+
 bool WebrtcAudioConduit::GetLocalSSRC(unsigned int* ssrc) {
   return !mPtrRTP->GetLocalSSRC(mChannel, *ssrc);
 }
 
 bool WebrtcAudioConduit::GetRemoteSSRC(unsigned int* ssrc) {
   return !mPtrRTP->GetRemoteSSRC(mChannel, *ssrc);
 }
 
+bool WebrtcAudioConduit::SetLocalCNAME(const char* cname)
+{
+  char temp[256];
+  strncpy(temp, cname, sizeof(temp) - 1);
+  temp[sizeof(temp) - 1] = 0;
+  return !mPtrRTP->SetRTCP_CNAME(mChannel, temp);
+}
+
 bool WebrtcAudioConduit::GetAVStats(int32_t* jitterBufferDelayMs,
                                     int32_t* playoutBufferDelayMs,
                                     int32_t* avSyncOffsetMs) {
   return !mPtrVoEVideoSync->GetDelayEstimate(mChannel,
                                              jitterBufferDelayMs,
                                              playoutBufferDelayMs,
                                              avSyncOffsetMs);
 }
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.h
@@ -172,18 +172,20 @@ public:
   }
 
   virtual ~WebrtcAudioConduit();
 
   MediaConduitErrorCode Init(WebrtcAudioConduit *other);
 
   int GetChannel() { return mChannel; }
   webrtc::VoiceEngine* GetVoiceEngine() { return mVoiceEngine; }
+  bool SetLocalSSRC(unsigned int ssrc) MOZ_OVERRIDE;
   bool GetLocalSSRC(unsigned int* ssrc);
   bool GetRemoteSSRC(unsigned int* ssrc);
+  bool SetLocalCNAME(const char* cname) MOZ_OVERRIDE;
   bool GetVideoEncoderStats(double* framerateMean,
                             double* framerateStdDev,
                             double* bitrateMean,
                             double* bitrateStdDev,
                             uint32_t* droppedFrames)
   {
     return false;
   }
--- a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
+++ b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
@@ -156,18 +156,20 @@ public:
   /**
    * Function to attach Transport end-point of the Media conduit.
    * @param aTransport: Reference to the concrete teansport implementation
    * Note: Multiple invocations of this call , replaces existing transport with
    * with the new one.
    */
   virtual MediaConduitErrorCode AttachTransport(RefPtr<TransportInterface> aTransport) = 0;
 
+  virtual bool SetLocalSSRC(unsigned int ssrc) = 0;
   virtual bool GetLocalSSRC(unsigned int* ssrc) = 0;
   virtual bool GetRemoteSSRC(unsigned int* ssrc) = 0;
+  virtual bool SetLocalCNAME(const char* cname) = 0;
 
   /**
    * Functions returning stats needed by w3c stats model.
    */
   virtual bool GetVideoEncoderStats(double* framerateMean,
                                     double* framerateStdDev,
                                     double* bitrateMean,
                                     double* bitrateStdDev,
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -167,26 +167,39 @@ WebrtcVideoConduit::~WebrtcVideoConduit(
     // only one opener can call Delete.  Have it be the last to close.
     if(mVideoEngine)
     {
       webrtc::VideoEngine::Delete(mVideoEngine);
     }
   }
 }
 
+bool WebrtcVideoConduit::SetLocalSSRC(unsigned int ssrc)
+{
+  return !mPtrRTP->SetLocalSSRC(mChannel, ssrc);
+}
+
 bool WebrtcVideoConduit::GetLocalSSRC(unsigned int* ssrc)
 {
   return !mPtrRTP->GetLocalSSRC(mChannel, *ssrc);
 }
 
 bool WebrtcVideoConduit::GetRemoteSSRC(unsigned int* ssrc)
 {
   return !mPtrRTP->GetRemoteSSRC(mChannel, *ssrc);
 }
 
+bool WebrtcVideoConduit::SetLocalCNAME(const char* cname)
+{
+  char temp[256];
+  strncpy(temp, cname, sizeof(temp) - 1);
+  temp[sizeof(temp) - 1] = 0;
+  return !mPtrRTP->SetRTCPCName(mChannel, temp);
+}
+
 bool WebrtcVideoConduit::GetVideoEncoderStats(double* framerateMean,
                                               double* framerateStdDev,
                                               double* bitrateMean,
                                               double* bitrateStdDev,
                                               uint32_t* droppedFrames)
 {
   if (!mEngineTransmitting) {
     return false;
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.h
@@ -225,18 +225,20 @@ public:
 
   WebrtcVideoConduit();
   virtual ~WebrtcVideoConduit();
 
   MediaConduitErrorCode Init(WebrtcVideoConduit *other);
 
   int GetChannel() { return mChannel; }
   webrtc::VideoEngine* GetVideoEngine() { return mVideoEngine; }
-  bool GetLocalSSRC(unsigned int* ssrc);
+  bool GetLocalSSRC(unsigned int* ssrc) MOZ_OVERRIDE;
+  bool SetLocalSSRC(unsigned int ssrc);
   bool GetRemoteSSRC(unsigned int* ssrc);
+  bool SetLocalCNAME(const char* cname) MOZ_OVERRIDE;
   bool GetVideoEncoderStats(double* framerateMean,
                             double* framerateStdDev,
                             double* bitrateMean,
                             double* bitrateStdDev,
                             uint32_t* droppedFrames);
   bool GetVideoDecoderStats(double* framerateMean,
                             double* framerateStdDev,
                             double* bitrateMean,
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -90,51 +90,31 @@ nsresult MediaPipeline::Init_s() {
 
   if (rtcp_.transport_ != rtp_.transport_) {
     res = ConnectTransport_s(rtcp_);
     if (NS_FAILED(res)) {
       return res;
     }
   }
 
-  if (possible_bundle_rtp_) {
-    MOZ_ASSERT(possible_bundle_rtcp_);
-    MOZ_ASSERT(possible_bundle_rtp_->transport_);
-    MOZ_ASSERT(possible_bundle_rtcp_->transport_);
-
-    res = ConnectTransport_s(*possible_bundle_rtp_);
-    if (NS_FAILED(res)) {
-      return res;
-    }
-
-    if (possible_bundle_rtcp_->transport_ != possible_bundle_rtp_->transport_) {
-      res = ConnectTransport_s(*possible_bundle_rtcp_);
-      if (NS_FAILED(res)) {
-        return res;
-      }
-    }
-  }
-
   return NS_OK;
 }
 
 
 // Disconnect us from the transport so that we can cleanly destruct the
 // pipeline on the main thread.  ShutdownMedia_m() must have already been
 // called
 void MediaPipeline::ShutdownTransport_s() {
   ASSERT_ON_THREAD(sts_thread_);
   MOZ_ASSERT(!stream_); // verifies that ShutdownMedia_m() has run
 
   disconnect_all();
   transport_->Detach();
   rtp_.transport_ = nullptr;
   rtcp_.transport_ = nullptr;
-  possible_bundle_rtp_ = nullptr;
-  possible_bundle_rtcp_ = nullptr;
 }
 
 void MediaPipeline::StateChange(TransportFlow *flow, TransportLayer::State state) {
   TransportInfo* info = GetTransportInfo_s(flow);
   MOZ_ASSERT(info);
 
   if (state == TransportLayer::TS_OPEN) {
     MOZ_MTLOG(ML_INFO, "Flow is ready");
@@ -300,23 +280,16 @@ nsresult MediaPipeline::TransportFailed_
 void MediaPipeline::UpdateRtcpMuxState(TransportInfo &info) {
   if (info.type_ == MUX) {
     if (info.transport_ == rtcp_.transport_) {
       rtcp_.state_ = info.state_;
       if (!rtcp_.send_srtp_) {
         rtcp_.send_srtp_ = info.send_srtp_;
         rtcp_.recv_srtp_ = info.recv_srtp_;
       }
-    } else if (possible_bundle_rtcp_ &&
-               info.transport_ == possible_bundle_rtcp_->transport_) {
-      possible_bundle_rtcp_->state_ = info.state_;
-      if (!possible_bundle_rtcp_->send_srtp_) {
-        possible_bundle_rtcp_->send_srtp_ = info.send_srtp_;
-        possible_bundle_rtcp_->recv_srtp_ = info.recv_srtp_;
-      }
     }
   }
 }
 
 nsresult MediaPipeline::SendPacket(TransportFlow *flow, const void *data,
                                    int len) {
   ASSERT_ON_THREAD(sts_thread_);
 
@@ -393,59 +366,33 @@ void MediaPipeline::RtpPacketReceived(Tr
     return;
   }
 
   if (!conduit_) {
     MOZ_MTLOG(ML_DEBUG, "Discarding incoming packet; media disconnected");
     return;
   }
 
-  TransportInfo* info = &rtp_;
-
-  if (possible_bundle_rtp_ &&
-      possible_bundle_rtp_->transport_->Contains(layer)) {
-    // Received this on our possible bundle transport. Override info.
-    info = possible_bundle_rtp_;
-  }
-
-  // TODO(bcampen@mozilla.com): Can either of these actually happen? If not,
-  // the info variable can be removed, and this function gets simpler.
-  if (info->state_ != MP_OPEN) {
+  if (rtp_.state_ != MP_OPEN) {
     MOZ_MTLOG(ML_ERROR, "Discarding incoming packet; pipeline not open");
     return;
   }
 
-  if (info->transport_->state() != TransportLayer::TS_OPEN) {
+  if (rtp_.transport_->state() != TransportLayer::TS_OPEN) {
     MOZ_MTLOG(ML_ERROR, "Discarding incoming packet; transport not open");
     return;
   }
 
   // This should never happen.
-  MOZ_ASSERT(info->recv_srtp_);
+  MOZ_ASSERT(rtp_.recv_srtp_);
 
   if (direction_ == TRANSMIT) {
     return;
   }
 
-  if (possible_bundle_rtp_ && (info == &rtp_))  {
-    // We were not sure we would be using rtp_ or possible_bundle_rtp_, but we
-    // have just received traffic that clears this up.
-    // Don't let our filter prevent us from noticing this, if the filter is
-    // incomplete (ie; no SSRCs in remote SDP, or no remote SDP at all).
-    SetUsingBundle_s(false);
-    MOZ_MTLOG(ML_INFO, "Ruled out the possibility that we're receiving bundle "
-                       "for " << description_);
-    // TODO(bcampen@mozilla.com): Might be nice to detect when every
-    // MediaPipeline but the master has determined that it isn't doing bundle,
-    // since that means the master isn't doing bundle either. We could maybe
-    // do this by putting some refcounted dummy variable in the filters, and
-    // checking the value of the refcount. It is not clear whether this is
-    // going to be useful in practice.
-  }
-
   if (!len) {
     return;
   }
 
   // Filter out everything but RTP/RTCP
   if (data[0] < 128 || data[0] > 191) {
     return;
   }
@@ -453,27 +400,16 @@ void MediaPipeline::RtpPacketReceived(Tr
   if (filter_) {
     webrtc::RTPHeader header;
     if (!rtp_parser_->Parse(data, len, &header) ||
         !filter_->Filter(header)) {
       return;
     }
   }
 
-  if (possible_bundle_rtp_) {
-    // Just got traffic that passed our filter on the potential bundle
-    // transport. Must be doing bundle.
-    SetUsingBundle_s(true);
-    MOZ_MTLOG(ML_INFO, "Confirmed the possibility that we're receiving bundle");
-  }
-
-  // Everything is decided now; just use rtp_
-  MOZ_ASSERT(!possible_bundle_rtp_);
-  MOZ_ASSERT(!possible_bundle_rtcp_);
-
   // Make a copy rather than cast away constness
   ScopedDeletePtr<unsigned char> inner_data(
       new unsigned char[len]);
   memcpy(inner_data, data, len);
   int out_len = 0;
   nsresult res = rtp_.recv_srtp_->UnprotectRtp(inner_data,
                                                len, len, &out_len);
   if (!NS_SUCCEEDED(res)) {
@@ -503,83 +439,62 @@ void MediaPipeline::RtcpPacketReceived(T
     return;
   }
 
   if (!conduit_) {
     MOZ_MTLOG(ML_DEBUG, "Discarding incoming packet; media disconnected");
     return;
   }
 
-  TransportInfo* info = &rtcp_;
-  if (possible_bundle_rtcp_ &&
-      possible_bundle_rtcp_->transport_->Contains(layer)) {
-    info = possible_bundle_rtcp_;
-  }
-
-  if (info->state_ != MP_OPEN) {
+  if (rtcp_.state_ != MP_OPEN) {
     MOZ_MTLOG(ML_DEBUG, "Discarding incoming packet; pipeline not open");
     return;
   }
 
-  if (info->transport_->state() != TransportLayer::TS_OPEN) {
+  if (rtcp_.transport_->state() != TransportLayer::TS_OPEN) {
     MOZ_MTLOG(ML_ERROR, "Discarding incoming packet; transport not open");
     return;
   }
 
-  if (possible_bundle_rtp_ && (info == &rtcp_)) {
-    // We have offered bundle, and received our first packet on a non-bundle
-    // address. We are definitely not using the bundle address.
-    SetUsingBundle_s(false);
-  }
-
   if (!len) {
     return;
   }
 
   // Filter out everything but RTP/RTCP
   if (data[0] < 128 || data[0] > 191) {
     return;
   }
 
-  MediaPipelineFilter::Result filter_result = MediaPipelineFilter::PASS;
-  if (filter_) {
-    filter_result = filter_->FilterRTCP(data, len);
-    if (filter_result == MediaPipelineFilter::FAIL) {
-      return;
-    }
-  }
-
-  if (filter_result == MediaPipelineFilter::PASS && possible_bundle_rtp_) {
-    // Just got traffic that passed our filter on the potential bundle
-    // transport. Must be doing bundle.
-    SetUsingBundle_s(true);
-  }
-
-  // We continue using info here, since it is possible that the filter did not
-  // support the payload type (ie; returned MediaPipelineFilter::UNSUPPORTED).
-  // In this case, we just let it pass, and hope the webrtc.org code does
-  // something sane.
-  increment_rtcp_packets_received();
-
-  MOZ_ASSERT(info->recv_srtp_);  // This should never happen
-
   // Make a copy rather than cast away constness
   ScopedDeletePtr<unsigned char> inner_data(
       new unsigned char[len]);
   memcpy(inner_data, data, len);
   int out_len;
 
-  nsresult res = info->recv_srtp_->UnprotectRtcp(inner_data,
+  nsresult res = rtcp_.recv_srtp_->UnprotectRtcp(inner_data,
                                                  len,
                                                  len,
                                                  &out_len);
 
   if (!NS_SUCCEEDED(res))
     return;
 
+  MediaPipelineFilter::Result filter_result = MediaPipelineFilter::PASS;
+  if (filter_) {
+    filter_result = filter_->FilterRTCP(inner_data, out_len);
+    if (filter_result == MediaPipelineFilter::FAIL) {
+      MOZ_MTLOG(ML_NOTICE, "Dropping rtcp packet");
+      return;
+    }
+  }
+
+  increment_rtcp_packets_received();
+
+  MOZ_ASSERT(rtcp_.recv_srtp_);  // This should never happen
+
   (void)conduit_->ReceivedRTCPPacket(inner_data, out_len);  // Ignore error codes
 }
 
 bool MediaPipeline::IsRtp(const unsigned char *data, size_t len) {
   if (len < 2)
     return false;
 
   // Check if this is a RTCP packet. Logic based on the types listed in
@@ -685,17 +600,16 @@ void MediaPipelineTransmit::UpdateSinkId
 #endif
 
 nsresult MediaPipelineTransmit::TransportReady_s(TransportInfo &info) {
   ASSERT_ON_THREAD(sts_thread_);
   // Call base ready function.
   MediaPipeline::TransportReady_s(info);
 
   // Should not be set for a transmitter
-  MOZ_ASSERT(!possible_bundle_rtp_);
   if (&info == &rtp_) {
     listener_->SetActive(true);
   }
 
   return NS_OK;
 }
 
 nsresult MediaPipelineTransmit::ReplaceTrack(DOMMediaStream *domstream,
@@ -760,67 +674,19 @@ MediaPipeline::TransportInfo* MediaPipel
   if (flow == rtp_.transport_) {
     return &rtp_;
   }
 
   if (flow == rtcp_.transport_) {
     return &rtcp_;
   }
 
-  if (possible_bundle_rtp_) {
-    if (flow == possible_bundle_rtp_->transport_) {
-      return possible_bundle_rtp_;
-    }
-
-    if (flow == possible_bundle_rtcp_->transport_) {
-      return possible_bundle_rtcp_;
-    }
-  }
-
   return nullptr;
 }
 
-void MediaPipeline::SetUsingBundle_s(bool decision) {
-  ASSERT_ON_THREAD(sts_thread_);
-  // Note: This can be called either because of events on the STS thread, or
-  // by events on the main thread (ie; receiving a remote description). It is
-  // best to be careful of races here, so don't assume that transports are open.
-  if (!possible_bundle_rtp_) {
-    if (!decision) {
-      // This can happen on the master pipeline.
-      filter_ = nullptr;
-    }
-    return;
-  }
-
-  if (direction_ == RECEIVE) {
-    if (decision) {
-      MOZ_MTLOG(ML_INFO, "Non-master pipeline confirmed bundle for "
-                         << description_);
-      // We're doing bundle. Release the unused flows, and copy the ones we
-      // are using into the less wishy-washy members.
-      DisconnectTransport_s(rtp_);
-      DisconnectTransport_s(rtcp_);
-      rtp_ = *possible_bundle_rtp_;
-      rtcp_ = *possible_bundle_rtcp_;
-    } else {
-      MOZ_MTLOG(ML_INFO, "Non-master pipeline confirmed no bundle for "
-                         << description_);
-      // We are not doing bundle
-      DisconnectTransport_s(*possible_bundle_rtp_);
-      DisconnectTransport_s(*possible_bundle_rtcp_);
-      filter_ = nullptr;
-    }
-
-    // We are no longer in an ambiguous state.
-    possible_bundle_rtp_ = nullptr;
-    possible_bundle_rtcp_ = nullptr;
-  }
-}
-
 MediaPipelineFilter* MediaPipeline::UpdateFilterFromRemoteDescription_s(
     nsAutoPtr<MediaPipelineFilter> filter) {
   ASSERT_ON_THREAD(sts_thread_);
   // This is only supposed to relax the filter. Relaxing a missing filter is
   // not possible.
   MOZ_ASSERT(filter_);
 
   if (!filter) {
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
@@ -76,17 +76,18 @@ class MediaPipeline : public sigslot::ha
                 Direction direction,
                 nsCOMPtr<nsIEventTarget> main_thread,
                 nsCOMPtr<nsIEventTarget> sts_thread,
                 MediaStream *stream,
                 TrackID track_id,
                 int level,
                 RefPtr<MediaSessionConduit> conduit,
                 RefPtr<TransportFlow> rtp_transport,
-                RefPtr<TransportFlow> rtcp_transport)
+                RefPtr<TransportFlow> rtcp_transport,
+                nsAutoPtr<MediaPipelineFilter> filter)
       : direction_(direction),
         stream_(stream),
         track_id_(track_id),
         level_(level),
         conduit_(conduit),
         rtp_(rtp_transport, rtcp_transport ? RTP : MUX),
         rtcp_(rtcp_transport ? rtcp_transport : rtp_transport,
               rtcp_transport ? RTCP : MUX),
@@ -94,17 +95,19 @@ class MediaPipeline : public sigslot::ha
         sts_thread_(sts_thread),
         rtp_packets_sent_(0),
         rtcp_packets_sent_(0),
         rtp_packets_received_(0),
         rtcp_packets_received_(0),
         rtp_bytes_sent_(0),
         rtp_bytes_received_(0),
         pc_(pc),
-        description_() {
+        description_(),
+        filter_(filter),
+        rtp_parser_(webrtc::RtpHeaderParser::Create()) {
       // To indicate rtcp-mux rtcp_transport should be nullptr.
       // Therefore it's an error to send in the same flow for
       // both rtp and rtcp.
       MOZ_ASSERT(rtp_transport != rtcp_transport);
 
       // PipelineTransport() will access this->sts_thread_; moved here for safety
       transport_ = new PipelineTransport(this);
   }
@@ -118,23 +121,16 @@ class MediaPipeline : public sigslot::ha
 
     if (stream_) {
       DetachMediaStream();
     }
   }
 
   virtual nsresult Init();
 
-  // When we have offered bundle, the MediaPipelines are created in an
-  // indeterminate state; we do not know whether the answerer will take us
-  // up on our offer. In the meantime, we need to behave in a manner that
-  // errs on the side of packet loss when it is unclear whether an arriving
-  // packet is meant for us. We want to get out of this indeterminate state
-  // ASAP, which is what this function can be used for.
-  void SetUsingBundle_s(bool decision);
   MediaPipelineFilter* UpdateFilterFromRemoteDescription_s(
       nsAutoPtr<MediaPipelineFilter> filter);
 
   virtual Direction direction() const { return direction_; }
   virtual TrackID trackid() const { return track_id_; }
   virtual int level() const { return level_; }
   virtual bool IsVideo() const { return false; }
 
@@ -242,26 +238,16 @@ class MediaPipeline : public sigslot::ha
                                 // Not used outside initialization in MediaPipelineTransmit
   int level_; // The m-line index (starting at 1, to match convention)
   RefPtr<MediaSessionConduit> conduit_;  // Our conduit. Written on the main
                                          // thread. Read on STS thread.
 
   // The transport objects. Read/written on STS thread.
   TransportInfo rtp_;
   TransportInfo rtcp_;
-  // These are for bundle. We have a separate set because when we have offered
-  // bundle, we do not know whether we will receive traffic on the transport
-  // in this pipeline's m-line, or the transport in the "master" m-line for
-  // the bundle. We need to be ready for either. Once this ambiguity is
-  // resolved, the transport we know that we'll be using will be set in
-  // rtp_transport_ and rtcp_transport_, and these will be unset.
-  // TODO(bcampen@mozilla.com): I'm pretty sure this could be leveraged for
-  // re-offer with a new address on an m-line too, with a little work.
-  nsAutoPtr<TransportInfo> possible_bundle_rtp_;
-  nsAutoPtr<TransportInfo> possible_bundle_rtcp_;
 
   // Pointers to the threads we need. Initialized at creation
   // and used all over the place.
   nsCOMPtr<nsIEventTarget> main_thread_;
   nsCOMPtr<nsIEventTarget> sts_thread_;
 
   // Created on Init. Referenced by the conduit and eventually
   // destroyed on the STS thread.
@@ -365,20 +351,21 @@ public:
   MediaPipelineTransmit(const std::string& pc,
                         nsCOMPtr<nsIEventTarget> main_thread,
                         nsCOMPtr<nsIEventTarget> sts_thread,
                         DOMMediaStream *domstream,
                         int level,
                         bool is_video,
                         RefPtr<MediaSessionConduit> conduit,
                         RefPtr<TransportFlow> rtp_transport,
-                        RefPtr<TransportFlow> rtcp_transport) :
+                        RefPtr<TransportFlow> rtcp_transport,
+                        nsAutoPtr<MediaPipelineFilter> filter) :
       MediaPipeline(pc, TRANSMIT, main_thread, sts_thread,
                     domstream->GetStream(), TRACK_INVALID, level,
-                    conduit, rtp_transport, rtcp_transport),
+                    conduit, rtp_transport, rtcp_transport, filter),
       listener_(new PipelineListener(conduit)),
       domstream_(domstream),
       is_video_(is_video)
   {}
 
   // Initialize (stuff here may fail)
   virtual nsresult Init() MOZ_OVERRIDE;
 
@@ -531,35 +518,21 @@ class MediaPipelineReceive : public Medi
                        nsCOMPtr<nsIEventTarget> main_thread,
                        nsCOMPtr<nsIEventTarget> sts_thread,
                        MediaStream *stream,
                        TrackID track_id,
                        int level,
                        RefPtr<MediaSessionConduit> conduit,
                        RefPtr<TransportFlow> rtp_transport,
                        RefPtr<TransportFlow> rtcp_transport,
-                       RefPtr<TransportFlow> bundle_rtp_transport,
-                       RefPtr<TransportFlow> bundle_rtcp_transport,
                        nsAutoPtr<MediaPipelineFilter> filter) :
       MediaPipeline(pc, RECEIVE, main_thread, sts_thread,
                     stream, track_id, level, conduit, rtp_transport,
-                    rtcp_transport),
+                    rtcp_transport, filter),
       segments_added_(0) {
-    filter_ = filter;
-    rtp_parser_ = webrtc::RtpHeaderParser::Create();
-    if (bundle_rtp_transport) {
-      if (bundle_rtcp_transport) {
-        MOZ_ASSERT(bundle_rtp_transport != bundle_rtcp_transport);
-        possible_bundle_rtp_ = new TransportInfo(bundle_rtp_transport, RTP);
-        possible_bundle_rtcp_ = new TransportInfo(bundle_rtcp_transport, RTCP);
-      } else {
-        possible_bundle_rtp_ = new TransportInfo(bundle_rtp_transport, MUX);
-        possible_bundle_rtcp_ = new TransportInfo(bundle_rtp_transport, MUX);
-      }
-    }
   }
 
   int segments_added() const { return segments_added_; }
 
  protected:
   int segments_added_;
 
  private:
@@ -574,23 +547,20 @@ class MediaPipelineReceiveAudio : public
                             nsCOMPtr<nsIEventTarget> main_thread,
                             nsCOMPtr<nsIEventTarget> sts_thread,
                             MediaStream *stream,
                             TrackID track_id,
                             int level,
                             RefPtr<AudioSessionConduit> conduit,
                             RefPtr<TransportFlow> rtp_transport,
                             RefPtr<TransportFlow> rtcp_transport,
-                            RefPtr<TransportFlow> bundle_rtp_transport,
-                            RefPtr<TransportFlow> bundle_rtcp_transport,
                             nsAutoPtr<MediaPipelineFilter> filter) :
       MediaPipelineReceive(pc, main_thread, sts_thread,
                            stream, track_id, level, conduit, rtp_transport,
-                           rtcp_transport, bundle_rtp_transport,
-                           bundle_rtcp_transport, filter),
+                           rtcp_transport, filter),
       listener_(new PipelineListener(stream->AsSourceStream(),
                                      track_id, conduit)) {
   }
 
   virtual void DetachMediaStream() {
     ASSERT_ON_THREAD(main_thread_);
     listener_->EndTrack();
     stream_->RemoveListener(listener_);
@@ -640,23 +610,20 @@ class MediaPipelineReceiveVideo : public
                             nsCOMPtr<nsIEventTarget> main_thread,
                             nsCOMPtr<nsIEventTarget> sts_thread,
                             MediaStream *stream,
                             TrackID track_id,
                             int level,
                             RefPtr<VideoSessionConduit> conduit,
                             RefPtr<TransportFlow> rtp_transport,
                             RefPtr<TransportFlow> rtcp_transport,
-                            RefPtr<TransportFlow> bundle_rtp_transport,
-                            RefPtr<TransportFlow> bundle_rtcp_transport,
                             nsAutoPtr<MediaPipelineFilter> filter) :
       MediaPipelineReceive(pc, main_thread, sts_thread,
                            stream, track_id, level, conduit, rtp_transport,
-                           rtcp_transport, bundle_rtp_transport,
-                           bundle_rtcp_transport, filter),
+                           rtcp_transport, filter),
       renderer_(new PipelineRenderer(MOZ_THIS_IN_INITIALIZER_LIST())),
       listener_(new PipelineListener(stream->AsSourceStream(), track_id)) {
   }
 
   // Called on the main thread.
   virtual void DetachMediaStream() {
     ASSERT_ON_THREAD(main_thread_);
 
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -237,36 +237,37 @@ MediaPipelineFactory::CreateMediaPipelin
   MOZ_MTLOG(ML_DEBUG,
             "Creating media pipeline"
                 << " m-line index=" << aTrackPair.mLevel
                 << " type=" << aTrack.GetMediaType()
                 << " direction=" << aTrack.GetDirection());
 
   MOZ_ASSERT(aTrackPair.mRtpTransport);
 
+  size_t transportLevel = aTrackPair.mBundleLevel.isSome() ?
+                          *aTrackPair.mBundleLevel :
+                          aTrackPair.mLevel;
+
   // First make sure the transport flow exists.
   RefPtr<TransportFlow> rtpFlow;
   nsresult rv = CreateOrGetTransportFlow(
-      aTrackPair.mLevel, false, *aTrackPair.mRtpTransport, &rtpFlow);
+      transportLevel, false, *aTrackPair.mRtpTransport, &rtpFlow);
   if (NS_FAILED(rv))
     return rv;
   MOZ_ASSERT(rtpFlow);
 
   RefPtr<TransportFlow> rtcpFlow;
   if (aTrackPair.mRtcpTransport) {
     rv = CreateOrGetTransportFlow(
-        aTrackPair.mLevel, true, *aTrackPair.mRtcpTransport, &rtcpFlow);
+        transportLevel, true, *aTrackPair.mRtcpTransport, &rtcpFlow);
     if (NS_FAILED(rv))
       return rv;
     MOZ_ASSERT(rtcpFlow);
   }
 
-  // TODO(bug 1016476): If bundle is in play, grab the TransportFlows for the
-  // bundle level too.
-
   bool receiving =
       aTrack.GetDirection() == JsepTrack::Direction::kJsepTrackReceiving;
 
   RefPtr<MediaSessionConduit> conduit;
   if (aTrack.GetMediaType() == SdpMediaSection::kAudio) {
     rv = CreateAudioConduit(aTrackPair, aTrack, &conduit);
     if (NS_FAILED(rv))
       return rv;
@@ -275,36 +276,34 @@ MediaPipelineFactory::CreateMediaPipelin
     if (NS_FAILED(rv))
       return rv;
   } else {
     // We've created the TransportFlow, nothing else to do here.
     return NS_OK;
   }
 
   if (receiving) {
-    rv = CreateMediaPipelineReceiving(rtpFlow, rtcpFlow, nullptr, nullptr,
+    rv = CreateMediaPipelineReceiving(rtpFlow, rtcpFlow,
                                       aTrackPair, aTrack, conduit);
     if (NS_FAILED(rv))
       return rv;
   } else {
-    rv = CreateMediaPipelineSending(rtpFlow, rtcpFlow, nullptr, nullptr,
+    rv = CreateMediaPipelineSending(rtpFlow, rtcpFlow,
                                     aTrackPair, aTrack, conduit);
     if (NS_FAILED(rv))
       return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
 MediaPipelineFactory::CreateMediaPipelineReceiving(
     RefPtr<TransportFlow> aRtpFlow,
     RefPtr<TransportFlow> aRtcpFlow,
-    RefPtr<TransportFlow> aBundleRtpFlow,
-    RefPtr<TransportFlow> aBundleRtcpFlow,
     const JsepTrackPair& aTrackPair,
     const JsepTrack& aTrack,
     const RefPtr<MediaSessionConduit>& aConduit)
 {
 
   // Find the stream we need
   nsRefPtr<RemoteSourceStreamInfo> stream =
       mPCMedia->GetRemoteStreamById(aTrack.GetStreamId());
@@ -313,49 +312,63 @@ MediaPipelineFactory::CreateMediaPipelin
     // This should never happen
     MOZ_ASSERT(false);
     MOZ_MTLOG(ML_ERROR, "Stream not found: " << aTrack.GetStreamId());
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<MediaPipelineReceive> pipeline;
 
-  // TODO(bug 1016476): Need bundle filter.
-  nsAutoPtr<MediaPipelineFilter> filter(nullptr);
+  nsAutoPtr<MediaPipelineFilter> filter;
+
+  if (aTrackPair.mBundleLevel.isSome()) {
+    filter = new MediaPipelineFilter;
+
+    // Add remote SSRCs so we can distinguish which RTP packets actually
+    // belong to this pipeline (also RTCP sender reports).
+    for (auto i = aTrack.GetSsrcs().begin();
+        i != aTrack.GetSsrcs().end(); ++i) {
+      filter->AddRemoteSSRC(*i);
+    }
+
+    // TODO(bug 1105005): Tell the filter about the mid for this track
+
+    // Add unique payload types as a last-ditch fallback
+    auto uniquePts = aTrack.GetNegotiatedDetails()->GetUniquePayloadTypes();
+    for (auto i = uniquePts.begin(); i != uniquePts.end(); ++i) {
+      filter->AddUniquePT(*i);
+    }
+  }
 
   if (aTrack.GetMediaType() == SdpMediaSection::kAudio) {
     pipeline = new MediaPipelineReceiveAudio(
         mPC->GetHandle(),
         mPC->GetMainThread().get(),
         mPC->GetSTSThread(),
         stream->GetMediaStream()->GetStream(),
         // Use the level + 1 as the track id. 0 is forbidden
         aTrackPair.mLevel + 1,
         aTrackPair.mLevel,
         static_cast<AudioSessionConduit*>(aConduit.get()), // Ugly downcast.
         aRtpFlow,
         aRtcpFlow,
-        aBundleRtpFlow,
-        aBundleRtcpFlow,
         filter);
 
   } else if (aTrack.GetMediaType() == SdpMediaSection::kVideo) {
     pipeline = new MediaPipelineReceiveVideo(
         mPC->GetHandle(),
         mPC->GetMainThread().get(),
         mPC->GetSTSThread(),
         stream->GetMediaStream()->GetStream(),
         // Use the level + 1 as the track id. 0 is forbidden
         aTrackPair.mLevel + 1,
         aTrackPair.mLevel,
         static_cast<VideoSessionConduit*>(aConduit.get()), // Ugly downcast.
         aRtpFlow,
         aRtcpFlow,
-        aBundleRtpFlow,
-        aBundleRtcpFlow,
         filter);
   } else {
     MOZ_ASSERT(false);
     MOZ_MTLOG(ML_ERROR, "Invalid media type in CreateMediaPipelineReceiving");
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv = pipeline->Init();
@@ -367,39 +380,50 @@ MediaPipelineFactory::CreateMediaPipelin
   stream->StorePipeline(aTrackPair.mLevel, SdpMediaSection::kVideo, pipeline);
   return NS_OK;
 }
 
 nsresult
 MediaPipelineFactory::CreateMediaPipelineSending(
     RefPtr<TransportFlow> aRtpFlow,
     RefPtr<TransportFlow> aRtcpFlow,
-    RefPtr<TransportFlow> aBundleRtpFlow,
-    RefPtr<TransportFlow> aBundleRtcpFlow,
     const JsepTrackPair& aTrackPair,
     const JsepTrack& aTrack,
     const RefPtr<MediaSessionConduit>& aConduit)
 {
   nsresult rv;
 
   nsRefPtr<LocalSourceStreamInfo> stream =
       mPCMedia->GetLocalStreamById(aTrack.GetStreamId());
   MOZ_ASSERT(stream);
   if (!stream) {
     // This should never happen
     MOZ_MTLOG(ML_ERROR, "Stream not found: " << aTrack.GetStreamId());
     return NS_ERROR_FAILURE;
   }
 
+  nsAutoPtr<MediaPipelineFilter> filter;
+
+  if (aTrackPair.mBundleLevel.isSome()) {
+    filter = new MediaPipelineFilter;
+
+    // Add local SSRCs so we can distinguish which RTCP packets actually
+    // belong to this pipeline.
+    for (auto i = aTrack.GetSsrcs().begin();
+         i != aTrack.GetSsrcs().end(); ++i) {
+      filter->AddLocalSSRC(*i);
+    }
+  }
+
   // Now we have all the pieces, create the pipeline
   RefPtr<MediaPipelineTransmit> pipeline = new MediaPipelineTransmit(
       mPC->GetHandle(), mPC->GetMainThread().get(), mPC->GetSTSThread(),
       stream->GetMediaStream(), aTrackPair.mLevel,
       aTrack.GetMediaType() == SdpMediaSection::kVideo, aConduit, aRtpFlow,
-      aRtcpFlow);
+      aRtcpFlow, filter);
 
 #ifdef MOZILLA_INTERNAL_API
   // implement checking for peerIdentity (where failure == black/silence)
   nsIDocument* doc = mPC->GetWindow()->GetExtantDoc();
   if (doc) {
     pipeline->UpdateSinkIdentity_m(doc->NodePrincipal(),
                                    mPC->GetPeerIdentity());
   } else {
@@ -409,34 +433,16 @@ MediaPipelineFactory::CreateMediaPipelin
 #endif
 
   rv = pipeline->Init();
   if (NS_FAILED(rv)) {
     MOZ_MTLOG(ML_ERROR, "Couldn't initialize sending pipeline");
     return rv;
   }
 
-// TODO(bug 1016476): Copied from vcmSIPCCBinding. Need to unifdef and port in.
-#if 0
-  // This tells the receive MediaPipeline (if there is one) whether we are
-  // doing bundle, and if so, updates the filter. Once the filter is finalized,
-  // it is then copied to the transmit pipeline so it can filter RTCP.
-  if (attrs->bundle_level) {
-    nsAutoPtr<MediaPipelineFilter> filter (new MediaPipelineFilter);
-    for (int s = 0; s < attrs->num_ssrcs; ++s) {
-      filter->AddRemoteSSRC(attrs->ssrcs[s]);
-    }
-    pc.impl()->media()->SetUsingBundle_m(level, true);
-    pc.impl()->media()->UpdateFilterFromRemoteDescription_m(level, filter);
-  } else {
-    // This will also clear the filter.
-    pc.impl()->media()->SetUsingBundle_m(level, false);
-  }
-#endif
-
   stream->StorePipeline(aTrackPair.mLevel, pipeline);
 
   return NS_OK;
 }
 
 nsresult
 MediaPipelineFactory::CreateAudioConduit(const JsepTrackPair& aTrackPair,
                                          const JsepTrack& aTrack,
@@ -496,16 +502,27 @@ MediaPipelineFactory::CreateAudioConduit
 
     auto error = conduit->ConfigureRecvMediaCodecs(configs.values);
 
     if (error) {
       MOZ_MTLOG(ML_ERROR, "ConfigureRecvMediaCodecs failed: " << error);
       return NS_ERROR_FAILURE;
     }
   } else {
+    // For now we only expect to have one ssrc per local track.
+    auto ssrcs = aTrack.GetSsrcs();
+    if (!ssrcs.empty()) {
+      if (!conduit->SetLocalSSRC(ssrcs.front())) {
+        MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
+        return NS_ERROR_FAILURE;
+      }
+    }
+
+    conduit->SetLocalCNAME(aTrack.GetCNAME().c_str());
+
     const JsepCodecDescription* cdesc;
     // Best codec.
     nsresult rv = aTrack.GetNegotiatedDetails()->GetCodec(0, &cdesc);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
     if (NS_FAILED(rv)) {
       MOZ_MTLOG(ML_ERROR, "Failed to get codec from jsep track, rv="
                               << static_cast<uint32_t>(rv));
       return rv;
@@ -609,16 +626,27 @@ MediaPipelineFactory::CreateVideoConduit
 
     auto error = conduit->ConfigureRecvMediaCodecs(configs.values);
 
     if (error) {
       MOZ_MTLOG(ML_ERROR, "ConfigureRecvMediaCodecs failed: " << error);
       return NS_ERROR_FAILURE;
     }
   } else {
+    // For now we only expect to have one ssrc per local track.
+    auto ssrcs = aTrack.GetSsrcs();
+    if (!ssrcs.empty()) {
+      if (!conduit->SetLocalSSRC(ssrcs.front())) {
+        MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
+        return NS_ERROR_FAILURE;
+      }
+    }
+
+    conduit->SetLocalCNAME(aTrack.GetCNAME().c_str());
+
     const JsepCodecDescription* cdesc;
     // Best codec.
     nsresult rv = aTrack.GetNegotiatedDetails()->GetCodec(0, &cdesc);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
     if (NS_FAILED(rv)) {
       MOZ_MTLOG(ML_ERROR, "Failed to get codec from jsep track, rv="
                               << static_cast<uint32_t>(rv));
       return rv;
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.h
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.h
@@ -24,27 +24,23 @@ public:
 
   nsresult CreateMediaPipeline(const JsepTrackPair& aTrackPair,
                                const JsepTrack& aTrack);
 
 private:
   nsresult CreateMediaPipelineReceiving(
       RefPtr<TransportFlow> aRtpFlow,
       RefPtr<TransportFlow> aRtcpFlow,
-      RefPtr<TransportFlow> aBundleRtpFlow,
-      RefPtr<TransportFlow> aBundleRtcpFlow,
       const JsepTrackPair& aTrackPair,
       const JsepTrack& aTrack,
       const RefPtr<MediaSessionConduit>& aConduit);
 
   nsresult CreateMediaPipelineSending(
       RefPtr<TransportFlow> aRtpFlow,
       RefPtr<TransportFlow> aRtcpFlow,
-      RefPtr<TransportFlow> aBundleRtpFlow,
-      RefPtr<TransportFlow> aBundleRtcpFlow,
       const JsepTrackPair& aTrackPair,
       const JsepTrack& aTrack,
       const RefPtr<MediaSessionConduit>& aConduit);
 
   nsresult CreateAudioConduit(const JsepTrackPair& aTrackPair,
                               const JsepTrack& aTrack,
                               RefPtr<MediaSessionConduit>* aConduitp);
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1027,17 +1027,21 @@ PeerConnectionImpl::GetDatachannelParame
                              "webrtc-datachannel (was instead %s). ",
                              __FUNCTION__,
                              codec->mName.c_str());
           continue;
         }
 
         *datachannelCodec =
           static_cast<const JsepApplicationCodecDescription*>(codec);
-        *level = static_cast<uint16_t>(trackPair->mLevel);
+        if (trackPair->mBundleLevel.isSome()) {
+          *level = static_cast<uint16_t>(*trackPair->mBundleLevel);
+        } else {
+          *level = static_cast<uint16_t>(trackPair->mLevel);
+        }
         return NS_OK;
       }
     }
   }
 
   *datachannelCodec = nullptr;
   *level = 0;
   return NS_OK;
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -358,18 +358,23 @@ PeerConnectionMedia::StartIceChecks(cons
     if (NS_FAILED(rv)) {
       CSFLogError(logTag, "JsepSession::GetTransport() failed: %u",
                           static_cast<unsigned>(rv));
       MOZ_ASSERT(false, "JsepSession::GetTransport() failed!");
       break;
     }
 
     if (transport->mState == JsepTransport::kJsepTransportClosed) {
+      CSFLogDebug(logTag, "Transport %u is disabled",
+                          static_cast<unsigned>(i));
       numComponentsByLevel.push_back(0);
     } else {
+      CSFLogDebug(logTag, "Transport %u has %u components",
+                          static_cast<unsigned>(i),
+                          static_cast<unsigned>(transport->mComponents));
       numComponentsByLevel.push_back(transport->mComponents);
     }
   }
 
   RUN_ON_THREAD(GetSTSThread(),
                 WrapRunnable(
                   RefPtr<PeerConnectionMedia>(this),
                   &PeerConnectionMedia::StartIceChecks_s,
@@ -735,32 +740,16 @@ PeerConnectionMedia::GetRemoteStreamById
 
   // This does not have a MOZ_ASSERT like GetLocalStreamById because in the
   // case of local streams, the stream id and stream info are created
   // simultaneously, whereas in the remote case the stream id exists first,
   // meaning we have to be able to check.
   return nullptr;
 }
 
-bool
-PeerConnectionMedia::SetUsingBundle_m(int aMLine, bool decision)
-{
-  ASSERT_ON_THREAD(mMainThread);
-  for (size_t i = 0; i < mRemoteSourceStreams.Length(); ++i) {
-    if (mRemoteSourceStreams[i]->SetUsingBundle_m(aMLine, decision)) {
-      // Found the MediaPipeline for |aMLine|
-      return true;
-    }
-  }
-  CSFLogWarn(logTag, "Could not locate level %d to set bundle flag to %s",
-                     static_cast<int>(aMLine),
-                     decision ? "true" : "false");
-  return false;
-}
-
 static void
 UpdateFilterFromRemoteDescription_s(
   RefPtr<mozilla::MediaPipeline> receive,
   RefPtr<mozilla::MediaPipeline> transmit,
   nsAutoPtr<mozilla::MediaPipelineFilter> filter) {
 
   // Update filter, and make a copy of the final version.
   mozilla::MediaPipelineFilter *finalFilter(
@@ -1194,28 +1183,9 @@ RefPtr<MediaPipeline> SourceStreamInfo::
         return p->second;
       }
     }
   }
 
   return nullptr;
 }
 
-bool RemoteSourceStreamInfo::SetUsingBundle_m(int aMLine, bool decision) {
-  ASSERT_ON_THREAD(mParent->GetMainThread());
-
-  // Avoid adding and dropping an extra ref
-  MediaPipeline *pipeline = GetPipelineByLevel_m(aMLine);
-
-  if (pipeline) {
-    RUN_ON_THREAD(mParent->GetSTSThread(),
-                  WrapRunnable(
-                      RefPtr<MediaPipeline>(pipeline),
-                      &MediaPipeline::SetUsingBundle_s,
-                      decision
-                  ),
-                  NS_DISPATCH_NORMAL);
-    return true;
-  }
-  return false;
-}
-
 }  // namespace mozilla
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -266,18 +266,16 @@ class RemoteSourceStreamInfo : public So
                          const std::string& aId)
     : SourceStreamInfo(aMediaStream, aParent, aId),
       mTrackTypeHints(0) {
   }
 
   void StorePipeline(int aMLine, bool aIsVideo,
                      mozilla::RefPtr<mozilla::MediaPipelineReceive> aPipeline);
 
-  bool SetUsingBundle_m(int aMLine, bool decision);
-
   void DetachTransport_s();
   void DetachMedia_m();
 
 #ifdef MOZILLA_INTERNAL_API
   void UpdatePrincipal_m(nsIPrincipal* aPrincipal);
 #endif
 
   bool AnyCodecHasPluginID(uint64_t aPluginID);
@@ -354,17 +352,16 @@ class PeerConnectionMedia : public sigsl
   uint32_t RemoteStreamsLength()
   {
     return mRemoteSourceStreams.Length();
   }
 
   RemoteSourceStreamInfo* GetRemoteStreamByIndex(size_t index);
   RemoteSourceStreamInfo* GetRemoteStreamById(const std::string& id);
 
-  bool SetUsingBundle_m(int aMLine, bool decision);
   bool UpdateFilterFromRemoteDescription_m(
       int aMLine,
       nsAutoPtr<mozilla::MediaPipelineFilter> filter);
 
   // Add a remote stream.
   nsresult AddRemoteStream(nsRefPtr<RemoteSourceStreamInfo> aInfo);
   nsresult AddRemoteStreamHint(int aIndex, bool aIsVideo);
 
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.h
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.h
@@ -2,16 +2,17 @@
 /* vim: set ts=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 _SDPATTRIBUTE_H_
 #define _SDPATTRIBUTE_H_
 
+#include <algorithm>
 #include <vector>
 #include <ostream>
 #include <sstream>
 #include <cstring>
 #include <iomanip>
 #include <string>
 
 #include "mozilla/UniquePtr.h"
@@ -421,16 +422,33 @@ public:
 
   void
   PushEntry(Semantics semantics, const std::vector<std::string>& tags)
   {
     Group value = { semantics, tags };
     mGroups.push_back(value);
   }
 
+  void
+  RemoveMid(const std::string& mid)
+  {
+    for (auto i = mGroups.begin(); i != mGroups.end();) {
+      auto tag = std::find(i->tags.begin(), i->tags.end(), mid);
+      if (tag != i->tags.end()) {
+        i->tags.erase(tag);
+      }
+
+      if (i->tags.empty()) {
+        i = mGroups.erase(i);
+      } else {
+        ++i;
+      }
+    }
+  }
+
   virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE;
 
   std::vector<Group> mGroups;
 };
 
 inline std::ostream& operator<<(std::ostream& os,
                                 SdpGroupAttributeList::Semantics s)
 {
--- a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
+++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
@@ -466,16 +466,37 @@ SipccSdpAttributeList::LoadSetup(sdp_t* 
       // Fall through to MOZ_CRASH() below.
       {
       }
   }
 
   MOZ_CRASH("Invalid setup type from sipcc. This is probably corruption.");
 }
 
+void
+SipccSdpAttributeList::LoadSsrc(sdp_t* sdp, uint16_t level)
+{
+  auto ssrcs = MakeUnique<SdpSsrcAttributeList>();
+
+  for (uint16_t i = 1; i < UINT16_MAX; ++i) {
+    sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_SSRC, i);
+
+    if (!attr) {
+      break;
+    }
+
+    sdp_ssrc_t* ssrc = &(attr->attr.ssrc);
+    ssrcs->PushEntry(ssrc->ssrc, ssrc->attribute);
+  }
+
+  if (!ssrcs->mSsrcs.empty()) {
+    SetAttribute(ssrcs.release());
+  }
+}
+
 bool
 SipccSdpAttributeList::LoadGroups(sdp_t* sdp, uint16_t level,
                                   SdpErrorHolder& errorHolder)
 {
   uint16_t attrCount = 0;
   if (sdp_attr_num_instances(sdp, level, 0, SDP_ATTR_GROUP, &attrCount) !=
       SDP_SUCCESS) {
     MOZ_ASSERT(false, "Could not get count of group attributes");
@@ -818,16 +839,17 @@ SipccSdpAttributeList::Load(sdp_t* sdp, 
       if (!LoadRtpmap(sdp, level, errorHolder)) {
         return false;
       }
     }
     LoadCandidate(sdp, level);
     LoadFmtp(sdp, level);
     LoadMsids(sdp, level, errorHolder);
     LoadRtcpFb(sdp, level, errorHolder);
+    LoadSsrc(sdp, level);
   }
 
   LoadIceAttributes(sdp, level);
   if (!LoadFingerprint(sdp, level, errorHolder)) {
     return false;
   }
   LoadSetup(sdp, level);
   LoadExtmap(sdp, level, errorHolder);
@@ -1093,17 +1115,21 @@ SipccSdpAttributeList::GetSetup() const
   }
   const SdpAttribute* attr = GetAttribute(SdpAttribute::kSetupAttribute);
   return *static_cast<const SdpSetupAttribute*>(attr);
 }
 
 const SdpSsrcAttributeList&
 SipccSdpAttributeList::GetSsrc() const
 {
-  MOZ_CRASH("Not yet implemented");
+  if (!HasAttribute(SdpAttribute::kSsrcAttribute)) {
+    MOZ_CRASH();
+  }
+  const SdpAttribute* attr = GetAttribute(SdpAttribute::kSsrcAttribute);
+  return *static_cast<const SdpSsrcAttributeList*>(attr);
 }
 
 const SdpSsrcGroupAttributeList&
 SipccSdpAttributeList::GetSsrcGroup() const
 {
   MOZ_CRASH("Not yet implemented");
 }
 
--- a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.h
+++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.h
@@ -98,16 +98,17 @@ private:
   void LoadFlags(sdp_t* sdp, uint16_t level);
   void LoadDirection(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
   bool LoadRtpmap(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
   bool LoadSctpmap(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
   void LoadIceAttributes(sdp_t* sdp, uint16_t level);
   bool LoadFingerprint(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
   void LoadCandidate(sdp_t* sdp, uint16_t level);
   void LoadSetup(sdp_t* sdp, uint16_t level);
+  void LoadSsrc(sdp_t* sdp, uint16_t level);
   bool LoadGroups(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
   void LoadFmtp(sdp_t* sdp, uint16_t level);
   void LoadMsids(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
   void LoadExtmap(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
   void LoadRtcpFb(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
   static SdpRtpmapAttributeList::CodecType GetCodecType(rtp_ptype type);
 
   bool
--- a/media/webrtc/signaling/src/sdp/sipcc/ccsdp.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/ccsdp.h
@@ -250,16 +250,17 @@ typedef enum {
     SDP_ATTR_CONNECTION,
     SDP_ATTR_EXTMAP,  /* RFC 5285 */
     SDP_ATTR_IDENTITY,
     SDP_ATTR_MSID,
     SDP_ATTR_MSID_SEMANTIC,
     SDP_ATTR_BUNDLE_ONLY,
     SDP_ATTR_END_OF_CANDIDATES,
     SDP_ATTR_ICE_OPTIONS,
+    SDP_ATTR_SSRC,
     SDP_MAX_ATTR_TYPES,
     SDP_ATTR_INVALID
 } sdp_attr_e;
 /* This is here so that it can be used in the VcmSIPCCBinding interface */
 typedef enum {
     SDP_SETUP_NOT_FOUND = -1,
     SDP_SETUP_ACTIVE = 0,
     SDP_SETUP_PASSIVE,
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp.h
@@ -914,16 +914,20 @@ typedef struct sdp_media_profiles {
 typedef struct sdp_extmap {
     uint16_t              id;
     sdp_direction_e  media_direction;
     tinybool         media_direction_specified;
     char             uri[SDP_MAX_STRING_LEN+1];
     char             extension_attributes[SDP_MAX_STRING_LEN+1];
 } sdp_extmap_t;
 
+typedef struct sdp_ssrc {
+    uint32_t ssrc;
+    char     attribute[SDP_MAX_STRING_LEN + 1];
+} sdp_ssrc_t;
 
 /*
  * sdp_srtp_crypto_context_t
  *  This type is used to hold cryptographic context information.
  *
  */
 typedef struct sdp_srtp_crypto_context_t_ {
     int32_t                   tag;
@@ -1006,16 +1010,17 @@ typedef struct sdp_attr {
         sdp_mptime_t          mptime;
         sdp_stream_data_t     stream_data;
         char                  unknown[SDP_MAX_STRING_LEN+1];
         sdp_source_filter_t   source_filter;
         sdp_fmtp_fb_t         rtcp_fb;
         sdp_setup_type_e      setup;
         sdp_connection_type_e connection;
         sdp_extmap_t          extmap;
+        sdp_ssrc_t            ssrc;
     } attr;
     struct sdp_attr          *next_p;
 } sdp_attr_t;
 typedef struct sdp_srtp_crypto_suite_list_ {
     sdp_srtp_crypto_suite_t crypto_suite_val;
     char * crypto_suite_str;
     unsigned char key_size_bytes;
     unsigned char salt_size_bytes;
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
@@ -5309,8 +5309,53 @@ sdp_result_e sdp_build_attr_msid(sdp_t *
                                  flex_string *fs)
 {
     flex_string_sprintf(fs, "a=msid:%s%s%s\r\n",
                         attr_p->attr.msid.identifier,
                         attr_p->attr.msid.appdata[0] ? " " : "",
                         attr_p->attr.msid.appdata);
     return SDP_SUCCESS;
 }
+
+sdp_result_e sdp_parse_attr_ssrc(sdp_t *sdp_p,
+                                 sdp_attr_t *attr_p,
+                                 const char *ptr)
+{
+    sdp_result_e result;
+
+    attr_p->attr.ssrc.ssrc =
+        (uint32_t)sdp_getnextnumtok(ptr, &ptr, " \t", &result);
+
+    if (result != SDP_SUCCESS) {
+        sdp_parse_error(sdp_p, "%s Warning: Bad ssrc attribute, cannot parse ssrc",
+                        sdp_p->debug_str);
+        sdp_p->conf_p->num_invalid_param++;
+        return SDP_INVALID_PARAMETER;
+    }
+
+    /* Skip any remaining WS  */
+    while (*ptr == ' ' || *ptr == '\t') {
+        ptr++;
+    }
+
+    /* Just store the rest of the line in "attribute" -- this will return
+       a failure result if there is no more text, but that's fine. */
+    ptr = sdp_getnextstrtok(ptr,
+                            attr_p->attr.ssrc.attribute,
+                            sizeof(attr_p->attr.ssrc.attribute),
+                            "\r\n",
+                            &result);
+
+    return SDP_SUCCESS;
+}
+
+
+sdp_result_e sdp_build_attr_ssrc(sdp_t *sdp_p,
+                                 sdp_attr_t *attr_p,
+                                 flex_string *fs)
+{
+    flex_string_sprintf(fs, "a=ssrc:%s%s%s\r\n",
+                        attr_p->attr.ssrc.ssrc,
+                        attr_p->attr.ssrc.attribute[0] ? " " : "",
+                        attr_p->attr.ssrc.attribute);
+    return SDP_SUCCESS;
+}
+
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_main.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_main.c
@@ -187,16 +187,18 @@ const sdp_attrarray_t sdp_attr[SDP_MAX_A
     {"msid-semantic", sizeof("msid-semantic"),
       sdp_parse_attr_simple_string, sdp_build_attr_simple_string},
     {"bundle-only", sizeof("bundle-only"),
       sdp_parse_attr_simple_flag, sdp_build_attr_simple_flag},
     {"end-of-candidates", sizeof("end-of-candidates"),
       sdp_parse_attr_simple_flag, sdp_build_attr_simple_flag},
     {"ice-options", sizeof("ice-options"),
       sdp_parse_attr_complete_line, sdp_build_attr_simple_string},
+    {"ssrc", sizeof("ssrc"),
+      sdp_parse_attr_ssrc, sdp_build_attr_ssrc},
 };
 
 /* Note: These *must* be in the same order as the enum types. */
 const sdp_namearray_t sdp_media[SDP_MAX_MEDIA_TYPES] =
 {
     {"audio",        sizeof("audio")},
     {"video",        sizeof("video")},
     {"application",  sizeof("application")},
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_private.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_private.h
@@ -75,16 +75,20 @@ extern sdp_result_e sdp_build_attr_fmtp(
 extern sdp_result_e sdp_parse_attr_sctpmap(sdp_t *sdp_p, sdp_attr_t *attr_p,
                                            const char *ptr);
 extern sdp_result_e sdp_build_attr_sctpmap(sdp_t *sdp_p, sdp_attr_t *attr_p,
                                            flex_string *fs);
 extern sdp_result_e sdp_parse_attr_msid(sdp_t *sdp_p, sdp_attr_t *attr_p,
                                         const char *ptr);
 extern sdp_result_e sdp_build_attr_msid(sdp_t *sdp_p, sdp_attr_t *attr_p,
                                         flex_string *fs);
+extern sdp_result_e sdp_parse_attr_ssrc(sdp_t *sdp_p, sdp_attr_t *attr_p,
+                                        const char *ptr);
+extern sdp_result_e sdp_build_attr_ssrc(sdp_t *sdp_p, sdp_attr_t *attr_p,
+                                        flex_string *fs);
 extern sdp_result_e sdp_parse_attr_direction(sdp_t *sdp_p, sdp_attr_t *attr_p,
                                      const char *ptr);
 extern sdp_result_e sdp_build_attr_direction(sdp_t *sdp_p, sdp_attr_t *attr_p,
                                      flex_string *fs);
 extern sdp_result_e sdp_parse_attr_qos(sdp_t *sdp_p, sdp_attr_t *attr_p,
                                      const char *ptr);
 extern sdp_result_e sdp_build_attr_qos(sdp_t *sdp_p, sdp_attr_t *attr_p,
                                      flex_string *fs);
--- a/media/webrtc/signaling/test/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_session_unittest.cpp
@@ -1195,16 +1195,72 @@ TEST_F(JsepSessionTest, TestRtcpFbStar)
     ASSERT_EQ(NS_OK, details->GetCodec(i, &codec));
     const JsepVideoCodecDescription* videoCodec =
       static_cast<const JsepVideoCodecDescription*>(codec);
     ASSERT_EQ(1U, videoCodec->mNackFbTypes.size());
     ASSERT_EQ("", videoCodec->mNackFbTypes[0]);
   }
 }
 
+TEST_F(JsepSessionTest, TestUniquePayloadTypes)
+{
+  // The audio payload types will all appear more than once, but the video
+  // payload types will be unique.
+  AddTracks(&mSessionOff, "audio,audio,video");
+  AddTracks(&mSessionAns, "audio,audio,video");
+
+  std::string offer = CreateOffer();
+  SetLocalOffer(offer, CHECK_SUCCESS);
+  SetRemoteOffer(offer, CHECK_SUCCESS);
+  std::string answer = CreateAnswer();
+  SetLocalAnswer(answer, CHECK_SUCCESS);
+  SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+  ASSERT_EQ(3U, mSessionOff.GetNegotiatedTrackPairCount());
+  ASSERT_EQ(3U, mSessionAns.GetNegotiatedTrackPairCount());
+
+  const JsepTrackPair* pair;
+  ASSERT_EQ(NS_OK, mSessionOff.GetNegotiatedTrackPair(0, &pair));
+  ASSERT_TRUE(pair->mReceiving);
+  ASSERT_TRUE(pair->mReceiving->GetNegotiatedDetails());
+  ASSERT_EQ(0U,
+      pair->mReceiving->GetNegotiatedDetails()->GetUniquePayloadTypes().size());
+
+  ASSERT_EQ(NS_OK, mSessionOff.GetNegotiatedTrackPair(1, &pair));
+  ASSERT_TRUE(pair->mReceiving);
+  ASSERT_TRUE(pair->mReceiving->GetNegotiatedDetails());
+  ASSERT_EQ(0U,
+      pair->mReceiving->GetNegotiatedDetails()->GetUniquePayloadTypes().size());
+
+  ASSERT_EQ(NS_OK, mSessionOff.GetNegotiatedTrackPair(2, &pair));
+  ASSERT_TRUE(pair->mReceiving);
+  ASSERT_TRUE(pair->mReceiving->GetNegotiatedDetails());
+  ASSERT_NE(0U,
+      pair->mReceiving->GetNegotiatedDetails()->GetUniquePayloadTypes().size());
+
+  ASSERT_EQ(NS_OK, mSessionAns.GetNegotiatedTrackPair(0, &pair));
+  ASSERT_TRUE(pair->mReceiving);
+  ASSERT_TRUE(pair->mReceiving->GetNegotiatedDetails());
+  ASSERT_EQ(0U,
+      pair->mReceiving->GetNegotiatedDetails()->GetUniquePayloadTypes().size());
+
+  ASSERT_EQ(NS_OK, mSessionAns.GetNegotiatedTrackPair(1, &pair));
+  ASSERT_TRUE(pair->mReceiving);
+  ASSERT_TRUE(pair->mReceiving->GetNegotiatedDetails());
+  ASSERT_EQ(0U,
+      pair->mReceiving->GetNegotiatedDetails()->GetUniquePayloadTypes().size());
+
+  ASSERT_EQ(NS_OK, mSessionAns.GetNegotiatedTrackPair(2, &pair));
+  ASSERT_TRUE(pair->mReceiving);
+  ASSERT_TRUE(pair->mReceiving->GetNegotiatedDetails());
+  ASSERT_NE(0U,
+      pair->mReceiving->GetNegotiatedDetails()->GetUniquePayloadTypes().size());
+
+}
+
 } // namespace mozilla
 
 int
 main(int argc, char** argv)
 {
   NSS_NoDB_Init(nullptr);
   NSS_SetDomesticPolicy();
 
--- a/media/webrtc/signaling/test/mediapipeline_unittest.cpp
+++ b/media/webrtc/signaling/test/mediapipeline_unittest.cpp
@@ -269,17 +269,18 @@ class TestAgentSend : public TestAgent {
         test_pc,
         nullptr,
         test_utils->sts_target(),
         audio_,
         1,
         false,
         audio_conduit_,
         rtp,
-        rtcp);
+        rtcp,
+        nsAutoPtr<MediaPipelineFilter>());
 
     audio_pipeline_->Init();
   }
 
   void SetUsingBundle(bool use_bundle) {
     use_bundle_ = use_bundle;
   }
 
@@ -309,47 +310,33 @@ class TestAgentReceive : public TestAgen
     EXPECT_EQ(mozilla::kMediaConduitNoError, err);
 
     std::string test_pc("PC");
 
     if (aIsRtcpMux) {
       ASSERT_FALSE(audio_rtcp_transport_.flow_);
     }
 
-    // For now, assume bundle always uses rtcp mux
-    RefPtr<TransportFlow> dummy;
-    RefPtr<TransportFlow> bundle_transport;
-    if (bundle_filter_) {
-      bundle_transport = bundle_transport_.flow_;
-      bundle_filter_->AddLocalSSRC(GetLocalSSRC());
-    }
-
     audio_pipeline_ = new mozilla::MediaPipelineReceiveAudio(
         test_pc,
         nullptr,
         test_utils->sts_target(),
         audio_->GetStream(), 1, 1,
         static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get()),
         audio_rtp_transport_.flow_,
         audio_rtcp_transport_.flow_,
-        bundle_transport,
-        dummy,
         bundle_filter_);
 
     audio_pipeline_->Init();
   }
 
   void SetBundleFilter(nsAutoPtr<MediaPipelineFilter> filter) {
     bundle_filter_ = filter;
   }
 
-  void SetUsingBundle_s(bool decision) {
-    audio_pipeline_->SetUsingBundle_s(decision);
-  }
-
   void UpdateFilterFromRemoteDescription_s(
       nsAutoPtr<MediaPipelineFilter> filter) {
     audio_pipeline_->UpdateFilterFromRemoteDescription_s(filter);
   }
 
  private:
   nsAutoPtr<MediaPipelineFilter> bundle_filter_;
 };
@@ -382,104 +369,97 @@ class MediaPipelineTest : public ::testi
     // BUNDLE, p1_ is server, p2_ is client
     mozilla::SyncRunnable::DispatchToThread(
       test_utils->sts_target(),
       WrapRunnableNM(&TestAgent::ConnectBundle, &p2_, &p1_));
   }
 
   // Verify RTP and RTCP
   void TestAudioSend(bool aIsRtcpMux,
-                     bool bundle = false,
-                     nsAutoPtr<MediaPipelineFilter> localFilter =
+                     nsAutoPtr<MediaPipelineFilter> initialFilter =
                         nsAutoPtr<MediaPipelineFilter>(nullptr),
-                     nsAutoPtr<MediaPipelineFilter> remoteFilter =
+                     nsAutoPtr<MediaPipelineFilter> refinedFilter =
                         nsAutoPtr<MediaPipelineFilter>(nullptr),
-                     unsigned int ms_until_answer = 500,
+                     unsigned int ms_until_filter_update = 500,
                      unsigned int ms_of_traffic_after_answer = 10000) {
 
+    bool bundle = !!(initialFilter);
     // We do not support testing bundle without rtcp mux, since that doesn't
     // make any sense.
     ASSERT_FALSE(!aIsRtcpMux && bundle);
 
-    p1_.SetUsingBundle(bundle);
-    p2_.SetBundleFilter(localFilter);
+    p2_.SetBundleFilter(initialFilter);
 
     // Setup transport flows
     InitTransports(aIsRtcpMux);
 
     mozilla::SyncRunnable::DispatchToThread(
       test_utils->sts_target(),
       WrapRunnable(&p1_, &TestAgent::CreatePipelines_s, aIsRtcpMux));
 
     mozilla::SyncRunnable::DispatchToThread(
       test_utils->sts_target(),
       WrapRunnable(&p2_, &TestAgent::CreatePipelines_s, aIsRtcpMux));
 
     p2_.Start();
     p1_.Start();
 
-    // Simulate pre-answer traffic
-    PR_Sleep(ms_until_answer);
-
-    mozilla::SyncRunnable::DispatchToThread(
-      test_utils->sts_target(),
-      WrapRunnable(&p2_, &TestAgentReceive::SetUsingBundle_s, bundle));
+    if (bundle) {
+      PR_Sleep(ms_until_filter_update);
 
-    if (bundle) {
-      // Leaving remoteFilter not set implies we want to test sunny-day
-      if (!remoteFilter) {
-        remoteFilter = new MediaPipelineFilter;
+      // Leaving refinedFilter not set implies we want to just update with
+      // the other side's SSRC
+      if (!refinedFilter) {
+        refinedFilter = new MediaPipelineFilter;
         // Might not be safe, strictly speaking.
-        remoteFilter->AddRemoteSSRC(p1_.GetLocalSSRC());
+        refinedFilter->AddRemoteSSRC(p1_.GetLocalSSRC());
       }
 
       mozilla::SyncRunnable::DispatchToThread(
           test_utils->sts_target(),
           WrapRunnable(&p2_,
                        &TestAgentReceive::UpdateFilterFromRemoteDescription_s,
-                       remoteFilter));
+                       refinedFilter));
     }
 
-
     // wait for some RTP/RTCP tx and rx to happen
     PR_Sleep(ms_of_traffic_after_answer);
 
     p1_.Stop();
     p2_.Stop();
 
     // wait for any packets in flight to arrive
     PR_Sleep(100);
 
     p1_.Shutdown();
     p2_.Shutdown();
 
     if (!bundle) {
-      // If we are doing bundle, allow the test-case to do this checking.
+      // If we are filtering, allow the test-case to do this checking.
       ASSERT_GE(p1_.GetAudioRtpCountSent(), 40);
       ASSERT_EQ(p1_.GetAudioRtpCountReceived(), p2_.GetAudioRtpCountSent());
       ASSERT_EQ(p1_.GetAudioRtpCountSent(), p2_.GetAudioRtpCountReceived());
 
       // Calling ShutdownMedia_m on both pipelines does not stop the flow of
       // RTCP. So, we might be off by one here.
       ASSERT_LE(p2_.GetAudioRtcpCountReceived(), p1_.GetAudioRtcpCountSent());
       ASSERT_GE(p2_.GetAudioRtcpCountReceived() + 1, p1_.GetAudioRtcpCountSent());
     }
 
   }
 
-  void TestAudioReceiverOffersBundle(bool bundle_accepted,
-      nsAutoPtr<MediaPipelineFilter> localFilter,
-      nsAutoPtr<MediaPipelineFilter> remoteFilter =
+  void TestAudioReceiverBundle(bool bundle_accepted,
+      nsAutoPtr<MediaPipelineFilter> initialFilter,
+      nsAutoPtr<MediaPipelineFilter> refinedFilter =
           nsAutoPtr<MediaPipelineFilter>(nullptr),
       unsigned int ms_until_answer = 500,
       unsigned int ms_of_traffic_after_answer = 10000) {
     TestAudioSend(true,
-                  bundle_accepted,
-                  localFilter,
-                  remoteFilter,
+                  initialFilter,
+                  refinedFilter,
                   ms_until_answer,
                   ms_of_traffic_after_answer);
   }
 protected:
   TestAgentSend p1_;
   TestAgentReceive p2_;
 };
 
@@ -869,50 +849,44 @@ TEST_F(MediaPipelineFilterTest, TestRemo
 TEST_F(MediaPipelineTest, TestAudioSendNoMux) {
   TestAudioSend(false);
 }
 
 TEST_F(MediaPipelineTest, TestAudioSendMux) {
   TestAudioSend(true);
 }
 
-TEST_F(MediaPipelineTest, TestAudioSendBundleOfferedAndDeclined) {
-  nsAutoPtr<MediaPipelineFilter> filter(new MediaPipelineFilter);
-  TestAudioReceiverOffersBundle(false, filter);
-}
-
-TEST_F(MediaPipelineTest, TestAudioSendBundleOfferedAndAccepted) {
+TEST_F(MediaPipelineTest, TestAudioSendBundle) {
   nsAutoPtr<MediaPipelineFilter> filter(new MediaPipelineFilter);
   // These durations have to be _extremely_ long to have any assurance that
   // some RTCP will be sent at all. This is because the first RTCP packet
   // is sometimes sent before the transports are ready, which causes it to
   // be dropped.
-  TestAudioReceiverOffersBundle(true,
-                                filter,
+  TestAudioReceiverBundle(true,
+                          filter,
   // We do not specify the filter for the remote description, so it will be
   // set to something sane after a short time.
-                                nsAutoPtr<MediaPipelineFilter>(),
-                                10000,
-                                10000);
+                          nsAutoPtr<MediaPipelineFilter>(),
+                          10000,
+                          10000);
 
   // Some packets should have been dropped, but not all
   ASSERT_GT(p1_.GetAudioRtpCountSent(), p2_.GetAudioRtpCountReceived());
   ASSERT_GT(p2_.GetAudioRtpCountReceived(), 40);
   ASSERT_GT(p1_.GetAudioRtcpCountSent(), 1);
   ASSERT_GT(p1_.GetAudioRtcpCountSent(), p2_.GetAudioRtcpCountReceived());
   ASSERT_GT(p2_.GetAudioRtcpCountReceived(), 0);
 }
 
-TEST_F(MediaPipelineTest, TestAudioSendBundleOfferedAndAcceptedEmptyFilter) {
+TEST_F(MediaPipelineTest, TestAudioSendEmptyBundleFilter) {
   nsAutoPtr<MediaPipelineFilter> filter(new MediaPipelineFilter);
   nsAutoPtr<MediaPipelineFilter> bad_answer_filter(new MediaPipelineFilter);
-  TestAudioReceiverOffersBundle(true, filter, bad_answer_filter);
+  TestAudioReceiverBundle(true, filter, bad_answer_filter);
   // Filter is empty, so should drop everything.
   ASSERT_EQ(0, p2_.GetAudioRtpCountReceived());
-  ASSERT_EQ(0, p2_.GetAudioRtcpCountReceived());
 }
 
 }  // end namespace
 
 
 int main(int argc, char **argv) {
   test_utils = new MtransportTestUtils();
   // Start the tests
--- a/media/webrtc/signaling/test/sdp_unittests.cpp
+++ b/media/webrtc/signaling/test/sdp_unittests.cpp
@@ -1154,16 +1154,17 @@ const std::string kBasicAudioVideoOffer 
 "a=candidate:2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr 10.0.0.36 rport 62453" CRLF
 "a=candidate:3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr 162.222.183.171 rport 49761" CRLF
 "a=candidate:6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 rport 51858" CRLF
 "a=candidate:3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 rport 62454" CRLF
 "a=candidate:2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport 55428" CRLF
 "a=candidate:6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 rport 50340" CRLF
 "a=candidate:0 2 UDP 2130379006 10.0.0.36 55428 typ host" CRLF
 "a=end-of-candidates" CRLF
+"a=ssrc:5150" CRLF
 "m=video 9 RTP/SAVPF 120" CRLF
 "c=IN IP6 ::1" CRLF
 "a=mid:second" CRLF
 "a=rtpmap:120 VP8/90000" CRLF
 "a=fmtp:120 max-fs=3600;max-fr=30" CRLF
 "a=recvonly" CRLF
 "a=rtcp-fb:120 nack" CRLF
 "a=rtcp-fb:120 nack pli" CRLF
@@ -1176,16 +1177,18 @@ const std::string kBasicAudioVideoOffer 
 "a=candidate:0 2 UDP 2130379006 10.0.0.36 64378 typ host" CRLF
 "a=candidate:2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr 10.0.0.36 rport 64378" CRLF
 "a=candidate:6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr 162.222.183.171 rport 64941" CRLF
 "a=candidate:6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr 162.222.183.171 rport 64800" CRLF
 "a=candidate:2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr 10.0.0.36 rport 59530" CRLF
 "a=candidate:3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr 162.222.183.171 rport 62935" CRLF
 "a=candidate:3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr 162.222.183.171 rport 61026" CRLF
 "a=end-of-candidates" CRLF
+"a=ssrc:1111 foo" CRLF
+"a=ssrc:1111 foo:bar" CRLF
 "m=audio 9 RTP/SAVPF 0" CRLF
 "a=mid:third" CRLF
 "a=rtpmap:0 PCMU/8000" CRLF
 "a=ice-lite" CRLF
 "a=ice-options:foo bar" CRLF
 "a=msid:noappdata" CRLF
 "a=bundle-only" CRLF;
 
@@ -1324,16 +1327,39 @@ TEST_P(NewSdpTest, CheckSetup) {
   ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
       SdpAttribute::kSetupAttribute));
   ASSERT_EQ(SdpSetupAttribute::kActive,
       mSdp->GetMediaSection(1).GetAttributeList().GetSetup().mRole);
   ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
         SdpAttribute::kSetupAttribute));
 }
 
+TEST_P(NewSdpTest, CheckSsrc)
+{
+  ParseSdp(kBasicAudioVideoOffer);
+  ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+  ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+      SdpAttribute::kSsrcAttribute));
+  auto ssrcs = mSdp->GetMediaSection(0).GetAttributeList().GetSsrc().mSsrcs;
+  ASSERT_EQ(1U, ssrcs.size());
+  ASSERT_EQ(5150U, ssrcs[0].ssrc);
+  ASSERT_EQ("", ssrcs[0].attribute);
+
+  ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+      SdpAttribute::kSsrcAttribute));
+  ssrcs = mSdp->GetMediaSection(1).GetAttributeList().GetSsrc().mSsrcs;
+  ASSERT_EQ(2U, ssrcs.size());
+  ASSERT_EQ(1111U, ssrcs[0].ssrc);
+  ASSERT_EQ("foo", ssrcs[0].attribute);
+  ASSERT_EQ(1111U, ssrcs[1].ssrc);
+  ASSERT_EQ("foo:bar", ssrcs[1].attribute);
+}
+
 TEST_P(NewSdpTest, CheckRtpmap) {
   ParseSdp(kBasicAudioVideoOffer);
   ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors();
   ASSERT_EQ(3U, mSdp->GetMediaSectionCount())
     << "Wrong number of media sections";
 
   const SdpMediaSection& audiosec = mSdp->GetMediaSection(0);
   const SdpRtpmapAttributeList& rtpmap = audiosec.GetAttributeList().GetRtpmap();
--- a/media/webrtc/signaling/test/signaling_unittests.cpp
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -925,17 +925,18 @@ NS_IMPL_ISUPPORTS(PCDispatchWrapper, nsI
 
 
 class SignalingAgent {
  public:
   explicit SignalingAgent(const std::string &aName,
     const std::string stun_addr = g_stun_server_address,
     uint16_t stun_port = g_stun_server_port) :
     pc(nullptr),
-    name(aName) {
+    name(aName),
+    mBundleEnabled(true) {
     cfg_.addStunServer(stun_addr, stun_port);
 
     PeerConnectionImpl *pcImpl =
       PeerConnectionImpl::CreatePeerConnection();
     EXPECT_TRUE(pcImpl);
     pcImpl->SetAllowIceLoopback(true);
     pc = new PCDispatchWrapper(pcImpl);
   }
@@ -955,16 +956,21 @@ class SignalingAgent {
   }
 
   void Init()
   {
     mozilla::SyncRunnable::DispatchToThread(gMainThread,
       WrapRunnable(this, &SignalingAgent::Init_m));
   }
 
+  void SetBundleEnabled(bool enabled)
+  {
+    mBundleEnabled = enabled;
+  }
+
   void WaitForGather() {
     ASSERT_TRUE_WAIT(ice_gathering_state() == PCImplIceGatheringState::Complete,
                      kDefaultTimeout);
 
     std::cout << name << ": Init Complete" << std::endl;
 
     // Check that the default candidate has been filled out with something
     std::string localSdp = getLocalDescription();
@@ -1052,16 +1058,22 @@ class SignalingAgent {
     if (!sdp) {
       return "";
     }
     std::string result(sdp);
     delete sdp;
     return result;
   }
 
+  std::string RemoveBundle(const std::string& sdp) const {
+    ParsedSDP parsed(sdp);
+    parsed.DeleteLines("a=group:BUNDLE");
+    return parsed.getSdp();
+  }
+
   // Adds a stream to the PeerConnection.
   void AddStream(uint32_t hint =
          DOMMediaStream::HINT_CONTENTS_AUDIO |
          DOMMediaStream::HINT_CONTENTS_VIDEO,
        MediaStream *stream = nullptr) {
 
     nsRefPtr<DOMMediaStream> domMediaStream = new DOMMediaStream(stream);
     domMediaStream->SetHintContents(hint);
@@ -1108,27 +1120,34 @@ class SignalingAgent {
     if (offerFlags & OFFER_VIDEO) {
       aHintContents |= DOMMediaStream::HINT_CONTENTS_VIDEO;
     }
     AddStream(aHintContents, audio_stream);
 
     // Now call CreateOffer as JS would
     pObserver->state = TestObserver::stateNoResponse;
     ASSERT_EQ(pc->CreateOffer(options), NS_OK);
+
     ASSERT_EQ(pObserver->state, TestObserver::stateSuccess);
     SDPSanityCheck(pObserver->lastString, sdpCheck, true);
     ASSERT_EQ(signaling_state(), endState);
     offer_ = pObserver->lastString;
+    if (!mBundleEnabled) {
+      offer_ = RemoveBundle(offer_);
+    }
   }
 
   // sets the offer to match the local description
   // which isn't good if you are the answerer
   void UpdateOffer(uint32_t sdpCheck) {
     offer_ = getLocalDescription();
     SDPSanityCheck(offer_, sdpCheck, true);
+    if (!mBundleEnabled) {
+      offer_ = RemoveBundle(offer_);
+    }
   }
 
   void CreateAnswer(uint32_t offerAnswerFlags,
                     uint32_t sdpCheck = DONT_CHECK_AUDIO|
                                         DONT_CHECK_VIDEO|
                                         DONT_CHECK_DATA,
                     PCImplSignalingState endState =
                     PCImplSignalingState::SignalingHaveRemoteOffer) {
@@ -1146,23 +1165,29 @@ class SignalingAgent {
     // then perform SDP checking based on which stream disabled
     pObserver->state = TestObserver::stateNoResponse;
     ASSERT_EQ(pc->CreateAnswer(), NS_OK);
     ASSERT_EQ(pObserver->state, TestObserver::stateSuccess);
     SDPSanityCheck(pObserver->lastString, sdpCheck, false);
     ASSERT_EQ(signaling_state(), endState);
 
     answer_ = pObserver->lastString;
+    if (!mBundleEnabled) {
+      answer_ = RemoveBundle(answer_);
+    }
   }
 
   // sets the answer to match the local description
   // which isn't good if you are the offerer
   void UpdateAnswer(uint32_t sdpCheck) {
     answer_ = getLocalDescription();
     SDPSanityCheck(answer_, sdpCheck, false);
+    if (!mBundleEnabled) {
+      answer_ = RemoveBundle(answer_);
+    }
   }
 
   // At present, we use the hints field in a stream to find and
   // remove it. This only works if the specified hints flags are
   // unique among all streams in the PeerConnection. This is not
   // generally true, and will need significant revision once
   // multiple streams are supported.
   void CreateOfferRemoveStream(OfferOptions& options,
@@ -1177,16 +1202,19 @@ class SignalingAgent {
     RemoveLastStreamAdded();
 
     // Now call CreateOffer as JS would
     pObserver->state = TestObserver::stateNoResponse;
     ASSERT_EQ(pc->CreateOffer(options), NS_OK);
     ASSERT_TRUE(pObserver->state == TestObserver::stateSuccess);
     SDPSanityCheck(pObserver->lastString, sdpCheck, true);
     offer_ = pObserver->lastString;
+    if (!mBundleEnabled) {
+      offer_ = RemoveBundle(offer_);
+    }
   }
 
   void SetRemote(TestObserver::Action action, const std::string& remote,
                  bool ignoreError = false,
                  PCImplSignalingState endState =
                  PCImplSignalingState::SignalingInvalid) {
 
     if (endState == PCImplSignalingState::SignalingInvalid) {
@@ -1380,16 +1408,17 @@ class SignalingAgent {
 public:
   nsRefPtr<PCDispatchWrapper> pc;
   nsRefPtr<TestObserver> pObserver;
   std::string offer_;
   std::string answer_;
   nsRefPtr<DOMMediaStream> domMediaStream_;
   IceConfiguration cfg_;
   const std::string name;
+  bool mBundleEnabled;
 
   typedef struct {
     std::string candidate;
     std::string mid;
     uint16_t level;
     bool expectSuccess;
   } DeferredCandidate;
 
@@ -1660,17 +1689,19 @@ class SignalingAgentTest : public ::test
     return agents_[i];
   }
 
  private:
   std::vector<SignalingAgent *> agents_;
 };
 
 
-class SignalingTest : public ::testing::Test {
+class SignalingTest : public ::testing::Test,
+                      public ::testing::WithParamInterface<std::string>
+{
 public:
   SignalingTest()
       : init_(false),
         a1_(nullptr),
         a2_(nullptr),
         stun_addr_(g_stun_server_address),
         stun_port_(g_stun_server_port) {}
 
@@ -1699,16 +1730,21 @@ public:
 
     if (init_)
       return;
 
     a1_ = new SignalingAgent(callerName, stun_addr_, stun_port_);
     a2_ = new SignalingAgent(calleeName, stun_addr_, stun_port_);
     a1_->Init();
     a2_->Init();
+    if (GetParam() == "no_bundle") {
+      a1_->SetBundleEnabled(false);
+    } else if(GetParam() == "reject_bundle") {
+      a2_->SetBundleEnabled(false);
+    }
 
     a1_->SetPeer(a2_.get());
     a2_->SetPeer(a1_.get());
 
     init_ = true;
   }
 
   void WaitForGather() {
@@ -2061,245 +2097,245 @@ class FsFrPrefClearer {
       const char *pref_name) {
       MOZ_ASSERT(NS_IsMainThread());
       prefs->ClearUserPref(pref_name);
     }
   private:
     nsCOMPtr<nsIPrefBranch> mPrefs;
 };
 
-TEST_F(SignalingTest, JustInit)
+TEST_P(SignalingTest, JustInit)
 {
 }
 
-TEST_F(SignalingTest, CreateSetOffer)
+TEST_P(SignalingTest, CreateSetOffer)
 {
   OfferOptions options;
   CreateSetOffer(options, SHOULD_SENDRECV_AV);
 }
 
-TEST_F(SignalingTest, CreateOfferAudioVideoOptionUndefined)
+TEST_P(SignalingTest, CreateOfferAudioVideoOptionUndefined)
 {
   OfferOptions options;
   CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
 }
 
-TEST_F(SignalingTest, CreateOfferNoVideoStreamRecvVideo)
+TEST_P(SignalingTest, CreateOfferNoVideoStreamRecvVideo)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   CreateOffer(options, OFFER_AUDIO,
               SHOULD_SENDRECV_AUDIO | SHOULD_RECV_VIDEO);
 }
 
-TEST_F(SignalingTest, CreateOfferNoAudioStreamRecvAudio)
+TEST_P(SignalingTest, CreateOfferNoAudioStreamRecvAudio)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   CreateOffer(options, OFFER_VIDEO,
               SHOULD_RECV_AUDIO | SHOULD_SENDRECV_VIDEO);
 }
 
-TEST_F(SignalingTest, CreateOfferNoVideoStream)
+TEST_P(SignalingTest, CreateOfferNoVideoStream)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 0);
   CreateOffer(options, OFFER_AUDIO,
               SHOULD_SENDRECV_AUDIO | SHOULD_OMIT_VIDEO);
 }
 
-TEST_F(SignalingTest, CreateOfferNoAudioStream)
+TEST_P(SignalingTest, CreateOfferNoAudioStream)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 0);
   options.setInt32Option("OfferToReceiveVideo", 1);
   CreateOffer(options, OFFER_VIDEO,
               SHOULD_OMIT_AUDIO | SHOULD_SENDRECV_VIDEO);
 }
 
-TEST_F(SignalingTest, CreateOfferDontReceiveAudio)
+TEST_P(SignalingTest, CreateOfferDontReceiveAudio)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 0);
   options.setInt32Option("OfferToReceiveVideo", 1);
   CreateOffer(options, OFFER_AV,
               SHOULD_SEND_AUDIO | SHOULD_SENDRECV_VIDEO);
 }
 
-TEST_F(SignalingTest, CreateOfferDontReceiveVideo)
+TEST_P(SignalingTest, CreateOfferDontReceiveVideo)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 0);
   CreateOffer(options, OFFER_AV,
               SHOULD_SENDRECV_AUDIO | SHOULD_SEND_VIDEO);
 }
 
 // XXX Disabled pending resolution of Bug 840728
-TEST_F(SignalingTest, DISABLED_CreateOfferRemoveAudioStream)
+TEST_P(SignalingTest, DISABLED_CreateOfferRemoveAudioStream)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   CreateOfferRemoveStream(options, DOMMediaStream::HINT_CONTENTS_AUDIO,
               SHOULD_RECV_AUDIO | SHOULD_SENDRECV_VIDEO);
 }
 
 // XXX Disabled pending resolution of Bug 840728
-TEST_F(SignalingTest, DISABLED_CreateOfferDontReceiveAudioRemoveAudioStream)
+TEST_P(SignalingTest, DISABLED_CreateOfferDontReceiveAudioRemoveAudioStream)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 0);
   options.setInt32Option("OfferToReceiveVideo", 1);
   CreateOfferRemoveStream(options, DOMMediaStream::HINT_CONTENTS_AUDIO,
               SHOULD_SENDRECV_VIDEO);
 }
 
 // XXX Disabled pending resolution of Bug 840728
-TEST_F(SignalingTest, DISABLED_CreateOfferDontReceiveVideoRemoveVideoStream)
+TEST_P(SignalingTest, DISABLED_CreateOfferDontReceiveVideoRemoveVideoStream)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 0);
   CreateOfferRemoveStream(options, DOMMediaStream::HINT_CONTENTS_VIDEO,
               SHOULD_SENDRECV_AUDIO);
 }
 
-TEST_F(SignalingTest, OfferAnswerNothingDisabled)
+TEST_P(SignalingTest, OfferAnswerNothingDisabled)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AV);
 }
 
-TEST_F(SignalingTest, OfferAnswerNoTrickle)
+TEST_P(SignalingTest, OfferAnswerNoTrickle)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV,
               SHOULD_SENDRECV_AV,
               NO_TRICKLE);
 }
 
-TEST_F(SignalingTest, OfferAnswerOffererTrickles)
+TEST_P(SignalingTest, OfferAnswerOffererTrickles)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV,
               SHOULD_SENDRECV_AV,
               OFFERER_TRICKLES);
 }
 
-TEST_F(SignalingTest, OfferAnswerAnswererTrickles)
+TEST_P(SignalingTest, OfferAnswerAnswererTrickles)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AV,
               ANSWERER_TRICKLES);
 }
 
-TEST_F(SignalingTest, OfferAnswerBothTrickle)
+TEST_P(SignalingTest, OfferAnswerBothTrickle)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AV,
               BOTH_TRICKLE);
 }
 
-TEST_F(SignalingTest, OfferAnswerAudioBothTrickle)
+TEST_P(SignalingTest, OfferAnswerAudioBothTrickle)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_AUDIO | ANSWER_AUDIO,
               SHOULD_SENDRECV_AUDIO, SHOULD_SENDRECV_AUDIO,
               BOTH_TRICKLE);
 }
 
 
-TEST_F(SignalingTest, OfferAnswerNothingDisabledFullCycle)
+TEST_P(SignalingTest, OfferAnswerNothingDisabledFullCycle)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AV);
   // verify the default codec priorities
   ASSERT_NE(a1_->getLocalDescription().find("RTP/SAVPF 109 9 0 8\r"), std::string::npos);
   // TODO(bug 1099351): Use commented out code instead.
   ASSERT_NE(a2_->getLocalDescription().find("RTP/SAVPF 109\r"), std::string::npos);;
   // verify that we echoed the same thing (as of SDParta we don't just pick one).
   // ASSERT_NE(a2_->getLocalDescription().find("RTP/SAVPF 109 9 0 8\r"), std::string::npos);;
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest, DISABLED_OfferAnswerDontReceiveAudioOnOffer)
+TEST_P(SignalingTest, DISABLED_OfferAnswerDontReceiveAudioOnOffer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 0);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SEND_AUDIO | SHOULD_SENDRECV_VIDEO,
               SHOULD_RECV_AUDIO | SHOULD_SENDRECV_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest, DISABLED_OfferAnswerDontReceiveVideoOnOffer)
+TEST_P(SignalingTest, DISABLED_OfferAnswerDontReceiveVideoOnOffer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 0);
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AUDIO | SHOULD_SEND_VIDEO,
               SHOULD_SENDRECV_AUDIO | SHOULD_RECV_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest, DISABLED_OfferAnswerDontReceiveAudioOnAnswer)
+TEST_P(SignalingTest, DISABLED_OfferAnswerDontReceiveAudioOnAnswer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV,
               SHOULD_SEND_AUDIO | SHOULD_SENDRECV_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest, DISABLED_OfferAnswerDontReceiveVideoOnAnswer)
+TEST_P(SignalingTest, DISABLED_OfferAnswerDontReceiveVideoOnAnswer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV,
               SHOULD_SENDRECV_AUDIO | SHOULD_SEND_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest, DISABLED_OfferAnswerDontAddAudioStreamOnOfferRecvAudio)
+TEST_P(SignalingTest, DISABLED_OfferAnswerDontAddAudioStreamOnOfferRecvAudio)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_VIDEO | ANSWER_AV,
               SHOULD_RECV_AUDIO | SHOULD_SENDRECV_VIDEO,
               SHOULD_SEND_AUDIO | SHOULD_SENDRECV_VIDEO);
 }
 
-TEST_F(SignalingTest, OfferAnswerAudioInactive)
+TEST_P(SignalingTest, OfferAnswerAudioInactive)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_VIDEO | ANSWER_VIDEO,
               SHOULD_SENDRECV_VIDEO | SHOULD_RECV_AUDIO,
               SHOULD_SENDRECV_VIDEO | SHOULD_INACTIVE_AUDIO);
 }
 
-TEST_F(SignalingTest, OfferAnswerVideoInactive)
+TEST_P(SignalingTest, OfferAnswerVideoInactive)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AUDIO | ANSWER_AUDIO,
               SHOULD_SENDRECV_AUDIO | SHOULD_RECV_VIDEO,
               SHOULD_SENDRECV_AUDIO | SHOULD_INACTIVE_VIDEO);
 
@@ -2311,203 +2347,203 @@ TEST_F(SignalingTest, OfferAnswerVideoIn
   a2_->CloseReceiveStreams();
   // Check that we wrote a bunch of data
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   //ASSERT_GE(a2_->GetPacketsSent(0), 40);
   //ASSERT_GE(a1_->GetPacketsReceived(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
-TEST_F(SignalingTest, OfferAnswerBothInactive)
+TEST_P(SignalingTest, OfferAnswerBothInactive)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_NONE,
               SHOULD_RECV_AUDIO | SHOULD_RECV_VIDEO,
               SHOULD_INACTIVE_AUDIO | SHOULD_INACTIVE_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest, DISABLED_OfferAnswerDontAddAudioStreamOnOffer)
+TEST_P(SignalingTest, DISABLED_OfferAnswerDontAddAudioStreamOnOffer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 0);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_VIDEO | ANSWER_AV,
               SHOULD_OMIT_AUDIO | SHOULD_SENDRECV_VIDEO,
               SHOULD_OMIT_AUDIO | SHOULD_SENDRECV_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest, DISABLED_OfferAnswerDontAddVideoStreamOnOfferRecvVideo)
+TEST_P(SignalingTest, DISABLED_OfferAnswerDontAddVideoStreamOnOfferRecvVideo)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AUDIO | ANSWER_AV,
               SHOULD_SENDRECV_AUDIO | SHOULD_RECV_VIDEO,
               SHOULD_SENDRECV_AUDIO | SHOULD_SEND_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest, DISABLED_OfferAnswerDontAddVideoStreamOnOffer)
+TEST_P(SignalingTest, DISABLED_OfferAnswerDontAddVideoStreamOnOffer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 0);
   OfferAnswer(options, OFFER_AUDIO | ANSWER_AV,
               SHOULD_SENDRECV_AUDIO | SHOULD_OMIT_VIDEO,
               SHOULD_SENDRECV_AUDIO | SHOULD_OMIT_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest, DISABLED_OfferAnswerDontAddAudioStreamOnAnswer)
+TEST_P(SignalingTest, DISABLED_OfferAnswerDontAddAudioStreamOnAnswer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AV | ANSWER_VIDEO,
               SHOULD_SENDRECV_AV,
               SHOULD_RECV_AUDIO | SHOULD_SENDRECV_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest, DISABLED_OfferAnswerDontAddVideoStreamOnAnswer)
+TEST_P(SignalingTest, DISABLED_OfferAnswerDontAddVideoStreamOnAnswer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AV | ANSWER_AUDIO,
               SHOULD_SENDRECV_AV,
               SHOULD_SENDRECV_AUDIO | SHOULD_RECV_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest,
+TEST_P(SignalingTest,
        DISABLED_OfferAnswerDontAddVideoStreamOnAnswerDontReceiveVideoOnAnswer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AV | ANSWER_AUDIO,
               SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AUDIO );
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest,
+TEST_P(SignalingTest,
        DISABLED_OfferAnswerDontAddAudioStreamOnAnswerDontReceiveAudioOnAnswer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AV | ANSWER_VIDEO,
               SHOULD_SENDRECV_AV,
               SHOULD_REJECT_AUDIO | SHOULD_SENDRECV_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest,
+TEST_P(SignalingTest,
        DISABLED_OfferAnswerDontAddAudioStreamOnOfferDontReceiveAudioOnOffer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 0);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_VIDEO | ANSWER_AV,
               SHOULD_SENDRECV_VIDEO, SHOULD_SENDRECV_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest,
+TEST_P(SignalingTest,
        DISABLED_OfferAnswerDontAddVideoStreamOnOfferDontReceiveVideoOnOffer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 0);
   OfferAnswer(options, OFFER_AUDIO | ANSWER_AV,
               SHOULD_SENDRECV_AUDIO | SHOULD_OMIT_VIDEO,
               SHOULD_SENDRECV_AUDIO | SHOULD_OMIT_VIDEO);
 }
 
 // XXX reject streams has changed. Re-enable when we can stop() received stream
-TEST_F(SignalingTest,
+TEST_P(SignalingTest,
   DISABLED_OfferAnswerDontReceiveAudioNoAudioStreamOnOfferDontReceiveVideoOnAnswer)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 0);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_VIDEO | ANSWER_AV,
               SHOULD_SENDRECV_VIDEO, SHOULD_SEND_VIDEO);
 }
 
-TEST_F(SignalingTest, CreateOfferAddCandidate)
+TEST_P(SignalingTest, CreateOfferAddCandidate)
 {
   OfferOptions options;
   CreateOfferAddCandidate(options, strSampleCandidate,
                           strSampleMid, nSamplelevel,
                           SHOULD_SENDRECV_AV);
 }
 
-TEST_F(SignalingTest, AddIceCandidateEarly)
+TEST_P(SignalingTest, AddIceCandidateEarly)
 {
   OfferOptions options;
   AddIceCandidateEarly(strSampleCandidate,
                        strSampleMid, nSamplelevel);
 }
 
 // XXX adam@nostrum.com -- This test seems questionable; we need to think
 // through what actually needs to be tested here.
-TEST_F(SignalingTest, DISABLED_OfferAnswerReNegotiateOfferAnswerDontReceiveVideoNoVideoStream)
+TEST_P(SignalingTest, DISABLED_OfferAnswerReNegotiateOfferAnswerDontReceiveVideoNoVideoStream)
 {
   OfferOptions aoptions;
   aoptions.setInt32Option("OfferToReceiveAudio", 1);
   aoptions.setInt32Option("OfferToReceiveVideo", 1);
 
   OfferOptions boptions;
   boptions.setInt32Option("OfferToReceiveAudio", 1);
   boptions.setInt32Option("OfferToReceiveVideo", 0);
 
   OfferAnswer(aoptions, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AV);
   OfferAnswer(boptions, OFFER_AUDIO | ANSWER_AV,
               SHOULD_SENDRECV_AUDIO | SHOULD_SEND_VIDEO,
               SHOULD_SENDRECV_AUDIO | SHOULD_INACTIVE_VIDEO);
 }
 
-TEST_F(SignalingTest, OfferAnswerDontAddAudioStreamOnAnswerNoOptions)
+TEST_P(SignalingTest, OfferAnswerDontAddAudioStreamOnAnswerNoOptions)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AV | ANSWER_VIDEO,
               SHOULD_SENDRECV_AV,
               SHOULD_RECV_AUDIO | SHOULD_SENDRECV_VIDEO);
 }
 
-TEST_F(SignalingTest, OfferAnswerDontAddVideoStreamOnAnswerNoOptions)
+TEST_P(SignalingTest, OfferAnswerDontAddVideoStreamOnAnswerNoOptions)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AV | ANSWER_AUDIO,
               SHOULD_SENDRECV_AV,
               SHOULD_SENDRECV_AUDIO | SHOULD_RECV_VIDEO);
 }
 
-TEST_F(SignalingTest, OfferAnswerDontAddAudioVideoStreamsOnAnswerNoOptions)
+TEST_P(SignalingTest, OfferAnswerDontAddAudioVideoStreamsOnAnswerNoOptions)
 {
   OfferOptions options;
   options.setInt32Option("OfferToReceiveAudio", 1);
   options.setInt32Option("OfferToReceiveVideo", 1);
   OfferAnswer(options, OFFER_AV | ANSWER_NONE,
               SHOULD_SENDRECV_AV,
               SHOULD_RECV_AUDIO | SHOULD_RECV_VIDEO);
 }
 
-TEST_F(SignalingTest, FullCall)
+TEST_P(SignalingTest, FullCall)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AV);
 
   // Wait for some data to get written
   ASSERT_TRUE_WAIT(a1_->GetPacketsSent(0) >= 40 &&
                    a2_->GetPacketsReceived(0) >= 40, kDefaultTimeout * 2);
@@ -2526,17 +2562,17 @@ TEST_F(SignalingTest, FullCall)
   a1_->CheckMediaPipeline(0, 0, fRtcpMux ?
     PIPELINE_LOCAL | PIPELINE_RTCP_MUX | PIPELINE_SEND :
     PIPELINE_LOCAL | PIPELINE_SEND);
 
   // The first Remote pipeline gets stored at 0
   a2_->CheckMediaPipeline(0, 0, (fRtcpMux ?  PIPELINE_RTCP_MUX : 0));
 }
 
-TEST_F(SignalingTest, FullCallAudioOnly)
+TEST_P(SignalingTest, FullCallAudioOnly)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_AUDIO | ANSWER_AUDIO,
               SHOULD_SENDRECV_AUDIO, SHOULD_SENDRECV_AUDIO);
 
   // Wait for some data to get written
   ASSERT_TRUE_WAIT(a1_->GetPacketsSent(0) >= 40 &&
                    a2_->GetPacketsReceived(0) >= 40, kDefaultTimeout * 2);
@@ -2547,17 +2583,17 @@ TEST_F(SignalingTest, FullCallAudioOnly)
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   //ASSERT_GE(a2_->GetPacketsSent(0), 40);
   //ASSERT_GE(a1_->GetPacketsReceived(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
 // FIXME -- reject offered stream by .stop()ing the MST that was offered instead,
 // or by setting .active property to false on the created RTPReceiver object.
-TEST_F(SignalingTest, DISABLED_FullCallAnswererRejectsVideo)
+TEST_P(SignalingTest, DISABLED_FullCallAnswererRejectsVideo)
 {
   OfferOptions offeroptions;
   OfferOptions answeroptions;
   answeroptions.setInt32Option("offerToReceiveAudio", 1);
   answeroptions.setInt32Option("offerToReceiveVideo", 0);
   OfferAnswer(offeroptions, OFFER_AV | ANSWER_AUDIO,
               SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AUDIO);
 
@@ -2569,17 +2605,17 @@ TEST_F(SignalingTest, DISABLED_FullCallA
   a2_->CloseReceiveStreams();
   // Check that we wrote a bunch of data
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   //ASSERT_GE(a2_->GetPacketsSent(0), 40);
   //ASSERT_GE(a1_->GetPacketsReceived(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
-TEST_F(SignalingTest, FullCallVideoOnly)
+TEST_P(SignalingTest, FullCallVideoOnly)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_VIDEO | ANSWER_VIDEO,
               SHOULD_SENDRECV_VIDEO | SHOULD_OMIT_AUDIO,
               SHOULD_SENDRECV_VIDEO | SHOULD_OMIT_AUDIO);
 
   // If we could check for video packets, we would wait for some to be written
   // here. Since we can't, we don't.
@@ -2595,17 +2631,17 @@ TEST_F(SignalingTest, FullCallVideoOnly)
   //
   // Check that we wrote a bunch of data
   // ASSERT_GE(a1_->GetPacketsSent(0), 40);
   //ASSERT_GE(a2_->GetPacketsSent(0), 40);
   //ASSERT_GE(a1_->GetPacketsReceived(0), 40);
   // ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
-TEST_F(SignalingTest, OfferAndAnswerWithExtraCodec)
+TEST_P(SignalingTest, OfferAndAnswerWithExtraCodec)
 {
   EnsureInit();
   OfferOptions options;
   Offer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
 
   a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO, SHOULD_SENDRECV_AUDIO);
   a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
   ParsedSDP sdpWrapper(a2_->answer());
@@ -2617,17 +2653,17 @@ TEST_F(SignalingTest, OfferAndAnswerWith
   a1_->SetRemote(TestObserver::ANSWER, sdpWrapper.getSdp());
 
   WaitForCompleted();
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
 }
 
-TEST_F(SignalingTest, FullCallTrickle)
+TEST_P(SignalingTest, FullCallTrickle)
 {
   OfferOptions options;
   OfferAnswer(options,
               OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV,
               SHOULD_SENDRECV_AV);
 
   std::cerr << "ICE handshake completed" << std::endl;
@@ -2638,17 +2674,17 @@ TEST_F(SignalingTest, FullCallTrickle)
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
 // Offer answer with trickle but with chrome-style candidates
-TEST_F(SignalingTest, DISABLED_FullCallTrickleChrome)
+TEST_P(SignalingTest, DISABLED_FullCallTrickleChrome)
 {
   OfferOptions options;
   OfferAnswerTrickleChrome(options,
                            OFFER_AV | ANSWER_AV,
                            SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AV);
 
   std::cerr << "ICE handshake completed" << std::endl;
 
@@ -2657,17 +2693,17 @@ TEST_F(SignalingTest, DISABLED_FullCallT
                    a2_->GetPacketsReceived(0) >= 40, kDefaultTimeout * 2);
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
-TEST_F(SignalingTest, FullCallTrickleBeforeSetLocal)
+TEST_P(SignalingTest, FullCallTrickleBeforeSetLocal)
 {
   OfferOptions options;
   Offer(options, OFFER_AV | ANSWER_AV, SHOULD_SENDRECV_AV);
   // ICE will succeed even if one side fails to trickle, so we need to disable
   // one side before performing a test that might cause candidates to be
   // dropped
   a2_->DropOutgoingTrickleCandidates();
   // Wait until all of a1's candidates have been trickled to a2, _before_ a2
@@ -2684,17 +2720,17 @@ TEST_F(SignalingTest, FullCallTrickleBef
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
 // This test comes from Bug 810220
-TEST_F(SignalingTest, AudioOnlyG711Call)
+TEST_P(SignalingTest, AudioOnlyG711Call)
 {
   EnsureInit();
 
   OfferOptions options;
   const std::string& offer(strG711SdpOffer);
 
   std::cout << "Setting offer to:" << std::endl << indent(offer) << std::endl;
   a2_->SetRemote(TestObserver::OFFER, offer);
@@ -2715,17 +2751,17 @@ TEST_F(SignalingTest, AudioOnlyG711Call)
   // We should answer with PCMU and telephone-event
   ASSERT_NE(answer.find(" PCMU/8000"), std::string::npos);
 
   // Double-check the directionality
   ASSERT_NE(answer.find("\r\na=sendrecv"), std::string::npos);
 
 }
 
-TEST_F(SignalingTest, IncomingOfferIceLite)
+TEST_P(SignalingTest, IncomingOfferIceLite)
 {
   EnsureInit();
 
   std::string offer =
     "v=0\r\n"
     "o=- 1936463 1936463 IN IP4 148.147.200.251\r\n"
     "s=-\r\n"
     "c=IN IP4 148.147.200.251\r\n"
@@ -2753,17 +2789,17 @@ TEST_F(SignalingTest, IncomingOfferIceLi
   a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
   a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
 
   ASSERT_EQ(a2_->pc->media()->ice_ctx()->GetControlling(),
             NrIceCtx::ICE_CONTROLLING);
 }
 
 // This test comes from Bug814038
-TEST_F(SignalingTest, ChromeOfferAnswer)
+TEST_P(SignalingTest, ChromeOfferAnswer)
 {
   EnsureInit();
 
   // This is captured SDP from an early interop attempt with Chrome.
   std::string offer =
     "v=0\r\n"
     "o=- 1713781661 2 IN IP4 127.0.0.1\r\n"
     "s=-\r\n"
@@ -2834,17 +2870,17 @@ TEST_F(SignalingTest, ChromeOfferAnswer)
 
   std::cout << "Creating answer:" << std::endl;
   a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
 
   std::string answer = a2_->answer();
 }
 
 
-TEST_F(SignalingTest, FullChromeHandshake)
+TEST_P(SignalingTest, FullChromeHandshake)
 {
   EnsureInit();
 
   std::string offer = "v=0\r\n"
       "o=- 3835809413 2 IN IP4 127.0.0.1\r\n"
       "s=-\r\n"
       "t=0 0\r\n"
       "a=group:BUNDLE audio video\r\n"
@@ -2910,17 +2946,17 @@ TEST_F(SignalingTest, FullChromeHandshak
 
   std::string answer = a2_->answer();
   ASSERT_NE(answer.find("111 opus/"), std::string::npos);
 }
 
 // Disabled pending resolution of bug 818640.
 // Actually, this test is completely broken; you can't just call
 // SetRemote/CreateAnswer over and over again.
-TEST_F(SignalingTest, DISABLED_OfferAllDynamicTypes)
+TEST_P(SignalingTest, DISABLED_OfferAllDynamicTypes)
 {
   EnsureInit();
 
   std::string offer;
   for (int i = 96; i < 128; i++)
   {
     std::stringstream ss;
     ss << i;
@@ -2954,17 +2990,17 @@ TEST_F(SignalingTest, DISABLED_OfferAllD
 
       std::string answer = a2_->answer();
 
       ASSERT_NE(answer.find(ss.str() + " opus/"), std::string::npos);
   }
 
 }
 
-TEST_F(SignalingTest, ipAddrAnyOffer)
+TEST_P(SignalingTest, ipAddrAnyOffer)
 {
   EnsureInit();
 
   std::string offer =
     "v=0\r\n"
     "o=- 1 1 IN IP4 127.0.0.1\r\n"
     "s=-\r\n"
     "b=AS:64\r\n"
@@ -3004,169 +3040,169 @@ static void CreateSDPForBigOTests(std::s
     "c=IN IP4 0.0.0.0\r\n"
     "a=rtpmap:99 opus/48000/2\r\n"
     "a=ice-ufrag:cYuakxkEKH+RApYE\r\n"
     "a=ice-pwd:bwtpzLZD+3jbu8vQHvEa6Xuq\r\n"
     "a=setup:active\r\n"
     "a=sendrecv\r\n";
 }
 
-TEST_F(SignalingTest, BigOValues)
+TEST_P(SignalingTest, BigOValues)
 {
   EnsureInit();
 
   std::string offer;
 
   CreateSDPForBigOTests(offer, "12345678901234567");
 
   a2_->SetRemote(TestObserver::OFFER, offer);
   ASSERT_EQ(a2_->pObserver->state, TestObserver::stateSuccess);
 }
 
-TEST_F(SignalingTest, BigOValuesExtraChars)
+TEST_P(SignalingTest, BigOValuesExtraChars)
 {
   EnsureInit();
 
   std::string offer;
 
   CreateSDPForBigOTests(offer, "12345678901234567FOOBAR");
 
   // The signaling state will remain "stable" because the unparsable
   // SDP leads to a failure in SetRemoteDescription.
   a2_->SetRemote(TestObserver::OFFER, offer, true,
                  PCImplSignalingState::SignalingStable);
   ASSERT_TRUE(a2_->pObserver->state == TestObserver::stateError);
 }
 
-TEST_F(SignalingTest, BigOValuesTooBig)
+TEST_P(SignalingTest, BigOValuesTooBig)
 {
   EnsureInit();
 
   std::string offer;
 
   CreateSDPForBigOTests(offer, "18446744073709551615");
 
   // The signaling state will remain "stable" because the unparsable
   // SDP leads to a failure in SetRemoteDescription.
   a2_->SetRemote(TestObserver::OFFER, offer, true,
                  PCImplSignalingState::SignalingStable);
   ASSERT_TRUE(a2_->pObserver->state == TestObserver::stateError);
 }
 
-TEST_F(SignalingTest, SetLocalAnswerInStable)
+TEST_P(SignalingTest, SetLocalAnswerInStable)
 {
   EnsureInit();
 
   OfferOptions options;
   CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
 
   // The signaling state will remain "stable" because the
   // SetLocalDescription call fails.
   a1_->SetLocal(TestObserver::ANSWER, a1_->offer(), true,
                 PCImplSignalingState::SignalingStable);
   ASSERT_EQ(a1_->pObserver->lastStatusCode,
             PeerConnectionImpl::kInvalidState);
 }
 
-TEST_F(SignalingTest, SetRemoteAnswerInStable) {
+TEST_P(SignalingTest, SetRemoteAnswerInStable) {
   EnsureInit();
 
   // The signaling state will remain "stable" because the
   // SetRemoteDescription call fails.
   a1_->SetRemote(TestObserver::ANSWER, strSampleSdpAudioVideoNoIce, true,
                 PCImplSignalingState::SignalingStable);
   ASSERT_EQ(a1_->pObserver->lastStatusCode,
             PeerConnectionImpl::kInvalidState);
 }
 
-TEST_F(SignalingTest, SetLocalAnswerInHaveLocalOffer) {
+TEST_P(SignalingTest, SetLocalAnswerInHaveLocalOffer) {
   OfferOptions options;
   CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer());
   ASSERT_EQ(a1_->pObserver->lastStatusCode,
             PeerConnectionImpl::kNoError);
 
   // The signaling state will remain "have-local-offer" because the
   // SetLocalDescription call fails.
   a1_->SetLocal(TestObserver::ANSWER, a1_->offer(), true,
                 PCImplSignalingState::SignalingHaveLocalOffer);
   ASSERT_EQ(a1_->pObserver->lastStatusCode,
             PeerConnectionImpl::kInvalidState);
 }
 
-TEST_F(SignalingTest, SetRemoteOfferInHaveLocalOffer) {
+TEST_P(SignalingTest, SetRemoteOfferInHaveLocalOffer) {
   OfferOptions options;
   CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer());
   ASSERT_EQ(a1_->pObserver->lastStatusCode,
             PeerConnectionImpl::kNoError);
 
   // The signaling state will remain "have-local-offer" because the
   // SetRemoteDescription call fails.
   a1_->SetRemote(TestObserver::OFFER, a1_->offer(), true,
                  PCImplSignalingState::SignalingHaveLocalOffer);
   ASSERT_EQ(a1_->pObserver->lastStatusCode,
             PeerConnectionImpl::kInvalidState);
 }
 
-TEST_F(SignalingTest, SetLocalOfferInHaveRemoteOffer) {
+TEST_P(SignalingTest, SetLocalOfferInHaveRemoteOffer) {
   OfferOptions options;
   CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
   a2_->SetRemote(TestObserver::OFFER, a1_->offer());
   ASSERT_EQ(a2_->pObserver->lastStatusCode,
             PeerConnectionImpl::kNoError);
 
   // The signaling state will remain "have-remote-offer" because the
   // SetLocalDescription call fails.
   a2_->SetLocal(TestObserver::OFFER, a1_->offer(), true,
                 PCImplSignalingState::SignalingHaveRemoteOffer);
   ASSERT_EQ(a2_->pObserver->lastStatusCode,
             PeerConnectionImpl::kInvalidState);
 }
 
-TEST_F(SignalingTest, SetRemoteAnswerInHaveRemoteOffer) {
+TEST_P(SignalingTest, SetRemoteAnswerInHaveRemoteOffer) {
   OfferOptions options;
   CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
   a2_->SetRemote(TestObserver::OFFER, a1_->offer());
   ASSERT_EQ(a2_->pObserver->lastStatusCode,
             PeerConnectionImpl::kNoError);
 
   // The signaling state will remain "have-remote-offer" because the
   // SetRemoteDescription call fails.
   a2_->SetRemote(TestObserver::ANSWER, a1_->offer(), true,
                PCImplSignalingState::SignalingHaveRemoteOffer);
   ASSERT_EQ(a2_->pObserver->lastStatusCode,
             PeerConnectionImpl::kInvalidState);
 }
 
 // Disabled until the spec adds a failure callback to addStream
-TEST_F(SignalingTest, DISABLED_AddStreamInHaveLocalOffer) {
+TEST_P(SignalingTest, DISABLED_AddStreamInHaveLocalOffer) {
   OfferOptions options;
   CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer());
   ASSERT_EQ(a1_->pObserver->lastStatusCode,
             PeerConnectionImpl::kNoError);
   a1_->AddStream();
   ASSERT_EQ(a1_->pObserver->lastStatusCode,
             PeerConnectionImpl::kInvalidState);
 }
 
 // Disabled until the spec adds a failure callback to removeStream
-TEST_F(SignalingTest, DISABLED_RemoveStreamInHaveLocalOffer) {
+TEST_P(SignalingTest, DISABLED_RemoveStreamInHaveLocalOffer) {
   OfferOptions options;
   CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer());
   ASSERT_EQ(a1_->pObserver->lastStatusCode,
             PeerConnectionImpl::kNoError);
   a1_->RemoveLastStreamAdded();
   ASSERT_EQ(a1_->pObserver->lastStatusCode,
             PeerConnectionImpl::kInvalidState);
 }
 
-TEST_F(SignalingTest, AddCandidateInHaveLocalOffer) {
+TEST_P(SignalingTest, AddCandidateInHaveLocalOffer) {
   OfferOptions options;
   CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer());
   ASSERT_EQ(a1_->pObserver->lastAddIceStatusCode,
             PeerConnectionImpl::kNoError);
   a1_->AddIceCandidate(strSampleCandidate,
                       strSampleMid, nSamplelevel, false);
   ASSERT_EQ(PeerConnectionImpl::kInvalidState,
@@ -3266,17 +3302,17 @@ TEST_F(SignalingAgentTest, CreateLotsAnd
 TEST_F(SignalingAgentTest, CreateNoInit) {
   CreateAgentNoInit();
 }
 
 
 /*
  * Test for Bug 843595
  */
-TEST_F(SignalingTest, missingUfrag)
+TEST_P(SignalingTest, missingUfrag)
 {
   EnsureInit();
 
   OfferOptions options;
   std::string offer =
     "v=0\r\n"
     "o=Mozilla-SIPUA 2208 0 IN IP4 0.0.0.0\r\n"
     "s=SIP Call\r\n"
@@ -3320,17 +3356,17 @@ TEST_F(SignalingTest, missingUfrag)
   a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer(), true);
   // We now detect the missing ICE parameters at SetRemoteDescription
   a2_->SetRemote(TestObserver::OFFER, offer, true,
                  PCImplSignalingState::SignalingStable);
   ASSERT_TRUE(a2_->pObserver->state == TestObserver::stateError);
 }
 
-TEST_F(SignalingTest, AudioOnlyCalleeNoRtcpMux)
+TEST_P(SignalingTest, AudioOnlyCalleeNoRtcpMux)
 {
   EnsureInit();
 
   OfferOptions options;
 
   a1_->CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer(), false);
   ParsedSDP sdpWrapper(a1_->offer());
@@ -3364,17 +3400,17 @@ TEST_F(SignalingTest, AudioOnlyCalleeNoR
   a1_->CheckMediaPipeline(0, 0, PIPELINE_LOCAL | PIPELINE_SEND);
 
   // The first Remote pipeline gets stored at 1
   a2_->CheckMediaPipeline(0, 0, 0);
 }
 
 
 
-TEST_F(SignalingTest, AudioOnlyG722Only)
+TEST_P(SignalingTest, AudioOnlyG722Only)
 {
   EnsureInit();
 
   OfferOptions options;
 
   a1_->CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer(), false);
   ParsedSDP sdpWrapper(a1_->offer());
@@ -3397,17 +3433,17 @@ TEST_F(SignalingTest, AudioOnlyG722Only)
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
 
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
-TEST_F(SignalingTest, AudioOnlyG722MostPreferred)
+TEST_P(SignalingTest, AudioOnlyG722MostPreferred)
 {
   EnsureInit();
 
   OfferOptions options;
 
   a1_->CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer(), false);
   ParsedSDP sdpWrapper(a1_->offer());
@@ -3421,17 +3457,17 @@ TEST_F(SignalingTest, AudioOnlyG722MostP
   a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
   ASSERT_NE(a2_->getLocalDescription().find("RTP/SAVPF 9"), std::string::npos);
   ASSERT_NE(a2_->getLocalDescription().find("a=rtpmap:9 G722/8000"), std::string::npos);
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
 }
 
-TEST_F(SignalingTest, AudioOnlyG722Rejected)
+TEST_P(SignalingTest, AudioOnlyG722Rejected)
 {
   EnsureInit();
 
   OfferOptions options;
 
   a1_->CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
   // creating different SDPs as a workaround for rejecting codecs
   // this way the answerer should pick a codec with lower priority
@@ -3451,18 +3487,23 @@ TEST_F(SignalingTest, AudioOnlyG722Rejec
   ASSERT_NE(a2_->getLocalDescription().find("a=rtpmap:0 PCMU/8000"), std::string::npos);
   ASSERT_EQ(a2_->getLocalDescription().find("a=rtpmap:109 opus/48000/2"), std::string::npos);
   ASSERT_EQ(a2_->getLocalDescription().find("a=rtpmap:9 G722/8000"), std::string::npos);
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
 }
 
-TEST_F(SignalingTest, FullCallAudioNoMuxVideoMux)
+TEST_P(SignalingTest, FullCallAudioNoMuxVideoMux)
 {
+  if (GetParam() == "bundle") {
+    // This test doesn't make sense for bundle
+    return;
+  }
+
   EnsureInit();
 
   OfferOptions options;
 
   a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer(), false);
   ParsedSDP sdpWrapper(a1_->offer());
   sdpWrapper.DeleteLine("a=rtcp-mux");
@@ -3506,156 +3547,156 @@ TEST_F(SignalingTest, FullCallAudioNoMux
   // The first Remote pipeline gets stored at 0
   a2_->CheckMediaPipeline(0, 0, 0);
 
   // Now check video mux.
   a2_->CheckMediaPipeline(0, 1, (fRtcpMux ?  PIPELINE_RTCP_MUX : 0) |
     PIPELINE_VIDEO | PIPELINE_RTCP_NACK, VideoSessionConduit::FrameRequestPli);
 }
 
-TEST_F(SignalingTest, RtcpFbInOffer)
+TEST_P(SignalingTest, RtcpFbInOffer)
 {
   EnsureInit();
   OfferOptions options;
   a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
   const char *expected[] = { "nack", "nack pli", "ccm fir" };
   CheckRtcpFbSdp(a1_->offer(), ARRAY_TO_SET(std::string, expected));
 }
 
-TEST_F(SignalingTest, RtcpFbOfferAll)
+TEST_P(SignalingTest, RtcpFbOfferAll)
 {
   const char *feedbackTypes[] = { "nack", "nack pli", "ccm fir" };
   TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
                   PIPELINE_RTCP_NACK,
                   VideoSessionConduit::FrameRequestPli);
 }
 
-TEST_F(SignalingTest, RtcpFbOfferNoNackBasic)
+TEST_P(SignalingTest, RtcpFbOfferNoNackBasic)
 {
   const char *feedbackTypes[] = { "nack pli", "ccm fir" };
   TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
                   0,
                   VideoSessionConduit::FrameRequestPli);
 }
 
-TEST_F(SignalingTest, RtcpFbOfferNoNackPli)
+TEST_P(SignalingTest, RtcpFbOfferNoNackPli)
 {
   const char *feedbackTypes[] = { "nack", "ccm fir" };
   TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
                   PIPELINE_RTCP_NACK,
                   VideoSessionConduit::FrameRequestFir);
 }
 
-TEST_F(SignalingTest, RtcpFbOfferNoCcmFir)
+TEST_P(SignalingTest, RtcpFbOfferNoCcmFir)
 {
   const char *feedbackTypes[] = { "nack", "nack pli" };
   TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
                   PIPELINE_RTCP_NACK,
                   VideoSessionConduit::FrameRequestPli);
 }
 
-TEST_F(SignalingTest, RtcpFbOfferNoNack)
+TEST_P(SignalingTest, RtcpFbOfferNoNack)
 {
   const char *feedbackTypes[] = { "ccm fir" };
   TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
                   0,
                   VideoSessionConduit::FrameRequestFir);
 }
 
-TEST_F(SignalingTest, RtcpFbOfferNoFrameRequest)
+TEST_P(SignalingTest, RtcpFbOfferNoFrameRequest)
 {
   const char *feedbackTypes[] = { "nack" };
   TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
                   PIPELINE_RTCP_NACK,
                   VideoSessionConduit::FrameRequestNone);
 }
 
-TEST_F(SignalingTest, RtcpFbOfferPliOnly)
+TEST_P(SignalingTest, RtcpFbOfferPliOnly)
 {
   const char *feedbackTypes[] = { "nack pli" };
   TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
                   0,
                   VideoSessionConduit::FrameRequestPli);
 }
 
-TEST_F(SignalingTest, RtcpFbOfferNoFeedback)
+TEST_P(SignalingTest, RtcpFbOfferNoFeedback)
 {
   const char *feedbackTypes[] = { };
   TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
                   0,
                   VideoSessionConduit::FrameRequestNone);
 }
 
-TEST_F(SignalingTest, RtcpFbAnswerAll)
+TEST_P(SignalingTest, RtcpFbAnswerAll)
 {
   const char *feedbackTypes[] = { "nack", "nack pli", "ccm fir" };
   TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
                   PIPELINE_RTCP_NACK,
                   VideoSessionConduit::FrameRequestPli);
 }
 
-TEST_F(SignalingTest, RtcpFbAnswerNoNackBasic)
+TEST_P(SignalingTest, RtcpFbAnswerNoNackBasic)
 {
   const char *feedbackTypes[] = { "nack pli", "ccm fir" };
   TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
                   0,
                   VideoSessionConduit::FrameRequestPli);
 }
 
-TEST_F(SignalingTest, RtcpFbAnswerNoNackPli)
+TEST_P(SignalingTest, RtcpFbAnswerNoNackPli)
 {
   const char *feedbackTypes[] = { "nack", "ccm fir" };
   TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
                   PIPELINE_RTCP_NACK,
                   VideoSessionConduit::FrameRequestFir);
 }
 
-TEST_F(SignalingTest, RtcpFbAnswerNoCcmFir)
+TEST_P(SignalingTest, RtcpFbAnswerNoCcmFir)
 {
   const char *feedbackTypes[] = { "nack", "nack pli" };
   TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
                   PIPELINE_RTCP_NACK,
                   VideoSessionConduit::FrameRequestPli);
 }
 
-TEST_F(SignalingTest, RtcpFbAnswerNoNack)
+TEST_P(SignalingTest, RtcpFbAnswerNoNack)
 {
   const char *feedbackTypes[] = { "ccm fir" };
   TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
                   0,
                   VideoSessionConduit::FrameRequestFir);
 }
 
-TEST_F(SignalingTest, RtcpFbAnswerNoFrameRequest)
+TEST_P(SignalingTest, RtcpFbAnswerNoFrameRequest)
 {
   const char *feedbackTypes[] = { "nack" };
   TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
                   PIPELINE_RTCP_NACK,
                   VideoSessionConduit::FrameRequestNone);
 }
 
-TEST_F(SignalingTest, RtcpFbAnswerPliOnly)
+TEST_P(SignalingTest, RtcpFbAnswerPliOnly)
 {
   const char *feedbackTypes[] = { "nack pli" };
   TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
                   0,
                   VideoSessionConduit::FrameRequestPli);
 }
 
-TEST_F(SignalingTest, RtcpFbAnswerNoFeedback)
+TEST_P(SignalingTest, RtcpFbAnswerNoFeedback)
 {
   const char *feedbackTypes[] = { };
   TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
                   0,
                   VideoSessionConduit::FrameRequestNone);
 }
 
 // In this test we will change the offer SDP's a=setup value
 // from actpass to passive.  This will make the answer do active.
-TEST_F(SignalingTest, AudioCallForceDtlsRoles)
+TEST_P(SignalingTest, AudioCallForceDtlsRoles)
 {
   EnsureInit();
 
   OfferOptions options;
   size_t match;
 
   a1_->CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
 
@@ -3694,17 +3735,17 @@ TEST_F(SignalingTest, AudioCallForceDtls
   a2_->CloseReceiveStreams();
 
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
 // In this test we will change the offer SDP's a=setup value
 // from actpass to active.  This will make the answer do passive
-TEST_F(SignalingTest, AudioCallReverseDtlsRoles)
+TEST_P(SignalingTest, AudioCallReverseDtlsRoles)
 {
   EnsureInit();
 
   OfferOptions options;
   size_t match;
 
   a1_->CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
 
@@ -3744,17 +3785,17 @@ TEST_F(SignalingTest, AudioCallReverseDt
 
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
 // In this test we will change the answer SDP's a=setup value
 // from active to passive.  This will make both sides do
 // active and should not connect.
-TEST_F(SignalingTest, AudioCallMismatchDtlsRoles)
+TEST_P(SignalingTest, AudioCallMismatchDtlsRoles)
 {
   EnsureInit();
 
   OfferOptions options;
   size_t match;
 
   a1_->CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
 
@@ -3793,17 +3834,17 @@ TEST_F(SignalingTest, AudioCallMismatchD
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   // In this case we should receive nothing.
   ASSERT_EQ(a2_->GetPacketsReceived(0), 0);
 }
 
 // In this test we will change the offer SDP's a=setup value
 // from actpass to garbage.  It should ignore the garbage value
 // and respond with setup:active
-TEST_F(SignalingTest, AudioCallGarbageSetup)
+TEST_P(SignalingTest, AudioCallGarbageSetup)
 {
   EnsureInit();
 
   OfferOptions options;
   size_t match;
 
   a1_->CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
 
@@ -3841,17 +3882,17 @@ TEST_F(SignalingTest, AudioCallGarbageSe
   a2_->CloseReceiveStreams();
 
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
 // In this test we will change the offer SDP to remove the
 // a=setup line.  Answer should respond with a=setup:active.
-TEST_F(SignalingTest, AudioCallOfferNoSetupOrConnection)
+TEST_P(SignalingTest, AudioCallOfferNoSetupOrConnection)
 {
   EnsureInit();
 
   OfferOptions options;
   size_t match;
 
   a1_->CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
 
@@ -3890,17 +3931,17 @@ TEST_F(SignalingTest, AudioCallOfferNoSe
 
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
 // In this test we will change the answer SDP to remove the
 // a=setup line.  ICE should still connect since active will
 // be assumed.
-TEST_F(SignalingTest, AudioCallAnswerNoSetupOrConnection)
+TEST_P(SignalingTest, AudioCallAnswerNoSetupOrConnection)
 {
   EnsureInit();
 
   OfferOptions options;
   size_t match;
 
   a1_->CreateOffer(options, OFFER_AUDIO, SHOULD_SENDRECV_AUDIO);
 
@@ -3936,33 +3977,33 @@ TEST_F(SignalingTest, AudioCallAnswerNoS
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
 
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
 
-TEST_F(SignalingTest, FullCallRealTrickle)
+TEST_P(SignalingTest, FullCallRealTrickle)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AV);
 
   // Wait for some data to get written
   ASSERT_TRUE_WAIT(a1_->GetPacketsSent(0) >= 40 &&
                    a2_->GetPacketsReceived(0) >= 40, kDefaultTimeout * 2);
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
-TEST_F(SignalingTest, FullCallRealTrickleTestServer)
+TEST_P(SignalingTest, FullCallRealTrickleTestServer)
 {
   SetTestStunServer();
 
   OfferOptions options;
   OfferAnswer(options, OFFER_AV | ANSWER_AV,
               SHOULD_SENDRECV_AV, SHOULD_SENDRECV_AV);
 
   TestStunServer::GetInstance()->SetActive(true);
@@ -3972,17 +4013,17 @@ TEST_F(SignalingTest, FullCallRealTrickl
                    a2_->GetPacketsReceived(0) >= 40, kDefaultTimeout * 2);
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
   ASSERT_GE(a1_->GetPacketsSent(0), 40);
   ASSERT_GE(a2_->GetPacketsReceived(0), 40);
 }
 
-TEST_F(SignalingTest, hugeSdp)
+TEST_P(SignalingTest, hugeSdp)
 {
   EnsureInit();
 
   OfferOptions options;
   std::string offer =
     "v=0\r\n"
     "o=- 1109973417102828257 2 IN IP4 127.0.0.1\r\n"
     "s=-\r\n"
@@ -4079,17 +4120,17 @@ TEST_F(SignalingTest, hugeSdp)
   a1_->SetLocal(TestObserver::OFFER, a1_->offer(), true);
 
   a2_->SetRemote(TestObserver::OFFER, offer, true);
   ASSERT_GE(a2_->getRemoteDescription().length(), 4096U);
   a2_->CreateAnswer(OFFER_AV);
 }
 
 // Test max_fs and max_fr prefs have proper impact on SDP offer
-TEST_F(SignalingTest, MaxFsFrInOffer)
+TEST_P(SignalingTest, MaxFsFrInOffer)
 {
   EnsureInit();
 
   OfferOptions options;
 
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   ASSERT_TRUE(prefs);
   FsFrPrefClearer prefClearer(prefs);
@@ -4098,17 +4139,17 @@ TEST_F(SignalingTest, MaxFsFrInOffer)
 
   a1_->CreateOffer(options, OFFER_AV, SHOULD_CHECK_AV);
 
   // Verify that SDP contains correct max-fs and max-fr
   CheckMaxFsFrSdp(a1_->offer(), 120, 300, 30);
 }
 
 // Test max_fs and max_fr prefs have proper impact on SDP answer
-TEST_F(SignalingTest, MaxFsFrInAnswer)
+TEST_P(SignalingTest, MaxFsFrInAnswer)
 {
   EnsureInit();
 
   OfferOptions options;
 
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   ASSERT_TRUE(prefs);
   FsFrPrefClearer prefClearer(prefs);
@@ -4121,17 +4162,17 @@ TEST_F(SignalingTest, MaxFsFrInAnswer)
 
   a2_->CreateAnswer(OFFER_AV | ANSWER_AV);
 
   // Verify that SDP contains correct max-fs and max-fr
   CheckMaxFsFrSdp(a2_->answer(), 120, 600, 60);
 }
 
 // Test SDP offer has proper impact on callee's codec configuration
-TEST_F(SignalingTest, MaxFsFrCalleeCodec)
+TEST_P(SignalingTest, MaxFsFrCalleeCodec)
 {
   EnsureInit();
 
   OfferOptions options;
 
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   ASSERT_TRUE(prefs);
   FsFrPrefClearer prefClearer(prefs);
@@ -4166,17 +4207,17 @@ TEST_F(SignalingTest, MaxFsFrCalleeCodec
   mozilla::VideoSessionConduit *video_conduit =
     static_cast<mozilla::VideoSessionConduit*>(conduit);
 
   ASSERT_EQ(video_conduit->SendingMaxFs(), (unsigned short) 300);
   ASSERT_EQ(video_conduit->SendingMaxFr(), (unsigned short) 30);
 }
 
 // Test SDP answer has proper impact on caller's codec configuration
-TEST_F(SignalingTest, MaxFsFrCallerCodec)
+TEST_P(SignalingTest, MaxFsFrCallerCodec)
 {
   EnsureInit();
 
   OfferOptions options;
 
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   ASSERT_TRUE(prefs);
   FsFrPrefClearer prefClearer(prefs);
@@ -4208,17 +4249,17 @@ TEST_F(SignalingTest, MaxFsFrCallerCodec
   mozilla::VideoSessionConduit *video_conduit =
     static_cast<mozilla::VideoSessionConduit*>(conduit);
 
   ASSERT_EQ(video_conduit->SendingMaxFs(), (unsigned short) 600);
   ASSERT_EQ(video_conduit->SendingMaxFr(), (unsigned short) 60);
 }
 
 // Validate offer with multiple video codecs
-TEST_F(SignalingTest, ValidateMultipleVideoCodecsInOffer)
+TEST_P(SignalingTest, ValidateMultipleVideoCodecsInOffer)
 {
   EnsureInit();
   OfferOptions options;
 
   a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
   std::string offer = a1_->offer();
 
 #ifdef H264_P0_SUPPORTED
@@ -4240,17 +4281,17 @@ TEST_F(SignalingTest, ValidateMultipleVi
   ASSERT_NE(offer.find("a=fmtp:97 profile-level-id="), std::string::npos);
   ASSERT_NE(offer.find("a=rtcp-fb:97 nack"), std::string::npos);
   ASSERT_NE(offer.find("a=rtcp-fb:97 nack pli"), std::string::npos);
   ASSERT_NE(offer.find("a=rtcp-fb:97 ccm fir"), std::string::npos);
 #endif
 }
 
 // Remove VP8 from offer and check that answer negotiates H264 P1 correctly and ignores unknown params
-TEST_F(SignalingTest, RemoveVP8FromOfferWithP1First)
+TEST_P(SignalingTest, RemoveVP8FromOfferWithP1First)
 {
   EnsureInit();
 
   OfferOptions options;
   size_t match;
 
   a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
 
@@ -4289,17 +4330,17 @@ TEST_F(SignalingTest, RemoveVP8FromOffer
   ASSERT_NE(answer.find("a=rtcp-fb:126 nack pli"), std::string::npos);
   ASSERT_NE(answer.find("a=rtcp-fb:126 ccm fir"), std::string::npos);
   // Ensure VP8 removed
   ASSERT_EQ(answer.find("a=rtpmap:120 VP8/90000"), std::string::npos);
   ASSERT_EQ(answer.find("a=rtcp-fb:120"), std::string::npos);
 }
 
 // Insert H.264 before VP8 in Offer, check answer selects H.264
-TEST_F(SignalingTest, OfferWithH264BeforeVP8)
+TEST_P(SignalingTest, OfferWithH264BeforeVP8)
 {
   EnsureInit();
 
   OfferOptions options;
   size_t match;
 
   a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
 
@@ -4352,17 +4393,17 @@ TEST_F(SignalingTest, OfferWithH264Befor
   ASSERT_NE(answer.find("a=rtpmap:126 H264/90000"), std::string::npos);
   ASSERT_NE(answer.find("a=rtcp-fb:126 nack"), std::string::npos);
   ASSERT_NE(answer.find("a=rtcp-fb:126 nack pli"), std::string::npos);
   ASSERT_NE(answer.find("a=rtcp-fb:126 ccm fir"), std::string::npos);
 }
 
 #ifdef H264_P0_SUPPORTED
 // Remove H.264 P1 and VP8 from offer, check answer negotiates H.264 P0
-TEST_F(SignalingTest, OfferWithOnlyH264P0)
+TEST_P(SignalingTest, OfferWithOnlyH264P0)
 {
   EnsureInit();
 
   OfferOptions options;
   size_t match;
 
   a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
 
@@ -4409,17 +4450,17 @@ TEST_F(SignalingTest, OfferWithOnlyH264P
   ASSERT_EQ(answer.find("a=rtpmap:120 VP8/90000"), std::string::npos);
   ASSERT_EQ(answer.find("a=rtcp-fb:120"), std::string::npos);
   ASSERT_EQ(answer.find("a=rtcp-fb:126"), std::string::npos);
 }
 #endif
 
 // Test negotiating an answer which has only H.264 P1
 // Which means replace VP8 with H.264 P1 in answer
-TEST_F(SignalingTest, AnswerWithoutVP8)
+TEST_P(SignalingTest, AnswerWithoutVP8)
 {
   EnsureInit();
 
   OfferOptions options;
 
   a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer());
   a2_->SetRemote(TestObserver::OFFER, a1_->offer(), false);
@@ -4478,17 +4519,17 @@ TEST_F(SignalingTest, AnswerWithoutVP8)
 
   WaitForCompleted();
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
 }
 
 // Test using a non preferred dynamic video payload type on answer negotiation
-TEST_F(SignalingTest, UseNonPrefferedPayloadTypeOnAnswer)
+TEST_P(SignalingTest, UseNonPrefferedPayloadTypeOnAnswer)
 {
   EnsureInit();
 
   OfferOptions options;
   a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer());
   a2_->SetRemote(TestObserver::OFFER, a1_->offer(), false);
   a2_->CreateAnswer(OFFER_AV|ANSWER_AV, SHOULD_SENDRECV_AV);
@@ -4546,17 +4587,17 @@ TEST_F(SignalingTest, UseNonPrefferedPay
   // Wait for some data to get written
   ASSERT_TRUE_WAIT(a1_->GetPacketsSent(0) >= 40 &&
                    a2_->GetPacketsReceived(0) >= 40, kDefaultTimeout * 2);
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
 }
 
-TEST_F(SignalingTest, VideoNegotiationFails)
+TEST_P(SignalingTest, VideoNegotiationFails)
 {
   EnsureInit();
 
   OfferOptions options;
 
   a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer());
 
@@ -4590,17 +4631,17 @@ TEST_F(SignalingTest, VideoNegotiationFa
   // Wait for some data to get written
   ASSERT_TRUE_WAIT(a1_->GetPacketsSent(0) >= 10 &&
                    a2_->GetPacketsReceived(0) >= 10, kDefaultTimeout * 2);
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
 }
 
-TEST_F(SignalingTest, AudioNegotiationFails)
+TEST_P(SignalingTest, AudioNegotiationFails)
 {
   EnsureInit();
 
   OfferOptions options;
 
   a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
   a1_->SetLocal(TestObserver::OFFER, a1_->offer());
 
@@ -4625,16 +4666,134 @@ TEST_F(SignalingTest, AudioNegotiationFa
             PeerConnectionImpl::kNoError);
 
   WaitForCompleted();
 
   a1_->CloseSendStreams();
   a2_->CloseReceiveStreams();
 }
 
+TEST_P(SignalingTest, BundleStreamCorrelationBySsrc)
+{
+  if (GetParam() != "bundle") {
+    return;
+  }
+
+  EnsureInit();
+
+  OfferOptions options;
+
+  // We pass DONT_CHECK_AUDIO because we monkey around with payload types
+  a1_->CreateOffer(options, OFFER_AV, DONT_CHECK_AUDIO | SHOULD_SENDRECV_VIDEO);
+  ParsedSDP parsedOffer(a1_->offer());
+
+  // Sabotage unique payload-type matching
+  // TODO(bug 1056650): once we have multistream support, all we need to do
+  // here is run a test with two audio streams, since that will prevent the
+  // PTs from being unique
+  parsedOffer.ReplaceLine("m=audio",
+                          "m=audio 9 RTP/SAVPF 120\r\n");
+  parsedOffer.ReplaceLine("a=rtpmap:109",
+                          "a=rtpmap:120 opus/48000/2\r\n");
+
+  // Sabotage mid-based matching
+  std::string modifiedOffer = parsedOffer.getSdp();
+  size_t midExtStart =
+    modifiedOffer.find("urn:ietf:params:rtp-hdrext:sdes:mid");
+  if (midExtStart != std::string::npos) {
+    // Just garble it a little
+    modifiedOffer[midExtStart] = 'q';
+  }
+
+  a1_->SetLocal(TestObserver::OFFER, modifiedOffer);
+
+  a2_->SetRemote(TestObserver::OFFER, modifiedOffer, false);
+  a2_->CreateAnswer(OFFER_AV|ANSWER_AV,
+                    DONT_CHECK_AUDIO | SHOULD_SENDRECV_VIDEO);
+
+  a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+
+  ASSERT_EQ(a2_->pObserver->lastStatusCode,
+            PeerConnectionImpl::kNoError);
+
+  a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+  ASSERT_EQ(a1_->pObserver->lastStatusCode,
+            PeerConnectionImpl::kNoError);
+
+  WaitForCompleted();
+
+  // Wait for some data to get written
+  ASSERT_TRUE_WAIT(a1_->GetPacketsSent(0) >= 10 &&
+                   a2_->GetPacketsReceived(0) >= 10, kDefaultTimeout * 2);
+
+  a1_->CloseSendStreams();
+  a2_->CloseReceiveStreams();
+}
+
+TEST_P(SignalingTest, BundleStreamCorrelationByUniquePt)
+{
+  if (GetParam() != "bundle") {
+    return;
+  }
+
+  EnsureInit();
+
+  OfferOptions options;
+
+  a1_->CreateOffer(options, OFFER_AV, SHOULD_SENDRECV_AV);
+  ParsedSDP parsedOffer(a1_->offer());
+
+  std::string modifiedOffer = parsedOffer.getSdp();
+  // Sabotage ssrc matching
+  size_t ssrcStart =
+    modifiedOffer.find("a=ssrc:");
+  ASSERT_NE(std::string::npos, ssrcStart);
+  // Garble
+  modifiedOffer[ssrcStart+2] = 'q';
+
+  // Sabotage mid-based matching
+  size_t midExtStart =
+    modifiedOffer.find("urn:ietf:params:rtp-hdrext:sdes:mid");
+  if (midExtStart != std::string::npos) {
+    // Just garble it a little
+    modifiedOffer[midExtStart] = 'q';
+  }
+
+  a1_->SetLocal(TestObserver::OFFER, modifiedOffer);
+
+  a2_->SetRemote(TestObserver::OFFER, modifiedOffer, false);
+  a2_->CreateAnswer(OFFER_AV|ANSWER_AV,
+                    SHOULD_SENDRECV_AV);
+
+  a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+
+  ASSERT_EQ(a2_->pObserver->lastStatusCode,
+            PeerConnectionImpl::kNoError);
+
+  a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+  ASSERT_EQ(a1_->pObserver->lastStatusCode,
+            PeerConnectionImpl::kNoError);
+
+  WaitForCompleted();
+
+  // Wait for some data to get written
+  ASSERT_TRUE_WAIT(a1_->GetPacketsSent(0) >= 10 &&
+                   a2_->GetPacketsReceived(0) >= 10, kDefaultTimeout * 2);
+
+  a1_->CloseSendStreams();
+  a2_->CloseReceiveStreams();
+}
+
+INSTANTIATE_TEST_CASE_P(Variants, SignalingTest,
+                        ::testing::Values("bundle",
+                                          "no_bundle",
+                                          "reject_bundle"));
+
 } // End namespace test.
 
 bool is_color_terminal(const char *terminal) {
   if (!terminal) {
     return false;
   }
   const char *color_terms[] = {
     "xterm",
--- a/mfbt/Attributes.h
+++ b/mfbt/Attributes.h
@@ -497,37 +497,41 @@
  *   need not be provided in such cases.
  * MOZ_HEAP_ALLOCATOR: Applies to any function. This indicates that the return
  *   value is allocated on the heap, and will as a result check such allocations
  *   during MOZ_STACK_CLASS and MOZ_NONHEAP_CLASS annotation checking.
  * MOZ_IMPLICIT: Applies to constructors. Implicit conversion constructors
  *   are disallowed by default unless they are marked as MOZ_IMPLICIT. This
  *   attribute must be used for constructors which intend to provide implicit
  *   conversions.
+ * MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT: Applies to functions. Makes it a compile
+ *   time error to pass arithmetic expressions on variables to the function.
  */
 #ifdef MOZ_CLANG_PLUGIN
 #  define MOZ_MUST_OVERRIDE __attribute__((annotate("moz_must_override")))
 #  define MOZ_STACK_CLASS __attribute__((annotate("moz_stack_class")))
 #  define MOZ_NONHEAP_CLASS __attribute__((annotate("moz_nonheap_class")))
 #  define MOZ_IMPLICIT __attribute__((annotate("moz_implicit")))
+#  define MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT __attribute__((annotate("moz_no_arith_expr_in_arg")))
 /*
  * It turns out that clang doesn't like void func() __attribute__ {} without a
  * warning, so use pragmas to disable the warning. This code won't work on GCC
  * anyways, so the warning is safe to ignore.
  */
 #  define MOZ_HEAP_ALLOCATOR \
     _Pragma("clang diagnostic push") \
     _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \
     __attribute__((annotate("moz_heap_allocator"))) \
     _Pragma("clang diagnostic pop")
 #else
 #  define MOZ_MUST_OVERRIDE /* nothing */
 #  define MOZ_STACK_CLASS /* nothing */
 #  define MOZ_NONHEAP_CLASS /* nothing */
 #  define MOZ_IMPLICIT /* nothing */
+#  define MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT /* nothing */
 #  define MOZ_HEAP_ALLOCATOR /* nothing */
 #endif /* MOZ_CLANG_PLUGIN */
 
 /*
  * MOZ_THIS_IN_INITIALIZER_LIST is used to avoid a warning when we know that
  * it's safe to use 'this' in an initializer list.
  */
 #ifdef _MSC_VER
--- a/mfbt/CheckedInt.h
+++ b/mfbt/CheckedInt.h
@@ -6,16 +6,17 @@
 
 /* Provides checked integers, detecting integer overflow and divide-by-0. */
 
 #ifndef mozilla_CheckedInt_h
 #define mozilla_CheckedInt_h
 
 #include <stdint.h>
 #include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
 #include "mozilla/IntegerTypeTraits.h"
 
 namespace mozilla {
 
 template<typename T> class CheckedInt;
 
 namespace detail {
 
@@ -520,17 +521,17 @@ public:
    *
    * This constructor is not explicit. Instead, the type of its argument is a
    * separate template parameter, ensuring that no conversion is performed
    * before this constructor is actually called. As explained in the above
    * documentation for class CheckedInt, this constructor checks that its
    * argument is valid.
    */
   template<typename U>
-  CheckedInt(U aValue)
+  CheckedInt(U aValue) MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT
     : mValue(T(aValue)),
       mIsValid(detail::IsInRange<T>(aValue))
   {
     static_assert(detail::IsSupported<T>::value &&
                   detail::IsSupported<U>::value,
                   "This type is not supported by CheckedInt");
   }
 
--- a/mfbt/UniquePtr.h
+++ b/mfbt/UniquePtr.h
@@ -629,19 +629,19 @@ struct UniqueSelector<T[N]>
  *
  * First, MakeUnique eliminates use of |new| from code entirely.  If objects are
  * only created through UniquePtr, then (assuming all explicit release() calls
  * are safe, including transitively, and no type-safety casting funniness)
  * correctly maintained ownership of the UniquePtr guarantees no leaks are
  * possible.  (This pays off best if a class is only ever created through a
  * factory method on the class, using a private constructor.)
  *
- * Second, initializing a UniquePtr using a |new| expression requires renaming
- * the new'd type, whereas MakeUnique in concert with the |auto| keyword names
- * it only once:
+ * Second, initializing a UniquePtr using a |new| expression requires repeating
+ * the name of the new'd type, whereas MakeUnique in concert with the |auto|
+ * keyword names it only once:
  *
  *   UniquePtr<char> ptr1(new char()); // repetitive
  *   auto ptr2 = MakeUnique<char>();   // shorter
  *
  * Of course this assumes the reader understands the operation MakeUnique
  * performs.  In the long run this is probably a reasonable assumption.  In the
  * short run you'll have to use your judgment about what readers can be expected
  * to know, or to quickly look up.
--- a/mfbt/tests/TestCheckedInt.cpp
+++ b/mfbt/tests/TestCheckedInt.cpp
@@ -516,17 +516,17 @@ void test()
     VERIFY_IS_VALID_IF(CheckedInt<T>(MaxValue<U>::value), \
       (sizeof(T) > sizeof(U) || ((sizeof(T) == sizeof(U)) && (isUSigned || !isTSigned)))); \
     VERIFY_IS_VALID_IF(CheckedInt<T>(MinValue<U>::value), \
       isUSigned == false ? 1 \
                          : bool(isTSigned) == false ? 0 \
                                                     : sizeof(T) >= sizeof(U)); \
   }
   #define VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(U) \
-    VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE2(U,U,+0) \
+    VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE2(U,U,+zero) \
     VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE2(U,CheckedInt<U>,.toChecked<T>())
 
   VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(int8_t)
   VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(uint8_t)
   VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(int16_t)
   VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(uint16_t)
   VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(int32_t)
   VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(uint32_t)
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1293,23 +1293,18 @@ pref("network.http.spdy.persistent-setti
 pref("network.http.spdy.ping-threshold", 58);
 pref("network.http.spdy.ping-timeout", 8);
 pref("network.http.spdy.send-buffer-size", 131072);
 pref("network.http.spdy.allow-push", true);
 pref("network.http.spdy.push-allowance", 131072);
 
 // alt-svc allows separation of transport routing from
 // the origin host without using a proxy.
-#ifdef RELEASE_BUILD
-pref("network.http.altsvc.enabled", false);
-pref("network.http.altsvc.oe", false);
-#else
 pref("network.http.altsvc.enabled", true);
 pref("network.http.altsvc.oe", true);
-#endif
 
 pref("network.http.diagnostics", false);
 
 pref("network.http.pacing.requests.enabled", true);
 pref("network.http.pacing.requests.min-parallelism", 6);
 pref("network.http.pacing.requests.hz", 100);
 pref("network.http.pacing.requests.burst", 32);
 
--- a/testing/mochitest/server.js
+++ b/testing/mochitest/server.js
@@ -5,21 +5,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Note that the server script itself already defines Cc, Ci, and Cr for us,
 // and because they're constants it's not safe to redefine them.  Scope leakage
 // sucks.
 
 // Disable automatic network detection, so tests work correctly when
 // not connected to a network.
-let (ios = Cc["@mozilla.org/network/io-service;1"]
-           .getService(Ci.nsIIOService2)) {
-  ios.manageOfflineStatus = false;
-  ios.offline = false;
-}
+let ios = Cc["@mozilla.org/network/io-service;1"]
+          .getService(Ci.nsIIOService2);
+ios.manageOfflineStatus = false;
+ios.offline = false;
 
 var server; // for use in the shutdown handler, if necessary
 
 //
 // HTML GENERATION
 //
 var tags = ['A', 'ABBR', 'ACRONYM', 'ADDRESS', 'APPLET', 'AREA', 'B', 'BASE',
             'BASEFONT', 'BDO', 'BIG', 'BLOCKQUOTE', 'BODY', 'BR', 'BUTTON',
--- a/testing/specialpowers/content/specialpowers.js
+++ b/testing/specialpowers/content/specialpowers.js
@@ -19,30 +19,29 @@ function SpecialPowers(window) {
           var win = this.window.get();
           if (!win)
               return null;
           return getRawComponents(win);
       }});
   this._pongHandlers = [];
   this._messageListener = this._messageReceived.bind(this);
   addMessageListener("SPPingService", this._messageListener);
-  let (self = this) {
-    Services.obs.addObserver(function onInnerWindowDestroyed(subject, topic, data) {
-      var id = subject.QueryInterface(Components.interfaces.nsISupportsPRUint64).data;
-      if (self._windowID === id) {
-        Services.obs.removeObserver(onInnerWindowDestroyed, "inner-window-destroyed");
-        try {
-          removeMessageListener("SPPingService", self._messageListener);
-        } catch (e if e.result == Components.results.NS_ERROR_ILLEGAL_VALUE) {
-          // Ignore the exception which the message manager has been destroyed.
-          ;
-        }
+  let self = this;
+  Services.obs.addObserver(function onInnerWindowDestroyed(subject, topic, data) {
+    var id = subject.QueryInterface(Components.interfaces.nsISupportsPRUint64).data;
+    if (self._windowID === id) {
+      Services.obs.removeObserver(onInnerWindowDestroyed, "inner-window-destroyed");
+      try {
+        removeMessageListener("SPPingService", self._messageListener);
+      } catch (e if e.result == Components.results.NS_ERROR_ILLEGAL_VALUE) {
+        // Ignore the exception which the message manager has been destroyed.
+        ;
       }
-    }, "inner-window-destroyed", false);
-  }
+    }
+  }, "inner-window-destroyed", false);
 }
 
 SpecialPowers.prototype = new SpecialPowersAPI();
 
 SpecialPowers.prototype.toString = function() { return "[SpecialPowers]"; };
 SpecialPowers.prototype.sanityCheck = function() { return "foo"; };
 
 // This gets filled in in the constructor.
--- a/toolkit/themes/osx/mozapps/extensions/selectAddons.css
+++ b/toolkit/themes/osx/mozapps/extensions/selectAddons.css
@@ -145,18 +145,18 @@
 .addon:not([active]) .addon-icon,
 #disable-list .addon-icon,
 #incompatible-list .addon-icon {
   filter: grayscale(1);
 }
 
 #footer {
   padding: 15px 12px;
-  -moz-binding: url("chrome://global/content/bindings/general.xml#windowdragbox");
   -moz-appearance: statusbar;
+  -moz-window-dragging: drag;
 }
 
 button {
   -moz-appearance: toolbarbutton;
   min-height: 22px;
   margin: 0 6px;
   padding: 0;
   text-shadow: @loweredShadow@;
--- a/toolkit/themes/osx/mozapps/update/updates.css
+++ b/toolkit/themes/osx/mozapps/update/updates.css
@@ -24,23 +24,23 @@ wizardpage {
 }
 
 .wizard-header {
   margin: 12px 12px 0 12px;
 }
 
 .wizard-buttons-btm {
   padding: 15px 12px;
-  -moz-binding: url("chrome://global/content/bindings/general.xml#windowdragbox");
 }
 
 /* Don't use top margin - it can cause a scrollbar on the incompatible page */
 .wizard-buttons {
   padding: 0;
   -moz-appearance: statusbar;
+  -moz-window-dragging: drag;
 }
 
 .wizard-buttons button {
   -moz-appearance: toolbarbutton;
   color: ButtonText;
   min-height: 22px;
   margin: 0 6px;
   padding: 0;
--- a/widget/VsyncDispatcher.cpp
+++ b/widget/VsyncDispatcher.cpp
@@ -18,56 +18,56 @@
 #ifdef MOZ_WIDGET_GONK
 #include "GeckoTouchDispatcher.h"
 #endif
 
 using namespace mozilla::layers;
 
 namespace mozilla {
 
-VsyncDispatcher::VsyncDispatcher()
+CompositorVsyncDispatcher::CompositorVsyncDispatcher()
   : mCompositorObserverLock("CompositorObserverLock")
 {
   MOZ_ASSERT(XRE_IsParentProcess());
-  gfxPlatform::GetPlatform()->GetHardwareVsync()->AddVsyncDispatcher(this);
+  gfxPlatform::GetPlatform()->GetHardwareVsync()->AddCompositorVsyncDispatcher(this);
 }
 
-VsyncDispatcher::~VsyncDispatcher()
+CompositorVsyncDispatcher::~CompositorVsyncDispatcher()
 {
   // We auto remove this vsync dispatcher from the vsync source in the nsBaseWidget
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 void
-VsyncDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp)
+CompositorVsyncDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp)
 {
   // In hardware vsync thread
 #ifdef MOZ_ENABLE_PROFILER_SPS
     if (profiler_is_active()) {
         CompositorParent::PostInsertVsyncProfilerMarker(aVsyncTimestamp);
     }
 #endif
 
   MutexAutoLock lock(mCompositorObserverLock);
   if (gfxPrefs::VsyncAlignedCompositor() && mCompositorVsyncObserver) {
     mCompositorVsyncObserver->NotifyVsync(aVsyncTimestamp);
   }
 }
 
 void
-VsyncDispatcher::SetCompositorVsyncObserver(VsyncObserver* aVsyncObserver)
+CompositorVsyncDispatcher::SetCompositorVsyncObserver(VsyncObserver* aVsyncObserver)
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   MutexAutoLock lock(mCompositorObserverLock);
   mCompositorVsyncObserver = aVsyncObserver;
 }
 
 void
-VsyncDispatcher::Shutdown()
+CompositorVsyncDispatcher::Shutdown()
 {
-  // Need to explicitly remove VsyncDispatcher when the nsBaseWidget shuts down.
+  // Need to explicitly remove CompositorVsyncDispatcher when the nsBaseWidget shuts down.
   // Otherwise, we would get dead vsync notifications between when the nsBaseWidget
   // shuts down and the CompositorParent shuts down.
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(NS_IsMainThread());
-  gfxPlatform::GetPlatform()->GetHardwareVsync()->RemoveVsyncDispatcher(this);
+  gfxPlatform::GetPlatform()->GetHardwareVsync()->RemoveCompositorVsyncDispatcher(this);
 }
 } // namespace mozilla
--- a/widget/VsyncDispatcher.h
+++ b/widget/VsyncDispatcher.h
@@ -16,50 +16,47 @@ namespace mozilla {
 class TimeStamp;
 
 namespace layers {
 class CompositorVsyncObserver;
 }
 
 class VsyncObserver
 {
-  // Must be destroyed on main thread since the compositor is as well
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncObserver)
 
 public:
   // The method called when a vsync occurs. Return true if some work was done.
   // Vsync notifications will occur on the hardware vsync thread
   virtual bool NotifyVsync(TimeStamp aVsyncTimestamp) = 0;
 
 protected:
   VsyncObserver() {}
   virtual ~VsyncObserver() {}
 }; // VsyncObserver
 
-// VsyncDispatcher is used to dispatch vsync events to the registered observers.
-class VsyncDispatcher
+class CompositorVsyncDispatcher MOZ_FINAL
 {
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncDispatcher)
-
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorVsyncDispatcher)
 public:
-  VsyncDispatcher();
+  CompositorVsyncDispatcher();
 
   // Called on the vsync thread when a hardware vsync occurs
   // The aVsyncTimestamp can mean different things depending on the platform:
   // b2g - The vsync timestamp of the previous frame that was just displayed
   // OSX - The vsync timestamp of the upcoming frame
   // TODO: Windows / Linux. DOCUMENT THIS WHEN IMPLEMENTING ON THOSE PLATFORMS
   // Android: TODO
   void NotifyVsync(TimeStamp aVsyncTimestamp);
 
   // Compositor vsync observers must be added/removed on the compositor thread
   void SetCompositorVsyncObserver(VsyncObserver* aVsyncObserver);
   void Shutdown();
 
 private:
-  virtual ~VsyncDispatcher();
+  virtual ~CompositorVsyncDispatcher();
   Mutex mCompositorObserverLock;
   nsRefPtr<VsyncObserver> mCompositorVsyncObserver;
-}; // VsyncDispatcher
+};
 
 } // namespace mozilla
 
 #endif // __mozilla_widget_VsyncDispatcher_h
--- a/widget/gonk/nsWindow.cpp
+++ b/widget/gonk/nsWindow.cpp
@@ -215,17 +215,17 @@ nsWindow::DoDraw(void)
 
 /* static */ void
 nsWindow::NotifyVsync(TimeStamp aVsyncTimestamp)
 {
     if (!gFocusedWindow) {
       return;
     }
 
-    VsyncDispatcher* vsyncDispatcher = gFocusedWindow->GetVsyncDispatcher();
+    CompositorVsyncDispatcher* vsyncDispatcher = gFocusedWindow->GetCompositorVsyncDispatcher();
     // During bootup, there is a delay between when the nsWindow is created
     // and when the Compositor is created, but vsync is already turned on
     if (vsyncDispatcher) {
       vsyncDispatcher->NotifyVsync(aVsyncTimestamp);
     }
 }
 
 nsEventStatus
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -109,17 +109,17 @@ nsAutoRollup::~nsAutoRollup()
 // nsBaseWidget constructor
 //
 //-------------------------------------------------------------------------
 
 nsBaseWidget::nsBaseWidget()
 : mWidgetListener(nullptr)
 , mAttachedWidgetListener(nullptr)
 , mContext(nullptr)
-, mVsyncDispatcher(nullptr)
+, mCompositorVsyncDispatcher(nullptr)
 , mCursor(eCursor_standard)
 , mUpdateCursor(true)
 , mBorderStyle(eBorderStyle_none)
 , mUseLayersAcceleration(false)
 , mForceLayersAcceleration(false)
 , mTemporarilyUseBasicLayerManager(false)
 , mUseAttachedEvents(false)
 , mContextInitialized(false)
@@ -229,19 +229,19 @@ nsBaseWidget::~nsBaseWidget()
 #ifdef NOISY_WIDGET_LEAKS
   gNumWidgets--;
   printf("WIDGETS- = %d\n", gNumWidgets);
 #endif
 
   NS_IF_RELEASE(mContext);
   delete mOriginalBounds;
 
-  // Can have base widgets that are things like tooltips which don't have vsyncDispatchers
-  if (mVsyncDispatcher) {
-    mVsyncDispatcher->Shutdown();
+  // Can have base widgets that are things like tooltips which don't have CompositorVsyncDispatchers
+  if (mCompositorVsyncDispatcher) {
+    mCompositorVsyncDispatcher->Shutdown();
   }
 }
 
 //-------------------------------------------------------------------------
 //
 // Basic create.
 //
 //-------------------------------------------------------------------------
@@ -968,32 +968,32 @@ nsBaseWidget::GetPreferredCompositorBack
 {
   if (mUseLayersAcceleration) {
     aHints.AppendElement(LayersBackend::LAYERS_OPENGL);
   }
 
   aHints.AppendElement(LayersBackend::LAYERS_BASIC);
 }
 
-void nsBaseWidget::CreateVsyncDispatcher()
+void nsBaseWidget::CreateCompositorVsyncDispatcher()
 {
   if (gfxPrefs::HardwareVsyncEnabled()) {
     // Parent directly listens to the vsync source whereas
     // child process communicate via IPC
     // Should be called AFTER gfxPlatform is initialized
     if (XRE_IsParentProcess()) {
-      mVsyncDispatcher = new VsyncDispatcher();
+      mCompositorVsyncDispatcher = new CompositorVsyncDispatcher();
     }
   }
 }
 
-VsyncDispatcher*
-nsBaseWidget::GetVsyncDispatcher()
+CompositorVsyncDispatcher*
+nsBaseWidget::GetCompositorVsyncDispatcher()
 {
-  return mVsyncDispatcher;
+  return mCompositorVsyncDispatcher;
 }
 
 void nsBaseWidget::CreateCompositor(int aWidth, int aHeight)
 {
   // This makes sure that gfxPlatforms gets initialized if it hasn't by now.
   gfxPlatform::GetPlatform();
 
   MOZ_ASSERT(gfxPlatform::UsesOffMainThreadCompositing(),
@@ -1003,17 +1003,17 @@ void nsBaseWidget::CreateCompositor(int 
   // to make sure it's properly destroyed by calling DestroyCompositor!
 
   // If we've already received a shutdown notification, don't try
   // create a new compositor.
   if (!mShutdownObserver) {
     return;
   }
 
-  CreateVsyncDispatcher();
+  CreateCompositorVsyncDispatcher();
   mCompositorParent = NewCompositorParent(aWidth, aHeight);
   MessageChannel *parentChannel = mCompositorParent->GetIPCChannel();
   nsRefPtr<ClientLayerManager> lm = new ClientLayerManager(this);
   MessageLoop *childMessageLoop = CompositorParent::CompositorLoop();
   mCompositorChild = new CompositorChild(lm);
   mCompositorChild->Open(parentChannel, childMessageLoop, ipc::ChildSide);
 
   if (gfxPrefs::AsyncPanZoomEnabled()) {
--- a/widget/nsBaseWidget.h
+++ b/widget/nsBaseWidget.h
@@ -34,17 +34,17 @@ namespace layers {
 class BasicLayerManager;
 class CompositorChild;
 class CompositorParent;
 class APZCTreeManager;
 class GeckoContentController;
 struct ScrollableLayerGuid;
 }
 
-class VsyncDispatcher;
+class CompositorVsyncDispatcher;
 }
 
 namespace base {
 class Thread;
 }
 
 // Windows specific constant indicating the maximum number of touch points the
 // inject api will allow. This also sets the maximum numerical value for touch
@@ -137,18 +137,18 @@ public:
   NS_IMETHOD              HideWindowChrome(bool aShouldHide);
   NS_IMETHOD              MakeFullScreen(bool aFullScreen, nsIScreen* aScreen = nullptr);
   virtual nsDeviceContext* GetDeviceContext();
   virtual LayerManager*   GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
                                           LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
                                           LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
                                           bool* aAllowRetaining = nullptr);
 
-  VsyncDispatcher*        GetVsyncDispatcher() MOZ_OVERRIDE;
-  virtual void            CreateVsyncDispatcher();
+  CompositorVsyncDispatcher* GetCompositorVsyncDispatcher() MOZ_OVERRIDE;
+  virtual void            CreateCompositorVsyncDispatcher();
   virtual CompositorParent* NewCompositorParent(int aSurfaceWidth, int aSurfaceHeight);
   virtual void            CreateCompositor();
   virtual void            CreateCompositor(int aWidth, int aHeight);
   virtual void            PrepareWindowEffects() {}
   virtual void            CleanupWindowEffects() {}
   virtual bool            PreRender(LayerManagerComposite* aManager) { return true; }
   virtual void            PostRender(LayerManagerComposite* aManager) {}
   virtual void            DrawWindowUnderlay(LayerManagerComposite* aManager, nsIntRect aRect) {}
@@ -421,17 +421,17 @@ protected:
 
   nsIWidgetListener* mWidgetListener;
   nsIWidgetListener* mAttachedWidgetListener;
   nsDeviceContext* mContext;
   nsRefPtr<LayerManager> mLayerManager;
   nsRefPtr<LayerManager> mBasicLayerManager;
   nsRefPtr<CompositorChild> mCompositorChild;
   nsRefPtr<CompositorParent> mCompositorParent;
-  nsRefPtr<mozilla::VsyncDispatcher> mVsyncDispatcher;
+  nsRefPtr<mozilla::CompositorVsyncDispatcher> mCompositorVsyncDispatcher;
   nsRefPtr<APZCTreeManager> mAPZC;
   nsRefPtr<WidgetShutdownObserver> mShutdownObserver;
   nsCursor          mCursor;
   bool              mUpdateCursor;
   nsBorderStyle     mBorderStyle;
   bool              mUseLayersAcceleration;
   bool              mForceLayersAcceleration;
   bool              mTemporarilyUseBasicLayerManager;
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -31,17 +31,17 @@ class   nsIRollupListener;
 class   imgIContainer;
 class   nsIContent;
 class   ViewWrapper;
 class   nsIWidgetListener;
 class   nsIntRegion;
 class   nsIScreen;
 
 namespace mozilla {
-class VsyncDispatcher;
+class CompositorVsyncDispatcher;
 namespace dom {
 class TabChild;
 }
 namespace plugins {
 class PluginWidgetChild;
 }
 namespace layers {
 class Composer2D;
@@ -687,17 +687,17 @@ class nsIWidget : public nsISupports {
     typedef mozilla::layers::LayersBackend LayersBackend;
     typedef mozilla::layers::PLayerTransactionChild PLayerTransactionChild;
     typedef mozilla::widget::IMEMessage IMEMessage;
     typedef mozilla::widget::IMENotification IMENotification;
     typedef mozilla::widget::IMEState IMEState;
     typedef mozilla::widget::InputContext InputContext;
     typedef mozilla::widget::InputContextAction InputContextAction;
     typedef mozilla::widget::SizeConstraints SizeConstraints;
-    typedef mozilla::VsyncDispatcher VsyncDispatcher;
+    typedef mozilla::CompositorVsyncDispatcher CompositorVsyncDispatcher;
 
     // Used in UpdateThemeGeometries.
     struct ThemeGeometry {
       // The -moz-appearance value for the themed widget
       uint8_t mWidgetType;
       // The device-pixel rect within the window for the themed widget
       nsIntRect mRect;
 
@@ -867,19 +867,19 @@ class nsIWidget : public nsISupports {
 
     /**
      * Return the physical DPI of the screen containing the window ...
      * the number of device pixels per inch.
      */
     virtual float GetDPI() = 0;
 
     /**
-     * Returns the VsyncDispatcher associated with this widget
+     * Returns the CompositorVsyncDispatcher associated with this widget
      */
-    virtual VsyncDispatcher* GetVsyncDispatcher() = 0;
+    virtual CompositorVsyncDispatcher* GetCompositorVsyncDispatcher() = 0;
 
     /**
      * Return the default scale factor for the window. This is the
      * default number of device pixels per CSS pixel to use. This should
      * depend on OS/platform settings such as the Mac's "UI scale factor"
      * or Windows' "font DPI". This will take into account Gecko preferences
      * overriding the system setting.
      */