Browse Source

chore: cherry-pick 8 changes from 3-M123 and M1nn (#41856)

* chore: [27-x-y] cherry-pick 3 changes from 3-M123

* a65e511a14b4 from DirectXShaderCompiler
* f6672dbbe223 from angle
* 1b1f34234346 from chromium

* chore: [27-x-y] cherry-pick 4 later changes from M1nn

* chore: update patches

---------

Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
Pedro Pontes 1 year ago
parent
commit
70a66589ae

+ 2 - 0
patches/DirectXShaderCompiler/.patches

@@ -0,0 +1,2 @@
+fix_hlmatrixlowerpass_leaving_call_to_dangling_functionval.patch
+cherry-pick-a65e511a14b4.patch

+ 66 - 0
patches/DirectXShaderCompiler/cherry-pick-a65e511a14b4.patch

@@ -0,0 +1,66 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Antonio Maiorano <[email protected]>
+Date: Wed, 3 Apr 2024 15:58:51 -0400
+Subject: Fix ASAN use-after-free on unreferenced self-assignment of struct
+ instance (#6466)
+
+When deleting an unused memcpy, ScalarReplAggregatesHLSL was attempting
+to delete both the target and the source of the memcpy without first
+checking if they were both same, resulting in a double-delete.
+
+Bug: chromium:331123811
+Change-Id: Idaef95a06b10a7fb6f0ca2e662972a44ec662fbc
+Reviewed-on: https://chromium-review.googlesource.com/c/external/github.com/microsoft/DirectXShaderCompiler/+/5419225
+Reviewed-by: David Neto <[email protected]>
+Reviewed-by: dan sinclair <[email protected]>
+Reviewed-by: Ben Clayton <[email protected]>
+
+diff --git a/lib/Transforms/Scalar/ScalarReplAggregatesHLSL.cpp b/lib/Transforms/Scalar/ScalarReplAggregatesHLSL.cpp
+index b3589884aa3b690e1aad5ca7a19218a222591d39..8e2eff7f69ec378393c7a9afc4040cd9e921c42d 100644
+--- a/lib/Transforms/Scalar/ScalarReplAggregatesHLSL.cpp
++++ b/lib/Transforms/Scalar/ScalarReplAggregatesHLSL.cpp
+@@ -989,9 +989,11 @@ void DeleteMemcpy(MemCpyInst *MI) {
+     if (op0->user_empty())
+       op0->eraseFromParent();
+   }
+-  if (Instruction *op1 = dyn_cast<Instruction>(Op1)) {
+-    if (op1->user_empty())
+-      op1->eraseFromParent();
++  if (Op0 != Op1) {
++    if (Instruction *op1 = dyn_cast<Instruction>(Op1)) {
++      if (op1->user_empty())
++        op1->eraseFromParent();
++    }
+   }
+ }
+ 
+diff --git a/tools/clang/test/DXC/unreferenced_struct_selft_assignment_crash.hlsl b/tools/clang/test/DXC/unreferenced_struct_selft_assignment_crash.hlsl
+new file mode 100644
+index 0000000000000000000000000000000000000000..81adf71867c9868992372e12dc1ba81aebb48344
+--- /dev/null
++++ b/tools/clang/test/DXC/unreferenced_struct_selft_assignment_crash.hlsl
+@@ -0,0 +1,24 @@
++// RUN: %dxc -T cs_6_0 %s | FileCheck %s
++
++// Validate that self-assignment of a static struct instance that is not
++// referenced does not crash the compiler. This was resulting in an ASAN
++// use-after-free in ScalarReplAggregatesHLSL because DeleteMemcpy would
++// attempt to delete both source and target, even if both were the same.
++// CHECK: define void @main() {
++// CHECK-NEXT:   ret void
++// CHECK-NEXT: }
++
++struct MyStruct {
++  int m0;
++};
++
++static MyStruct s;
++
++void foo() {
++  s = s;
++}
++
++[numthreads(1, 1, 1)]
++void main() {
++  foo();
++}

+ 0 - 0
patches/dxc/fix_hlmatrixlowerpass_leaving_call_to_dangling_functionval.patch → patches/DirectXShaderCompiler/fix_hlmatrixlowerpass_leaving_call_to_dangling_functionval.patch


+ 3 - 0
patches/angle/.patches

@@ -3,3 +3,6 @@ m120_translator_fail_compilation_if_too_many_struct_fields.patch
 m120_translator_limit_private_variable_size_to_64kb.patch
 m120_vulkan_don_t_crash_when_glcopyteximage2d_redefines_itself.patch
 m123_vulkan_fix_access_to_inactive_attributes.patch
+cherry-pick-f6672dbbe223.patch
+m119_move_invalid_uniform_protection_to_the_frontend.patch
+m120_fix_off-by-one_bounds_check_on_uniform_location.patch

+ 267 - 0
patches/angle/cherry-pick-f6672dbbe223.patch

@@ -0,0 +1,267 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Shahbaz Youssefi <[email protected]>
+Date: Mon, 25 Mar 2024 14:46:56 -0400
+Subject: M123: Translator: Disallow samplers in structs in interface blocks
+
+As disallowed by the spec:
+
+> Types and declarators are the same as for other uniform variable
+> declarations outside blocks, with these exceptions:
+>
+> * opaque types are not allowed
+
+Bug: chromium:328859176
+Change-Id: Ib94977860102329e520e635c3757827c93ca2163
+Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/5391986
+Auto-Submit: Shahbaz Youssefi <[email protected]>
+Reviewed-by: Geoff Lang <[email protected]>
+Commit-Queue: Shahbaz Youssefi <[email protected]>
+(cherry picked from commit a0fa06f6d79ced897c0fe2795551268199d29806)
+Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/5435737
+Reviewed-by: Yuly Novikov <[email protected]>
+
+diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
+index e174725beb764407185e471a9916ffd164493cd8..cb9eb3a4a566348f11aa5962037164147bc65684 100644
+--- a/src/compiler/translator/ParseContext.cpp
++++ b/src/compiler/translator/ParseContext.cpp
+@@ -34,27 +34,39 @@ namespace
+ 
+ const int kWebGLMaxStructNesting = 4;
+ 
+-bool ContainsSampler(const TStructure *structType);
++struct IsSamplerFunc
++{
++    bool operator()(TBasicType type) { return IsSampler(type); }
++};
++struct IsOpaqueFunc
++{
++    bool operator()(TBasicType type) { return IsOpaqueType(type); }
++};
++
++template <typename OpaqueFunc>
++bool ContainsOpaque(const TStructure *structType);
+ 
+-bool ContainsSampler(const TType &type)
++template <typename OpaqueFunc>
++bool ContainsOpaque(const TType &type)
+ {
+-    if (IsSampler(type.getBasicType()))
++    if (OpaqueFunc{}(type.getBasicType()))
+     {
+         return true;
+     }
+     if (type.getBasicType() == EbtStruct)
+     {
+-        return ContainsSampler(type.getStruct());
++        return ContainsOpaque<OpaqueFunc>(type.getStruct());
+     }
+ 
+     return false;
+ }
+ 
+-bool ContainsSampler(const TStructure *structType)
++template <typename OpaqueFunc>
++bool ContainsOpaque(const TStructure *structType)
+ {
+     for (const auto &field : structType->fields())
+     {
+-        if (ContainsSampler(*field->type()))
++        if (ContainsOpaque<OpaqueFunc>(*field->type()))
+             return true;
+     }
+     return false;
+@@ -1057,7 +1069,7 @@ bool TParseContext::checkIsNotOpaqueType(const TSourceLoc &line,
+ {
+     if (pType.type == EbtStruct)
+     {
+-        if (ContainsSampler(pType.userDef))
++        if (ContainsOpaque<IsSamplerFunc>(pType.userDef))
+         {
+             std::stringstream reasonStream = sh::InitializeStream<std::stringstream>();
+             reasonStream << reason << " (structure contains a sampler)";
+@@ -4923,12 +4935,9 @@ TIntermDeclaration *TParseContext::addInterfaceBlock(
+     {
+         TField *field    = (*fieldList)[memberIndex];
+         TType *fieldType = field->type();
+-        if (IsOpaqueType(fieldType->getBasicType()))
++        if (ContainsOpaque<IsOpaqueFunc>(*fieldType))
+         {
+-            std::string reason("unsupported type - ");
+-            reason += fieldType->getBasicString();
+-            reason += " types are not allowed in interface blocks";
+-            error(field->line(), reason.c_str(), fieldType->getBasicString());
++            error(field->line(), "Opaque types are not allowed in interface blocks", blockName);
+         }
+ 
+         const TQualifier qualifier = fieldType->getQualifier();
+diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp
+index f4b7ce6222ff4a29cb20b75bc102e2c8ae478189..8ac5b758b128ded933d727f7dccb0ce8f8eb338b 100644
+--- a/src/tests/gl_tests/GLSLTest.cpp
++++ b/src/tests/gl_tests/GLSLTest.cpp
+@@ -6716,7 +6716,34 @@ void main()
+     gl_FragColor = vec4(f(us), 0, 0, 1);
+ })";
+ 
+-    CompileShader(GL_FRAGMENT_SHADER, kFS);
++    GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
++    EXPECT_NE(fs, 0u);
++    ASSERT_GL_NO_ERROR();
++}
++
++// Test that structs with samplers are not allowed in interface blocks.  This is forbidden per
++// GLES3:
++//
++// > Types and declarators are the same as for other uniform variable declarations outside blocks,
++// > with these exceptions:
++// > * opaque types are not allowed
++TEST_P(GLSLTest_ES3, StructWithSamplersDisallowedInInterfaceBlock)
++{
++    const char kFS[] = R"(#version 300 es
++precision mediump float;
++struct S { sampler2D samp; bool b; };
++
++layout(std140) uniform Buffer { S s; } buffer;
++
++out vec4 color;
++
++void main()
++{
++    color = texture(buffer.s.samp, vec2(0));
++})";
++
++    GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
++    EXPECT_EQ(fs, 0u);
+     ASSERT_GL_NO_ERROR();
+ }
+ 
+@@ -18195,6 +18222,116 @@ void main() {
+     EXPECT_EQ(0u, shader);
+ }
+ 
++// Same as TooManyFieldsInStruct, but with samplers in the struct.
++TEST_P(GLSLTest_ES3, TooManySamplerFieldsInStruct)
++{
++    std::ostringstream fs;
++    fs << R"(#version 300 es
++precision highp float;
++struct TooManyFields
++{
++)";
++    for (uint32_t i = 0; i < (1 << 16); ++i)
++    {
++        fs << "    sampler2D field" << i << ";\n";
++    }
++    fs << R"(};
++uniform TooManyFields s;
++out vec4 color;
++void main() {
++    color = texture(s.field0, vec2(0));
++})";
++
++    GLuint shader = CompileShader(GL_FRAGMENT_SHADER, fs.str().c_str());
++    EXPECT_EQ(0u, shader);
++}
++
++// More complex variation of ManySamplerFieldsInStruct.  This one compiles fine.
++TEST_P(GLSLTest_ES3, ManySamplerFieldsInStructComplex)
++{
++    // D3D and OpenGL may be more restrictive about this many samplers.
++    ANGLE_SKIP_TEST_IF(IsD3D() || IsOpenGL());
++
++    std::ostringstream fs;
++    fs << R"(#version 300 es
++precision highp float;
++
++struct X {
++    mediump sampler2D a[0xf00];
++    mediump sampler2D b[0xf00];
++    mediump sampler2D c[0xf000];
++    mediump sampler2D d[0xf00];
++};
++
++struct Y {
++  X s1;
++  mediump sampler2D a[0xf00];
++  mediump sampler2D b[0xf000];
++  mediump sampler2D c[0x14000];
++};
++
++struct S {
++    Y s1;
++};
++
++struct structBuffer { S s; };
++
++uniform structBuffer b;
++
++out vec4 color;
++void main()
++{
++    color = texture(b.s.s1.s1.c[0], vec2(0));
++})";
++
++    GLuint shader = CompileShader(GL_FRAGMENT_SHADER, fs.str().c_str());
++    EXPECT_NE(0u, shader);
++}
++
++// Make sure a large array of samplers works.
++TEST_P(GLSLTest, ManySamplers)
++{
++    // D3D and OpenGL may be more restrictive about this many samplers.
++    ANGLE_SKIP_TEST_IF(IsD3D() || IsOpenGL());
++
++    std::ostringstream fs;
++    fs << R"(precision highp float;
++
++uniform mediump sampler2D c[0x12000];
++
++void main()
++{
++    gl_FragColor = texture2D(c[0], vec2(0));
++})";
++
++    GLuint shader = CompileShader(GL_FRAGMENT_SHADER, fs.str().c_str());
++    EXPECT_NE(0u, shader);
++}
++
++// Make sure a large array of samplers works when declared in a struct.
++TEST_P(GLSLTest, ManySamplersInStruct)
++{
++    // D3D and OpenGL may be more restrictive about this many samplers.
++    ANGLE_SKIP_TEST_IF(IsD3D() || IsOpenGL());
++
++    std::ostringstream fs;
++    fs << R"(precision highp float;
++
++struct X {
++    mediump sampler2D c[0x12000];
++};
++
++uniform X x;
++
++void main()
++{
++    gl_FragColor = texture2D(x.c[0], vec2(0));
++})";
++
++    GLuint shader = CompileShader(GL_FRAGMENT_SHADER, fs.str().c_str());
++    EXPECT_NE(0u, shader);
++}
++
+ // Test that passing large arrays to functions are compiled correctly.  Regression test for the
+ // SPIR-V generator that made a copy of the array to pass to the function, by decomposing and
+ // reconstructing it (in the absence of OpCopyLogical), but the reconstruction instruction has a
+diff --git a/src/tests/gl_tests/PixelLocalStorageTest.cpp b/src/tests/gl_tests/PixelLocalStorageTest.cpp
+index c49ba5741ad565ad9637fb2188a472ccbebc6284..126936271eb25eec601349a560fabc6f0f7d4b75 100644
+--- a/src/tests/gl_tests/PixelLocalStorageTest.cpp
++++ b/src/tests/gl_tests/PixelLocalStorageTest.cpp
+@@ -5573,8 +5573,7 @@ TEST_P(PixelLocalStorageCompilerTest, Declarations)
+     EXPECT_FALSE(log.compileFragmentShader(kPLSInStruct));
+     EXPECT_TRUE(log.has("ERROR: 0:5: 'pixelLocalANGLE' : disallowed type in struct"));
+     EXPECT_TRUE(
+-        log.has("ERROR: 0:10: 'pixelLocalANGLE' : unsupported type - pixelLocalANGLE types are not "
+-                "allowed in interface blocks"));
++        log.has("ERROR: 0:10: 'PLSBlock' : Opaque types are not allowed in interface blocks"));
+ 
+     ASSERT_GL_NO_ERROR();
+ }

+ 60 - 0
patches/angle/m119_move_invalid_uniform_protection_to_the_frontend.patch

@@ -0,0 +1,60 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Geoff Lang <[email protected]>
+Date: Tue, 17 Oct 2023 11:39:10 -0400
+Subject: M119: Move invalid uniform protection to the frontend.
+
+The frontend potentialy indexes into mUniformLocations with invalid
+uniform locations while validation is disabled too. Move the validation
+from the Metal backend to the frontend.
+
+Bug: chromium:1484878
+Change-Id: I92bc43aa28cfa26d601bb28f318860375f618608
+Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4947652
+Reviewed-by: Shahbaz Youssefi <[email protected]>
+Commit-Queue: Shahbaz Youssefi <[email protected]>
+(cherry picked from commit e076d6cfd0e1d6948623bb344c5a38753b0c2b2e)
+Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4969232
+Reviewed-by: Kenneth Russell <[email protected]>
+
+diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp
+index 5caab257c909d81ea40c8bdad6bca8ce5e9c72c7..5dc66027831b0d1ac1915faf0d32223d6a71cd1c 100644
+--- a/src/libANGLE/Program.cpp
++++ b/src/libANGLE/Program.cpp
+@@ -2192,7 +2192,7 @@ GLuint Program::getUniformIndex(const std::string &name) const
+ 
+ bool Program::shouldIgnoreUniform(UniformLocation location) const
+ {
+-    if (location.value == -1)
++    if (location.value < 0 || static_cast<size_t>(location.value) >= mUniformLocations.size())
+     {
+         return true;
+     }
+diff --git a/src/libANGLE/renderer/metal/ProgramMtl.mm b/src/libANGLE/renderer/metal/ProgramMtl.mm
+index 5b86c4a8e0fa00012460d2e14101494c1dc4fa82..c7b98020e47c2f881815685e122ecaa57dbc1ad9 100644
+--- a/src/libANGLE/renderer/metal/ProgramMtl.mm
++++ b/src/libANGLE/renderer/metal/ProgramMtl.mm
+@@ -766,22 +766,10 @@ void operator()() override
+     ProgramExecutableMtl *executableMtl = getExecutable();
+ 
+     const std::vector<gl::VariableLocation> &uniformLocations = mState.getUniformLocations();
+-    if (location < 0 || static_cast<size_t>(location) >= uniformLocations.size())
+-    {
+-        ERR() << "Invalid uniform location " << location << ", expected [0, "
+-              << uniformLocations.size() << ")";
+-        return;
+-    }
+-    const gl::VariableLocation &locationInfo = uniformLocations[location];
++    const gl::VariableLocation &locationInfo                  = uniformLocations[location];
+ 
+     const std::vector<gl::LinkedUniform> &linkedUniforms = mState.getUniforms();
+-    if (locationInfo.index >= linkedUniforms.size())
+-    {
+-        ERR() << "Invalid uniform location index " << locationInfo.index << ", expected [0, "
+-              << linkedUniforms.size() << ")";
+-        return;
+-    }
+-    const gl::LinkedUniform &linkedUniform = linkedUniforms[locationInfo.index];
++    const gl::LinkedUniform &linkedUniform               = linkedUniforms[locationInfo.index];
+ 
+     if (linkedUniform.isSampler())
+     {

+ 39 - 0
patches/angle/m120_fix_off-by-one_bounds_check_on_uniform_location.patch

@@ -0,0 +1,39 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Geoff Lang <[email protected]>
+Date: Wed, 22 Nov 2023 12:01:31 -0500
+Subject: M120: Fix off-by-one bounds check on uniform location.
+
+Log an error when the user provides an invalid uniform location.
+
+Bug: chromium:1504162
+Bug: chromium:1484878
+Change-Id: Ieb7eb964d508954ac8dfa8e4d9bd941778185223
+Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/5054238
+Commit-Queue: Geoff Lang <[email protected]>
+Reviewed-by: James Godfrey-Kittle <[email protected]>
+(cherry picked from commit fba482b7107ccf3e178f6bf56b6b0285407ace3a)
+Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/5080772
+Reviewed-by: Shahbaz Youssefi <[email protected]>
+
+diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp
+index 5dc66027831b0d1ac1915faf0d32223d6a71cd1c..d9e9582679b1e16e122c7e7b0a699e23cf45ae46 100644
+--- a/src/libANGLE/Program.cpp
++++ b/src/libANGLE/Program.cpp
+@@ -2192,13 +2192,15 @@ GLuint Program::getUniformIndex(const std::string &name) const
+ 
+ bool Program::shouldIgnoreUniform(UniformLocation location) const
+ {
+-    if (location.value < 0 || static_cast<size_t>(location.value) >= mUniformLocations.size())
++    if (location.value < 0)
+     {
+         return true;
+     }
+ 
+-    if (mState.mExecutable->mUniformLocations[static_cast<size_t>(location.value)].ignored)
++    if (static_cast<size_t>(location.value) >= mState.mExecutable->mUniformLocations.size())
+     {
++        ERR() << "Invalid uniform location " << location.value << ", expected [0, "
++              << mState.mExecutable->mUniformLocations.size() << ")";
+         return true;
+     }
+ 

+ 1 - 0
patches/chromium/.patches

@@ -163,3 +163,4 @@ update_crashpad_to_37afd37401253ebcebcf6e07ce15c8cfecb1a1cc.patch
 m122_webcodecs_disable_async_videoframe_readback_to_mitigate_a.patch
 fix_paintimage_deserialization_arbitrary-read_issue.patch
 reland_sensors_winrt_call_onreadingchangedcallback_via.patch
+cherry-pick-1b1f34234346.patch

+ 42 - 0
patches/chromium/cherry-pick-1b1f34234346.patch

@@ -0,0 +1,42 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: kylechar <[email protected]>
+Date: Tue, 9 Apr 2024 17:14:26 +0000
+Subject: Validate buffer length
+
+The BitmapInSharedMemory mojo traits were only validating row length and
+not total buffer length.
+
+(cherry picked from commit 1a19ff70bd54847d818566bd7a1e7c384c419746)
+
+(cherry picked from commit f15315f1cb7897e208947a40d538aac693283d7f)
+
+Bug: 331237485
+Change-Id: Ia2318899c44e9e7ac72fc7183954e6ce2c702179
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5396796
+Commit-Queue: Kyle Charbonneau <[email protected]>
+Cr-Original-Original-Commit-Position: refs/heads/main@{#1278417}
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5420432
+Commit-Queue: danakj <[email protected]>
+Cr-Original-Commit-Position: refs/branch-heads/6312@{#786}
+Cr-Original-Branched-From: 6711dcdae48edaf98cbc6964f90fac85b7d9986e-refs/heads/main@{#1262506}
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5433678
+Reviewed-by: danakj <[email protected]>
+Reviewed-by: Kyle Charbonneau <[email protected]>
+Cr-Commit-Position: refs/branch-heads/6099@{#2003}
+Cr-Branched-From: e6ee4500f7d6549a9ac1354f8d056da49ef406be-refs/heads/main@{#1217362}
+
+diff --git a/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc b/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
+index a6e5f45d9e72b9ac48e536c3a7756966b3c263cf..519d554055e5182cdcbae44fafdac339a64a923b 100644
+--- a/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
++++ b/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
+@@ -76,6 +76,10 @@ bool StructTraits<viz::mojom::BitmapInSharedMemoryDataView, SkBitmap>::Read(
+   if (!mapping_ptr->IsValid())
+     return false;
+ 
++  if (mapping_ptr->size() < image_info.computeByteSize(data.row_bytes())) {
++    return false;
++  }
++
+   if (!sk_bitmap->installPixels(image_info, mapping_ptr->memory(),
+                                 data.row_bytes(), &DeleteSharedMemoryMapping,
+                                 mapping_ptr.get())) {

+ 1 - 1
patches/config.json

@@ -15,5 +15,5 @@
   { "patch_dir": "src/electron/patches/angle", "repo": "src/third_party/angle" },
   { "patch_dir": "src/electron/patches/sqlite", "repo": "src/third_party/sqlite/src" },
   { "patch_dir": "src/electron/patches/libvpx", "repo": "src/third_party/libvpx/source/libvpx" },
-  { "patch_dir": "src/electron/patches/dxc", "repo": "src/third_party/dawn/third_party/dxc" }
+  { "patch_dir": "src/electron/patches/DirectXShaderCompiler", "repo": "src/third_party/dawn/third_party/dxc" }
 ]

+ 1 - 0
patches/v8/.patches

@@ -11,3 +11,4 @@ merged_wasm_add_bounds_check_in_tier-up_of_wasm-to-js_wrapper.patch
 merged_parser_fix_home_object_proxy_to_work_off-thread.patch
 merged_wasm_check_for_type-definition_count_limit.patch
 merged_runtime_recreate_enum_cache_on_map_update_if_any_previous.patch
+merged_wasm_check_the_cached_memory_for_growability.patch

+ 49 - 0
patches/v8/merged_wasm_check_the_cached_memory_for_growability.patch

@@ -0,0 +1,49 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Clemens Backes <[email protected]>
+Date: Wed, 17 Jan 2024 09:41:46 +0000
+Subject: Merged: [wasm] Check the cached memory for growability
+
+Instead of always checking memory 0, do check if the actually cached
+memory is growable.
+
[email protected]
+
+(cherry picked from commit 1285ee44a9e5207a8c1302bfaa745ca06f0a90a8)
+Bug: chromium:1518257
+
+Change-Id: Ic415e281d85bb979f1fd27aa5e14a5fcc52ffbf1
+Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5232161
+Reviewed-by: Jakob Kummerow <[email protected]>
+Commit-Queue: Clemens Backes <[email protected]>
+Cr-Commit-Position: refs/branch-heads/12.0@{#36}
+Cr-Branched-From: ed7b4caf1fb8184ad9e24346c84424055d4d430a-refs/heads/12.0.267@{#1}
+Cr-Branched-From: 210e75b19db4352c9b78dce0bae11c2dc3077df4-refs/heads/main@{#90651}
+
+diff --git a/src/wasm/graph-builder-interface.cc b/src/wasm/graph-builder-interface.cc
+index 4cbd8a9032622382c15321c82409db6e3da3a511..2ed779148501c45db238e805eb3c4a8c7009bdc9 100644
+--- a/src/wasm/graph-builder-interface.cc
++++ b/src/wasm/graph-builder-interface.cc
+@@ -265,17 +265,18 @@ class WasmGraphBuildingInterface {
+     }
+   }
+ 
+-  // Load the instance cache entries into the Ssa Environment.
++  // Load the instance cache entries into the SSA Environment.
+   void LoadInstanceCacheIntoSsa(SsaEnv* ssa_env) {
+     builder_->InitInstanceCache(&ssa_env->instance_cache);
+   }
+ 
+-  // Reload the instance cache entries into the Ssa Environment, if memory can
++  // Reload the instance cache entries into the SSA Environment, if memory can
+   // actually grow.
+   void ReloadInstanceCacheIntoSsa(SsaEnv* ssa_env, const WasmModule* module) {
+-    if (module->memories.empty()) return;
+-    const WasmMemory* memory0 = &module->memories[0];
+-    if (memory0->initial_pages == memory0->maximum_pages) return;
++    if (!builder_->has_cached_memory()) return;
++    const WasmMemory* cached_memory =
++        &module->memories[builder_->cached_memory_index()];
++    if (cached_memory->initial_pages == cached_memory->maximum_pages) return;
+     LoadInstanceCacheIntoSsa(ssa_env);
+   }
+ 

+ 2 - 0
patches/webrtc/.patches

@@ -3,3 +3,5 @@ fix_mark_pipewire_capturer_as_failed_after_session_is_closed.patch
 fix_check_pipewire_init_before_creating_generic_capturer.patch
 pipewire_capturer_make_restore_tokens_re-usable_more_than_one_time.patch
 tighten_som_dchecks_to_checks_in_vp9_packetization.patch
+m122_merge_limit_max_frame_size_in_dav1d_decoder.patch
+revert_2_m120_jseptransportcontroller_remove_raw_pointers_to.patch

+ 35 - 0
patches/webrtc/m122_merge_limit_max_frame_size_in_dav1d_decoder.patch

@@ -0,0 +1,35 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Sergey Silkin <[email protected]>
+Date: Wed, 21 Feb 2024 10:35:54 +0100
+Subject: Limit max frame size in DAV1D decoder
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+(cherry picked from commit 74a4038eaddcac773b9fc172ad446df6eb704b11)
+
+Bug: chromium:325284120
+Change-Id: Iea0aea0a17bb0b1f73b3c1cbd408b7a6cd2b216e
+Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/340180
+Commit-Queue: Sergey Silkin <[email protected]>
+Reviewed-by: Erik Språng <[email protected]>
+Cr-Original-Commit-Position: refs/heads/main@{#41776}
+Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/340580
+Reviewed-by: Philip Eliasson <[email protected]>
+Commit-Queue: Erik Språng <[email protected]>
+Cr-Commit-Position: refs/branch-heads/6261@{#1}
+Cr-Branched-From: be2786cd2383b7ec5d158add166275d19e246763-refs/heads/main@{#41596}
+
+diff --git a/modules/video_coding/codecs/av1/dav1d_decoder.cc b/modules/video_coding/codecs/av1/dav1d_decoder.cc
+index 3100c0d41bf429fa55984e07f137983d24ef58f8..77c360b9449ff784a15f0ba6e132fe8511d537ae 100644
+--- a/modules/video_coding/codecs/av1/dav1d_decoder.cc
++++ b/modules/video_coding/codecs/av1/dav1d_decoder.cc
+@@ -87,6 +87,8 @@ bool Dav1dDecoder::Configure(const Settings& settings) {
+   s.n_threads = std::max(2, settings.number_of_cores());
+   s.max_frame_delay = 1;   // For low latency decoding.
+   s.all_layers = 0;        // Don't output a frame for every spatial layer.
++  // Limit max frame size to avoid OOM'ing fuzzers. crbug.com/325284120.
++  s.frame_size_limit = 16384 * 16384;
+   s.operating_point = 31;  // Decode all operating points.
+ 
+   return dav1d_open(&context_, &s) == 0;

+ 1580 - 0
patches/webrtc/revert_2_m120_jseptransportcontroller_remove_raw_pointers_to.patch

@@ -0,0 +1,1580 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Tomas Gunnarsson <[email protected]>
+Date: Fri, 26 Jan 2024 12:01:53 +0000
+Subject: Revert^2 "[M120] JsepTransportController: Remove raw pointers to
+ description objects"
+
+This reverts commit bf2e30678e0e5b21f2a2d49180a13225fdcfaa1a.
+
+Reason for revert: Time to reland the fix for M120 now.
+
+Original change's description:
+> Revert "[M120] JsepTransportController: Remove raw pointers to description objects"
+>
+> This reverts commit e79a99060f70a356b131d9f2f7497914984944d1.
+>
+> Reason for revert: Merged too early. Will re-land for the next spin.
+>
+> Original change's description:
+> > [M120] JsepTransportController: Remove raw pointers to description objects
+> >
+> > Remove member variables that point to objects owned externally (in practice by SdpOfferAnswerHandler). The objects also live on the
+> > signaling thread whereas JsepTransportController performs
+> > operations on the network thread. Removing the raw pointers avoids
+> > the risk of referencing the description objects after they've been
+> > deleted or if the state is inconsistent across threads.
+> >
+> > (cherry picked from commit c56052001dd747ae37c0cf7bab604791fe7912b0)
+> >
+> > Bug: webrtc:1515832
+> > No-Try: true
+> > Change-Id: I852b2a3993964be817f93c46b5bc4b03121cde86
+> > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/334061
+> > Commit-Queue: Tomas Gunnarsson <[email protected]>
+> > Reviewed-by: Harald Alvestrand <[email protected]>
+> > Cr-Original-Commit-Position: refs/heads/main@{#41505}
+> > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/335180
+> > Reviewed-by: Mirko Bonadei <[email protected]>
+> > Cr-Commit-Position: refs/branch-heads/6099@{#2}
+> > Cr-Branched-From: 507f1cc3270d0577f79882acbd78e63e66008f3d-refs/heads/main@{#41042}
+>
+> Change-Id: Id4bd21fbc8b7306de1aba0854815ada5c9333468
+> No-Try: true
+> Bug: chromium:1515832
+> Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/335620
+> Bot-Commit: [email protected] <[email protected]>
+> Commit-Queue: Tomas Gunnarsson <[email protected]>
+> Reviewed-by: Mirko Bonadei <[email protected]>
+> Cr-Commit-Position: refs/branch-heads/6099@{#3}
+> Cr-Branched-From: 507f1cc3270d0577f79882acbd78e63e66008f3d-refs/heads/main@{#41042}
+
+No-Try: true
+Bug: chromium:1515832
+Change-Id: I13a24182908d7a616234d9c701bf7000a162df8e
+Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/336282
+Commit-Queue: Tomas Gunnarsson <[email protected]>
+Bot-Commit: [email protected] <[email protected]>
+Reviewed-by: Mirko Bonadei <[email protected]>
+Cr-Commit-Position: refs/branch-heads/6099@{#4}
+Cr-Branched-From: 507f1cc3270d0577f79882acbd78e63e66008f3d-refs/heads/main@{#41042}
+
+diff --git a/pc/jsep_transport_controller.cc b/pc/jsep_transport_controller.cc
+index 792365b521c3a6bb40ff23be3dce0ec3db24dccb..251e28c4c0ab6055495fb76fd64f38677b94ec5a 100644
+--- a/pc/jsep_transport_controller.cc
++++ b/pc/jsep_transport_controller.cc
+@@ -76,14 +76,18 @@ JsepTransportController::~JsepTransportController() {
+ 
+ RTCError JsepTransportController::SetLocalDescription(
+     SdpType type,
+-    const cricket::SessionDescription* description) {
++    const cricket::SessionDescription* local_desc,
++    const cricket::SessionDescription* remote_desc) {
++  RTC_DCHECK(local_desc);
+   TRACE_EVENT0("webrtc", "JsepTransportController::SetLocalDescription");
++
+   if (!network_thread_->IsCurrent()) {
+     return network_thread_->BlockingCall(
+-        [=] { return SetLocalDescription(type, description); });
++        [=] { return SetLocalDescription(type, local_desc, remote_desc); });
+   }
+ 
+   RTC_DCHECK_RUN_ON(network_thread_);
++
+   if (!initial_offerer_.has_value()) {
+     initial_offerer_.emplace(type == SdpType::kOffer);
+     if (*initial_offerer_) {
+@@ -92,20 +96,22 @@ RTCError JsepTransportController::SetLocalDescription(
+       SetIceRole_n(cricket::ICEROLE_CONTROLLED);
+     }
+   }
+-  return ApplyDescription_n(/*local=*/true, type, description);
++  return ApplyDescription_n(/*local=*/true, type, local_desc, remote_desc);
+ }
+ 
+ RTCError JsepTransportController::SetRemoteDescription(
+     SdpType type,
+-    const cricket::SessionDescription* description) {
++    const cricket::SessionDescription* local_desc,
++    const cricket::SessionDescription* remote_desc) {
++  RTC_DCHECK(remote_desc);
+   TRACE_EVENT0("webrtc", "JsepTransportController::SetRemoteDescription");
+   if (!network_thread_->IsCurrent()) {
+     return network_thread_->BlockingCall(
+-        [=] { return SetRemoteDescription(type, description); });
++        [=] { return SetRemoteDescription(type, local_desc, remote_desc); });
+   }
+ 
+   RTC_DCHECK_RUN_ON(network_thread_);
+-  return ApplyDescription_n(/*local=*/false, type, description);
++  return ApplyDescription_n(/*local=*/false, type, local_desc, remote_desc);
+ }
+ 
+ RtpTransportInternal* JsepTransportController::GetRtpTransport(
+@@ -555,18 +561,20 @@ JsepTransportController::GetActiveDtlsTransports() {
+ RTCError JsepTransportController::ApplyDescription_n(
+     bool local,
+     SdpType type,
+-    const cricket::SessionDescription* description) {
++    const cricket::SessionDescription* local_desc,
++    const cricket::SessionDescription* remote_desc) {
+   TRACE_EVENT0("webrtc", "JsepTransportController::ApplyDescription_n");
+-  RTC_DCHECK(description);
+ 
+-  if (local) {
+-    local_desc_ = description;
+-  } else {
+-    remote_desc_ = description;
+-  }
++  // Stash away the description object that we'll be applying (since this
++  // function is used for both local and remote).
++  const cricket::SessionDescription* description =
++      local ? local_desc : remote_desc;
++
++  RTC_DCHECK(description);
+ 
+   RTCError error;
+-  error = ValidateAndMaybeUpdateBundleGroups(local, type, description);
++  error =
++      ValidateAndMaybeUpdateBundleGroups(local, type, local_desc, remote_desc);
+   if (!error.ok()) {
+     return error;
+   }
+@@ -673,7 +681,11 @@ RTCError JsepTransportController::ApplyDescription_n(
+ RTCError JsepTransportController::ValidateAndMaybeUpdateBundleGroups(
+     bool local,
+     SdpType type,
+-    const cricket::SessionDescription* description) {
++    const cricket::SessionDescription* local_desc,
++    const cricket::SessionDescription* remote_desc) {
++  const cricket::SessionDescription* description =
++      local ? local_desc : remote_desc;
++
+   RTC_DCHECK(description);
+ 
+   std::vector<const cricket::ContentGroup*> new_bundle_groups =
+@@ -739,72 +751,74 @@ RTCError JsepTransportController::ValidateAndMaybeUpdateBundleGroups(
+       }
+     }
+   } else if (type == SdpType::kAnswer) {
+-    std::vector<const cricket::ContentGroup*> offered_bundle_groups =
+-        local ? remote_desc_->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE)
+-              : local_desc_->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE);
+-
+-    std::map<std::string, const cricket::ContentGroup*>
+-        offered_bundle_groups_by_mid;
+-    for (const cricket::ContentGroup* offered_bundle_group :
+-         offered_bundle_groups) {
+-      for (const std::string& content_name :
+-           offered_bundle_group->content_names()) {
+-        offered_bundle_groups_by_mid[content_name] = offered_bundle_group;
++    if ((local && remote_desc) || (!local && local_desc)) {
++      std::vector<const cricket::ContentGroup*> offered_bundle_groups =
++          local ? remote_desc->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE)
++                : local_desc->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE);
++
++      std::map<std::string, const cricket::ContentGroup*>
++          offered_bundle_groups_by_mid;
++      for (const cricket::ContentGroup* offered_bundle_group :
++           offered_bundle_groups) {
++        for (const std::string& content_name :
++             offered_bundle_group->content_names()) {
++          offered_bundle_groups_by_mid[content_name] = offered_bundle_group;
++        }
+       }
+-    }
+ 
+-    std::map<const cricket::ContentGroup*, const cricket::ContentGroup*>
+-        new_bundle_groups_by_offered_bundle_groups;
+-    for (const cricket::ContentGroup* new_bundle_group : new_bundle_groups) {
+-      if (!new_bundle_group->FirstContentName()) {
+-        // Empty groups could be a subset of any group.
+-        continue;
+-      }
+-      // The group in the answer (new_bundle_group) must have a corresponding
+-      // group in the offer (original_group), because the answer groups may only
+-      // be subsets of the offer groups.
+-      auto it = offered_bundle_groups_by_mid.find(
+-          *new_bundle_group->FirstContentName());
+-      if (it == offered_bundle_groups_by_mid.end()) {
+-        return RTCError(RTCErrorType::INVALID_PARAMETER,
+-                        "A BUNDLE group was added in the answer that did not "
+-                        "exist in the offer.");
+-      }
+-      const cricket::ContentGroup* offered_bundle_group = it->second;
+-      if (new_bundle_groups_by_offered_bundle_groups.find(
+-              offered_bundle_group) !=
+-          new_bundle_groups_by_offered_bundle_groups.end()) {
+-        return RTCError(RTCErrorType::INVALID_PARAMETER,
+-                        "A MID in the answer has changed group.");
+-      }
+-      new_bundle_groups_by_offered_bundle_groups.insert(
+-          std::make_pair(offered_bundle_group, new_bundle_group));
+-      for (const std::string& content_name :
+-           new_bundle_group->content_names()) {
+-        it = offered_bundle_groups_by_mid.find(content_name);
+-        // The BUNDLE group in answer should be a subset of offered group.
+-        if (it == offered_bundle_groups_by_mid.end() ||
+-            it->second != offered_bundle_group) {
++      std::map<const cricket::ContentGroup*, const cricket::ContentGroup*>
++          new_bundle_groups_by_offered_bundle_groups;
++      for (const cricket::ContentGroup* new_bundle_group : new_bundle_groups) {
++        if (!new_bundle_group->FirstContentName()) {
++          // Empty groups could be a subset of any group.
++          continue;
++        }
++        // The group in the answer (new_bundle_group) must have a corresponding
++        // group in the offer (original_group), because the answer groups may
++        // only be subsets of the offer groups.
++        auto it = offered_bundle_groups_by_mid.find(
++            *new_bundle_group->FirstContentName());
++        if (it == offered_bundle_groups_by_mid.end()) {
+           return RTCError(RTCErrorType::INVALID_PARAMETER,
+-                          "A BUNDLE group in answer contains a MID='" +
+-                              content_name +
+-                              "' that was not in the offered group.");
++                          "A BUNDLE group was added in the answer that did not "
++                          "exist in the offer.");
+         }
+-      }
+-    }
+-
+-    for (const auto& bundle_group : bundles_.bundle_groups()) {
+-      for (const std::string& content_name : bundle_group->content_names()) {
+-        // An answer that removes m= sections from pre-negotiated BUNDLE group
+-        // without rejecting it, is invalid.
+-        auto it = new_bundle_groups_by_mid.find(content_name);
+-        if (it == new_bundle_groups_by_mid.end()) {
+-          auto* content_info = description->GetContentByName(content_name);
+-          if (!content_info || !content_info->rejected) {
++        const cricket::ContentGroup* offered_bundle_group = it->second;
++        if (new_bundle_groups_by_offered_bundle_groups.find(
++                offered_bundle_group) !=
++            new_bundle_groups_by_offered_bundle_groups.end()) {
++          return RTCError(RTCErrorType::INVALID_PARAMETER,
++                          "A MID in the answer has changed group.");
++        }
++        new_bundle_groups_by_offered_bundle_groups.insert(
++            std::make_pair(offered_bundle_group, new_bundle_group));
++        for (const std::string& content_name :
++             new_bundle_group->content_names()) {
++          it = offered_bundle_groups_by_mid.find(content_name);
++          // The BUNDLE group in answer should be a subset of offered group.
++          if (it == offered_bundle_groups_by_mid.end() ||
++              it->second != offered_bundle_group) {
+             return RTCError(RTCErrorType::INVALID_PARAMETER,
+-                            "Answer cannot remove m= section with mid='" +
++                            "A BUNDLE group in answer contains a MID='" +
+                                 content_name +
+-                                "' from already-established BUNDLE group.");
++                                "' that was not in the offered group.");
++          }
++        }
++      }
++
++      for (const auto& bundle_group : bundles_.bundle_groups()) {
++        for (const std::string& content_name : bundle_group->content_names()) {
++          // An answer that removes m= sections from pre-negotiated BUNDLE group
++          // without rejecting it, is invalid.
++          auto it = new_bundle_groups_by_mid.find(content_name);
++          if (it == new_bundle_groups_by_mid.end()) {
++            auto* content_info = description->GetContentByName(content_name);
++            if (!content_info || !content_info->rejected) {
++              return RTCError(RTCErrorType::INVALID_PARAMETER,
++                              "Answer cannot remove m= section with mid='" +
++                                  content_name +
++                                  "' from already-established BUNDLE group.");
++            }
+           }
+         }
+       }
+diff --git a/pc/jsep_transport_controller.h b/pc/jsep_transport_controller.h
+index 5880e346cdd4a2d0290ea68bb63ba0caa51b949a..8eae3a020346d84e7ab48d7e6226e07872ba79a1 100644
+--- a/pc/jsep_transport_controller.h
++++ b/pc/jsep_transport_controller.h
+@@ -161,11 +161,24 @@ class JsepTransportController : public sigslot::has_slots<> {
+   // level, creating/destroying transport objects as needed and updating their
+   // properties. This includes RTP, DTLS, and ICE (but not SCTP). At least not
+   // yet? May make sense to in the future.
++  //
++  // `local_desc` must always be valid. If a remote description has previously
++  // been set via a call to `SetRemoteDescription()` then `remote_desc` should
++  // point to that description object in order to keep the current local and
++  // remote session descriptions in sync.
+   RTCError SetLocalDescription(SdpType type,
+-                               const cricket::SessionDescription* description);
+-
++                               const cricket::SessionDescription* local_desc,
++                               const cricket::SessionDescription* remote_desc);
++
++  // Call to apply a remote description (See `SetLocalDescription()` for local).
++  //
++  // `remote_desc` must always be valid. If a local description has previously
++  // been set via a call to `SetLocalDescription()` then `local_desc` should
++  // point to that description object in order to keep the current local and
++  // remote session descriptions in sync.
+   RTCError SetRemoteDescription(SdpType type,
+-                                const cricket::SessionDescription* description);
++                                const cricket::SessionDescription* local_desc,
++                                const cricket::SessionDescription* remote_desc);
+ 
+   // Get transports to be used for the provided `mid`. If bundling is enabled,
+   // calling GetRtpTransport for multiple MIDs may yield the same object.
+@@ -325,14 +338,23 @@ class JsepTransportController : public sigslot::has_slots<> {
+   CallbackList<const cricket::CandidatePairChangeEvent&>
+       signal_ice_candidate_pair_changed_ RTC_GUARDED_BY(network_thread_);
+ 
++  // Called from SetLocalDescription and SetRemoteDescription.
++  // When `local` is true, local_desc must be valid. Similarly when
++  // `local` is false, remote_desc must be valid. The description counterpart
++  // to the one that's being applied, may be nullptr but when it's supplied
++  // the counterpart description's content groups will  be kept up to date for
++  // `type == SdpType::kAnswer`.
+   RTCError ApplyDescription_n(bool local,
+                               SdpType type,
+-                              const cricket::SessionDescription* description)
++                              const cricket::SessionDescription* local_desc,
++                              const cricket::SessionDescription* remote_desc)
+       RTC_RUN_ON(network_thread_);
+   RTCError ValidateAndMaybeUpdateBundleGroups(
+       bool local,
+       SdpType type,
+-      const cricket::SessionDescription* description);
++      const cricket::SessionDescription* local_desc,
++      const cricket::SessionDescription* remote_desc)
++      RTC_RUN_ON(network_thread_);
+   RTCError ValidateContent(const cricket::ContentInfo& content_info);
+ 
+   void HandleRejectedContent(const cricket::ContentInfo& content_info)
+@@ -481,8 +503,6 @@ class JsepTransportController : public sigslot::has_slots<> {
+   const Config config_;
+   bool active_reset_srtp_params_ RTC_GUARDED_BY(network_thread_);
+ 
+-  const cricket::SessionDescription* local_desc_ = nullptr;
+-  const cricket::SessionDescription* remote_desc_ = nullptr;
+   absl::optional<bool> initial_offerer_;
+ 
+   cricket::IceConfig ice_config_;
+diff --git a/pc/jsep_transport_controller_unittest.cc b/pc/jsep_transport_controller_unittest.cc
+index faa8842e35f427a65632347ae42505fd49d87351..2a4e7875ee91e784fd7ab8f4d1aadd46975b3ec0 100644
+--- a/pc/jsep_transport_controller_unittest.cc
++++ b/pc/jsep_transport_controller_unittest.cc
+@@ -265,9 +265,10 @@ class JsepTransportControllerTest : public JsepTransportController::Observer,
+     }
+ 
+     auto description = CreateSessionDescriptionWithBundleGroup();
+-    EXPECT_TRUE(transport_controller_
+-                    ->SetLocalDescription(SdpType::kOffer, description.get())
+-                    .ok());
++    EXPECT_TRUE(
++        transport_controller_
++            ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++            .ok());
+ 
+     transport_controller_->MaybeStartGathering();
+     auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+@@ -385,9 +386,10 @@ class JsepTransportControllerTest : public JsepTransportController::Observer,
+ TEST_F(JsepTransportControllerTest, GetRtpTransport) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithoutBundle();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+   auto audio_rtp_transport = transport_controller_->GetRtpTransport(kAudioMid1);
+   auto video_rtp_transport = transport_controller_->GetRtpTransport(kVideoMid1);
+   EXPECT_NE(nullptr, audio_rtp_transport);
+@@ -402,9 +404,10 @@ TEST_F(JsepTransportControllerTest, GetDtlsTransport) {
+   config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyNegotiate;
+   CreateJsepTransportController(std::move(config));
+   auto description = CreateSessionDescriptionWithoutBundle();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+   EXPECT_NE(nullptr, transport_controller_->GetDtlsTransport(kAudioMid1));
+   EXPECT_NE(nullptr, transport_controller_->GetRtcpDtlsTransport(kAudioMid1));
+   EXPECT_NE(nullptr,
+@@ -437,9 +440,10 @@ TEST_F(JsepTransportControllerTest, GetDtlsTransportWithRtcpMux) {
+   config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
+   CreateJsepTransportController(std::move(config));
+   auto description = CreateSessionDescriptionWithoutBundle();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+   EXPECT_NE(nullptr, transport_controller_->GetDtlsTransport(kAudioMid1));
+   EXPECT_EQ(nullptr, transport_controller_->GetRtcpDtlsTransport(kAudioMid1));
+   EXPECT_NE(nullptr, transport_controller_->GetDtlsTransport(kVideoMid1));
+@@ -449,9 +453,10 @@ TEST_F(JsepTransportControllerTest, GetDtlsTransportWithRtcpMux) {
+ TEST_F(JsepTransportControllerTest, SetIceConfig) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithoutBundle();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+ 
+   transport_controller_->SetIceConfig(
+       CreateIceConfig(kTimeout, cricket::GATHER_CONTINUALLY));
+@@ -467,9 +472,10 @@ TEST_F(JsepTransportControllerTest, SetIceConfig) {
+                   cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
+                   nullptr);
+ 
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+   fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+       transport_controller_->GetDtlsTransport(kAudioMid2));
+   ASSERT_NE(nullptr, fake_audio_dtls);
+@@ -482,11 +488,14 @@ TEST_F(JsepTransportControllerTest, SetIceConfig) {
+ TEST_F(JsepTransportControllerTest, NeedIceRestart) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithoutBundle();
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
++  // TODO(tommi): Note that _now_ we set `remote`. (was not set before).
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, description.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, description.get(),
++                                         description.get())
+                   .ok());
+ 
+   // Initially NeedsIceRestart should return false.
+@@ -505,7 +514,8 @@ TEST_F(JsepTransportControllerTest, NeedIceRestart) {
+   audio_transport_info->description.ice_ufrag = kIceUfrag2;
+   audio_transport_info->description.ice_pwd = kIcePwd2;
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
++                  ->SetLocalDescription(SdpType::kOffer, description.get(),
++                                        description.get())
+                   .ok());
+   // Because the ICE is only restarted for audio, NeedsIceRestart is expected to
+   // return false for audio and true for video.
+@@ -516,9 +526,10 @@ TEST_F(JsepTransportControllerTest, NeedIceRestart) {
+ TEST_F(JsepTransportControllerTest, MaybeStartGathering) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithBundleGroup();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+   // After setting the local description, we should be able to start gathering
+   // candidates.
+   transport_controller_->MaybeStartGathering();
+@@ -529,10 +540,10 @@ TEST_F(JsepTransportControllerTest, MaybeStartGathering) {
+ TEST_F(JsepTransportControllerTest, AddRemoveRemoteCandidates) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithoutBundle();
+-  transport_controller_->SetLocalDescription(SdpType::kOffer,
+-                                             description.get());
+-  transport_controller_->SetRemoteDescription(SdpType::kAnswer,
+-                                              description.get());
++  transport_controller_->SetLocalDescription(SdpType::kOffer, description.get(),
++                                             nullptr);
++  transport_controller_->SetRemoteDescription(
++      SdpType::kAnswer, description.get(), description.get());
+   auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+       transport_controller_->GetDtlsTransport(kAudioMid1));
+   ASSERT_NE(nullptr, fake_audio_dtls);
+@@ -565,9 +576,10 @@ TEST_F(JsepTransportControllerTest, SetAndGetLocalCertificate) {
+   // Apply the local certificate.
+   EXPECT_TRUE(transport_controller_->SetLocalCertificate(certificate1));
+   // Apply the local description.
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+   returned_certificate = transport_controller_->GetLocalCertificate(kAudioMid1);
+   EXPECT_TRUE(returned_certificate);
+   EXPECT_EQ(certificate1->identity()->certificate().ToPEMString(),
+@@ -586,9 +598,10 @@ TEST_F(JsepTransportControllerTest, SetAndGetLocalCertificate) {
+ TEST_F(JsepTransportControllerTest, GetRemoteSSLCertChain) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithBundleGroup();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+   rtc::FakeSSLCertificate fake_certificate("fake_data");
+ 
+   auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+@@ -622,16 +635,18 @@ TEST_F(JsepTransportControllerTest, GetDtlsRole) {
+                   cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
+                   answer_certificate);
+ 
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, offer_desc.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, offer_desc.get(), nullptr)
++          .ok());
+ 
+   absl::optional<rtc::SSLRole> role =
+       transport_controller_->GetDtlsRole(kAudioMid1);
+   // The DTLS role is not decided yet.
+   EXPECT_FALSE(role);
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, answer_desc.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, offer_desc.get(),
++                                         answer_desc.get())
+                   .ok());
+   role = transport_controller_->GetDtlsRole(kAudioMid1);
+ 
+@@ -642,9 +657,10 @@ TEST_F(JsepTransportControllerTest, GetDtlsRole) {
+ TEST_F(JsepTransportControllerTest, GetStats) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithBundleGroup();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+ 
+   cricket::TransportStats stats;
+   EXPECT_TRUE(transport_controller_->GetStats(kAudioMid1, &stats));
+@@ -657,9 +673,10 @@ TEST_F(JsepTransportControllerTest, GetStats) {
+ TEST_F(JsepTransportControllerTest, SignalConnectionStateFailed) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithoutBundle();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+ 
+   auto fake_ice = static_cast<cricket::FakeIceTransport*>(
+       transport_controller_->GetDtlsTransport(kAudioMid1)->ice_transport());
+@@ -681,9 +698,10 @@ TEST_F(JsepTransportControllerTest,
+        SignalConnectionStateConnectedNoMediaTransport) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithoutBundle();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+ 
+   auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+       transport_controller_->GetDtlsTransport(kAudioMid1));
+@@ -729,9 +747,10 @@ TEST_F(JsepTransportControllerTest,
+ TEST_F(JsepTransportControllerTest, SignalConnectionStateComplete) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithoutBundle();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+ 
+   auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+       transport_controller_->GetDtlsTransport(kAudioMid1));
+@@ -788,9 +807,10 @@ TEST_F(JsepTransportControllerTest, SignalConnectionStateComplete) {
+ TEST_F(JsepTransportControllerTest, SignalIceGatheringStateGathering) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithoutBundle();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+ 
+   auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+       transport_controller_->GetDtlsTransport(kAudioMid1));
+@@ -803,9 +823,10 @@ TEST_F(JsepTransportControllerTest, SignalIceGatheringStateGathering) {
+ TEST_F(JsepTransportControllerTest, SignalIceGatheringStateComplete) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithoutBundle();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+ 
+   auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+       transport_controller_->GetDtlsTransport(kAudioMid1));
+@@ -838,9 +859,10 @@ TEST_F(JsepTransportControllerTest,
+        SignalingWhenLastIncompleteTransportDestroyed) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithBundleGroup();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+ 
+   auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+       transport_controller_->GetDtlsTransport(kAudioMid1));
+@@ -861,7 +883,8 @@ TEST_F(JsepTransportControllerTest,
+ 
+   // Set the remote description and enable the bundle.
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, description.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, description.get(),
++                                         description.get())
+                   .ok());
+   // The BUNDLE should be enabled, the incomplete video transport should be
+   // deleted and the states should be updated.
+@@ -887,11 +910,13 @@ TEST_F(JsepTransportControllerTest,
+   AddAudioSection(description.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
+                   cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
+                   nullptr);
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, description.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, description.get(),
++                                         description.get())
+                   .ok());
+ 
+   // Trigger and verify initial non-new states.
+@@ -914,7 +939,8 @@ TEST_F(JsepTransportControllerTest,
+   // to "new".
+   description->contents()[0].rejected = true;
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kOffer, description.get())
++                  ->SetRemoteDescription(SdpType::kOffer, description.get(),
++                                         description.get())
+                   .ok());
+   EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionNew,
+                  ice_connection_state_, kTimeout);
+@@ -941,9 +967,10 @@ TEST_F(JsepTransportControllerTest,
+ TEST_F(JsepTransportControllerTest, SignalCandidatesGathered) {
+   CreateJsepTransportController(JsepTransportController::Config());
+   auto description = CreateSessionDescriptionWithBundleGroup();
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, description.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, description.get(), nullptr)
++          .ok());
+   transport_controller_->MaybeStartGathering();
+ 
+   auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+@@ -998,11 +1025,13 @@ TEST_F(JsepTransportControllerTest, IceRoleNotRedetermined) {
+                   cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
+                   nullptr);
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetRemoteDescription(SdpType::kOffer, nullptr, remote_offer.get())
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kOffer, remote_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kAnswer, local_answer.get())
++                  ->SetLocalDescription(SdpType::kAnswer, local_answer.get(),
++                                        remote_offer.get())
+                   .ok());
+ 
+   auto fake_dtls = static_cast<FakeDtlsTransport*>(
+@@ -1015,10 +1044,11 @@ TEST_F(JsepTransportControllerTest, IceRoleNotRedetermined) {
+   AddAudioSection(restart_local_offer.get(), kAudioMid1, kIceUfrag3, kIcePwd3,
+                   cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
+                   nullptr);
+-  EXPECT_TRUE(
+-      transport_controller_
+-          ->SetLocalDescription(SdpType::kOffer, restart_local_offer.get())
+-          .ok());
++  EXPECT_TRUE(transport_controller_
++                  ->SetLocalDescription(SdpType::kOffer,
++                                        restart_local_offer.get(),
++                                        remote_offer.get())
++                  .ok());
+   EXPECT_EQ(cricket::ICEROLE_CONTROLLED,
+             fake_dtls->fake_ice_transport()->GetIceRole());
+ }
+@@ -1030,9 +1060,10 @@ TEST_F(JsepTransportControllerTest, SetIceRoleWhenIceLiteInRemoteAnswer) {
+   AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
+                   cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
+                   nullptr);
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   auto fake_dtls = static_cast<FakeDtlsTransport*>(
+       transport_controller_->GetDtlsTransport(kAudioMid1));
+   EXPECT_EQ(cricket::ICEROLE_CONTROLLING,
+@@ -1045,7 +1076,8 @@ TEST_F(JsepTransportControllerTest, SetIceRoleWhenIceLiteInRemoteAnswer) {
+                   cricket::ICEMODE_LITE, cricket::CONNECTIONROLE_PASSIVE,
+                   nullptr);
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+   EXPECT_EQ(cricket::ICEROLE_CONTROLLING,
+             fake_dtls->fake_ice_transport()->GetIceRole());
+@@ -1069,11 +1101,13 @@ TEST_F(JsepTransportControllerTest,
+                   nullptr);
+   // Initial Offer/Answer exchange. If the remote offerer is ICE-Lite, then the
+   // local side is the controlling.
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetRemoteDescription(SdpType::kOffer, nullptr, remote_offer.get())
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kOffer, remote_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kAnswer, local_answer.get())
++                  ->SetLocalDescription(SdpType::kAnswer, local_answer.get(),
++                                        remote_offer.get())
+                   .ok());
+   auto fake_dtls = static_cast<FakeDtlsTransport*>(
+       transport_controller_->GetDtlsTransport(kAudioMid1));
+@@ -1085,15 +1119,17 @@ TEST_F(JsepTransportControllerTest,
+   AddAudioSection(remote_offer2.get(), kAudioMid1, kIceUfrag2, kIcePwd2,
+                   cricket::ICEMODE_LITE, cricket::CONNECTIONROLE_ACTPASS,
+                   nullptr);
++  EXPECT_TRUE(transport_controller_
++                  ->SetRemoteDescription(SdpType::kOffer, local_answer.get(),
++                                         remote_offer2.get())
++                  .ok());
+   auto local_answer2 = std::make_unique<cricket::SessionDescription>();
+   AddAudioSection(local_answer2.get(), kAudioMid1, kIceUfrag2, kIcePwd2,
+                   cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
+                   nullptr);
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kOffer, remote_offer2.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kAnswer, local_answer2.get())
++                  ->SetLocalDescription(SdpType::kAnswer, local_answer2.get(),
++                                        remote_offer2.get())
+                   .ok());
+   fake_dtls = static_cast<FakeDtlsTransport*>(
+       transport_controller_->GetDtlsTransport(kAudioMid1));
+@@ -1145,11 +1181,13 @@ TEST_F(JsepTransportControllerTest, MultipleMediaSectionsOfSameTypeWithBundle) {
+   local_offer->AddGroup(bundle_group);
+   remote_answer->AddGroup(bundle_group);
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+   // Verify that all the sections are bundled on kAudio1.
+   auto transport1 = transport_controller_->GetRtpTransport(kAudioMid1);
+@@ -1224,11 +1262,13 @@ TEST_F(JsepTransportControllerTest, MultipleBundleGroups) {
+   remote_answer->AddGroup(bundle_group1);
+   remote_answer->AddGroup(bundle_group2);
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   // Verify that (kMid1Audio,kMid2Video) and (kMid3Audio,kMid4Video) form two
+@@ -1307,11 +1347,13 @@ TEST_F(JsepTransportControllerTest,
+   // endpoint that does not have support for multiple BUNDLE groups.
+   remote_answer->AddGroup(bundle_group1);
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   // Verify that (kMid1Audio,kMid2Video) form a bundle group, but that
+@@ -1382,12 +1424,14 @@ TEST_F(JsepTransportControllerTest, MultipleBundleGroupsIllegallyChangeGroup) {
+   remote_answer->AddGroup(answer_bundle_group2);
+ 
+   // Accept offer.
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   // Reject answer!
+   EXPECT_FALSE(transport_controller_
+-                   ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                   ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                          remote_answer.get())
+                    .ok());
+ }
+ 
+@@ -1445,12 +1489,14 @@ TEST_F(JsepTransportControllerTest, MultipleBundleGroupsInvalidSubsets) {
+   remote_answer->AddGroup(answer_bundle_group2);
+ 
+   // Accept offer.
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   // Reject answer!
+   EXPECT_FALSE(transport_controller_
+-                   ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                   ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                          remote_answer.get())
+                    .ok());
+ }
+ 
+@@ -1483,11 +1529,12 @@ TEST_F(JsepTransportControllerTest, MultipleBundleGroupsInvalidOverlap) {
+   offer->AddGroup(offer_bundle_group2);
+ 
+   // Reject offer, both if set as local or remote.
++  EXPECT_FALSE(transport_controller_
++                   ->SetLocalDescription(SdpType::kOffer, offer.get(), nullptr)
++                   .ok());
+   EXPECT_FALSE(
+-      transport_controller_->SetLocalDescription(SdpType::kOffer, offer.get())
+-          .ok());
+-  EXPECT_FALSE(
+-      transport_controller_->SetRemoteDescription(SdpType::kOffer, offer.get())
++      transport_controller_
++          ->SetRemoteDescription(SdpType::kOffer, offer.get(), offer.get())
+           .ok());
+ }
+ 
+@@ -1563,11 +1610,13 @@ TEST_F(JsepTransportControllerTest, MultipleBundleGroupsUnbundleFirstMid) {
+   remote_answer->AddGroup(answer_bundle_group1);
+   remote_answer->AddGroup(answer_bundle_group2);
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
+@@ -1659,9 +1708,10 @@ TEST_F(JsepTransportControllerTest, MultipleBundleGroupsChangeFirstMid) {
+   remote_answer->AddGroup(answer_bundle_group1);
+   remote_answer->AddGroup(answer_bundle_group2);
+ 
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+ 
+   // The fact that we accept this answer is actually a bug. If we accept the
+   // first MID to be in the group, we should also accept that it is the tagged
+@@ -1669,7 +1719,8 @@ TEST_F(JsepTransportControllerTest, MultipleBundleGroupsChangeFirstMid) {
+   // TODO(https://crbug.com/webrtc/12699): When this issue is fixed, change this
+   // to EXPECT_FALSE and remove the below expectations about transports.
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+   auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
+   auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
+@@ -1734,11 +1785,13 @@ TEST_F(JsepTransportControllerTest,
+   remote_answer->AddGroup(bundle_group1);
+   remote_answer->AddGroup(bundle_group2);
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   // Add kMid3Audio and kMid6Video to the respective audio/video bundle groups.
+@@ -1769,7 +1822,8 @@ TEST_F(JsepTransportControllerTest,
+   subsequent_offer->AddGroup(bundle_group1);
+   subsequent_offer->AddGroup(bundle_group2);
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, subsequent_offer.get())
++                  ->SetLocalDescription(SdpType::kOffer, subsequent_offer.get(),
++                                        remote_answer.get())
+                   .ok());
+   auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
+   auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
+@@ -1832,11 +1886,13 @@ TEST_F(JsepTransportControllerTest,
+   remote_answer->AddGroup(bundle_group1);
+   remote_answer->AddGroup(bundle_group2);
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   // Switch to grouping (kMid1Audio,kMid2Audio,kMid3Video,kMid4Video).
+@@ -1861,10 +1917,11 @@ TEST_F(JsepTransportControllerTest,
+                   cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
+                   nullptr);
+   subsequent_offer->AddGroup(new_bundle_group);
+-  EXPECT_FALSE(
+-      transport_controller_
+-          ->SetLocalDescription(SdpType::kOffer, subsequent_offer.get())
+-          .ok());
++  EXPECT_FALSE(transport_controller_
++                   ->SetLocalDescription(SdpType::kOffer,
++                                         subsequent_offer.get(),
++                                         remote_answer.get())
++                   .ok());
+ }
+ 
+ TEST_F(JsepTransportControllerTest,
+@@ -1912,11 +1969,13 @@ TEST_F(JsepTransportControllerTest,
+                   nullptr);
+   remote_answer->AddGroup(bundle_group);
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   // Switch to grouping (kMid1Audio,kMid2Audio) and (kMid3Video,kMid4Video).
+@@ -1943,10 +2002,11 @@ TEST_F(JsepTransportControllerTest,
+                   nullptr);
+   subsequent_offer->AddGroup(new_bundle_group1);
+   subsequent_offer->AddGroup(new_bundle_group2);
+-  EXPECT_FALSE(
+-      transport_controller_
+-          ->SetLocalDescription(SdpType::kOffer, subsequent_offer.get())
+-          .ok());
++  EXPECT_FALSE(transport_controller_
++                   ->SetLocalDescription(SdpType::kOffer,
++                                         subsequent_offer.get(),
++                                         remote_answer.get())
++                   .ok());
+ }
+ 
+ TEST_F(JsepTransportControllerTest,
+@@ -1997,11 +2057,13 @@ TEST_F(JsepTransportControllerTest,
+   remote_answer->AddGroup(bundle_group1);
+   remote_answer->AddGroup(bundle_group2);
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   // Switch to grouping (kMid1Audio,kMid3Video) and (kMid2Audio,kMid3Video).
+@@ -2028,10 +2090,11 @@ TEST_F(JsepTransportControllerTest,
+                   nullptr);
+   subsequent_offer->AddGroup(new_bundle_group1);
+   subsequent_offer->AddGroup(new_bundle_group2);
+-  EXPECT_FALSE(
+-      transport_controller_
+-          ->SetLocalDescription(SdpType::kOffer, subsequent_offer.get())
+-          .ok());
++  EXPECT_FALSE(transport_controller_
++                   ->SetLocalDescription(SdpType::kOffer,
++                                         subsequent_offer.get(),
++                                         remote_answer.get())
++                   .ok());
+ }
+ 
+ // Tests that only a subset of all the m= sections are bundled.
+@@ -2065,11 +2128,13 @@ TEST_F(JsepTransportControllerTest, BundleSubsetOfMediaSections) {
+ 
+   local_offer->AddGroup(bundle_group);
+   remote_answer->AddGroup(bundle_group);
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   // Verifiy that only `kAudio1` and `kVideo1` are bundled.
+@@ -2106,11 +2171,13 @@ TEST_F(JsepTransportControllerTest, BundleOnDataSectionInSubsequentOffer) {
+   local_offer->AddGroup(bundle_group);
+   remote_answer->AddGroup(bundle_group);
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+   auto data_transport = transport_controller_->GetRtpTransport(kDataMid1);
+ 
+@@ -2132,15 +2199,17 @@ TEST_F(JsepTransportControllerTest, BundleOnDataSectionInSubsequentOffer) {
+   bundle_group.AddContentName(kAudioMid1);
+   bundle_group.AddContentName(kVideoMid1);
+   local_offer->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
+-  remote_answer->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
+   local_offer->AddGroup(bundle_group);
+-  remote_answer->AddGroup(bundle_group);
+ 
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
++                  ->SetLocalDescription(SdpType::kOffer, local_offer.get(),
++                                        remote_answer.get())
+                   .ok());
++  remote_answer->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
++  remote_answer->AddGroup(bundle_group);
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   auto audio_transport = transport_controller_->GetRtpTransport(kAudioMid1);
+@@ -2186,11 +2255,13 @@ TEST_F(JsepTransportControllerTest, VideoDataRejectedInAnswer) {
+   local_offer->AddGroup(bundle_group);
+   remote_answer->AddGroup(bundle_group);
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   // Verify the RtpTransport/DtlsTransport is destroyed correctly.
+@@ -2233,11 +2304,13 @@ TEST_F(JsepTransportControllerTest, ChangeBundledMidNotSupported) {
+ 
+   local_offer->AddGroup(bundle_group);
+   remote_answer->AddGroup(bundle_group);
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+   EXPECT_EQ(transport_controller_->GetRtpTransport(kAudioMid1),
+             transport_controller_->GetRtpTransport(kVideoMid1));
+@@ -2245,15 +2318,17 @@ TEST_F(JsepTransportControllerTest, ChangeBundledMidNotSupported) {
+   // Reorder the bundle group.
+   EXPECT_TRUE(bundle_group.RemoveContentName(kAudioMid1));
+   bundle_group.AddContentName(kAudioMid1);
++  EXPECT_TRUE(transport_controller_
++                  ->SetLocalDescription(SdpType::kOffer, local_offer.get(),
++                                        remote_answer.get())
++                  .ok());
+   // The answerer uses the new bundle group and now the bundle mid is changed to
+   // `kVideo1`.
+   remote_answer->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
+   remote_answer->AddGroup(bundle_group);
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+   EXPECT_FALSE(transport_controller_
+-                   ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                   ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                          remote_answer.get())
+                    .ok());
+ }
+ // Test that rejecting only the first m= section of a BUNDLE group is treated as
+@@ -2294,18 +2369,21 @@ TEST_F(JsepTransportControllerTest, RejectFirstContentInBundleGroup) {
+   local_offer->AddGroup(bundle_group);
+   remote_answer->AddGroup(bundle_group);
+ 
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_FALSE(transport_controller_
+-                   ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                   ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                          remote_answer.get())
+                    .ok());
+ 
+   // Reject all the contents.
+   remote_answer->contents()[1].rejected = true;
+   remote_answer->contents()[2].rejected = true;
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+   EXPECT_EQ(nullptr, transport_controller_->GetRtpTransport(kAudioMid1));
+   EXPECT_EQ(nullptr, transport_controller_->GetRtpTransport(kVideoMid1));
+@@ -2325,9 +2403,10 @@ TEST_F(JsepTransportControllerTest, ApplyNonRtcpMuxOfferWhenMuxingRequired) {
+ 
+   local_offer->contents()[0].media_description()->set_rtcp_mux(false);
+   // Applying a non-RTCP-mux offer is expected to fail.
+-  EXPECT_FALSE(transport_controller_
+-                   ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                   .ok());
++  EXPECT_FALSE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+ }
+ 
+ // Tests that applying non-RTCP-mux answer would fail when kRtcpMuxPolicyRequire
+@@ -2340,9 +2419,10 @@ TEST_F(JsepTransportControllerTest, ApplyNonRtcpMuxAnswerWhenMuxingRequired) {
+   AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
+                   cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
+                   nullptr);
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+ 
+   auto remote_answer = std::make_unique<cricket::SessionDescription>();
+   AddAudioSection(remote_answer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
+@@ -2351,7 +2431,8 @@ TEST_F(JsepTransportControllerTest, ApplyNonRtcpMuxAnswerWhenMuxingRequired) {
+   // Applying a non-RTCP-mux answer is expected to fail.
+   remote_answer->contents()[0].media_description()->set_rtcp_mux(false);
+   EXPECT_FALSE(transport_controller_
+-                   ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                   ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                          remote_answer.get())
+                    .ok());
+ }
+ 
+@@ -2371,11 +2452,13 @@ TEST_F(JsepTransportControllerTest,
+   answer_bundle_group.AddContentName(kAudioMid1);
+   answer_bundle_group.AddContentName(kVideoMid1);
+   remote_answer->AddGroup(answer_bundle_group);
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_FALSE(transport_controller_
+-                   ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                   ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                          remote_answer.get())
+                    .ok());
+ }
+ 
+@@ -2392,11 +2475,13 @@ TEST_F(JsepTransportControllerTest, RejectBundleGroupWithNonExistingMid) {
+   local_offer->AddGroup(invalid_bundle_group);
+   remote_answer->AddGroup(invalid_bundle_group);
+ 
++  EXPECT_FALSE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_FALSE(transport_controller_
+-                   ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                   .ok());
+-  EXPECT_FALSE(transport_controller_
+-                   ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                   ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                          remote_answer.get())
+                    .ok());
+ }
+ 
+@@ -2407,16 +2492,19 @@ TEST_F(JsepTransportControllerTest, RemoveContentFromBundleGroup) {
+ 
+   auto local_offer = CreateSessionDescriptionWithBundleGroup();
+   auto remote_answer = CreateSessionDescriptionWithBundleGroup();
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   // Do an re-offer/answer.
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
++                  ->SetLocalDescription(SdpType::kOffer, local_offer.get(),
++                                        remote_answer.get())
+                   .ok());
+   auto new_answer = CreateSessionDescriptionWithoutBundle();
+   cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
+@@ -2427,7 +2515,8 @@ TEST_F(JsepTransportControllerTest, RemoveContentFromBundleGroup) {
+ 
+   // Applying invalid answer is expected to fail.
+   EXPECT_FALSE(transport_controller_
+-                   ->SetRemoteDescription(SdpType::kAnswer, new_answer.get())
++                   ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                          new_answer.get())
+                    .ok());
+ 
+   // Rejected the video content.
+@@ -2435,7 +2524,8 @@ TEST_F(JsepTransportControllerTest, RemoveContentFromBundleGroup) {
+   ASSERT_TRUE(video_content);
+   video_content->rejected = true;
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, new_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         new_answer.get())
+                   .ok());
+ }
+ 
+@@ -2453,14 +2543,16 @@ TEST_F(JsepTransportControllerTest, ChangeTaggedMediaSectionMaxBundle) {
+   cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
+   bundle_group.AddContentName(kAudioMid1);
+   local_offer->AddGroup(bundle_group);
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+ 
+   std::unique_ptr<cricket::SessionDescription> remote_answer(
+       local_offer->Clone());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   std::unique_ptr<cricket::SessionDescription> local_reoffer(
+@@ -2475,14 +2567,15 @@ TEST_F(JsepTransportControllerTest, ChangeTaggedMediaSectionMaxBundle) {
+   local_reoffer->AddGroup(new_bundle_group);
+ 
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_reoffer.get())
++                  ->SetLocalDescription(SdpType::kOffer, local_reoffer.get(),
++                                        remote_answer.get())
+                   .ok());
+   std::unique_ptr<cricket::SessionDescription> remote_reanswer(
+       local_reoffer->Clone());
+-  EXPECT_TRUE(
+-      transport_controller_
+-          ->SetRemoteDescription(SdpType::kAnswer, remote_reanswer.get())
+-          .ok());
++  EXPECT_TRUE(transport_controller_
++                  ->SetRemoteDescription(SdpType::kAnswer, local_reoffer.get(),
++                                         remote_reanswer.get())
++                  .ok());
+ }
+ 
+ TEST_F(JsepTransportControllerTest, RollbackRestoresRejectedTransport) {
+@@ -2496,11 +2589,13 @@ TEST_F(JsepTransportControllerTest, RollbackRestoresRejectedTransport) {
+                   nullptr);
+   std::unique_ptr<cricket::SessionDescription> remote_answer(
+       local_offer->Clone());
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
+@@ -2514,7 +2609,8 @@ TEST_F(JsepTransportControllerTest, RollbackRestoresRejectedTransport) {
+   local_reoffer->contents()[0].rejected = true;
+ 
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_reoffer.get())
++                  ->SetLocalDescription(SdpType::kOffer, local_reoffer.get(),
++                                        remote_answer.get())
+                   .ok());
+   auto old_mid1_transport = mid1_transport;
+   mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
+@@ -2556,11 +2652,13 @@ TEST_F(JsepTransportControllerTest, RollbackRestoresPreviousTransportMapping) {
+   std::unique_ptr<cricket::SessionDescription> remote_answer(
+       local_offer->Clone());
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
+@@ -2585,7 +2683,8 @@ TEST_F(JsepTransportControllerTest, RollbackRestoresPreviousTransportMapping) {
+   local_reoffer->AddGroup(bundle_group);
+ 
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_reoffer.get())
++                  ->SetLocalDescription(SdpType::kOffer, local_reoffer.get(),
++                                        remote_answer.get())
+                   .ok());
+ 
+   // Store the old transport pointer and verify that the offer actually changed
+@@ -2633,11 +2732,13 @@ TEST_F(JsepTransportControllerTest, RollbackAndAddToDifferentBundleGroup) {
+   std::unique_ptr<cricket::SessionDescription> remote_answer(
+       local_offer->Clone());
+ 
++  EXPECT_TRUE(
++      transport_controller_
++          ->SetLocalDescription(SdpType::kOffer, local_offer.get(), nullptr)
++          .ok());
+   EXPECT_TRUE(transport_controller_
+-                  ->SetLocalDescription(SdpType::kOffer, local_offer.get())
+-                  .ok());
+-  EXPECT_TRUE(transport_controller_
+-                  ->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
++                  ->SetRemoteDescription(SdpType::kAnswer, local_offer.get(),
++                                         remote_answer.get())
+                   .ok());
+ 
+   // Apply an offer that adds kMid3Audio to the first BUNDLE group.,
+@@ -2657,10 +2758,11 @@ TEST_F(JsepTransportControllerTest, RollbackAndAddToDifferentBundleGroup) {
+   subsequent_offer_1->AddGroup(modified_bundle_group1);
+   subsequent_offer_1->AddGroup(bundle_group2);
+ 
+-  EXPECT_TRUE(
+-      transport_controller_
+-          ->SetLocalDescription(SdpType::kOffer, subsequent_offer_1.get())
+-          .ok());
++  EXPECT_TRUE(transport_controller_
++                  ->SetLocalDescription(SdpType::kOffer,
++                                        subsequent_offer_1.get(),
++                                        remote_answer.get())
++                  .ok());
+ 
+   auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
+   auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
+@@ -2689,10 +2791,11 @@ TEST_F(JsepTransportControllerTest, RollbackAndAddToDifferentBundleGroup) {
+   subsequent_offer_2->AddGroup(bundle_group1);
+   subsequent_offer_2->AddGroup(modified_bundle_group2);
+ 
+-  EXPECT_TRUE(
+-      transport_controller_
+-          ->SetLocalDescription(SdpType::kOffer, subsequent_offer_2.get())
+-          .ok());
++  EXPECT_TRUE(transport_controller_
++                  ->SetLocalDescription(SdpType::kOffer,
++                                        subsequent_offer_2.get(),
++                                        remote_answer.get())
++                  .ok());
+ 
+   mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
+   mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
+@@ -2722,9 +2825,9 @@ TEST_F(JsepTransportControllerTest, BundleOnlySectionDoesNotNeedRtcpMux) {
+   offer->contents()[1].media_description()->set_rtcp_mux(false);
+   offer->contents()[1].bundle_only = true;
+ 
+-  EXPECT_TRUE(
+-      transport_controller_->SetRemoteDescription(SdpType::kOffer, offer.get())
+-          .ok());
++  EXPECT_TRUE(transport_controller_
++                  ->SetRemoteDescription(SdpType::kOffer, nullptr, offer.get())
++                  .ok());
+ }
+ 
+ // Test that with max-bundle a single unbundled m-line is accepted.
+@@ -2738,9 +2841,9 @@ TEST_F(JsepTransportControllerTest,
+   AddAudioSection(offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
+                   cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
+                   nullptr);
+-  EXPECT_TRUE(
+-      transport_controller_->SetRemoteDescription(SdpType::kOffer, offer.get())
+-          .ok());
++  EXPECT_TRUE(transport_controller_
++                  ->SetRemoteDescription(SdpType::kOffer, nullptr, offer.get())
++                  .ok());
+ }
+ 
+ }  // namespace webrtc
+diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
+index 06303de4412d7e8f2b29920e29004daeae4ef54c..56321e7a80b3903376b812b5f7e357cc15711c95 100644
+--- a/pc/sdp_offer_answer.cc
++++ b/pc/sdp_offer_answer.cc
+@@ -1975,8 +1975,11 @@ RTCError SdpOfferAnswerHandler::ReplaceRemoteDescription(
+   const cricket::SessionDescription* session_desc =
+       remote_description()->description();
+ 
++  const auto* local = local_description();
++
+   // NOTE: This will perform a BlockingCall() to the network thread.
+-  return transport_controller_s()->SetRemoteDescription(sdp_type, session_desc);
++  return transport_controller_s()->SetRemoteDescription(
++      sdp_type, local ? local->description() : nullptr, session_desc);
+ }
+ 
+ void SdpOfferAnswerHandler::ApplyRemoteDescription(
+@@ -4893,13 +4896,15 @@ RTCError SdpOfferAnswerHandler::PushdownTransportDescription(
+   if (source == cricket::CS_LOCAL) {
+     const SessionDescriptionInterface* sdesc = local_description();
+     RTC_DCHECK(sdesc);
+-    return transport_controller_s()->SetLocalDescription(type,
+-                                                         sdesc->description());
++    const auto* remote = remote_description();
++    return transport_controller_s()->SetLocalDescription(
++        type, sdesc->description(), remote ? remote->description() : nullptr);
+   } else {
+     const SessionDescriptionInterface* sdesc = remote_description();
+     RTC_DCHECK(sdesc);
+-    return transport_controller_s()->SetRemoteDescription(type,
+-                                                          sdesc->description());
++    const auto* local = local_description();
++    return transport_controller_s()->SetRemoteDescription(
++        type, local ? local->description() : nullptr, sdesc->description());
+   }
+ }
+ 
+diff --git a/test/peer_scenario/scenario_connection.cc b/test/peer_scenario/scenario_connection.cc
+index 66eca275d18d0af4941cb52c6f07cf3f61981a73..338de74401c76aed89c447ffe24ad49395e13c5f 100644
+--- a/test/peer_scenario/scenario_connection.cc
++++ b/test/peer_scenario/scenario_connection.cc
+@@ -173,7 +173,9 @@ void ScenarioIceConnectionImpl::SetRemoteSdp(SdpType type,
+       });
+ 
+   auto res = jsep_controller_->SetRemoteDescription(
+-      remote_description_->GetType(), remote_description_->description());
++      remote_description_->GetType(),
++      local_description_ ? local_description_->description() : nullptr,
++      remote_description_->description());
+   RTC_CHECK(res.ok()) << res.message();
+   RtpDemuxerCriteria criteria;
+   for (const auto& content : remote_description_->description()->contents()) {
+@@ -203,7 +205,8 @@ void ScenarioIceConnectionImpl::SetLocalSdp(SdpType type,
+   RTC_DCHECK_RUN_ON(signaling_thread_);
+   local_description_ = webrtc::CreateSessionDescription(type, local_sdp);
+   auto res = jsep_controller_->SetLocalDescription(
+-      local_description_->GetType(), local_description_->description());
++      local_description_->GetType(), local_description_->description(),
++      remote_description_ ? remote_description_->description() : nullptr);
+   RTC_CHECK(res.ok()) << res.message();
+   jsep_controller_->MaybeStartGathering();
+ }