diff --git a/DEPS b/DEPS
index e1bacdaa..37cac0a 100644
--- a/DEPS
+++ b/DEPS
@@ -204,7 +204,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '961f93287f84e7e7a7f5ff8a43c7adf14ca7fc42',
+  'v8_revision': '9eaea2245f5c5133f2e05fcf0ad0062d009ca383',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -212,7 +212,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'fcb6b5a5c55ee35d232f35bcfa9c35aefc00f89e',
+  'angle_revision': '10e6c1e43b65f24bac71ccd12c9a2a099e390ce8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -251,7 +251,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': '3facafa44e2ac49ac15359bf6c83110614a6cbf7',
+  'freetype_revision': '56c610b145212b7acfb24a17e86fc0ba15aa3052',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -267,7 +267,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'ae003f552e4e16247758303d671076690c405c45',
+  'catapult_revision': '759827265102502cc7f814572675b9685c351908',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -275,7 +275,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'a91304b7b16006cbf084e5cb7da6080b0c1cf62d',
+  'devtools_frontend_revision': '14d4051e8a9f1fab057de9538e1db37bc742fd77',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -327,7 +327,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '4814bdbdeae40186a61dfb7494390e64820daa4f',
+  'dawn_revision': 'e96986149080ca37e82788c907f439bc070aaa7b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -366,7 +366,7 @@
   'ukey2_revision': '0275885d8e6038c39b8a8ca55e75d1d4d1727f47',
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'tint_revision': '12ed862c7ef2d64711cb7e5200f9c28992e2cd12',
+  'tint_revision': '456aad3bced07d62e2b3bbf13f52bd1ac76c4094',
 
   # TODO(crbug.com/941824): The values below need to be kept in sync
   # between //DEPS and //buildtools/DEPS, so if you're updating one,
@@ -899,7 +899,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'fc23bc1c96acb0fa2fcc526204333fb9a373ecb6',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '34d74e1ebe96ea0136e999f661edf266519a9e68',
       'condition': 'checkout_chromeos',
   },
 
@@ -1291,7 +1291,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '254ea334b65f8a7d5d4ab087f861df0572fa6af7',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '6cdb496e99b434eb28978a9a5519042cce88c3a9',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1551,7 +1551,7 @@
   },
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '0d863f72a8c747c1b41f2798e5201e1abcdaec2b',
+    Var('webrtc_git') + '/src.git' + '@' + 'cde4a9f66990ae8e78157dc24f876fc6418b1d3b',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1623,7 +1623,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@2a242a6da146fbdfc29108e7f663f8af10c4fe08',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@fc8456596a54c59d9f8d5eea8e99bcf982a2f4c3',
     'condition': 'checkout_src_internal',
   },
 
@@ -1631,7 +1631,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'bGWD0dCUQSIgdz4mBoxEa-RzGSnD9QMw17rfdCA1yJMC',
+        'version': 'zt2uwc_OGFM3k0PtlXL7onfFpGgcKV4FqGixO2OUNEoC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1642,7 +1642,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': '2vgKL8p-SUW0S-OFCowtelp1xuW-s-25Zm0z7AwTFPMC',
+        'version': 'YTx5HPjHBFm4GXJxmOuhrfSBAL579U9AIc0Tpcnug2IC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/browser/gfx/child_frame.cc b/android_webview/browser/gfx/child_frame.cc
index d9d460b..ecab0ea 100644
--- a/android_webview/browser/gfx/child_frame.cc
+++ b/android_webview/browser/gfx/child_frame.cc
@@ -43,7 +43,7 @@
   if (frame_ptr) {
     layer_tree_frame_sink_id = frame_ptr->layer_tree_frame_sink_id;
     frame = std::move(frame_ptr->frame);
-    local_surface_id = frame_future->local_surface_id();
+    local_surface_id = frame_ptr->local_surface_id;
     hit_test_region_list = std::move(frame_ptr->hit_test_region_list);
   }
   frame_future = nullptr;
diff --git a/android_webview/browser/gfx/child_frame.h b/android_webview/browser/gfx/child_frame.h
index 880cef9..16116a2 100644
--- a/android_webview/browser/gfx/child_frame.h
+++ b/android_webview/browser/gfx/child_frame.h
@@ -51,7 +51,7 @@
   base::Optional<viz::HitTestRegionList> hit_test_region_list;
   // The id of the compositor this |frame| comes from.
   const viz::FrameSinkId frame_sink_id;
-  // local surface id of the frame, used with viz for webview
+  // Local surface id of the frame. Invalid if |frame| is null.
   viz::LocalSurfaceId local_surface_id;
   const gfx::Size viewport_size_for_tile_priority;
   const gfx::Transform transform_for_tile_priority;
diff --git a/android_webview/browser/gfx/hardware_renderer_viz.cc b/android_webview/browser/gfx/hardware_renderer_viz.cc
index f6e08458..5e08a918 100644
--- a/android_webview/browser/gfx/hardware_renderer_viz.cc
+++ b/android_webview/browser/gfx/hardware_renderer_viz.cc
@@ -159,8 +159,7 @@
 
   if (child_frame->frame) {
     DCHECK(!viz_frame_submission_);
-    without_gpu_->SubmitChildCompositorFrame(child_id.local_surface_id(),
-                                             child_frame);
+    without_gpu_->SubmitChildCompositorFrame(child_frame);
   }
 
   gfx::Size frame_size = without_gpu_->GetChildFrameSize();
@@ -276,9 +275,7 @@
     RenderThreadManager* state,
     RootFrameSinkGetter root_frame_sink_getter,
     AwVulkanContextProvider* context_provider)
-    : HardwareRenderer(state),
-      viz_frame_submission_(features::IsUsingVizFrameSubmissionForWebView()),
-      output_surface_provider_(context_provider) {
+    : HardwareRenderer(state), output_surface_provider_(context_provider) {
   DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
   DCHECK(output_surface_provider_.renderer_settings().use_skia_renderer);
 
@@ -339,43 +336,10 @@
   if (!child_frame_)
     return;
 
-  // Two problems currently can cause the content-generated LocalSurfaceId
-  // to remain the same even if a frame of a new size is submitted:
-  // * The content LocalSurfaceId is currently copied from browser, not
-  //   the renderer when the renderer submits a frame
-  // * Synchronous compositor can resize the viewport in LTHI::OnDraw before
-  //   a new LocalSurfaceId is committed.
-  // Therefore we do not use the LocalSurfaceId allocated by content, and
-  // instead generate our own LocalSurfaceId here for the root renderer frame.
-  if (!renderer_root_local_surface_id_allocator_ ||
-      child_frame_->frame_sink_id != surface_id_.frame_sink_id() ||
-      layer_tree_frame_sink_id_ != child_frame_->layer_tree_frame_sink_id) {
-    renderer_root_local_surface_id_allocator_ =
-        std::make_unique<viz::ParentLocalSurfaceIdAllocator>();
-    layer_tree_frame_sink_id_ = child_frame_->layer_tree_frame_sink_id;
-    renderer_root_local_surface_id_ = viz::LocalSurfaceId();
-  }
-
-  if (!viz_frame_submission_ && child_frame_->frame) {
-    if (!renderer_root_local_surface_id_.is_valid() ||
-        child_frame_->frame->size_in_pixels() != frame_size_ ||
-        child_frame_->frame->device_scale_factor() != device_scale_factor_) {
-      renderer_root_local_surface_id_allocator_->GenerateId();
-      renderer_root_local_surface_id_ =
-          renderer_root_local_surface_id_allocator_->GetCurrentLocalSurfaceId();
-      frame_size_ = child_frame_->frame->size_in_pixels();
-      device_scale_factor_ = child_frame_->frame->device_scale_factor();
-    }
-  }
-
-  viz::SurfaceId child_surface_id =
-      viz_frame_submission_ ? child_frame_->GetSurfaceId()
-                            : viz::SurfaceId(child_frame_->frame_sink_id,
-                                             renderer_root_local_surface_id_);
+  viz::SurfaceId child_surface_id = child_frame_->GetSurfaceId();
   if (child_surface_id.is_valid() && child_surface_id != surface_id_) {
     surface_id_ = child_surface_id;
-    if (viz_frame_submission_)
-      device_scale_factor_ = child_frame_->device_scale_factor;
+    device_scale_factor_ = child_frame_->device_scale_factor;
   }
 
   if (!surface_id_.is_valid())
diff --git a/android_webview/browser/gfx/hardware_renderer_viz.h b/android_webview/browser/gfx/hardware_renderer_viz.h
index 7b97e6d..539912b 100644
--- a/android_webview/browser/gfx/hardware_renderer_viz.h
+++ b/android_webview/browser/gfx/hardware_renderer_viz.h
@@ -35,19 +35,14 @@
   void DestroyOnViz();
   bool IsUsingVulkan() const;
 
-  const bool viz_frame_submission_;
+  // Information about last delegated frame.
+  float device_scale_factor_ = 0;
+
   viz::SurfaceId surface_id_;
 
   // Used to create viz::OutputSurface and gl::GLSurface
   OutputSurfaceProviderWebview output_surface_provider_;
 
-  std::unique_ptr<viz::ParentLocalSurfaceIdAllocator>
-      renderer_root_local_surface_id_allocator_;
-  uint32_t layer_tree_frame_sink_id_ = 0u;
-  viz::LocalSurfaceId renderer_root_local_surface_id_;
-  float device_scale_factor_ = 1.0f;
-  gfx::Size frame_size_;
-
   // These are accessed on the viz thread.
   std::unique_ptr<OnViz> on_viz_;
 
diff --git a/android_webview/browser/gfx/root_frame_sink.cc b/android_webview/browser/gfx/root_frame_sink.cc
index 4eac7a7e..72eae52 100644
--- a/android_webview/browser/gfx/root_frame_sink.cc
+++ b/android_webview/browser/gfx/root_frame_sink.cc
@@ -195,10 +195,9 @@
   client_ = nullptr;
 }
 
-void RootFrameSink::SubmitChildCompositorFrame(
-    const viz::LocalSurfaceId& local_surface_id,
-    ChildFrame* child_frame) {
+void RootFrameSink::SubmitChildCompositorFrame(ChildFrame* child_frame) {
   DCHECK(child_frame->frame);
+  DCHECK(child_frame->local_surface_id.is_valid());
   if (!child_sink_support_ ||
       child_sink_support_->frame_sink_id() != child_frame->frame_sink_id ||
       child_sink_support_->layer_tree_frame_sink_id() !=
@@ -211,7 +210,7 @@
   }
 
   child_sink_support_->SubmitCompositorFrame(
-      local_surface_id, std::move(*child_frame->frame),
+      child_frame->local_surface_id, std::move(*child_frame->frame),
       std::move(child_frame->hit_test_region_list));
   child_frame->frame.reset();
 }
diff --git a/android_webview/browser/gfx/root_frame_sink.h b/android_webview/browser/gfx/root_frame_sink.h
index afa412f..c607602 100644
--- a/android_webview/browser/gfx/root_frame_sink.h
+++ b/android_webview/browser/gfx/root_frame_sink.h
@@ -58,8 +58,7 @@
   bool IsChildSurface(const viz::FrameSinkId& frame_sink_id);
   void DettachClient();
 
-  void SubmitChildCompositorFrame(const viz::LocalSurfaceId& local_surface_id,
-                                  ChildFrame* child_frame);
+  void SubmitChildCompositorFrame(ChildFrame* child_frame);
   viz::FrameTimingDetailsMap TakeChildFrameTimingDetailsMap();
   gfx::Size GetChildFrameSize();
 
diff --git a/ash/public/cpp/external_arc/overlay/OWNERS b/ash/public/cpp/external_arc/overlay/OWNERS
new file mode 100644
index 0000000..3511ed9
--- /dev/null
+++ b/ash/public/cpp/external_arc/overlay/OWNERS
@@ -0,0 +1,3 @@
+file://components/arc/OWNERS
+
+lpique@chromium.org
diff --git a/ash/public/cpp/external_arc/overlay/arc_overlay_manager.cc b/ash/public/cpp/external_arc/overlay/arc_overlay_manager.cc
index ed0d9f9..25befe1e 100644
--- a/ash/public/cpp/external_arc/overlay/arc_overlay_manager.cc
+++ b/ash/public/cpp/external_arc/overlay/arc_overlay_manager.cc
@@ -119,9 +119,6 @@
     return;
   }
 
-  // Set a few properties of the shell root surface for its use as an overlay
-  shell_surface_base->root_surface()->SetFrame(exo::SurfaceFrameType::OVERLAY);
-
   // Use the shell surface widget window as the overlay
   DCHECK(shell_surface_base->GetWidget());
   DCHECK(shell_surface_base->GetWidget()->GetNativeWindow());
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 6b8ecb62..c2d829a 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3874,7 +3874,6 @@
     ]
 
     sources = [
-      "test/android/javatests/src/org/chromium/base/test/BaseActivityTestRule.java",
       "test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java",
       "test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java",
       "test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java",
diff --git a/base/cpu_unittest.cc b/base/cpu_unittest.cc
index b0403ef..6caebc0 100644
--- a/base/cpu_unittest.cc
+++ b/base/cpu_unittest.cc
@@ -137,8 +137,6 @@
     // sure that it's not optimized out by the compiler.
     __asm__ __volatile__("irg %0, %1" : "=r"(val) : "r"(ptr));
 #endif  // __ARM_FEATURE_MEMORY_TAGGING
-  } else {
-    GTEST_SKIP();
   }
 #endif  // ARCH_CPU_ARM64
 }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseActivityTestRule.java b/base/test/android/javatests/src/org/chromium/base/test/BaseActivityTestRule.java
deleted file mode 100644
index af570fb..0000000
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseActivityTestRule.java
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.base.test;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.support.test.runner.lifecycle.Stage;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Assert;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.Log;
-import org.chromium.base.test.util.ApplicationTestUtils;
-
-/**
- * A replacement for ActivityTestRule, designed for use in Chromium. This implementation supports
- * launching the target activity through a launcher or redirect from another Activity.
- *
- * @param <T> The type of Activity this Rule will use.
- */
-public class BaseActivityTestRule<T extends Activity> implements TestRule {
-    private static final String TAG = "BaseActivityTestRule";
-
-    private final Class<T> mActivityClass;
-    private boolean mFinishActivity = true;
-    private T mActivity;
-
-    /**
-     * @param activityClass The Class of the Activity the TestRule will use.
-     */
-    public BaseActivityTestRule(Class<T> activityClass) {
-        mActivityClass = activityClass;
-    }
-
-    @Override
-    public Statement apply(final Statement base, final Description desc) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                base.evaluate();
-                if (mFinishActivity && mActivity != null) {
-                    ApplicationTestUtils.finishActivity(mActivity);
-                }
-            }
-        };
-    }
-
-    /**
-     * @param finishActivity Whether to finish the Activity between tests. This is only meaningful
-     *     in the context of {@link Batch} tests. Non-batched tests will always finish Activities
-     *     between tests.
-     */
-    public void setFinishActivity(boolean finishActivity) {
-        mFinishActivity = finishActivity;
-    }
-
-    /**
-     * @return The activity under test.
-     */
-    public T getActivity() {
-        return mActivity;
-    }
-
-    /**
-     * Set the Activity to be used by this TestRule.
-     */
-    public void setActivity(T activity) {
-        mActivity = activity;
-    }
-
-    /**
-     * Launches the Activity under test using the provided intent.
-     */
-    public void launchActivity(@NonNull Intent startIntent) {
-        String packageName = ContextUtils.getApplicationContext().getPackageName();
-        Assert.assertTrue(TextUtils.equals(startIntent.getPackage(), packageName)
-                || TextUtils.equals(startIntent.getComponent().getPackageName(), packageName));
-
-        startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        Log.d(TAG, String.format("Launching activity %s", mActivityClass.getName()));
-
-        mActivity = ApplicationTestUtils.waitForActivityWithClass(mActivityClass, Stage.CREATED,
-                () -> ContextUtils.getApplicationContext().startActivity(startIntent));
-    }
-}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java
index c044cb4..799c803 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java
@@ -5,9 +5,6 @@
 package org.chromium.base.test.util;
 
 import android.app.Activity;
-import android.content.Intent;
-import android.os.Build;
-import android.provider.Settings;
 import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
 import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
 import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
@@ -29,16 +26,9 @@
 
     /** Waits until the given activity transitions to the given state. */
     public static void waitForActivityState(Activity activity, Stage stage) {
-        waitForActivityState(null, activity, stage);
-    }
-
-    /** Waits until the given activity transitions to the given state. */
-    public static void waitForActivityState(String failureReason, Activity activity, Stage stage) {
-        CriteriaHelper.pollUiThread(
-                ()
-                        -> { return sMonitor.getLifecycleStageOf(activity) == stage; },
-                failureReason, ScalableTimeout.scaleTimeout(10000),
-                CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        CriteriaHelper.pollUiThread(() -> {
+            return sMonitor.getLifecycleStageOf(activity) == stage;
+        }, ScalableTimeout.scaleTimeout(10000), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
     }
 
     /** Finishes the given activity and waits for its onDestroy() to be called. */
@@ -48,24 +38,7 @@
                 activity.finish();
             }
         });
-        try {
-            waitForActivityState(
-                    "Failed to finish the Activity. Did you start a second Activity and not finish"
-                            + " it?",
-                    activity, Stage.DESTROYED);
-        } catch (Throwable e) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) throw e;
-
-            // On L, there's a framework bug where Activities sometimes just don't get finished
-            // unless you start another Activity.
-            Intent intent = new Intent(Settings.ACTION_SETTINGS);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            activity.startActivity(intent);
-            waitForActivityState(
-                    "Failed to finish the Activity. Did you start a second Activity and not finish"
-                            + " it?",
-                    activity, Stage.DESTROYED);
-        }
+        waitForActivityState(activity, Stage.DESTROYED);
     }
 
     /**
@@ -75,25 +48,11 @@
      * @return The newly created Activity.
      */
     public static <T extends Activity> T recreateActivity(T activity) {
-        return waitForActivityWithClass(
-                activity.getClass(), Stage.RESUMED, () -> activity.recreate());
-    }
-
-    /**
-     * Waits for an activity of the specified class to reach the specified Activity {@link Stage},
-     * triggered by running the provided trigger.
-     *
-     * @param activityClass The class type to wait for.
-     * @param state The Activity {@link Stage} to wait for an activity of the right class type to
-     * reach.
-     * @param trigger The Runnable that will trigger the state change to wait for.
-     */
-    public static <T extends Activity> T waitForActivityWithClass(
-            Class<? extends Activity> activityClass, Stage stage, Runnable trigger) {
+        final Class<?> activityClass = activity.getClass();
         final CallbackHelper activityCallback = new CallbackHelper();
         final AtomicReference<T> activityRef = new AtomicReference<>();
-        ActivityLifecycleCallback stateListener = (Activity newActivity, Stage newStage) -> {
-            if (newStage == stage) {
+        ActivityLifecycleCallback stateListener = (Activity newActivity, Stage stage) -> {
+            if (stage == Stage.RESUMED) {
                 if (!activityClass.isAssignableFrom(newActivity.getClass())) return;
 
                 activityRef.set((T) newActivity);
@@ -103,8 +62,8 @@
         sMonitor.addLifecycleCallback(stateListener);
 
         try {
-            ThreadUtils.runOnUiThreadBlocking(() -> trigger.run());
-            activityCallback.waitForCallback("No Activity reached target state.", 0);
+            ThreadUtils.runOnUiThreadBlocking(() -> activity.recreate());
+            activityCallback.waitForCallback("Activity did not start as expected", 0);
             T createdActivity = activityRef.get();
             Assert.assertNotNull("Activity reference is null.", createdActivity);
             return createdActivity;
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 265801d..9912ea7 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20201126.3.1
+0.20201127.1.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index e61f1e3..9912ea7 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20201126.2.1
+0.20201127.1.1
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java
index 25938528..2349270 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java
@@ -46,9 +46,9 @@
             BrowserControlsStateProvider browserControls, CompositorViewHolder compositorViewHolder,
             Context context, @NonNull WebContents webContents,
             ActivityKeyboardVisibilityDelegate keyboardVisibilityDelegate,
-            ApplicationViewportInsetSupplier bottomInsetProvider, boolean skipOnboarding,
-            boolean isChromeCustomTab, @NonNull String initialUrl, Map<String, String> parameters,
-            String experimentIds, @Nullable String callerAccount, @Nullable String userName) {
+            ApplicationViewportInsetSupplier bottomInsetProvider, boolean isChromeCustomTab,
+            @NonNull String initialUrl, Map<String, String> parameters, String experimentIds,
+            @Nullable String callerAccount, @Nullable String userName) {
         if (shouldStartTriggerScript(parameters)) {
             if (TextUtils.isEmpty(parameters.get(PARAMETER_TRIGGER_SCRIPTS_BASE64))
                     && !UnifiedConsentServiceBridge.isUrlKeyedAnonymizedDataCollectionEnabled(
@@ -80,9 +80,8 @@
                                     parameters.put(PARAMETER_STARTED_WITH_TRIGGER_SCRIPT, "true");
                                     startAutofillAssistantRegular(bottomSheetController,
                                             browserControls, compositorViewHolder, context,
-                                            webContents, skipOnboarding, isChromeCustomTab,
-                                            initialUrl, parameters, experimentIds, callerAccount,
-                                            userName);
+                                            webContents, isChromeCustomTab, initialUrl, parameters,
+                                            experimentIds, callerAccount, userName);
                                 }
                             }
                         });
@@ -99,9 +98,8 @@
                                     isFirstTimeUser ? firstTimeUserScriptPath
                                                     : returningUserScriptPath);
                             startAutofillAssistantRegular(bottomSheetController, browserControls,
-                                    compositorViewHolder, context, webContents, skipOnboarding,
-                                    isChromeCustomTab, initialUrl, parameters, experimentIds,
-                                    callerAccount, userName);
+                                    compositorViewHolder, context, webContents, isChromeCustomTab,
+                                    initialUrl, parameters, experimentIds, callerAccount, userName);
                         }
                     });
             return;
@@ -109,8 +107,8 @@
 
         // Regular flow for starting without dedicated trigger script.
         startAutofillAssistantRegular(bottomSheetController, browserControls, compositorViewHolder,
-                context, webContents, skipOnboarding, isChromeCustomTab, initialUrl, parameters,
-                experimentIds, callerAccount, userName);
+                context, webContents, isChromeCustomTab, initialUrl, parameters, experimentIds,
+                callerAccount, userName);
     }
 
     /** Whether {@code parameters} indicate that a trigger script should be started. */
@@ -140,10 +138,10 @@
      */
     private void startAutofillAssistantRegular(BottomSheetController bottomSheetController,
             BrowserControlsStateProvider browserControls, CompositorViewHolder compositorViewHolder,
-            Context context, @NonNull WebContents webContents, boolean skipOnboarding,
-            boolean isChromeCustomTab, @NonNull String initialUrl, Map<String, String> parameters,
-            String experimentIds, @Nullable String callerAccount, @Nullable String userName) {
-        if (skipOnboarding) {
+            Context context, @NonNull WebContents webContents, boolean isChromeCustomTab,
+            @NonNull String initialUrl, Map<String, String> parameters, String experimentIds,
+            @Nullable String callerAccount, @Nullable String userName) {
+        if (!AutofillAssistantPreferencesUtil.getShowOnboarding()) {
             if (parameters.containsKey(PARAMETER_TRIGGER_SCRIPT_USED)
                     || parameters.containsKey(PARAMETER_STARTED_WITH_TRIGGER_SCRIPT)) {
                 AutofillAssistantMetrics.recordLiteScriptOnboarding(
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantCollectUserDataModel.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantCollectUserDataModel.java
index 38274c2..60810d6e 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantCollectUserDataModel.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantCollectUserDataModel.java
@@ -365,9 +365,9 @@
     @CalledByNative
     private static void addLoginChoice(List<AssistantLoginChoice> loginChoices, String identifier,
             String label, String sublabel, String sublabelAccessibilityHint, int priority,
-            @Nullable AssistantInfoPopup infoPopup) {
-        loginChoices.add(new AssistantLoginChoice(
-                identifier, label, sublabel, sublabelAccessibilityHint, priority, infoPopup));
+            @Nullable AssistantInfoPopup infoPopup, @Nullable String editButtonContentDescription) {
+        loginChoices.add(new AssistantLoginChoice(identifier, label, sublabel,
+                sublabelAccessibilityHint, priority, infoPopup, editButtonContentDescription));
     }
 
     /** Sets the list of available login choices. */
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantLoginChoice.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantLoginChoice.java
index d5a7c57..6dc9951 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantLoginChoice.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantLoginChoice.java
@@ -18,6 +18,7 @@
     private final int mPriority;
     private final String mSublabelAccessibilityHint;
     private final @Nullable AssistantInfoPopup mInfoPopup;
+    private final @Nullable String mEditButtonContentDescription;
 
     /**
      * @param identifier The unique identifier of this login choice.
@@ -27,14 +28,16 @@
      * @param priority The priority of this login choice (lower value == higher priority). Can be -1
      * to indicate default/auto.
      * @param infoPopup Optional popup that provides further information for this login choice.
+     * @param editButtonContentDescription Optional content description for the edit button.
      */
     public AssistantLoginChoice(String identifier, String label, String sublabel,
-            String sublabelAccessibilityHint, int priority,
-            @Nullable AssistantInfoPopup infoPopup) {
+            String sublabelAccessibilityHint, int priority, @Nullable AssistantInfoPopup infoPopup,
+            @Nullable String editButtonContentDescription) {
         super(identifier, label, sublabel, null);
         mPriority = priority;
         mSublabelAccessibilityHint = sublabelAccessibilityHint;
         mInfoPopup = infoPopup;
+        mEditButtonContentDescription = editButtonContentDescription;
     }
 
     public int getPriority() {
@@ -48,4 +51,8 @@
     public String getSublabelAccessibilityHint() {
         return mSublabelAccessibilityHint;
     }
+
+    public @Nullable String getEditButtonContentDescription() {
+        return mEditButtonContentDescription;
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantLoginSection.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantLoginSection.java
index 18b3e33..4cf04166 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantLoginSection.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantLoginSection.java
@@ -70,8 +70,11 @@
 
     @Override
     protected String getEditButtonContentDescription(AssistantLoginChoice choice) {
-        // TODO(b/143862732): Send this a11y string from the backend.
-        return mContext.getString(R.string.learn_more);
+        if (choice.getEditButtonContentDescription() != null) {
+            return choice.getEditButtonContentDescription();
+        } else {
+            return mContext.getString(R.string.learn_more);
+        }
     }
 
     @Override
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java
index 2717c72..80b4009 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java
@@ -23,9 +23,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.verify;
 
+import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.waitUntil;
 import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.waitUntilViewMatchesCondition;
 
-import android.support.test.runner.lifecycle.Stage;
+import android.app.Activity;
 import android.text.Spanned;
 import android.text.style.ClickableSpan;
 import android.view.View;
@@ -42,10 +43,10 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import org.chromium.base.ActivityState;
+import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Callback;
-import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator;
@@ -56,6 +57,7 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -293,11 +295,8 @@
                                     spannedMessage.getSpanEnd(spans[0]))
                             .toString());
         });
-        CustomTabActivity activity = ApplicationTestUtils.waitForActivityWithClass(
-                CustomTabActivity.class, Stage.RESUMED, () -> spans[0].onClick(termsMessage));
-        CriteriaHelper.pollUiThread(
-                () -> activity.getActivityTab().getUrlString().equals(expectedTermsUrl));
-        activity.finish();
+        spans[0].onClick(termsMessage);
+        waitUntil(() -> getOpenedUrlSpec().equals(expectedTermsUrl));
     }
 
     @Test
@@ -357,13 +356,12 @@
                             .toString()
                             .replaceAll("\\s+", " "));
         });
-        CustomTabActivity activity = ApplicationTestUtils.waitForActivityWithClass(
-                CustomTabActivity.class, Stage.RESUMED, () -> spans[0].onClick(termsMessage));
-        String url = mActivity.getResources()
-                             .getText(R.string.autofill_assistant_google_terms_url)
-                             .toString();
-        CriteriaHelper.pollUiThread(() -> activity.getActivityTab().getUrlString().equals(url));
-        activity.finish();
+        spans[0].onClick(termsMessage);
+        waitUntil(()
+                          -> getOpenedUrlSpec().equals(
+                                  mActivity.getResources()
+                                          .getText(R.string.autofill_assistant_google_terms_url)
+                                          .toString()));
     }
 
     /** Trigger onboarding and wait until it is fully displayed. */
@@ -372,4 +370,21 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> coordinator.show(callback));
         waitUntilViewMatchesCondition(withId(R.id.button_init_ok), isCompletelyDisplayed());
     }
+
+    // Get the newly opened Activity (through CustomTabActivity.showInfoPage) that happens on
+    // terms click. Return the URL of the current tab on that activity.
+    private String getOpenedUrlSpec() {
+        for (Activity runningActivity : ApplicationStatus.getRunningActivities()) {
+            if (runningActivity instanceof CustomTabActivity
+                    && ApplicationStatus.getStateForActivity(runningActivity)
+                            == ActivityState.RESUMED) {
+                return ChromeTabUtils
+                        .getUrlOnUiThread(((CustomTabActivity) runningActivity)
+                                                  .getTabModelSelector()
+                                                  .getCurrentTab())
+                        .getSpec();
+            }
+        }
+        return "";
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java
index e830ba8..07bb330 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java
@@ -667,7 +667,7 @@
             model.set(AssistantCollectUserDataModel.REQUEST_LOGIN_CHOICE, true);
             model.set(AssistantCollectUserDataModel.AVAILABLE_LOGINS,
                     Collections.singletonList(new AssistantLoginChoice(
-                            "id", "Guest", "Description of guest checkout", "", 0, null)));
+                            "id", "Guest", "Description of guest checkout", "", 0, null, "")));
         });
 
         /* Non-empty sections should not display the 'add' button in their title. */
@@ -1525,16 +1525,90 @@
             model.set(AssistantCollectUserDataModel.LOGIN_SECTION_TITLE, "Login options");
             model.set(AssistantCollectUserDataModel.REQUEST_LOGIN_CHOICE, true);
             model.set(AssistantCollectUserDataModel.AVAILABLE_LOGINS,
+                    Collections.singletonList(new AssistantLoginChoice("id", "Guest checkout", "",
+                            "", 0, infoPopup, "Description of edit button")));
+        });
+
+        onView(withText("Login options")).perform(click());
+        onView(withContentDescription("Description of edit button")).perform(click());
+        onView(withText("Guest checkout")).check(matches(isDisplayed()));
+        onView(withText("Text explanation.")).check(matches(isDisplayed()));
+        onView(withText(mTestRule.getActivity().getString(R.string.close))).perform(click());
+        onView(withContentDescription("Description of edit button")).check(matches(isDisplayed()));
+    }
+
+    @Test
+    @MediumTest
+    public void testSuppliedNonEmptyEditContentDescriptionIsUsed() throws Exception {
+        String contentDescription = "Description of edit button";
+        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
+        AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
+                new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
+
+        AssistantInfoPopup infoPopup = new AssistantInfoPopup("", "", null, null, null);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            model.set(AssistantCollectUserDataModel.DELEGATE, delegate);
+            model.set(AssistantCollectUserDataModel.VISIBLE, true);
+            model.set(AssistantCollectUserDataModel.LOGIN_SECTION_TITLE, "Login options");
+            model.set(AssistantCollectUserDataModel.REQUEST_LOGIN_CHOICE, true);
+            model.set(AssistantCollectUserDataModel.AVAILABLE_LOGINS,
                     Collections.singletonList(new AssistantLoginChoice(
-                            "id", "Guest checkout", "", "", 0, infoPopup)));
+                            "id", "Guest checkout", "", "", 0, infoPopup, contentDescription)));
+        });
+
+        onView(withText("Login options")).perform(click());
+        onView(withContentDescription(contentDescription)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    @MediumTest
+    public void testSuppliedEmptyEditContentDescriptionIsUsed() throws Exception {
+        String contentDescription = "";
+        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
+        AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
+                new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
+
+        AssistantInfoPopup infoPopup = new AssistantInfoPopup("", "", null, null, null);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            model.set(AssistantCollectUserDataModel.DELEGATE, delegate);
+            model.set(AssistantCollectUserDataModel.VISIBLE, true);
+            model.set(AssistantCollectUserDataModel.LOGIN_SECTION_TITLE, "Login options");
+            model.set(AssistantCollectUserDataModel.REQUEST_LOGIN_CHOICE, true);
+            model.set(AssistantCollectUserDataModel.AVAILABLE_LOGINS,
+                    Collections.singletonList(new AssistantLoginChoice(
+                            "id", "Guest checkout", "", "", 0, infoPopup, contentDescription)));
+        });
+
+        onView(withText("Login options")).perform(click());
+        onView(withContentDescription(contentDescription)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    @MediumTest
+    public void testWhenNullEditContentDescriptionIsSuppliedIconDescriptionIsUsed()
+            throws Exception {
+        String contentDescription = null;
+        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
+        AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
+                new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
+
+        AssistantInfoPopup infoPopup = new AssistantInfoPopup("", "", null, null, null);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            model.set(AssistantCollectUserDataModel.DELEGATE, delegate);
+            model.set(AssistantCollectUserDataModel.VISIBLE, true);
+            model.set(AssistantCollectUserDataModel.LOGIN_SECTION_TITLE, "Login options");
+            model.set(AssistantCollectUserDataModel.REQUEST_LOGIN_CHOICE, true);
+            model.set(AssistantCollectUserDataModel.AVAILABLE_LOGINS,
+                    Collections.singletonList(new AssistantLoginChoice(
+                            "id", "Guest checkout", "", "", 0, infoPopup, contentDescription)));
         });
 
         onView(withText("Login options")).perform(click());
         onView(withContentDescription(mTestRule.getActivity().getString(R.string.learn_more)))
-                .perform(click());
-        onView(withText("Guest checkout")).check(matches(isDisplayed()));
-        onView(withText("Text explanation.")).check(matches(isDisplayed()));
-        onView(withText(mTestRule.getActivity().getString(R.string.close))).perform(click());
+                .check(matches(isDisplayed()));
     }
 
     private View getPaymentSummaryErrorView(ViewHolder viewHolder) {
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInputActionIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInputActionIntegrationTest.java
index 51cef43..03a1924 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInputActionIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInputActionIntegrationTest.java
@@ -31,24 +31,36 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.autofill_assistant.proto.ActionProto;
+import org.chromium.chrome.browser.autofill_assistant.proto.CheckElementIsOnTopProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ChipProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ChipType;
 import org.chromium.chrome.browser.autofill_assistant.proto.ClickProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ClickType;
+import org.chromium.chrome.browser.autofill_assistant.proto.ClientIdProto;
+import org.chromium.chrome.browser.autofill_assistant.proto.ElementConditionProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.KeyboardValueFillStrategy;
 import org.chromium.chrome.browser.autofill_assistant.proto.OptionalStep;
 import org.chromium.chrome.browser.autofill_assistant.proto.ProcessedActionProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ProcessedActionStatusProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.PromptProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.PromptProto.Choice;
+import org.chromium.chrome.browser.autofill_assistant.proto.ReleaseElementsProto;
+import org.chromium.chrome.browser.autofill_assistant.proto.ScrollIntoViewProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.SelectOptionProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.SelectorProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.SelectorProto.Filter;
+import org.chromium.chrome.browser.autofill_assistant.proto.SendClickEventProto;
+import org.chromium.chrome.browser.autofill_assistant.proto.SendKeystrokeEventsProto;
+import org.chromium.chrome.browser.autofill_assistant.proto.SetFieldValueProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.SetFormFieldValueProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.SetFormFieldValueProto.KeyPress;
 import org.chromium.chrome.browser.autofill_assistant.proto.SupportedScriptProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.SupportedScriptProto.PresentationProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.TextFilter;
+import org.chromium.chrome.browser.autofill_assistant.proto.TextValue;
+import org.chromium.chrome.browser.autofill_assistant.proto.WaitForDocumentToBecomeInteractiveProto;
+import org.chromium.chrome.browser.autofill_assistant.proto.WaitForDomProto;
+import org.chromium.chrome.browser.autofill_assistant.proto.WaitForElementToBecomeStableProto;
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
@@ -472,6 +484,76 @@
         assertThat(processed.get(2).getStatus(), is(ProcessedActionStatusProto.ELEMENT_NOT_ON_TOP));
     }
 
+    @Test
+    @MediumTest
+    public void setTextWithMiniActions() throws Exception {
+        ArrayList<ActionProto> list = new ArrayList<>();
+
+        SelectorProto element = SelectorProto.newBuilder()
+                                        .addFilters(Filter.newBuilder().setCssSelector("#input1"))
+                                        .build();
+        ClientIdProto clientId = ClientIdProto.newBuilder().setIdentifier("e").build();
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setWaitForDom(
+                                 WaitForDomProto.newBuilder().setTimeoutMs(1000).setWaitCondition(
+                                         ElementConditionProto.newBuilder()
+                                                 .setMatch(element)
+                                                 .setClientId(clientId)))
+                         .build());
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setWaitForDocumentToBecomeInteractive(
+                                 WaitForDocumentToBecomeInteractiveProto.newBuilder()
+                                         .setClientId(clientId)
+                                         .setTimeoutInMs(1000))
+                         .build());
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setWaitForElementToBecomeStable(
+                                 WaitForElementToBecomeStableProto.newBuilder()
+                                         .setClientId(clientId)
+                                         .setStableCheckMaxRounds(10)
+                                         .setStableCheckIntervalMs(200))
+                         .build());
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setScrollIntoView(ScrollIntoViewProto.newBuilder().setClientId(clientId))
+                         .build());
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setCheckElementIsOnTop(
+                                 CheckElementIsOnTopProto.newBuilder().setClientId(clientId))
+                         .build());
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setSetFieldValue(
+                                 SetFieldValueProto.newBuilder().setClientId(clientId).setValue(
+                                         TextValue.newBuilder().setText("")))
+                         .build());
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setSendClickEvent(SendClickEventProto.newBuilder().setClientId(clientId))
+                         .build());
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setSendKeystrokeEvents(
+                                 SendKeystrokeEventsProto.newBuilder()
+                                         .setClientId(clientId)
+                                         .setDelayInMs(0)
+                                         .setValue(TextValue.newBuilder().setText("Value")))
+                         .build());
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setReleaseElements(
+                                 ReleaseElementsProto.newBuilder().addClientIds(clientId))
+                         .build());
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setPrompt(PromptProto.newBuilder().setMessage("Done").addChoices(
+                                 Choice.newBuilder()))
+                         .build());
+
+        AutofillAssistantTestScript script = new AutofillAssistantTestScript(TEST_SCRIPT, list);
+
+        assertThat(getElementValue(mTestRule.getWebContents(), "input1"), is("helloworld1"));
+
+        runScript(script);
+
+        waitUntilViewMatchesCondition(withText("Done"), isCompletelyDisplayed());
+        assertThat(getElementValue(mTestRule.getWebContents(), "input1"), is("Value"));
+    }
+
     private void runScript(AutofillAssistantTestScript script) {
         AutofillAssistantTestService testService =
                 new AutofillAssistantTestService(Collections.singletonList(script));
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantTriggerScriptIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantTriggerScriptIntegrationTest.java
index 6cfaeb4..a300227 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantTriggerScriptIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantTriggerScriptIntegrationTest.java
@@ -431,4 +431,54 @@
 
         waitUntilViewMatchesCondition(withText("Trigger script"), isCompletelyDisplayed());
     }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures(ChromeFeatureList.AUTOFILL_ASSISTANT_PROACTIVE_HELP)
+    public void dontShownOnboardingIfAcceptedInDifferentTab() {
+        TriggerScriptProto.Builder triggerScript =
+                TriggerScriptProto
+                        .newBuilder()
+                        /* no trigger condition */
+                        .setUserInterface(createDefaultUI("Trigger script",
+                                /* bubbleMessage = */ "",
+                                /* withProgressBar = */ true)
+                                                  .setRegularScriptLoadingStatusMessage(
+                                                          "Loading regular script"));
+
+        GetTriggerScriptsResponseProto triggerScripts =
+                (GetTriggerScriptsResponseProto) GetTriggerScriptsResponseProto.newBuilder()
+                        .addTriggerScripts(triggerScript)
+                        .build();
+        setupTriggerScripts(triggerScripts);
+        AutofillAssistantPreferencesUtil.setInitialPreferences(true);
+        SharedPreferencesManager.getInstance().writeBoolean(
+                ChromePreferenceKeys.AUTOFILL_ASSISTANT_ONBOARDING_ACCEPTED, false);
+        startAutofillAssistantOnTab(TEST_PAGE);
+
+        waitUntilViewMatchesCondition(withText("Trigger script"), isCompletelyDisplayed());
+
+        // Simulate the user accepting the onboarding in a different tab.
+        SharedPreferencesManager.getInstance().writeBoolean(
+                ChromePreferenceKeys.AUTOFILL_ASSISTANT_ONBOARDING_ACCEPTED, true);
+
+        ArrayList<ActionProto> list = new ArrayList<>();
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setPrompt(PromptProto.newBuilder().addChoices(
+                                 PromptProto.Choice.newBuilder().setChip(
+                                         ChipProto.newBuilder().setText("Done"))))
+                         .build());
+        AutofillAssistantTestScript script = new AutofillAssistantTestScript(
+                (SupportedScriptProto) SupportedScriptProto.newBuilder()
+                        .setPath(TEST_PAGE)
+                        .setPresentation(PresentationProto.newBuilder().setAutostart(true).setChip(
+                                ChipProto.newBuilder().setText("Done")))
+                        .build(),
+                list);
+        setupRegularScripts(script);
+
+        onView(withText("Continue")).perform(click());
+        waitUntilViewMatchesCondition(withText("Done"), isCompletelyDisplayed());
+        onView(withText("Loading regular script")).check(matches(isDisplayed()));
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/TestingAutofillAssistantModuleEntryProvider.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/TestingAutofillAssistantModuleEntryProvider.java
index 9d7762e..a6fe542 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/TestingAutofillAssistantModuleEntryProvider.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/TestingAutofillAssistantModuleEntryProvider.java
@@ -69,9 +69,8 @@
                 CompositorViewHolder compositorViewHolder, Context context,
                 @NonNull WebContents webContents,
                 ActivityKeyboardVisibilityDelegate keyboardVisibilityDelegate,
-                ApplicationViewportInsetSupplier bottomInsetProvider, boolean skipOnboarding,
-                boolean isChromeCustomTab, @NonNull String initialUrl,
-                Map<String, String> parameters, String experimentIds,
+                ApplicationViewportInsetSupplier bottomInsetProvider, boolean isChromeCustomTab,
+                @NonNull String initialUrl, Map<String, String> parameters, String experimentIds,
                 @Nullable String callerAccount, @Nullable String userName) {}
 
         @Override
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
index 4a75cf5..c1d6dd3 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
@@ -164,7 +164,6 @@
                                 activity.getCompositorViewHolder(), activity, tab.getWebContents(),
                                 activity.getWindowAndroid().getKeyboardDelegate(),
                                 activity.getWindowAndroid().getApplicationBottomInsetProvider(),
-                                !AutofillAssistantPreferencesUtil.getShowOnboarding(),
                                 activity instanceof CustomTabActivity, arguments.getInitialUrl(),
                                 arguments.getParameters(), arguments.getExperimentIds(),
                                 arguments.getCallerAccount(), arguments.getUserName());
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java
index dc194378..062db851 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java
@@ -37,9 +37,9 @@
             BrowserControlsStateProvider browserControls, CompositorViewHolder compositorViewHolder,
             Context context, @NonNull WebContents webContents,
             ActivityKeyboardVisibilityDelegate keyboardVisibilityDelegate,
-            ApplicationViewportInsetSupplier bottomInsetProvider, boolean skipOnboarding,
-            boolean isChromeCustomTab, @NonNull String initialUrl, Map<String, String> parameters,
-            String experimentIds, @Nullable String callerAccount, @Nullable String userName);
+            ApplicationViewportInsetSupplier bottomInsetProvider, boolean isChromeCustomTab,
+            @NonNull String initialUrl, Map<String, String> parameters, String experimentIds,
+            @Nullable String callerAccount, @Nullable String userName);
     /**
      * Returns a {@link AutofillAssistantActionHandler} instance tied to the activity owning the
      * given bottom sheet, and scrim view.
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
index a3ae2c4..51b95d2 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
@@ -65,7 +65,6 @@
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
-import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChromePhone;
@@ -173,7 +172,7 @@
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         mActivityTestRule.prepareUrlIntent(intent, null);
-        mActivityTestRule.launchActivity(intent);
+        mActivityTestRule.startActivityCompletely(intent);
     }
 
     public static Bitmap createThumbnailBitmapAndWriteToFile(int tabId) {
@@ -272,8 +271,7 @@
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().startDelayedNativeInitializationForTests());
         CriteriaHelper.pollUiThread(
-                mActivityTestRule.getActivity().getTabModelSelector()::isTabStateInitialized,
-                ScalableTimeout.scaleTimeout(10000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+                mActivityTestRule.getActivity().getTabModelSelector()::isTabStateInitialized);
         Assert.assertTrue(LibraryLoader.getInstance().isInitialized());
     }
 
@@ -570,7 +568,6 @@
             RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(i);
             if (viewHolder != null) {
                 ImageView thumbnail = viewHolder.itemView.findViewById(R.id.tab_thumbnail);
-                if (!(thumbnail.getDrawable() instanceof BitmapDrawable)) return false;
                 BitmapDrawable drawable = (BitmapDrawable) thumbnail.getDrawable();
                 Bitmap bitmap = drawable.getBitmap();
                 if (bitmap == null) return false;
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
index 260b84d6..19a30c7 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
@@ -91,7 +91,7 @@
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         mActivityTestRule.prepareUrlIntent(intent, null);
-        mActivityTestRule.launchActivity(intent);
+        mActivityTestRule.startActivityCompletely(intent);
     }
 
     @Before
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index c7b94a6..9d29913 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -811,7 +811,7 @@
     @CommandLineFlags.Add({BASE_PARAMS + "/single/exclude_mv_tiles/true"
             + "/show_last_active_tab_only/true/show_stack_tab_switcher/true"})
     public void
-    testShow_SingleAsHomepageV2_FromResumeShowStart() throws Exception {
+    testShow_SingleAsHomepageV2_FromResumeShowStart() throws ExecutionException {
         // clang-format on
         if (!mImmediateReturn) return;
 
@@ -831,7 +831,7 @@
         pressHome();
 
         // Simulates pressing Chrome's icon and launching Chrome from warm start.
-        mActivityTestRule.resumeMainActivityFromLauncher();
+        startMainActivityFromLauncher();
 
         CriteriaHelper.pollUiThread(
                 () -> cta.getLayoutManager() != null && cta.getLayoutManager().overviewVisible());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java
index 8d81d3b..79cb0ae 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java
@@ -214,11 +214,6 @@
         }
     }
 
-    @Override
-    public boolean isShowingUi() {
-        return mPaymentUiService.isShowingUI();
-    }
-
     // Implements BrowserPaymentRequest:
     @Override
     public String showAppSelector(boolean isShowWaitingForUpdatedDetails, PaymentItem total,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java
index f3d994cd0..66cee2d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java
@@ -447,11 +447,6 @@
         return mMinimalUi;
     }
 
-    /** @return Whether the Payment Request or Minimal UIs are showing. */
-    public boolean isShowingUI() {
-        return mPaymentRequestUI != null || mMinimalUi != null;
-    }
-
     // Implements PaymentUiServiceTestInterface:
     @Override
     @VisibleForTesting(otherwise = VisibleForTesting.NONE)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java
index 5fb5ba9..1b8a581 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java
@@ -12,6 +12,7 @@
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.components.external_intents.AuthenticatorNavigationInterceptor;
 import org.chromium.components.external_intents.ExternalNavigationHandler;
+import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
 import org.chromium.components.external_intents.InterceptNavigationDelegateClient;
 import org.chromium.components.external_intents.InterceptNavigationDelegateImpl;
 import org.chromium.components.external_intents.RedirectHandler;
@@ -116,6 +117,10 @@
     @Override
     public void onNavigationStarted(NavigationParams params) {}
 
+    @Override
+    public void onDecisionReachedForNavigation(
+            NavigationParams params, OverrideUrlLoadingResult overrideUrlLoadingResult) {}
+
     public void initializeWithDelegate(InterceptNavigationDelegateImpl delegate) {
         mInterceptNavigationDelegate = delegate;
         mTab.addObserver(mTabObserver);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
index 55cc509..2d53db6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -130,10 +130,10 @@
 
     @Override
     public boolean hasTab() {
-        // TODO(https://crbug.com/1147131): Remove the isInitialized() and isDestroyed checks when
-        // we no longer wait for TAB_CLOSED events to remove this tab.  Otherwise there is a chance
-        // we use this tab after {@link Tab#destroy()} is called.
-        return mTab != null && mTab.isInitialized() && !mTab.isDestroyed();
+        // TODO(dtrainor, tedchoc): Remove the isInitialized() check when we no longer wait for
+        // TAB_CLOSED events to remove this tab.  Otherwise there is a chance we use this tab after
+        // {@link ChromeTab#destroy()} is called.
+        return mTab != null && mTab.isInitialized();
     }
 
     @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java
index d2e0260..279eb8d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java
@@ -15,6 +15,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -71,6 +72,7 @@
     @After
     public void tearDown() throws Exception {
         AppIndexingUtil.setCallbackForTesting(null);
+        ApplicationTestUtils.finishActivity(mActivityTestRule.getActivity());
     }
 
     private static class CopylessHelper extends CallbackHelper {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/MainActivityWithURLTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/MainActivityWithURLTest.java
index 723653c..cee2800 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/MainActivityWithURLTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/MainActivityWithURLTest.java
@@ -8,11 +8,13 @@
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -34,6 +36,11 @@
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
 
+    @After
+    public void tearDown() throws Exception {
+        ApplicationTestUtils.finishActivity(mActivityTestRule.getActivity());
+    }
+
     /**
      * Verify launch the activity with URL.
      */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java
index 8640a48..2d586b71 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java
@@ -11,11 +11,8 @@
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 
-import static org.hamcrest.CoreMatchers.allOf;
 import static org.junit.Assert.assertEquals;
 
-import static org.chromium.chrome.test.util.ViewUtils.onViewWaiting;
-
 import android.support.test.InstrumentationRegistry;
 
 import androidx.test.filters.MediumTest;
@@ -84,7 +81,7 @@
     @MediumTest
     public void testPromoNotShownAfterBeingDismissed() {
         mBookmarkTestRule.showBookmarkManager(mSyncTestRule.getActivity());
-        onViewWaiting(allOf(withId(R.id.signin_promo_view_container), isDisplayed()));
+        onView(withId(R.id.signin_promo_view_container)).check(matches(isDisplayed()));
         onView(withId(R.id.signin_promo_close_button)).perform(click());
         onView(withId(R.id.signin_promo_view_container)).check(doesNotExist());
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
index 733c40d..645fad0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
@@ -204,9 +204,8 @@
     }
 
     @After
-    public void tearDown() throws Exception {
+    public void tearDown() {
         if (mTestServer != null) mTestServer.stopAndDestroyServer();
-        if (mBookmarkActivity != null) ApplicationTestUtils.finishActivity(mBookmarkActivity);
     }
 
     @AfterClass
@@ -284,7 +283,7 @@
         // Click the star button again to launch the edit activity.
         MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
                 mActivityTestRule.getActivity(), R.id.bookmark_this_page_id);
-        waitForEditActivity().finish();
+        waitForEditActivity();
     }
 
     @Test
@@ -315,9 +314,8 @@
             currentSnackbar.getController().onAction(null);
         });
 
-        BookmarkEditActivity activity = waitForEditActivity();
+        waitForEditActivity();
         SnackbarManager.setDurationForTesting(0);
-        activity.finish();
     }
 
     @Test
@@ -1733,13 +1731,12 @@
         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
     }
 
-    private BookmarkEditActivity waitForEditActivity() {
+    private void waitForEditActivity() {
         CriteriaHelper.pollUiThread(() -> {
             Criteria.checkThat(ApplicationStatus.getLastTrackedFocusedActivity(),
                     IsInstanceOf.instanceOf(BookmarkEditActivity.class));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        return (BookmarkEditActivity) ApplicationStatus.getLastTrackedFocusedActivity();
     }
 
     private ChromeTabbedActivity waitForTabbedActivity() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentBasicTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentBasicTest.java
index c6d9b533..6fc157e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentBasicTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentBasicTest.java
@@ -17,7 +17,6 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
@@ -45,18 +44,14 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class ClearBrowsingDataFragmentBasicTest {
+    @Rule
     public final ChromeTabbedActivityTestRule mActivityTestRule =
             new ChromeTabbedActivityTestRule();
+    @Rule
     public final SettingsActivityTestRule<ClearBrowsingDataFragmentBasic>
             mSettingsActivityTestRule =
                     new SettingsActivityTestRule<>(ClearBrowsingDataFragmentBasic.class);
 
-    // SettingsActivity has to be finished before the outer CTA can be finished or trying to finish
-    // CTA won't work.
-    @Rule
-    public final RuleChain mRuleChain =
-            RuleChain.outerRule(mActivityTestRule).around(mSettingsActivityTestRule);
-
     @Rule
     public final AccountManagerTestRule mAccountManagerTestRule = new AccountManagerTestRule();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
index c47a13b..8bc1feb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
@@ -2466,10 +2466,10 @@
                         sActivityTestRule.getActivity().getActivityTab()));
         final NavigationParams navigationParams = new NavigationParams(
                 "intent://test/#Intent;scheme=test;package=com.chrome.test;end", "",
-                false /* isPost */, true /* hasUserGesture */, PageTransition.LINK,
-                false /* isRedirect */, true /* isExternalProtocol */, true /* isMainFrame */,
-                true /* isRendererInitiated */, false /* hasUserGestureCarryover */,
-                null /* initiatorOrigin */);
+                0 /* navigationId */, false /* isPost */, true /* hasUserGesture */,
+                PageTransition.LINK, false /* isRedirect */, true /* isExternalProtocol */,
+                true /* isMainFrame */, true /* isRendererInitiated */,
+                false /* hasUserGestureCarryover */, null /* initiatorOrigin */);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
@@ -2496,16 +2496,16 @@
                         sActivityTestRule.getActivity().getActivityTab()));
 
         final NavigationParams initialNavigationParams = new NavigationParams("http://test.com", "",
-                false /* isPost */, true /* hasUserGesture */, PageTransition.LINK,
-                false /* isRedirect */, false /* isExternalProtocol */, true /* isMainFrame */,
-                true /* isRendererInitiated */, false /* hasUserGestureCarryover */,
-                null /* initiatorOrigin */);
+                0 /* navigationId */, false /* isPost */, true /* hasUserGesture */,
+                PageTransition.LINK, false /* isRedirect */, false /* isExternalProtocol */,
+                true /* isMainFrame */, true /* isRendererInitiated */,
+                false /* hasUserGestureCarryover */, null /* initiatorOrigin */);
         final NavigationParams redirectedNavigationParams = new NavigationParams(
                 "intent://test/#Intent;scheme=test;package=com.chrome.test;end", "",
-                false /* isPost */, false /* hasUserGesture */, PageTransition.LINK,
-                true /* isRedirect */, true /* isExternalProtocol */, true /* isMainFrame */,
-                true /* isRendererInitiated */, false /* hasUserGestureCarryover */,
-                null /* initiatorOrigin */);
+                0 /* navigationId */, false /* isPost */, false /* hasUserGesture */,
+                PageTransition.LINK, true /* isRedirect */, true /* isExternalProtocol */,
+                true /* isMainFrame */, true /* isRendererInitiated */,
+                false /* hasUserGestureCarryover */, null /* initiatorOrigin */);
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
@@ -2535,10 +2535,10 @@
                         sActivityTestRule.getActivity().getActivityTab()));
         final NavigationParams navigationParams = new NavigationParams(
                 "intent://test/#Intent;scheme=test;package=com.chrome.test;end", "",
-                false /* isPost */, false /* hasUserGesture */, PageTransition.LINK,
-                false /* isRedirect */, true /* isExternalProtocol */, true /* isMainFrame */,
-                true /* isRendererInitiated */, false /* hasUserGestureCarryover */,
-                null /* initiatorOrigin */);
+                0 /* navigationId */, false /* isPost */, false /* hasUserGesture */,
+                PageTransition.LINK, false /* isRedirect */, true /* isExternalProtocol */,
+                true /* isMainFrame */, true /* isRendererInitiated */,
+                false /* hasUserGestureCarryover */, null /* initiatorOrigin */);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index 96b2323a..8101eec 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -707,7 +707,7 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
         filter.addDataScheme(Uri.parse(mTestServer.getURL("/")).getScheme());
         final ActivityMonitor monitor =
-                InstrumentationRegistry.getInstrumentation().addMonitor(filter, null, true);
+                InstrumentationRegistry.getInstrumentation().addMonitor(filter, null, false);
         openAppMenuAndAssertMenuShown();
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
             MenuItem item =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTestRule.java
index e832ecf..ff48c21 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTestRule.java
@@ -4,20 +4,29 @@
 
 package org.chromium.chrome.browser.customtabs;
 
+import android.app.Activity;
 import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
 
-import androidx.annotation.NonNull;
-
+import org.hamcrest.Matchers;
 import org.junit.Assert;
 
+import org.chromium.base.ApplicationStatus;
 import org.chromium.base.FeatureList;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.ScalableTimeout;
+import org.chromium.chrome.browser.DeferredStartupHandler;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabTestUtils;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 
 import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Custom ActivityTestRule for all instrumentation tests that require a {@link CustomTabActivity}.
@@ -45,13 +54,29 @@
     }
 
     @Override
-    public void launchActivity(@NonNull Intent intent) {
+    public void startActivityCompletely(Intent intent) {
         if (!FeatureList.hasTestFeatures()) {
             FeatureList.setTestFeatures(
                     Collections.singletonMap(ChromeFeatureList.SHARE_BY_DEFAULT_IN_CCT, true));
         }
         putCustomTabIdInIntent(intent);
-        super.launchActivity(intent);
+        int currentIntentId = getCustomTabIdFromIntent(intent);
+
+        Activity activity = InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+        Assert.assertNotNull("Main activity did not start", activity);
+        CriteriaHelper.pollUiThread(() -> {
+            for (Activity runningActivity : ApplicationStatus.getRunningActivities()) {
+                if (runningActivity instanceof CustomTabActivity) {
+                    CustomTabActivity customTabActivity = (CustomTabActivity) runningActivity;
+                    final int customTabIdInActivity =
+                            getCustomTabIdFromIntent(customTabActivity.getIntent());
+                    if (currentIntentId != customTabIdInActivity) continue;
+                    setActivity(customTabActivity);
+                    return true;
+                }
+            }
+            return false;
+        });
     }
 
     /**
@@ -59,8 +84,33 @@
      * initialized.
      */
     public void startCustomTabActivityWithIntent(Intent intent) {
+        DeferredStartupHandler.setExpectingActivityStartupForTesting();
         startActivityCompletely(intent);
+        waitForActivityNativeInitializationComplete();
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(getActivity().getActivityTab(), Matchers.notNullValue());
+        });
         final Tab tab = getActivity().getActivityTab();
+        final CallbackHelper pageLoadFinishedHelper = new CallbackHelper();
+        tab.addObserver(new EmptyTabObserver() {
+            @Override
+            public void onLoadStopped(Tab tab, boolean toDifferentDocument) {
+                pageLoadFinishedHelper.notifyCalled();
+            }
+        });
+        try {
+            if (tab.isLoading()) {
+                pageLoadFinishedHelper.waitForCallback(
+                        0, 1, LONG_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            }
+        } catch (TimeoutException e) {
+            Assert.fail();
+        }
+        Assert.assertTrue("Deferred startup never completed",
+                DeferredStartupHandler.waitForDeferredStartupCompleteForTesting(
+                        STARTUP_TIMEOUT_MS));
+        Assert.assertNotNull(tab);
+        Assert.assertNotNull(tab.getView());
         Assert.assertTrue(TabTestUtils.isCustomTab(tab));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabFromChromeExternalNavigationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabFromChromeExternalNavigationTest.java
index 3821874..a07414a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabFromChromeExternalNavigationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabFromChromeExternalNavigationTest.java
@@ -111,7 +111,8 @@
     @Test
     @Feature("CustomTabFromChrome")
     @LargeTest
-    public void testIntentWithRedirectToApp() {
+    public void
+    testIntentWithRedirectToApp() {
         final String redirectUrl = "https://maps.google.com/maps?q=1600+amphitheatre+parkway";
         final String initialUrl =
                 mServerRule.getServer().getURL("/chrome/test/data/android/redirect/js_redirect.html"
@@ -122,7 +123,7 @@
                         + Base64.encodeToString(
                                 ApiCompatibilityUtils.getBytesUtf8(redirectUrl), Base64.URL_SAFE));
 
-        mActivityRule.launchActivity(getCustomTabFromChromeIntent(initialUrl, true));
+        mActivityRule.startActivityCompletely(getCustomTabFromChromeIntent(initialUrl, true));
         mActivityRule.waitForActivityNativeInitializationComplete();
 
         final AtomicReference<InterceptNavigationDelegateImpl> navigationDelegate =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTest.java
index af9727f..0899713 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTest.java
@@ -9,10 +9,12 @@
 
 import androidx.test.filters.LargeTest;
 
+import org.junit.After;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
@@ -35,6 +37,11 @@
     public DisplayCutoutTestRule mTestRule =
             new DisplayCutoutTestRule<ChromeActivity>(ChromeActivity.class);
 
+    @After
+    public void tearDown() throws Exception {
+        ApplicationTestUtils.finishActivity(mTestRule.getActivity());
+    }
+
     /**
      * Test that no safe area is applied when we have viewport fit auto
      */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
index cc76495e..3dc82eb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
@@ -13,7 +13,6 @@
 import android.net.Uri;
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.lifecycle.Stage;
 import android.text.TextUtils;
 import android.util.Base64;
 
@@ -29,14 +28,10 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.ContextUtils;
-import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.ScalableTimeout;
-import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
@@ -429,36 +424,24 @@
 
     @Test
     @SmallTest
-    public void testRedirectionFromIntentCold() throws Exception {
-        Context context = ContextUtils.getApplicationContext();
+    public void testRedirectionFromIntent() {
+        // Test cold-start.
         Intent intent = new Intent(Intent.ACTION_VIEW,
                 Uri.parse(mTestServer.getURL(NAVIGATION_FROM_JAVA_REDIRECTION_PAGE)));
-        intent.setClassName(context, ChromeLauncherActivity.class.getName());
+        Context targetContext = InstrumentationRegistry.getTargetContext();
+        intent.setClassName(targetContext, ChromeLauncherActivity.class.getName());
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        ChromeTabbedActivity activity = ApplicationTestUtils.waitForActivityWithClass(
-                ChromeTabbedActivity.class, Stage.CREATED, () -> context.startActivity(intent));
-        mActivityTestRule.setActivity(activity);
-
-        CriteriaHelper.pollUiThread(() -> {
-            Criteria.checkThat(mActivityMonitor.getHits(), Matchers.is(1));
-        }, ScalableTimeout.scaleTimeout(10000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
-        ApplicationTestUtils.waitForActivityState(activity, Stage.STOPPED);
-    }
-
-    @Test
-    @SmallTest
-    public void testRedirectionFromIntentWarm() throws Exception {
-        Context context = ContextUtils.getApplicationContext();
-        mActivityTestRule.startMainActivityOnBlankPage();
-        Intent intent = new Intent(Intent.ACTION_VIEW,
-                Uri.parse(mTestServer.getURL(NAVIGATION_FROM_JAVA_REDIRECTION_PAGE)));
-        intent.setClassName(context, ChromeLauncherActivity.class.getName());
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        context.startActivity(intent);
+        InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
 
         CriteriaHelper.pollUiThread(
                 () -> Criteria.checkThat(mActivityMonitor.getHits(), Matchers.is(1)));
+
+        // Test warm start.
+        mActivityTestRule.startMainActivityOnBlankPage();
+        targetContext.startActivity(intent);
+
+        CriteriaHelper.pollUiThread(
+                () -> Criteria.checkThat(mActivityMonitor.getHits(), Matchers.is(2)));
     }
 
     @Test
@@ -506,6 +489,7 @@
         String originalUrl = mTestServer.getURL(NAVIGATION_TO_FILE_SCHEME_FROM_INTENT_URI);
         loadUrlAndWaitForIntentUrl(originalUrl, true, false, false, null, false, "null_scheme");
     }
+
     @Test
     @LargeTest
     public void testIntentURIWithEmptySchemeDoesNothing() throws TimeoutException {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java
index 3579486..7934a16 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java
@@ -16,7 +16,6 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ActivityState;
@@ -65,16 +64,12 @@
 
     private EmbeddedTestServer mTestServer;
 
+    @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    @Rule
     public SettingsActivityTestRule<HomepageSettings> mSettingsActivityTestRule =
             new SettingsActivityTestRule<>(HomepageSettings.class);
 
-    // SettingsActivity has to be finished before the outer CTA can be finished or trying to finish
-    // CTA won't work.
-    @Rule
-    public final RuleChain mRuleChain =
-            RuleChain.outerRule(mActivityTestRule).around(mSettingsActivityTestRule);
-
     @Rule
     public HomepageTestRule mHomepageTestRule = new HomepageTestRule();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java
index a273db8..703521dd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java
@@ -14,7 +14,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.UserActionTester;
@@ -105,8 +104,9 @@
         Assert.assertNotNull("Custom URI radio button is null.", mCustomUriRadioButton);
     }
 
-    private void finishSettingsActivity() throws Exception {
-        ApplicationTestUtils.finishActivity(mTestRule.getActivity());
+    private void finishSettingsActivity() {
+        mTestRule.getActivity().finish();
+        mTestRule.waitTillActivityIsDestroyed();
     }
 
     @Test
@@ -394,7 +394,7 @@
     @Test
     @SmallTest
     @Feature({"Homepage"})
-    public void testCheckRadioButtons() throws Exception {
+    public void testCheckRadioButtons() {
         mHomepageTestRule.useCustomizedHomepageForTest(TEST_URL_FOO);
         launchSettingsActivity();
         LocationChangedCounter counter = new LocationChangedCounter();
@@ -446,7 +446,7 @@
     @Test
     @SmallTest
     @Feature({"Homepage"})
-    public void testChangeCustomized() throws Exception {
+    public void testChangeCustomized() {
         mHomepageTestRule.useChromeNTPForTest();
         launchSettingsActivity();
         LocationChangedCounter actionCounter = new LocationChangedCounter();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
index 98254843..5c0af74 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
@@ -218,7 +218,7 @@
             // mSlowPage will hang for 2 seconds before sending a response. It should be enough to
             // put Chrome in background before the page is committed.
             mTabbedActivityTestRule.prepareUrlIntent(intent, mSlowPage);
-            mTabbedActivityTestRule.launchActivity(intent);
+            mTabbedActivityTestRule.startActivityCompletely(intent);
 
             // Put Chrome in background before the page is committed.
             ChromeApplicationTestUtils.fireHomeScreenIntent(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java
index 8c2cbab..cdbf6b55 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java
@@ -250,7 +250,6 @@
 
         // Offline indicator should not be shown.
         checkOfflineIndicatorVisibility(downloadActivity, false);
-        downloadActivity.finish();
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java
index 012b466..4485b232 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java
@@ -13,8 +13,6 @@
 
 import static org.chromium.chrome.browser.preferences.ChromePreferenceKeys.ASSISTANT_VOICE_SEARCH_ENABLED;
 
-import android.support.test.runner.lifecycle.Stage;
-
 import androidx.test.filters.MediumTest;
 
 import org.junit.After;
@@ -28,7 +26,6 @@
 import org.mockito.junit.MockitoRule;
 
 import org.chromium.base.Callback;
-import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
@@ -36,7 +33,6 @@
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
-import org.chromium.chrome.browser.settings.SettingsActivity;
 import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
@@ -134,19 +130,16 @@
     public void testDialogInteractivity_LearnMoreButton() {
         showConsentUi();
 
-        SettingsActivity activity = ApplicationTestUtils.waitForActivityWithClass(
-                SettingsActivity.class, Stage.RESUMED, () -> {
-                    ClickUtils.clickButton(
-                            mAssistantVoiceSearchConsentUi.getContentView().findViewById(
-                                    R.id.avs_consent_ui_learn_more));
-                    mBottomSheetTestSupport.endAllAnimations();
-                });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ClickUtils.clickButton(mAssistantVoiceSearchConsentUi.getContentView().findViewById(
+                    R.id.avs_consent_ui_learn_more));
+            mBottomSheetTestSupport.endAllAnimations();
+        });
 
         onView(withText(mActivityTestRule.getActivity().getResources().getString(
                        R.string.avs_setting_category_title)))
                 .check(matches(isDisplayed()));
         Mockito.verify(mCallback, Mockito.times(0)).onResult(/* meaningless value */ true);
-        activity.finish();
     }
 
     @Test
@@ -163,4 +156,4 @@
         });
         Mockito.verify(mCallback, Mockito.timeout(1000)).onResult(false);
     }
-}
+}
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
index 419276e..61515dd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
@@ -108,7 +108,7 @@
 
     private void loadUrlAndOpenPageInfo(String url) {
         mActivityTestRule.loadUrl(url);
-        onViewWaiting(allOf(withId(R.id.location_bar_status_icon), isDisplayed())).perform(click());
+        onView(withId(R.id.location_bar_status_icon)).perform(click());
     }
 
     private View getPageInfoView() {
@@ -187,6 +187,8 @@
         // Choose a fixed, "random" port to create stable screenshots.
         mTestServerRule.setServerPort(424242);
         mTestServerRule.setServerUsesHttps(true);
+
+        mActivityTestRule.startMainActivityOnBlankPage();
     }
 
     @After
@@ -210,7 +212,6 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowOnInsecureHttpWebsite() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         mTestServerRule.setServerUsesHttps(false);
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_HttpWebsite");
@@ -224,7 +225,6 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowOnSecureWebsite() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_SecureWebsite");
     }
@@ -238,7 +238,6 @@
     @DisabledTest(message = "https://crbug.com/1133770")
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowOnExpiredCertificateWebsite() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         mTestServerRule.setCertificateType(ServerCertificate.CERT_EXPIRED);
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_ExpiredCertWebsite");
@@ -252,7 +251,6 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testChromePage() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         loadUrlAndOpenPageInfo("chrome://version/");
         mRenderTestRule.render(getPageInfoView(), "PageInfo_InternalSite");
     }
@@ -266,7 +264,6 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowWithPermissions() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         mIsSystemLocationSettingEnabled = false;
         addSomePermissions(mTestServerRule.getServer().getURL("/"));
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
@@ -281,7 +278,6 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowWithCookieBlocking() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         setThirdPartyCookieBlocking(CookieControlsMode.BLOCK_THIRD_PARTY);
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_CookieBlocking");
@@ -295,7 +291,6 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowWithPermissionsAndCookieBlocking() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         addSomePermissions(mTestServerRule.getServer().getURL("/"));
         setThirdPartyCookieBlocking(CookieControlsMode.BLOCK_THIRD_PARTY);
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
@@ -310,7 +305,6 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowWithDefaultSettingPermissions() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         addDefaultSettingPermissions(mTestServerRule.getServer().getURL("/"));
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_DefaultSettingPermissions");
@@ -324,7 +318,6 @@
     @Feature({"RenderTest"})
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowOnSecureWebsiteV2() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_SecureWebsiteV2");
     }
@@ -353,7 +346,6 @@
     @Feature({"RenderTest"})
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowConnectionInfoSubpage() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         onView(withId(R.id.page_info_connection_row)).perform(click());
         mRenderTestRule.render(getPageInfoView(), "PageInfo_ConnectionInfoSubpage");
@@ -367,7 +359,6 @@
     @Feature({"RenderTest"})
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowPermissionsSubpage() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         addSomePermissions(mTestServerRule.getServer().getURL("/"));
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         onView(withId(R.id.page_info_permissions_row)).perform(click());
@@ -382,7 +373,6 @@
     @Feature({"RenderTest"})
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowCookiesSubpage() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         setThirdPartyCookieBlocking(CookieControlsMode.BLOCK_THIRD_PARTY);
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         onView(withId(R.id.page_info_cookies_row)).perform(click());
@@ -397,7 +387,6 @@
     @MediumTest
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testNoPermissionsSubpage() throws IOException {
-        mActivityTestRule.startMainActivityOnBlankPage();
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         View dialog = (View) getPageInfoView().getParent();
         onView(withId(R.id.page_info_permissions_row))
@@ -412,7 +401,6 @@
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     @FlakyTest(message = "https://crbug.com/1147236")
     public void testClearCookiesOnSubpage() throws Exception {
-        mActivityTestRule.startMainActivityOnBlankPage();
         mActivityTestRule.loadUrl(mTestServerRule.getServer().getURL(sSiteDataHtml));
         // Create cookies.
         expectHasCookies(false);
@@ -438,7 +426,6 @@
     @MediumTest
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testResetPermissionsOnSubpage() throws Exception {
-        mActivityTestRule.startMainActivityOnBlankPage();
         mActivityTestRule.loadUrl(mTestServerRule.getServer().getURL(sSiteDataHtml));
         String url = mTestServerRule.getServer().getURL("/");
         // Create permissions.
@@ -466,7 +453,6 @@
     @MediumTest
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testPaintPreview() {
-        mActivityTestRule.startMainActivityOnBlankPage();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             final ChromeActivity activity = mActivityTestRule.getActivity();
             final Tab tab = activity.getActivityTab();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
index 2c90ae8..3b5375ab2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
@@ -1245,7 +1245,6 @@
         @Override
         public void create(PaymentAppFactoryDelegate delegate) {
             Runnable createApp = () -> {
-                if (delegate.getParams().hasClosed()) return;
                 boolean canMakePayment =
                         delegate.getParams().getMethodData().containsKey(mAppMethodName);
                 delegate.onCanMakePaymentCalculated(canMakePayment);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
index 947221f..aa1888d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
@@ -15,7 +15,6 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
-import android.support.test.InstrumentationRegistry;
 import android.text.TextUtils;
 import android.view.View;
 
@@ -102,7 +101,7 @@
     private final SyncTestRule mSyncTestRule = new SyncTestRule();
 
     private final SettingsActivityTestRule<MainSettings> mSettingsActivityTestRule =
-            new SettingsActivityTestRule<>(MainSettings.class);
+            new SettingsActivityTestRule<>(MainSettings.class, true);
 
     // SettingsActivity needs to be initialized and destroyed with the mock
     // signin environment setup in SyncTestRule
@@ -134,7 +133,6 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        InstrumentationRegistry.getInstrumentation().setInTouchMode(true);
         PasswordCheckFactory.setPasswordCheckForTesting(mPasswordCheck);
         SigninActivityLauncherImpl.setLauncherForTest(mMockSigninActivityLauncherImpl);
         DeveloperSettings.setIsEnabledForTests(true);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetRenderTest.java
index 6783423..516bc1db0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetRenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetRenderTest.java
@@ -22,6 +22,7 @@
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.filters.MediumTest;
 
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -33,6 +34,7 @@
 import org.chromium.base.Callback;
 import org.chromium.base.test.params.ParameterAnnotations;
 import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
@@ -118,6 +120,11 @@
         mActivityTestRule.startMainActivityOnBlankPage();
     }
 
+    @After
+    public void tearDown() throws Exception {
+        ApplicationTestUtils.finishActivity(mActivityTestRule.getActivity());
+    }
+
     @AfterClass
     public static void tearDownAfterActivityDestroyed() {
         ChromeNightModeTestUtils.tearDownNightModeAfterChromeActivityDestroyed();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java
index 5383798..16095c4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java
@@ -100,7 +100,7 @@
     @Test
     @SmallTest
     public void testLaunchActivity() {
-        startManageSpaceActivity().finish();
+        startManageSpaceActivity();
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java
index d0a1c47..b42cd0d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java
@@ -18,7 +18,6 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
@@ -41,18 +40,14 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class AccountManagementFragmentTest {
+    @Rule
     public final SettingsActivityTestRule<AccountManagementFragment> mSettingsActivityTestRule =
             new SettingsActivityTestRule<>(AccountManagementFragment.class);
 
+    @Rule
     public final ChromeTabbedActivityTestRule mActivityTestRule =
             new ChromeTabbedActivityTestRule();
 
-    // SettingsActivity has to be finished before the outer CTA can be finished or trying to finish
-    // CTA won't work.
-    @Rule
-    public final RuleChain mRuleChain =
-            RuleChain.outerRule(mActivityTestRule).around(mSettingsActivityTestRule);
-
     @Rule
     public final AccountManagerTestRule mAccountManagerTestRule = new AccountManagerTestRule();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java
index 9b38421..dc9d76a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java
@@ -16,7 +16,6 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
@@ -52,17 +51,13 @@
     @Rule
     public final AccountManagerTestRule mAccountManagerTestRule = new AccountManagerTestRule();
 
+    @Rule
     public final ChromeTabbedActivityTestRule mActivityTestRule =
             new ChromeTabbedActivityTestRule();
 
-    public final SettingsActivityTestRule<GoogleServicesSettings> mSettingsActivityTestRule =
-            new SettingsActivityTestRule<>(GoogleServicesSettings.class);
-
-    // SettingsActivity has to be finished before the outer CTA can be finished or trying to finish
-    // CTA won't work.
     @Rule
-    public final RuleChain mRuleChain =
-            RuleChain.outerRule(mActivityTestRule).around(mSettingsActivityTestRule);
+    public final SettingsActivityTestRule<GoogleServicesSettings> mSettingsActivityTestRule =
+            new SettingsActivityTestRule<>(GoogleServicesSettings.class, true);
 
     @Before
     public void setUp() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
index 2acd095..9c90fe6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
@@ -90,7 +90,7 @@
     private final SyncTestRule mSyncTestRule = new SyncTestRule();
 
     private final SettingsActivityTestRule<ManageSyncSettings> mSettingsActivityTestRule =
-            new SettingsActivityTestRule<>(ManageSyncSettings.class);
+            new SettingsActivityTestRule<>(ManageSyncSettings.class, true);
 
     // SettingsActivity needs to be initialized and destroyed with the mock
     // signin environment setup in SyncTestRule
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java
index 559a75f..76da0b0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java
@@ -62,7 +62,7 @@
 
     @Rule
     public final SettingsActivityTestRule<ManageSyncSettings> mSettingsActivityTestRule =
-            new SettingsActivityTestRule<>(ManageSyncSettings.class);
+            new SettingsActivityTestRule<>(ManageSyncSettings.class, true);
 
     @Rule
     public final ChromeRenderTestRule mRenderTestRule =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestRule.java
index 458cdfa..cfd5d948 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestRule.java
@@ -4,12 +4,6 @@
 
 package org.chromium.chrome.browser.sync;
 
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withText;
-
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -19,7 +13,6 @@
 
 import androidx.annotation.Nullable;
 import androidx.preference.TwoStatePreference;
-import androidx.test.espresso.contrib.RecyclerViewActions;
 
 import org.junit.Assert;
 import org.junit.runner.Description;
@@ -38,7 +31,6 @@
 import org.chromium.chrome.test.util.browser.signin.AccountManagerTestRule;
 import org.chromium.chrome.test.util.browser.signin.SigninTestUtil;
 import org.chromium.chrome.test.util.browser.sync.SyncTestUtil;
-import org.chromium.components.browser_ui.widget.R;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.sync.ModelType;
 import org.chromium.components.sync.protocol.AutofillWalletSpecifics;
@@ -418,9 +410,12 @@
 
     // UI interaction convenience methods.
     public void togglePreference(final TwoStatePreference pref) {
-        onView(withId(R.id.recycler_view))
-                .perform(RecyclerViewActions.actionOnItem(
-                        hasDescendant(withText(pref.getTitle().toString())), click()));
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            boolean newValue = !pref.isChecked();
+            pref.getOnPreferenceChangeListener().onPreferenceChange(pref, newValue);
+            pref.setChecked(newValue);
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/LevelDBPersistedTabDataStorageFactoryTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/LevelDBPersistedTabDataStorageFactoryTest.java
index c715a63..2611d9a5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/LevelDBPersistedTabDataStorageFactoryTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/LevelDBPersistedTabDataStorageFactoryTest.java
@@ -64,11 +64,9 @@
         LevelDBPersistedTabDataStorage.setSkipNativeAssertionsForTesting(true);
     }
 
-    @UiThreadTest
     @SmallTest
     @Test
     public void testFactoryMethod() {
-        Profile realProfile = Profile.getLastUsedRegularProfile();
         LevelDBPersistedTabDataStorageFactory factory = new LevelDBPersistedTabDataStorageFactory();
         Profile.setLastUsedProfileForTesting(mProfile1);
         LevelDBPersistedTabDataStorage profile1Storage = factory.create();
@@ -78,8 +76,6 @@
         LevelDBPersistedTabDataStorage profile1StorageAgain = factory.create();
         Assert.assertEquals(profile1Storage, profile1StorageAgain);
         Assert.assertNotEquals(profile1Storage, profile2Storage);
-        // Restore the original profile so the Activity can shut down correctly.
-        Profile.setLastUsedProfileForTesting(realProfile);
     }
 
     @UiThreadTest
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java
index cf1a94e0..12907e88 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java
@@ -597,7 +597,7 @@
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         mActivityTestRule.prepareUrlIntent(intent, url);
         Assert.assertFalse(mInflated.get());
-        mActivityTestRule.launchActivity(intent);
+        mActivityTestRule.startActivityCompletely(intent);
         if (mUseInstantStart) {
             CriteriaHelper.pollUiThread(mInflated::get);
         } else {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java
index fc5fc02..33af832 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java
@@ -263,7 +263,7 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
         filter.addDataScheme("https");
         final ActivityMonitor monitor =
-                InstrumentationRegistry.getInstrumentation().addMonitor(filter, null, true);
+                InstrumentationRegistry.getInstrumentation().addMonitor(filter, null, false);
 
         RevampedContextMenuUtils.selectContextMenuItem(InstrumentationRegistry.getInstrumentation(),
                 null /* activity to check for focus after click */,
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index f6e4c82..02a736c 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-89.0.4336.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-89.0.4337.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index fccea13..0a4b3ed 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1411,6 +1411,8 @@
     "previews/previews_ui_tab_helper.h",
     "previews/resource_loading_hints/resource_loading_hints_web_contents_observer.cc",
     "previews/resource_loading_hints/resource_loading_hints_web_contents_observer.h",
+    "privacy_sandbox/privacy_sandbox_prefs.cc",
+    "privacy_sandbox/privacy_sandbox_prefs.h",
     "process_resource_usage.cc",
     "process_resource_usage.h",
     "process_singleton.h",
diff --git a/chrome/browser/android/autofill_assistant/ui_controller_android.cc b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
index ed3119a..334bf20c 100644
--- a/chrome/browser/android/autofill_assistant/ui_controller_android.cc
+++ b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
@@ -112,12 +112,18 @@
       jsublabel_accessibility_hint = ConvertUTF8ToJavaString(
           env, login_choice.sublabel_accessibility_hint.value());
     }
+    base::android::ScopedJavaLocalRef<jstring>
+        jedit_button_content_description = nullptr;
+    if (login_choice.edit_button_content_description.has_value()) {
+      jedit_button_content_description = base::android::ConvertUTF8ToJavaString(
+          env, login_choice.edit_button_content_description.value());
+    }
     Java_AssistantCollectUserDataModel_addLoginChoice(
         env, jlist, ConvertUTF8ToJavaString(env, login_choice.identifier),
         ConvertUTF8ToJavaString(env, login_choice.label),
         ConvertUTF8ToJavaString(env, login_choice.sublabel),
         jsublabel_accessibility_hint, login_choice.preselect_priority,
-        jinfo_popup);
+        jinfo_popup, jedit_button_content_description);
   }
   return jlist;
 }
diff --git a/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc b/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc
index 68aa455..36956c1 100644
--- a/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc
+++ b/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc
@@ -257,7 +257,7 @@
 
   watcher_ = std::make_unique<base::FilePathWatcher>();
   // On Linux, base::FilePathWatcher::Watch() always returns true.
-  watcher_->Watch(cros_dir_, true,
+  watcher_->Watch(cros_dir_, base::FilePathWatcher::Type::kRecursive,
                   base::BindRepeating(&FileSystemWatcher::OnFilePathChanged,
                                       weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/chrome/browser/chromeos/crosapi/test_mojo_connection_manager_unittest.cc b/chrome/browser/chromeos/crosapi/test_mojo_connection_manager_unittest.cc
index f0535ec6..c8696d1 100644
--- a/chrome/browser/chromeos/crosapi/test_mojo_connection_manager_unittest.cc
+++ b/chrome/browser/chromeos/crosapi/test_mojo_connection_manager_unittest.cc
@@ -100,7 +100,8 @@
   // so this test should NOT depend on the assumption.
   base::FilePathWatcher watcher;
   base::RunLoop run_loop1;
-  watcher.Watch(base::FilePath(socket_path), false,
+  watcher.Watch(base::FilePath(socket_path),
+                base::FilePathWatcher::Type::kNonRecursive,
                 base::BindRepeating(base::BindLambdaForTesting(
                     [&run_loop1](const base::FilePath& path, bool error) {
                       EXPECT_FALSE(error);
diff --git a/chrome/browser/chromeos/file_manager/file_watcher.cc b/chrome/browser/chromeos/file_manager/file_watcher.cc
index 4adea11..47b10b2 100644
--- a/chrome/browser/chromeos/file_manager/file_watcher.cc
+++ b/chrome/browser/chromeos/file_manager/file_watcher.cc
@@ -30,7 +30,8 @@
   DCHECK(!callback.is_null());
 
   std::unique_ptr<base::FilePathWatcher> watcher(new base::FilePathWatcher);
-  if (!watcher->Watch(watch_path, false /* recursive */, callback))
+  if (!watcher->Watch(watch_path, base::FilePathWatcher::Type::kNonRecursive,
+                      callback))
     return nullptr;
 
   return watcher.release();
diff --git a/chrome/browser/chromeos/login/screens/welcome_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/welcome_screen_browsertest.cc
index 4c4e7788..42854758 100644
--- a/chrome/browser/chromeos/login/screens/welcome_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/welcome_screen_browsertest.cc
@@ -377,12 +377,19 @@
       "en-US");
   OobeScreenWaiter(WelcomeView::kScreenId).Wait();
   const std::string locale = "ru";
+  test::LanguageReloadObserver observer(welcome_screen());
   welcome_screen()->SetApplicationLocale(locale);
-  test::OobeJS().TapOnPath({"connect", "welcomeScreen", "welcomeNextButton"});
-  WaitForScreenExit();
+  observer.Wait();
+
   EXPECT_EQ(g_browser_process->local_state()->GetString(
                 language::prefs::kApplicationLocale),
             locale);
+  EXPECT_EQ(g_browser_process->GetApplicationLocale(), locale);
+
+  // We need to proceed otherwise welcome screen would reset language on the
+  // next show.
+  test::OobeJS().TapOnPath({"connect", "welcomeScreen", "welcomeNextButton"});
+  WaitForScreenExit();
 }
 
 IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, SelectedLanguage) {
@@ -390,6 +397,7 @@
   EXPECT_EQ(g_browser_process->local_state()->GetString(
                 language::prefs::kApplicationLocale),
             locale);
+  EXPECT_EQ(g_browser_process->GetApplicationLocale(), locale);
 }
 
 IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, A11yVirtualKeyboard) {
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index 9df1100..9510dc5d 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -1709,9 +1709,11 @@
   if (!GetOobeUI())
     return;
 
-  // Check for tests configuration.
-  if (wizard_context_->skip_to_update_for_tests)
+  // Check for tests configurations.
+  if (wizard_context_->skip_to_update_for_tests ||
+      wizard_context_->skip_to_login_for_tests) {
     return;
+  }
 
   if (screen_needed)
     ShowHIDDetectionScreen();
diff --git a/chrome/browser/chromeos/policy/policy_oauth2_token_fetcher.cc b/chrome/browser/chromeos/policy/policy_oauth2_token_fetcher.cc
index ccccf06..0b976e07 100644
--- a/chrome/browser/chromeos/policy/policy_oauth2_token_fetcher.cc
+++ b/chrome/browser/chromeos/policy/policy_oauth2_token_fetcher.cc
@@ -15,11 +15,11 @@
 #include "chromeos/constants/chromeos_switches.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "google_apis/gaia/gaia_access_token_fetcher.h"
 #include "google_apis/gaia/gaia_auth_fetcher.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "google_apis/gaia/google_service_auth_error.h"
-#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 using content::BrowserThread;
@@ -170,8 +170,9 @@
   std::vector<std::string> scopes;
   scopes.push_back(GaiaConstants::kDeviceManagementServiceOAuth);
   scopes.push_back(GaiaConstants::kOAuthWrapBridgeUserInfoScope);
-  access_token_fetcher_.reset(new OAuth2AccessTokenFetcherImpl(
-      this, system_url_loader_factory_, oauth2_refresh_token_));
+  access_token_fetcher_ =
+      GaiaAccessTokenFetcher::CreateExchangeRefreshTokenForAccessTokenInstance(
+          this, system_url_loader_factory_, oauth2_refresh_token_);
   access_token_fetcher_->Start(
       GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
       GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
diff --git a/chrome/browser/chromeos/policy/upload_job_unittest.cc b/chrome/browser/chromeos/policy/upload_job_unittest.cc
index 07177b8..0632222c 100644
--- a/chrome/browser/chromeos/policy/upload_job_unittest.cc
+++ b/chrome/browser/chromeos/policy/upload_job_unittest.cc
@@ -23,8 +23,8 @@
 #include "content/public/test/browser_task_environment.h"
 #include "google_apis/gaia/core_account_id.h"
 #include "google_apis/gaia/fake_oauth2_access_token_manager.h"
+#include "google_apis/gaia/gaia_access_token_fetcher.h"
 #include "google_apis/gaia/google_service_auth_error.h"
-#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
 #include "google_apis/gaia/oauth2_access_token_manager.h"
 #include "net/http/http_status_code.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -172,8 +172,9 @@
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       OAuth2AccessTokenConsumer* consumer) override {
     EXPECT_EQ(CoreAccountId(kRobotAccountId), account_id);
-    return std::make_unique<OAuth2AccessTokenFetcherImpl>(
-        consumer, url_loader_factory, "fake_refresh_token");
+    return GaiaAccessTokenFetcher::
+        CreateExchangeRefreshTokenForAccessTokenInstance(
+            consumer, url_loader_factory, "fake_refresh_token");
   }
 
   bool HasRefreshToken(const CoreAccountId& account_id) const override {
diff --git a/chrome/browser/chromeos/printing/automatic_usb_printer_configurer.cc b/chrome/browser/chromeos/printing/automatic_usb_printer_configurer.cc
index 3dc2025..86933cf 100644
--- a/chrome/browser/chromeos/printing/automatic_usb_printer_configurer.cc
+++ b/chrome/browser/chromeos/printing/automatic_usb_printer_configurer.cc
@@ -89,6 +89,10 @@
 void AutomaticUsbPrinterConfigurer::OnSetupComplete(const Printer& printer,
                                                     PrinterSetupResult result) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
+  if (result == PrinterSetupResult::kPrinterIsNotAutoconfigurable) {
+    installation_manager_->PrinterIsNotAutoconfigurable(printer);
+    return;
+  }
   if (result != PrinterSetupResult::kSuccess) {
     LOG(ERROR) << "Unable to autoconfigure usb printer " << printer.id();
     return;
diff --git a/chrome/browser/chromeos/printing/automatic_usb_printer_configurer_unittest.cc b/chrome/browser/chromeos/printing/automatic_usb_printer_configurer_unittest.cc
index 5eb7b8c0..8c24442 100644
--- a/chrome/browser/chromeos/printing/automatic_usb_printer_configurer_unittest.cc
+++ b/chrome/browser/chromeos/printing/automatic_usb_printer_configurer_unittest.cc
@@ -104,8 +104,17 @@
     return installed_printers_.contains(printer.id());
   }
 
+  void PrinterIsNotAutoconfigurable(const Printer& printer) override {
+    printers_marked_as_not_autoconf_.insert(printer.id());
+  }
+
+  bool IsMarkedAsNotAutoconfigurable(const Printer& printer) {
+    return printers_marked_as_not_autoconf_.contains(printer.id());
+  }
+
  private:
   base::flat_set<std::string> installed_printers_;
+  base::flat_set<std::string> printers_marked_as_not_autoconf_;
 };
 
 class FakeUsbPrinterNotificationController
@@ -326,4 +335,22 @@
       fake_notification_controller_->IsNotificationDisplayed(printer_id));
 }
 
+TEST_F(AutomaticUsbPrinterConfigurerTest, RegisterAutoconfFailureForIppUsb) {
+  const std::string printer1_id = "id1";
+  const std::string printer2_id = "id2";
+  const Printer printer1 = CreateIppUsbPrinter(printer1_id);
+  const Printer printer2 = CreateIppUsbPrinter(printer2_id);
+
+  fake_printer_configurer_->AssignPrinterSetupResult(
+      printer1_id, PrinterSetupResult::kPrinterIsNotAutoconfigurable);
+
+  fake_observable_printers_manager_.AddNearbyAutomaticPrinter(printer1);
+  fake_observable_printers_manager_.AddNearbyAutomaticPrinter(printer2);
+
+  EXPECT_TRUE(
+      fake_installation_manager_->IsMarkedAsNotAutoconfigurable(printer1));
+  EXPECT_FALSE(
+      fake_installation_manager_->IsMarkedAsNotAutoconfigurable(printer2));
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager.cc b/chrome/browser/chromeos/printing/cups_printers_manager.cc
index bfa6e46..f18c19f 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager.cc
+++ b/chrome/browser/chromeos/printing/cups_printers_manager.cc
@@ -222,6 +222,13 @@
   }
 
   // Public API function.
+  void PrinterIsNotAutoconfigurable(const Printer& printer) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
+    ppd_resolution_tracker_.MarkPrinterAsNotAutoconfigurable(printer.id());
+    RebuildDetectedLists();
+  }
+
+  // Public API function.
   base::Optional<Printer> GetPrinter(const std::string& id) const override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
     if (!user_printers_allowed_.GetValue()) {
@@ -559,37 +566,7 @@
         printers_.Insert(PrinterClass::kAutomatic, detected.printer);
         continue;
       }
-      if (ppd_resolution_tracker_.IsResolutionComplete(detected_printer_id)) {
-        auto printer = detected.printer;
-        if (!ppd_resolution_tracker_.WasResolutionSuccessful(
-                detected_printer_id)) {
-          if (!printer.supports_ippusb()) {
-            // We couldn't figure out this printer, so it's in the discovered
-            // class.
-            if (printer.IsUsbProtocol()) {
-              printer.set_manufacturer(
-                  ppd_resolution_tracker_.GetManufacturer(detected_printer_id));
-            }
-            printers_.Insert(PrinterClass::kDiscovered, printer);
-            continue;
-          }
-          // If the detected printer supports ipp-over-usb and we could not find
-          // a ppd for it, then we switch to the ippusb scheme and mark it as
-          // autoconf.
-          printer.SetUri(
-              Uri(base::StringPrintf("ippusb://%04x_%04x/ipp/print",
-                                     detected.ppd_search_data.usb_vendor_id,
-                                     detected.ppd_search_data.usb_product_id)));
-          printer.mutable_ppd_reference()->autoconf = true;
-          printers_.Insert(PrinterClass::kAutomatic, printer);
-        } else {
-          // We have a ppd reference, so we think we can set this up
-          // automatically.
-          *printer.mutable_ppd_reference() =
-              ppd_resolution_tracker_.GetPpdReference(detected_printer_id);
-          printers_.Insert(PrinterClass::kAutomatic, printer);
-        }
-      } else {
+      if (!ppd_resolution_tracker_.IsResolutionComplete(detected_printer_id)) {
         // Didn't find an entry for this printer in the PpdReferences cache.  We
         // need to ask PpdProvider whether or not it can determine a
         // PpdReference.  If there's not already an outstanding request for one,
@@ -603,7 +580,47 @@
                              weak_ptr_factory_.GetWeakPtr(),
                              detected_printer_id));
         }
+        continue;
       }
+      auto printer = detected.printer;
+      if (ppd_resolution_tracker_.WasResolutionSuccessful(
+              detected_printer_id)) {
+        // We have a ppd reference, so we think we can set this up
+        // automatically.
+        *printer.mutable_ppd_reference() =
+            ppd_resolution_tracker_.GetPpdReference(detected_printer_id);
+        printers_.Insert(PrinterClass::kAutomatic, printer);
+        continue;
+      }
+      if (!printer.supports_ippusb()) {
+        // Detected printer does not supports ipp-over-usb, so we cannot set it
+        // up automatically. We have to move it to the discovered class.
+        if (printer.IsUsbProtocol()) {
+          printer.set_manufacturer(
+              ppd_resolution_tracker_.GetManufacturer(detected_printer_id));
+        }
+        printers_.Insert(PrinterClass::kDiscovered, printer);
+        continue;
+      }
+      // Detected printer supports ipp-over-usb and we could not find a ppd for
+      // it. We can try to set it up automatically (by IPP Everywhere).
+      if (ppd_resolution_tracker_.IsMarkedAsNotAutoconfigurable(
+              detected_printer_id)) {
+        // We have tried to autoconfigure the printer in the past and the
+        // process failed because of the lack of IPP Everywhere support.
+        // The printer must be treated as discovered printer.
+        printer.mutable_ppd_reference()->autoconf = false;
+        printers_.Insert(PrinterClass::kDiscovered, printer);
+        continue;
+      }
+      // We will try to autoconfigure the printer. We have to switch to
+      // the ippusb scheme.
+      printer.SetUri(
+          Uri(base::StringPrintf("ippusb://%04x_%04x/ipp/print",
+                                 detected.ppd_search_data.usb_vendor_id,
+                                 detected.ppd_search_data.usb_product_id)));
+      printer.mutable_ppd_reference()->autoconf = true;
+      printers_.Insert(PrinterClass::kAutomatic, printer);
     }
   }
 
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager.h b/chrome/browser/chromeos/printing/cups_printers_manager.h
index 1afb078..56a5ad77 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager.h
+++ b/chrome/browser/chromeos/printing/cups_printers_manager.h
@@ -103,15 +103,10 @@
   virtual void AddObserver(Observer* observer) = 0;
   virtual void RemoveObserver(Observer* observer) = 0;
 
-  // Record that the given printers has been installed in CUPS for usage.
-  // Parameter |is_automatic| should be set to true if the printer was
-  // saved automatically (without requesting additional information
-  // from the user).
+  // Implementation of PrinterInstallationManager interface.
   void PrinterInstalled(const Printer& printer, bool is_automatic) override = 0;
-
-  // Returns true if |printer| is currently installed in CUPS with this
-  // configuration.
   bool IsPrinterInstalled(const Printer& printer) const override = 0;
+  void PrinterIsNotAutoconfigurable(const Printer& printer) override = 0;
 
   // Look for a printer with the given id in any class.  Returns a copy of the
   // printer if found, base::nullopt if not found.
diff --git a/chrome/browser/chromeos/printing/ppd_resolution_state.cc b/chrome/browser/chromeos/printing/ppd_resolution_state.cc
index a766dd8..5e32ff6 100644
--- a/chrome/browser/chromeos/printing/ppd_resolution_state.cc
+++ b/chrome/browser/chromeos/printing/ppd_resolution_state.cc
@@ -9,7 +9,9 @@
 namespace chromeos {
 
 PpdResolutionState::PpdResolutionState()
-    : is_inflight_(true), is_ppd_resolution_successful_(false) {}
+    : is_inflight_(true),
+      is_ppd_resolution_successful_(false),
+      is_not_autoconfigurable_(false) {}
 PpdResolutionState::PpdResolutionState(PpdResolutionState&& other) = default;
 PpdResolutionState& PpdResolutionState::operator=(PpdResolutionState&& rhs) =
     default;
@@ -58,4 +60,12 @@
   return is_ppd_resolution_successful_;
 }
 
+void PpdResolutionState::MarkPrinterAsNotAutoconfigurable() {
+  is_not_autoconfigurable_ = true;
+}
+
+bool PpdResolutionState::IsMarkedAsNotAutoconfigurable() const {
+  return is_not_autoconfigurable_;
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/ppd_resolution_state.h b/chrome/browser/chromeos/printing/ppd_resolution_state.h
index 1c13d20..d376097 100644
--- a/chrome/browser/chromeos/printing/ppd_resolution_state.h
+++ b/chrome/browser/chromeos/printing/ppd_resolution_state.h
@@ -40,9 +40,17 @@
   // Returns true if a PpdReference was retrieved.
   bool WasResolutionSuccessful() const;
 
+  // Marks the printer as not autoconfigurable. This flag is set after
+  // unsuccessful attempt to configure the printer automatically.
+  void MarkPrinterAsNotAutoconfigurable();
+
+  // Returns true <=> the method above was called for the printer.
+  bool IsMarkedAsNotAutoconfigurable() const;
+
  private:
   bool is_inflight_;
   bool is_ppd_resolution_successful_;
+  bool is_not_autoconfigurable_;
   Printer::PpdReference ppd_reference_;
   std::string usb_manufacturer_;
 
diff --git a/chrome/browser/chromeos/printing/ppd_resolution_tracker.cc b/chrome/browser/chromeos/printing/ppd_resolution_tracker.cc
index c7016a5..d55455d 100644
--- a/chrome/browser/chromeos/printing/ppd_resolution_tracker.cc
+++ b/chrome/browser/chromeos/printing/ppd_resolution_tracker.cc
@@ -90,4 +90,14 @@
   return base::Contains(printer_state_, printer_id);
 }
 
+void PpdResolutionTracker::MarkPrinterAsNotAutoconfigurable(
+    const std::string& printer_id) {
+  printer_state_.at(printer_id).MarkPrinterAsNotAutoconfigurable();
+}
+
+bool PpdResolutionTracker::IsMarkedAsNotAutoconfigurable(
+    const std::string& printer_id) const {
+  return printer_state_.at(printer_id).IsMarkedAsNotAutoconfigurable();
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/ppd_resolution_tracker.h b/chrome/browser/chromeos/printing/ppd_resolution_tracker.h
index ccdd00980..9af996ce 100644
--- a/chrome/browser/chromeos/printing/ppd_resolution_tracker.h
+++ b/chrome/browser/chromeos/printing/ppd_resolution_tracker.h
@@ -55,6 +55,14 @@
   const Printer::PpdReference& GetPpdReference(
       const std::string& printer_id) const;
 
+  // Mark the printer as not autconfigurable. It is set when the configuration
+  // of IPP USB printer fails because the printer does not meet the IPP
+  // Everywhere requirements.
+  void MarkPrinterAsNotAutoconfigurable(const std::string& printer_id);
+
+  // Returns true <=> the function above was called for |printer_id|.
+  bool IsMarkedAsNotAutoconfigurable(const std::string& printer_id) const;
+
  private:
   // Returns true if |printer_id| exists in |printer_state_|.
   bool PrinterStateExists(const std::string& printer_id) const;
diff --git a/chrome/browser/chromeos/printing/printer_installation_manager.h b/chrome/browser/chromeos/printing/printer_installation_manager.h
index c91d6575..c0aee76 100644
--- a/chrome/browser/chromeos/printing/printer_installation_manager.h
+++ b/chrome/browser/chromeos/printing/printer_installation_manager.h
@@ -13,9 +13,7 @@
  public:
   virtual ~PrinterInstallationManager() = default;
 
-  // Record that the given printers has been installed in CUPS for usage.  If
-  // |printer| is not a saved or enterprise printer, this will have the
-  // side effect of moving |printer| into the saved class.
+  // Record that the given printers has been installed in CUPS for usage.
   // Parameter |is_automatic| should be set to true if the printer was
   // saved automatically (without requesting additional information
   // from the user).
@@ -24,6 +22,11 @@
   // Returns true if |printer| is currently installed in CUPS with this
   // configuration.
   virtual bool IsPrinterInstalled(const Printer& printer) const = 0;
+
+  // Record that a requested printer installation failed because the printer
+  // is not autoconfigurable (does not meet IPP Everywhere requirements).
+  // This results in shifting the printer from automatic to discovered class.
+  virtual void PrinterIsNotAutoconfigurable(const Printer& printer) = 0;
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/printing_stubs.h b/chrome/browser/chromeos/printing/printing_stubs.h
index b59dd7c..7f8d61e 100644
--- a/chrome/browser/chromeos/printing/printing_stubs.h
+++ b/chrome/browser/chromeos/printing/printing_stubs.h
@@ -29,6 +29,7 @@
   void AddObserver(CupsPrintersManager::Observer* observer) override {}
   void RemoveObserver(CupsPrintersManager::Observer* observer) override {}
   void PrinterInstalled(const Printer& printer, bool is_automatic) override {}
+  void PrinterIsNotAutoconfigurable(const Printer& printer) override {}
   void RecordSetupAbandoned(const Printer& printer) override {}
   void FetchPrinterStatus(const std::string& printer_id,
                           PrinterStatusCallback cb) override {}
diff --git a/chrome/browser/chromeos/printing/test_printer_configurer.cc b/chrome/browser/chromeos/printing/test_printer_configurer.cc
index 4e5cd99..8f722266 100644
--- a/chrome/browser/chromeos/printing/test_printer_configurer.cc
+++ b/chrome/browser/chromeos/printing/test_printer_configurer.cc
@@ -16,7 +16,11 @@
 void TestPrinterConfigurer::SetUpPrinter(const Printer& printer,
                                          PrinterSetupCallback callback) {
   MarkConfigured(printer.id());
-  std::move(callback).Run(PrinterSetupResult::kSuccess);
+  PrinterSetupResult result = PrinterSetupResult::kSuccess;
+  if (assigned_results_.count(printer.id())) {
+    result = assigned_results_[printer.id()];
+  }
+  std::move(callback).Run(result);
 }
 
 bool TestPrinterConfigurer::IsConfigured(const std::string& printer_id) const {
@@ -27,4 +31,10 @@
   configured_printers_.insert(printer_id);
 }
 
+void TestPrinterConfigurer::AssignPrinterSetupResult(
+    const std::string& printer_id,
+    PrinterSetupResult result) {
+  assigned_results_[printer_id] = result;
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/test_printer_configurer.h b/chrome/browser/chromeos/printing/test_printer_configurer.h
index 475b037..1914d2d 100644
--- a/chrome/browser/chromeos/printing/test_printer_configurer.h
+++ b/chrome/browser/chromeos/printing/test_printer_configurer.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "chrome/browser/chromeos/printing/printer_configurer.h"
 
@@ -33,8 +34,12 @@
 
   void MarkConfigured(const std::string& printer_id);
 
+  void AssignPrinterSetupResult(const std::string& printer_id,
+                                PrinterSetupResult result);
+
  private:
   base::flat_set<std::string> configured_printers_;
+  base::flat_map<std::string, PrinterSetupResult> assigned_results_;
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/tpm_firmware_update.cc b/chrome/browser/chromeos/tpm_firmware_update.cc
index 0a2d075..e177a8f 100644
--- a/chrome/browser/chromeos/tpm_firmware_update.cc
+++ b/chrome/browser/chromeos/tpm_firmware_update.cc
@@ -157,8 +157,8 @@
   static void StartOnBackgroundThread(
       base::FilePathWatcher* watcher,
       base::FilePathWatcher::Callback watch_callback) {
-    watcher->Watch(GetUpdateLocationFilePath(), false /* recursive */,
-                   watch_callback);
+    watcher->Watch(GetUpdateLocationFilePath(),
+                   base::FilePathWatcher::Type::kNonRecursive, watch_callback);
     watch_callback.Run(base::FilePath(), false /* error */);
   }
 
diff --git a/chrome/browser/device_identity/device_oauth2_token_service.cc b/chrome/browser/device_identity/device_oauth2_token_service.cc
index cb3b699..c483d391 100644
--- a/chrome/browser/device_identity/device_oauth2_token_service.cc
+++ b/chrome/browser/device_identity/device_oauth2_token_service.cc
@@ -18,12 +18,12 @@
 #include "chrome/browser/device_identity/device_oauth2_token_store.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "components/prefs/pref_service.h"
+#include "google_apis/gaia/gaia_access_token_fetcher.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "google_apis/gaia/oauth2_access_token_consumer.h"
 #include "google_apis/gaia/oauth2_access_token_fetcher.h"
-#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 struct DeviceOAuth2TokenService::PendingRequest {
@@ -229,8 +229,9 @@
     OAuth2AccessTokenConsumer* consumer) {
   std::string refresh_token = GetRefreshToken();
   DCHECK(!refresh_token.empty());
-  return std::make_unique<OAuth2AccessTokenFetcherImpl>(
-      consumer, url_loader_factory, refresh_token);
+  return GaiaAccessTokenFetcher::
+      CreateExchangeRefreshTokenForAccessTokenInstance(
+          consumer, url_loader_factory, refresh_token);
 }
 
 bool DeviceOAuth2TokenService::HasRefreshToken(
diff --git a/chrome/browser/devtools/devtools_file_watcher.cc b/chrome/browser/devtools/devtools_file_watcher.cc
index 5203156..054985a 100644
--- a/chrome/browser/devtools/devtools_file_watcher.cc
+++ b/chrome/browser/devtools/devtools_file_watcher.cc
@@ -131,7 +131,7 @@
     return;
   watchers_[path].reset(new base::FilePathWatcher());
   bool success = watchers_[path]->Watch(
-      path, true,
+      path, base::FilePathWatcher::Type::kRecursive,
       base::Bind(&SharedFileWatcher::DirectoryChanged, base::Unretained(this)));
   if (!success)
     return;
diff --git a/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc b/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc
index d40d576..be45c03 100644
--- a/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc
+++ b/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc
@@ -189,7 +189,7 @@
                                                 base::BlockingType::MAY_BLOCK);
   base::FilePath brlapi_dir(BRLAPI_SOCKETPATH);
   if (!file_path_watcher_.Watch(
-          brlapi_dir, false,
+          brlapi_dir, base::FilePathWatcher::Type::kNonRecursive,
           base::Bind(&BrailleControllerImpl::OnSocketDirChangedOnTaskThread,
                      base::Unretained(this)))) {
     LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH;
diff --git a/chrome/browser/media_galleries/gallery_watch_manager.cc b/chrome/browser/media_galleries/gallery_watch_manager.cc
index 696f68e..f6ae5b9 100644
--- a/chrome/browser/media_galleries/gallery_watch_manager.cc
+++ b/chrome/browser/media_galleries/gallery_watch_manager.cc
@@ -127,8 +127,7 @@
   }
 
   auto watcher = std::make_unique<base::FilePathWatcher>();
-  bool success = watcher->Watch(path,
-                                true /*recursive*/,
+  bool success = watcher->Watch(path, base::FilePathWatcher::Type::kRecursive,
                                 base::Bind(&FileWatchManager::OnFilePathChanged,
                                            weak_factory_.GetWeakPtr()));
 
diff --git a/chrome/browser/metrics/startup_metrics_browsertest.cc b/chrome/browser/metrics/startup_metrics_browsertest.cc
index 80f46c15..4edaf52 100644
--- a/chrome/browser/metrics/startup_metrics_browsertest.cc
+++ b/chrome/browser/metrics/startup_metrics_browsertest.cc
@@ -4,9 +4,11 @@
 
 #include <set>
 
+#include "base/metrics/histogram_base.h"
 #include "base/metrics/histogram_samples.h"
 #include "base/metrics/statistics_recorder.h"
 #include "base/run_loop.h"
+#include "base/test/bind.h"
 #include "build/build_config.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/startup_metric_utils/browser/startup_metric_utils.h"
@@ -45,7 +47,18 @@
   // Wait for all histograms to be recorded. The test will hang if an histogram
   // is not recorded.
   for (auto* const histogram : kStartupMetrics) {
-    while (!base::StatisticsRecorder::FindHistogram(histogram))
-      base::RunLoop().RunUntilIdle();
+    // Continue if histograms was already recorded.
+    if (base::StatisticsRecorder::FindHistogram(histogram))
+      continue;
+
+    // Else, wait until the histogram is recorded.
+    base::RunLoop run_loop;
+    base::StatisticsRecorder::SetCallback(
+        histogram,
+        base::BindLambdaForTesting(
+            [&](const char* histogram_name, uint64_t name_hash,
+                base::HistogramBase::Sample sample) { run_loop.Quit(); }));
+    run_loop.Run();
+    base::StatisticsRecorder::ClearCallback(histogram);
   }
 }
diff --git a/chrome/browser/notifications/notification_platform_bridge_linux.cc b/chrome/browser/notifications/notification_platform_bridge_linux.cc
index bdea464..855850a 100644
--- a/chrome/browser/notifications/notification_platform_bridge_linux.cc
+++ b/chrome/browser/notifications/notification_platform_bridge_linux.cc
@@ -1015,7 +1015,8 @@
     // long-running Chrome process.
     product_logo_file_watcher_ = std::make_unique<base::FilePathWatcher>();
     if (!product_logo_file_watcher_->Watch(
-            product_logo_file_->file_path(), false,
+            product_logo_file_->file_path(),
+            base::FilePathWatcher::Type::kNonRecursive,
             base::Bind(
                 &NotificationPlatformBridgeLinuxImpl::OnProductLogoFileChanged,
                 this))) {
diff --git a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java
index cb72f9c..0e65d8c 100644
--- a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java
+++ b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java
@@ -99,8 +99,6 @@
         activity.getWindow().setLocalFocus(true, true);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             InstrumentationRegistry.getInstrumentation().callActivityOnRestart(activity);
-            InstrumentationRegistry.getInstrumentation().callActivityOnStart(activity);
-            InstrumentationRegistry.getInstrumentation().callActivityOnResume(activity);
         });
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index fd694ae..33ab250 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -59,6 +59,7 @@
 #include "chrome/browser/prefs/session_startup_pref.h"
 #include "chrome/browser/previews/previews_https_notification_infobar_decider.h"
 #include "chrome/browser/printing/print_preview_sticky_settings.h"
+#include "chrome/browser/privacy_sandbox/privacy_sandbox_prefs.h"
 #include "chrome/browser/profiles/chrome_version_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
@@ -827,6 +828,7 @@
   PrefetchProxyOriginDecider::RegisterPrefs(registry);
   PrefsTabHelper::RegisterProfilePrefs(registry, locale);
   PreviewsHTTPSNotificationInfoBarDecider::RegisterProfilePrefs(registry);
+  privacy_sandbox::RegisterProfilePrefs(registry);
   Profile::RegisterProfilePrefs(registry);
   ProfileImpl::RegisterProfilePrefs(registry);
   ProfileNetworkContextService::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_prefs.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_prefs.cc
new file mode 100644
index 0000000..f4e7273
--- /dev/null
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_prefs.cc
@@ -0,0 +1,28 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/privacy_sandbox/privacy_sandbox_prefs.h"
+
+#include "chrome/common/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace prefs {
+
+const char kPrivacySandboxApisEnabled[] = "privacy_sandbox.apis_enabled";
+
+const char kPrivacySandboxManauallyControlled[] =
+    "privacy_sandbox.manually_controlled";
+
+}  // namespace prefs
+
+namespace privacy_sandbox {
+
+void RegisterProfilePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterBooleanPref(prefs::kPrivacySandboxApisEnabled, false);
+  registry->RegisterBooleanPref(prefs::kPrivacySandboxManauallyControlled,
+                                false);
+}
+}  // namespace privacy_sandbox
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_prefs.h b/chrome/browser/privacy_sandbox/privacy_sandbox_prefs.h
new file mode 100644
index 0000000..60cc44b8
--- /dev/null
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_prefs.h
@@ -0,0 +1,31 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PRIVACY_SANDBOX_PRIVACY_SANDBOX_PREFS_H_
+#define CHROME_BROWSER_PRIVACY_SANDBOX_PRIVACY_SANDBOX_PREFS_H_
+
+#include "components/prefs/pref_registry_simple.h"
+
+namespace prefs {
+
+// Boolean that is true when Privacy Sandbox APIs are enabled. If the
+// PrivacySandboxSettings feature is enabled, this Boolean is controlled by the
+// associated UI; if it is disabled, it is controlled by third party cookie
+// blocking settings.
+extern const char kPrivacySandboxApisEnabled[];
+
+// Boolean that indicates if a user has manually toggled the settings associated
+// with the PrivacySandboxSettings feature.
+extern const char kPrivacySandboxManauallyControlled[];
+
+}  // namespace prefs
+
+namespace privacy_sandbox {
+
+// Registers user preferences related to privacy sandbox.
+void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
+}  // namespace privacy_sandbox
+
+#endif  // CHROME_BROWSER_PRIVACY_SANDBOX_PRIVACY_SANDBOX_PREFS_H_
diff --git a/chrome/browser/profiles/profile_browsertest_android.cc b/chrome/browser/profiles/profile_browsertest_android.cc
index aa46156..c31fa92 100644
--- a/chrome/browser/profiles/profile_browsertest_android.cc
+++ b/chrome/browser/profiles/profile_browsertest_android.cc
@@ -95,7 +95,7 @@
     // destroyed between the existence check and when we start watching, if the
     // order were reversed.
     EXPECT_TRUE(watcher_->Watch(
-        watched_file_path_, false /* recursive */,
+        watched_file_path_, base::FilePathWatcher::Type::kNonRecursive,
         base::BindRepeating(&FileDestructionWatcher::OnPathChanged,
                             base::Unretained(this))));
     CheckIfPathExists();
diff --git a/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsActivityTestRule.java b/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsActivityTestRule.java
index 22bcb58..250dbd2e 100644
--- a/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsActivityTestRule.java
+++ b/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsActivityTestRule.java
@@ -8,15 +8,17 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.lifecycle.Stage;
+import android.support.test.rule.ActivityTestRule;
 
 import androidx.fragment.app.Fragment;
 
+import org.hamcrest.Matchers;
 import org.junit.Assert;
 
-import org.chromium.base.test.BaseActivityTestRule;
-import org.chromium.base.test.util.ApplicationTestUtils;
-
+import org.chromium.base.ActivityState;
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
 /**
  * Activity test rule that launch {@link SettingsActivity} in tests.
  *
@@ -26,7 +28,7 @@
  * @param <T> Fragment that will be attached to the SettingsActivity.
  */
 public class SettingsActivityTestRule<T extends Fragment>
-        extends BaseActivityTestRule<SettingsActivity> {
+        extends ActivityTestRule<SettingsActivity> {
     private final Class<T> mFragmentClass;
 
     /**
@@ -34,7 +36,16 @@
      * @param fragmentClass Fragment that will be attached after the activity starts.
      */
     public SettingsActivityTestRule(Class<T> fragmentClass) {
-        super(SettingsActivity.class);
+        this(fragmentClass, false);
+    }
+
+    /**
+     * Create the settings activity test rule with an specific fragment class.
+     * @param fragmentClass Fragment that will be attached after the activity starts.
+     * @param initialTouchMode Whether in touch mode after the activity starts.
+     */
+    public SettingsActivityTestRule(Class<T> fragmentClass, boolean initialTouchMode) {
+        super(SettingsActivity.class, initialTouchMode, false);
         mFragmentClass = fragmentClass;
     }
 
@@ -56,9 +67,35 @@
         SettingsLauncher settingsLauncher = new SettingsLauncherImpl();
         Intent intent = settingsLauncher.createSettingsActivityIntent(
                 context, mFragmentClass.getName(), fragmentArgs);
-        launchActivity(intent);
-        ApplicationTestUtils.waitForActivityState(getActivity(), Stage.RESUMED);
-        return getActivity();
+        SettingsActivity activity = super.launchActivity(intent);
+        Assert.assertNotNull(activity);
+
+        return activity;
+    }
+
+    /**
+     * We need to ensure that SettingsActivity gets destroyed in the TestRule because sometimes
+     * it uses the mock signin environment like fake AccountManagerFacade, if the activity starts
+     * with the stub then it also needs to finish with it. That's why we need to wait till the
+     * activity state becomes destroyed before tearing down the mock signin environment.
+     */
+    @Override
+    protected void afterActivityFinished() {
+        super.afterActivityFinished();
+        waitTillActivityIsDestroyed();
+    }
+
+    /**
+     * Block the execution till the SettingsActivity is destroyed.
+     */
+    public void waitTillActivityIsDestroyed() {
+        SettingsActivity activity = getActivity();
+        if (activity != null) {
+            CriteriaHelper.pollUiThread(() -> {
+                Criteria.checkThat(ApplicationStatus.getStateForActivity(activity),
+                        Matchers.is(ActivityState.DESTROYED));
+            });
+        }
     }
 
     /**
diff --git a/chrome/browser/signin/dice_intercepted_session_startup_helper.cc b/chrome/browser/signin/dice_intercepted_session_startup_helper.cc
index 60be08c..b4e28848 100644
--- a/chrome/browser/signin/dice_intercepted_session_startup_helper.cc
+++ b/chrome/browser/signin/dice_intercepted_session_startup_helper.cc
@@ -16,11 +16,13 @@
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
+#include "chrome/common/webui_url_constants.h"
 #include "components/signin/public/identity_manager/accounts_cookie_mutator.h"
 #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
 #include "content/public/browser/web_contents.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/google_service_auth_error.h"
+#include "url/gurl.h"
 
 namespace {
 
@@ -43,8 +45,6 @@
     content::WebContents* tab_to_move)
     : profile_(profile), account_id_(account_id) {
   Observe(tab_to_move);
-  if (tab_to_move)
-    url_to_open_ = tab_to_move->GetURL();
 }
 
 DiceInterceptedSessionStartupHelper::~DiceInterceptedSessionStartupHelper() =
@@ -127,16 +127,15 @@
   base::UmaHistogramBoolean("Signin.Intercept.SessionStartupReconcileError",
                             reconcile_error_encountered_);
 
+  GURL url_to_open = GURL(chrome::kChromeUINewTabURL);
   // If the intercepted web contents is still alive, close it now.
   if (web_contents()) {
-    // Update the URL once again to catch any potential navigation happening
-    // while the cookie was updated.
-    url_to_open_ = web_contents()->GetURL();
+    url_to_open = web_contents()->GetURL();
     web_contents()->Close();
   }
 
   // Open a new browser.
-  NavigateParams params(profile_, url_to_open_,
+  NavigateParams params(profile_, url_to_open,
                         ui::PAGE_TRANSITION_AUTO_BOOKMARK);
   Navigate(&params);
 
diff --git a/chrome/browser/signin/dice_intercepted_session_startup_helper.h b/chrome/browser/signin/dice_intercepted_session_startup_helper.h
index 77338a9..7b1ebd63 100644
--- a/chrome/browser/signin/dice_intercepted_session_startup_helper.h
+++ b/chrome/browser/signin/dice_intercepted_session_startup_helper.h
@@ -13,7 +13,6 @@
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "google_apis/gaia/core_account_id.h"
-#include "url/gurl.h"
 
 namespace content {
 class WebContents;
@@ -81,7 +80,6 @@
   // Timeout while waiting for the account to be added to the cookies in the new
   // profile.
   base::CancelableOnceCallback<void()> on_cookie_update_timeout_;
-  GURL url_to_open_;
 };
 
 #endif  // CHROME_BROWSER_SIGNIN_DICE_INTERCEPTED_SESSION_STARTUP_HELPER_H_
diff --git a/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc b/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
index 67ca620e..091d95d 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
@@ -38,6 +38,7 @@
 #include "content/public/test/browser_test.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
 
 namespace {
 
@@ -421,3 +422,58 @@
             nullptr);
   EXPECT_EQ(GetInterceptorDelegate(profile())->customized_browser(), nullptr);
 }
+
+// Close the source tab during the interception and check that the NTP is opened
+// in the new profile (regression test for https://crbug.com/1153321).
+IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest, CloseSourceTab) {
+  // Setup profile for interception.
+  identity_test_env()->MakeAccountAvailable("alice@example.com");
+  AccountInfo account_info =
+      identity_test_env()->MakeAccountAvailable("bob@example.com");
+  // Fill the account info, in particular for the hosted_domain field.
+  account_info.full_name = "fullname";
+  account_info.given_name = "givenname";
+  account_info.hosted_domain = kNoHostedDomainFound;
+  account_info.locale = "en";
+  account_info.picture_url = "https://example.com";
+  account_info.is_child_account = false;
+  DCHECK(account_info.IsValid());
+  identity_test_env()->UpdateAccountInfoForAccount(account_info);
+
+  // Add a tab.
+  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
+  content::WebContents* contents = AddTab(intercepted_url);
+  int original_tab_count = browser()->tab_strip_model()->count();
+
+  // Do the signin interception.
+  ProfileWaiter profile_waiter;
+  DiceWebSigninInterceptor* interceptor =
+      DiceWebSigninInterceptorFactory::GetForProfile(
+          Profile::FromBrowserContext(contents->GetBrowserContext()));
+  interceptor->MaybeInterceptWebSignin(contents, account_info.account_id,
+                                       /*is_new_account=*/true,
+                                       /*is_sync_signin=*/false);
+  // Close the source tab during the profile creation.
+  contents->Close();
+  // Wait for the interception to be complete.
+  Profile* new_profile = profile_waiter.WaitForProfileAdded();
+  ASSERT_TRUE(new_profile);
+  signin::IdentityManager* new_identity_manager =
+      IdentityManagerFactory::GetForProfile(new_profile);
+  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
+      account_info.account_id));
+
+  // Add the account to the cookies (simulates the account reconcilor).
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
+  signin::SetCookieAccounts(new_identity_manager, test_url_loader_factory(),
+                            {{account_info.email, account_info.gaia}});
+
+  // A browser has been created for the new profile on the new tab page.
+  ASSERT_EQ(BrowserList::GetInstance()->size(), 2u);
+  Browser* added_browser = BrowserList::GetInstance()->get(1);
+  ASSERT_TRUE(added_browser);
+  EXPECT_EQ(added_browser->profile(), new_profile);
+  EXPECT_EQ(browser()->tab_strip_model()->count(), original_tab_count - 1);
+  EXPECT_EQ(added_browser->tab_strip_model()->GetActiveWebContents()->GetURL(),
+            GURL("chrome://newtab/"));
+}
diff --git a/chrome/browser/sync/test/integration/password_manager_sync_test.cc b/chrome/browser/sync/test/integration/password_manager_sync_test.cc
index 797c792..8887744 100644
--- a/chrome/browser/sync/test/integration/password_manager_sync_test.cc
+++ b/chrome/browser/sync/test/integration/password_manager_sync_test.cc
@@ -90,7 +90,7 @@
     if (!base::PathExists(path_)) {
       return true;
     }
-    watcher_.Watch(path_, /*recursive=*/true,
+    watcher_.Watch(path_, base::FilePathWatcher::Type::kRecursive,
                    base::BindRepeating(&PathDeletionWaiter::PathChanged,
                                        base::Unretained(this)));
     run_loop_.Run();
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 08594c3..50448acd 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -5,6 +5,7 @@
 import("//build/config/buildflags_paint_preview.gni")
 import("//build/config/chromecast_build.gni")
 import("//build/config/chromeos/ui_mode.gni")
+import("//build/config/chromeos/ui_mode.gni")
 import("//build/config/compiler/compiler.gni")
 import("//build/config/crypto.gni")
 import("//build/config/features.gni")
@@ -36,7 +37,7 @@
     "app_list/app_list_util.cc",
     "app_list/app_list_util.h",
 
-    # All other browser/ui/app_list files go under is_chromeos below.
+    # All other browser/ui/app_list files go under is_chromeos_ash below.
     "autofill/autofill_bubble_handler.h",
     "autofill/autofill_popup_controller.h",
     "autofill/autofill_popup_controller_impl.cc",
@@ -1569,14 +1570,14 @@
     ]
     public_deps += [ "//ui/base/dragdrop/mojom:mojom_headers" ]
 
-    if (use_ozone && !is_chromeos) {
+    if (use_ozone && !is_chromeos_ash) {
       deps += [
         "//ui/base:features",
         "//ui/ozone",
       ]
     }
 
-    if (is_linux && !is_chromeos) {
+    if (is_linux || is_chromeos_lacros) {
       deps += [ "//ui/base/ime/linux" ]
     }
 
@@ -1619,7 +1620,7 @@
     deps += [ "//chrome/browser/supervised_user/supervised_user_error_page" ]
   }
 
-  if (is_chromeos) {
+  if (is_chromeos_ash) {
     assert(enable_extensions)
     assert(enable_supervised_users)
     assert(toolkit_views)
@@ -2830,7 +2831,7 @@
     deps += [ "//ui/webui" ]
   }
 
-  if (is_win || is_mac || is_linux) {
+  if (is_win || is_mac || (is_linux || is_chromeos_lacros)) {
     sources += [
       "bookmarks/bookmark_bubble_sign_in_delegate.cc",
       "bookmarks/bookmark_bubble_sign_in_delegate.h",
@@ -3256,7 +3257,7 @@
     }
   }
 
-  if (is_linux) {
+  if (is_linux || is_chromeos_lacros) {
     sources += [
       "views/apps/chrome_app_window_client_views_linux.cc",
       "views/first_run_dialog.cc",
@@ -3313,7 +3314,7 @@
       ]
     }
     if (use_ozone) {
-      if (!is_linux) {
+      if (!is_linux || is_chromeos_lacros) {
         sources += [
           "views/frame/browser_desktop_window_tree_host_platform.cc",
           "views/frame/browser_desktop_window_tree_host_platform.h",
@@ -3321,7 +3322,7 @@
       }
       sources += [ "views/frame/native_browser_frame_factory_ozone.cc" ]
     }
-    if (is_linux) {
+    if (is_linux || is_chromeos_lacros) {
       sources += [
         "views/frame/browser_desktop_window_tree_host_linux.cc",
         "views/frame/browser_desktop_window_tree_host_linux.h",
@@ -4164,7 +4165,7 @@
 
     allow_circular_includes_from += [ "//chrome/browser/ui/views" ]
 
-    if (is_linux) {
+    if (is_linux || is_chromeos_lacros) {
       sources += [
         "views/chrome_views_delegate_linux.cc",
         "views/frame/desktop_linux_browser_frame_view.cc",
@@ -4216,7 +4217,7 @@
       sources += [ "views/chrome_views_delegate_win.cc" ]
     }
 
-    if (is_win || is_linux) {
+    if (is_win || (is_linux || is_chromeos_lacros)) {
       sources += [
         "views/native_widget_factory.cc",
         "views/native_widget_factory.h",
@@ -4225,7 +4226,7 @@
 
     if (use_aura) {
       # These files can do Gtk+-based theming for builds with gtk enabled.
-      if (is_linux) {
+      if (is_linux || is_chromeos_lacros) {
         sources += [
           "views/chrome_browser_main_extra_parts_views_linux.cc",
           "views/chrome_browser_main_extra_parts_views_linux.h",
@@ -4248,7 +4249,7 @@
       }
     }
 
-    if (!is_chromeos) {
+    if (!is_chromeos_ash) {
       sources += [
         "views/accessibility/accessibility_focus_highlight.cc",
         "views/accessibility/accessibility_focus_highlight.h",
@@ -4320,12 +4321,12 @@
       "//ui/wm",
     ]
 
-    if (!is_chromeos && !chromeos_is_browser_only) {
+    if (!is_chromeos_ash && !is_chromeos_lacros) {
       sources +=
           [ "views/frame/browser_non_client_frame_view_factory_views.cc" ]
     }
 
-    if (!is_chromeos) {
+    if (!is_chromeos_ash) {
       sources += [
         "views/frame/desktop_browser_frame_aura.cc",
         "views/frame/desktop_browser_frame_aura.h",
@@ -4514,7 +4515,7 @@
       "//services/device/public/mojom:usb",
     ]
 
-    if (is_chromeos) {
+    if (is_chromeos_ash) {
       sources += [
         "webui/print_preview/local_printer_handler_chromeos.cc",
         "webui/print_preview/local_printer_handler_chromeos.h",
@@ -4532,7 +4533,7 @@
         "webui/print_preview/privet_printer_handler.h",
       ]
 
-      if (!is_chromeos) {
+      if (!is_chromeos_ash) {
         deps += [ "//chrome/common:service_process_mojom" ]
       }
     }
@@ -4728,7 +4729,7 @@
     ]
   }
 
-  if (is_chromeos) {
+  if (is_chromeos_ash) {
     sources += [
       "ash/ash_test_util.cc",
       "ash/ash_test_util.h",
@@ -4759,7 +4760,7 @@
   }
 }
 
-if (is_chromeos) {
+if (is_chromeos_ash) {
   source_set("ash_test_support") {
     check_includes = false
     sources = [
diff --git a/chrome/browser/ui/android/external_protocol_dialog_android.cc b/chrome/browser/ui/android/external_protocol_dialog_android.cc
index 21626b8d..d4202125 100644
--- a/chrome/browser/ui/android/external_protocol_dialog_android.cc
+++ b/chrome/browser/ui/android/external_protocol_dialog_android.cc
@@ -28,6 +28,9 @@
 
   navigation_interception::NavigationParams navigation_params(
       url, content::Referrer(),
+      // Pass 0 as the navigation ID to specify that this instance doesn't
+      // correspond to a NavigationHandle.
+      0,
       has_user_gesture,  // has_user_gesture
       false,             // is_post, doesn't matter here.
       page_transition,
diff --git a/chrome/browser/ui/app_list/app_list_util.cc b/chrome/browser/ui/app_list/app_list_util.cc
index dfa3698..61c8575 100644
--- a/chrome/browser/ui/app_list/app_list_util.cc
+++ b/chrome/browser/ui/app_list/app_list_util.cc
@@ -5,9 +5,10 @@
 #include "chrome/browser/ui/app_list/app_list_util.h"
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
 bool IsAppLauncherEnabled() {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   return true;
 #else
   return false;
diff --git a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
index 267cc42..05c976c1 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
@@ -27,6 +27,7 @@
 #include "base/test/scoped_command_line.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_icon_factory.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
@@ -2922,7 +2923,7 @@
 }
 
 // TODO(crbug.com/1005069) Disabled on Chrome OS due to flake
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #define MAYBE_IconLoadNonSupportedScales DISABLED_IconLoadNonSupportedScales
 #else
 #define MAYBE_IconLoadNonSupportedScales IconLoadNonSupportedScales
diff --git a/chrome/browser/ui/app_list/chrome_app_list_item.cc b/chrome/browser/ui/app_list/chrome_app_list_item.cc
index faf376a..926c539b 100644
--- a/chrome/browser/ui/app_list/chrome_app_list_item.cc
+++ b/chrome/browser/ui/app_list/chrome_app_list_item.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "ash/public/cpp/tablet_mode.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
@@ -16,7 +17,7 @@
 #include "ui/gfx/color_utils.h"
 #include "ui/gfx/image/image_skia_operations.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
 #endif
 
@@ -83,7 +84,7 @@
 }
 
 void ChromeAppListItem::PerformActivate(int event_flags) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Handle recording app launch source from the AppList in Demo Mode.
   chromeos::DemoSession::RecordAppLaunchSourceIfInDemoMode(
       chromeos::DemoSession::AppLaunchSource::kAppList);
diff --git a/chrome/browser/ui/apps/app_info_dialog.h b/chrome/browser/ui/apps/app_info_dialog.h
index 5efbf77..8711dd3 100644
--- a/chrome/browser/ui/apps/app_info_dialog.h
+++ b/chrome/browser/ui/apps/app_info_dialog.h
@@ -8,8 +8,9 @@
 #include <string>
 
 #include "base/callback_forward.h"
+#include "build/chromeos_buildflags.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ui/gfx/native_widget_types.h"
 #endif
 
@@ -43,7 +44,7 @@
 // Returns true if the app info dialog is available for an app.
 bool CanShowAppInfoDialog(Profile* profile, const std::string& extension_id);
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // Shows the chrome app information as a frameless window for the given |app|
 // and |profile| at the given |app_info_bounds|.
 void ShowAppInfoInAppList(gfx::NativeWindow parent,
diff --git a/chrome/browser/ui/apps/chrome_app_delegate.cc b/chrome/browser/ui/apps/chrome_app_delegate.cc
index 8188dc0c..7117c6e 100644
--- a/chrome/browser/ui/apps/chrome_app_delegate.cc
+++ b/chrome/browser/ui/apps/chrome_app_delegate.cc
@@ -11,6 +11,7 @@
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/strings/stringprintf.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/app_mode/app_mode_utils.h"
 #include "chrome/browser/apps/platform_apps/audio_focus_web_contents_observer.h"
 #include "chrome/browser/chrome_notification_types.h"
@@ -49,7 +50,7 @@
 #include "printing/buildflags/buildflags.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/lock_screen_apps/state_controller.h"
 #endif
 
@@ -310,7 +311,7 @@
 }
 
 int ChromeAppDelegate::PreferredIconSize() const {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Use a size appropriate for the ash shelf (see ash::kShelfSize).
   return extension_misc::EXTENSION_ICON_MEDIUM;
 #else
@@ -369,7 +370,7 @@
                                   bool reverse) {
   if (!for_lock_screen_app_)
     return false;
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   return lock_screen_apps::StateController::Get()->HandleTakeFocus(web_contents,
                                                                    reverse);
 #else
diff --git a/chrome/browser/ui/apps/chrome_app_window_client.cc b/chrome/browser/ui/apps/chrome_app_window_client.cc
index 09c7e00a..53acdc1 100644
--- a/chrome/browser/ui/apps/chrome_app_window_client.cc
+++ b/chrome/browser/ui/apps/chrome_app_window_client.cc
@@ -9,6 +9,7 @@
 
 #include "base/memory/singleton.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/devtools/devtools_window.h"
 #include "components/version_info/version_info.h"
 #include "content/public/browser/devtools_agent_host.h"
@@ -16,7 +17,7 @@
 #include "extensions/common/extension.h"
 #include "extensions/common/features/feature_channel.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/lock_screen_apps/state_controller.h"
 #endif
 
@@ -55,7 +56,7 @@
     content::BrowserContext* context,
     const extensions::Extension* extension,
     extensions::api::app_runtime::ActionType action) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   auto app_delegate = std::make_unique<ChromeAppDelegate>(true /*keep_alive*/);
   app_delegate->set_for_lock_screen_app(true);
 
diff --git a/chrome/browser/ui/ash/chrome_screenshot_grabber.cc b/chrome/browser/ui/ash/chrome_screenshot_grabber.cc
index b2b3fc68..06c9849 100644
--- a/chrome/browser/ui/ash/chrome_screenshot_grabber.cc
+++ b/chrome/browser/ui/ash/chrome_screenshot_grabber.cc
@@ -27,6 +27,7 @@
 #include "base/task/thread_pool.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/file_manager/open_util.h"
@@ -433,7 +434,7 @@
     return;
   }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   SYSLOG(INFO) << "Screenshot taken";
 #endif
 
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc b/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc
index 74b98cf..5edb638 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc
@@ -38,7 +38,7 @@
       return;
     watchers_[file_path] = std::make_unique<base::FilePathWatcher>();
     watchers_[file_path]->Watch(
-        file_path, /*recursive=*/false,
+        file_path, base::FilePathWatcher::Type::kNonRecursive,
         base::Bind(&FileSystemWatcher::OnFilePathChanged,
                    weak_factory_.GetWeakPtr()));
   }
diff --git a/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc b/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
index 58e49ef..e0d00ed 100644
--- a/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
+++ b/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/no_destructor.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/crash/core/common/crash_key.h"
 #include "ui/accessibility/aura/aura_window_properties.h"
 #include "ui/accessibility/ax_action_data.h"
@@ -29,7 +30,7 @@
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shell.h"
 #include "ash/wm/window_util.h"
@@ -45,7 +46,7 @@
   enabled_ = true;
   Reset(false);
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Seed the views::AXAuraObjCache with per-display root windows so
   // GetTopLevelWindows() returns the correct values when automation is enabled
   // with multiple displays connected.
@@ -62,7 +63,7 @@
   // ordering of two base::Singletons.
   cache_->SetDelegate(this);
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   aura::Window* active_window = ash::window_util::GetActiveWindow();
   if (active_window) {
     views::AXAuraObjWrapper* focus = cache_->GetOrCreate(active_window);
@@ -162,7 +163,7 @@
   } else {
     current_tree_serializer_ =
         std::make_unique<AuraAXTreeSerializer>(current_tree_.get());
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     ash::Shell* shell = ash::Shell::Get();
     // Windows within the overlay container get moved to the new monitor when
     // the primary display gets swapped.
@@ -170,7 +171,7 @@
         shell->GetContainer(shell->GetPrimaryRootWindow(),
                             ash::kShellWindowId_OverlayContainer),
         cache_.get());
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   }
 }
 
@@ -248,7 +249,7 @@
 
 void AutomationManagerAura::PerformHitTest(
     const ui::AXActionData& original_action) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   ui::AXActionData action = original_action;
   aura::Window* root_window = ash::Shell::Get()->GetPrimaryRootWindow();
   if (!root_window)
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc b/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
index 7b247d30..9d59f8a 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/optional.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/accessibility/accessibility_state_utils.h"
 #include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
 #include "chrome/browser/ui/autofill/autofill_popup_view.h"
@@ -37,7 +38,7 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/text_utils.h"
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 #include "content/public/browser/browser_accessibility_state.h"
 #endif
 
@@ -281,7 +282,7 @@
   NiceMock<TestAutofillPopupController>* autofill_popup_controller_;
 };
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 class AutofillPopupControllerAccessibilityUnitTest
     : public AutofillPopupControllerUnitTest {
  public:
@@ -700,7 +701,7 @@
   Mock::VerifyAndClearExpectations(autofill_popup_view());
 }
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 TEST_F(AutofillPopupControllerAccessibilityUnitTest, FireControlsChangedEvent) {
   StrictMock<MockAxPlatformNodeDelegate> mock_ax_platform_node_delegate;
   StrictMock<MockAxPlatformNode> mock_ax_platform_node;
diff --git a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
index 19d2688..b6e1182 100644
--- a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
@@ -12,6 +12,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/history/history_test_utils.h"
 #include "chrome/browser/policy/profile_policy_connector_builder.h"
@@ -270,7 +271,8 @@
 }
 
 // TODO(crbug.com/1115886): Flaky on Mac ASAN and Chrome OS.
-#if (defined(OS_MAC) && defined(ADDRESS_SANITIZER)) || defined(OS_CHROMEOS)
+#if (defined(OS_MAC) && defined(ADDRESS_SANITIZER)) || \
+    BUILDFLAG(IS_CHROMEOS_ASH)
 #define MAYBE_BlockWebContentsCreationIncognito \
   DISABLED_BlockWebContentsCreationIncognito
 #else
diff --git a/chrome/browser/ui/blocked_content/popup_tracker_browsertest.cc b/chrome/browser/ui/blocked_content/popup_tracker_browsertest.cc
index 056e83f..5b2c9ce 100644
--- a/chrome/browser/ui/blocked_content/popup_tracker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/popup_tracker_browsertest.cc
@@ -11,6 +11,7 @@
 #include "base/supports_user_data.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/test_safe_browsing_database_helper.h"
 #include "chrome/browser/ui/browser.h"
@@ -573,7 +574,7 @@
 }
 
 // TODO(crbug.com/1146598): Test is flaky on Lacros.
-#if BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
 #define MAYBE_PopupNoRedirect_RedirectCountZero DISABLED_PopupNoRedirect_RedirectCountZero
 #else
 #define MAYBE_PopupNoRedirect_RedirectCountZero PopupNoRedirect_RedirectCountZero
diff --git a/chrome/browser/ui/bluetooth/bluetooth_chooser_controller.cc b/chrome/browser/ui/bluetooth/bluetooth_chooser_controller.cc
index 54bd8b3..7cfdf44 100644
--- a/chrome/browser/ui/bluetooth/bluetooth_chooser_controller.cc
+++ b/chrome/browser/ui/bluetooth/bluetooth_chooser_controller.cc
@@ -10,6 +10,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/notreached.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/net/referrer.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/bluetooth/bluetooth_chooser_desktop.h"
@@ -20,11 +21,11 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
 #include "chrome/common/webui_url_constants.h"
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 namespace {
 
@@ -108,7 +109,7 @@
 }
 
 void BluetoothChooserController::OpenAdapterOffHelpUrl() const {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Chrome OS can directly link to the OS setting to turn on the adapter.
   chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
       GetBrowser()->profile(),
diff --git a/chrome/browser/ui/bookmarks/bookmark_browsertest.cc b/chrome/browser/ui/bookmarks/bookmark_browsertest.cc
index c03e0503..c308f84 100644
--- a/chrome/browser/ui/bookmarks/bookmark_browsertest.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/timer/timer.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/browser_process.h"
@@ -155,7 +156,7 @@
   ASSERT_EQ(base::ASCIIToUTF16(kPersistBookmarkTitle), urls[0].title);
 }
 
-#if !defined(OS_CHROMEOS)  // No multi-profile on ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS_ASH)  // No multi-profile on ChromeOS.
 
 // Sanity check that bookmarks from different profiles are separate.
 // DISABLED_ because it regularly times out: http://crbug.com/159002.
@@ -318,7 +319,7 @@
 
 // ChromeOS initializes two profiles (Default and test-user) and it's impossible
 // to distinguish UMA samples separately.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 
 IN_PROC_BROWSER_TEST_F(BookmarkBrowsertest, PRE_EmitUmaForDuplicates) {
   BookmarkModel* bookmark_model = WaitForBookmarkModel(browser()->profile());
@@ -384,4 +385,4 @@
               testing::ElementsAre(base::Bucket(/*min=*/6, /*count=*/1)));
 }
 
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/bookmarks/bookmark_tab_helper.cc b/chrome/browser/ui/bookmarks/bookmark_tab_helper.cc
index e8240497..1d15141f 100644
--- a/chrome/browser/ui/bookmarks/bookmark_tab_helper.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_tab_helper.cc
@@ -6,6 +6,7 @@
 
 #include "base/observer_list.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/defaults.h"
 #include "chrome/browser/profiles/profile.h"
@@ -58,7 +59,7 @@
   Profile* profile =
       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   if (profile->IsGuestSession() || profile->IsEphemeralGuestProfile())
     return false;
 #endif
diff --git a/chrome/browser/ui/bookmarks/bookmark_utils.cc b/chrome/browser/ui/bookmarks/bookmark_utils.cc
index c750eaf..d24185e7 100644
--- a/chrome/browser/ui/bookmarks/bookmark_utils.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_utils.cc
@@ -10,6 +10,7 @@
 #include "base/notreached.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search/search.h"
@@ -142,7 +143,7 @@
   if (profile->IsLegacySupervised())
     return false;
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Chrome OS uses the app list / app launcher.
   return false;
 #else
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index ef1c039..3dd7a7f 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -31,6 +31,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/app_mode/app_mode_utils.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
@@ -246,7 +247,7 @@
 #include "ui/base/win/shell.h"
 #endif  // defined(OS_WIN)
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
 #include "components/session_manager/core/session_manager.h"
 #endif
@@ -320,7 +321,7 @@
 }
 
 bool IsOnKioskSplashScreen() {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   session_manager::SessionManager* session_manager =
       session_manager::SessionManager::Get();
   if (!session_manager)
@@ -1360,7 +1361,7 @@
 bool Browser::CanDragEnter(content::WebContents* source,
                            const content::DropData& data,
                            blink::DragOperationsMask operations_allowed) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Disallow drag-and-drop navigation for Settings windows which do not support
   // external navigation.
   if ((operations_allowed & blink::kDragOperationLink) &&
@@ -1505,11 +1506,11 @@
 }
 
 bool Browser::ShouldShowStaleContentOnEviction(content::WebContents* source) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   return source == tab_strip_model_->GetActiveWebContents();
 #else
   return false;
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 bool Browser::IsFrameLowPriority(
@@ -2727,7 +2728,7 @@
 // Browser, In-progress download termination handling (private):
 
 bool Browser::CanCloseWithInProgressDownloads() {
-#if defined(OS_MAC) || defined(OS_CHROMEOS)
+#if defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
   // On Mac and ChromeOS, non-incognito and non-Guest downloads can still
   // continue after window is closed.
   if (!profile_->IsOffTheRecord() && !profile_->IsEphemeralGuestProfile())
@@ -2934,7 +2935,7 @@
   }
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // TODO(b/64863368): Consider Fullscreen mode.
 bool Browser::CustomTabBrowserSupportsWindowFeature(
     WindowFeature feature) const {
@@ -2966,7 +2967,7 @@
     case TYPE_DEVTOOLS:
     case TYPE_APP_POPUP:
       return AppPopupBrowserSupportsWindowFeature(feature, check_can_support);
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     case TYPE_CUSTOM_TAB:
       return CustomTabBrowserSupportsWindowFeature(feature);
 #endif
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index afd85299..9c880452 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -21,6 +21,7 @@
 #include "base/strings/string16.h"
 #include "base/timer/elapsed_timer.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/devtools/devtools_toggle_action.h"
 #include "chrome/browser/ui/bookmarks/bookmark_bar.h"
 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper_observer.h"
@@ -137,7 +138,7 @@
     // AppBrowserController) but looks like a popup (e.g. it never has a tab
     // strip).
     TYPE_APP_POPUP,
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     // Browser for ARC++ Chrome custom tabs.
     // It's an enhanced version of TYPE_POPUP, and is used to show the Chrome
     // Custom Tab toolbar for ARC++ apps. It has UI customizations like using
@@ -640,7 +641,7 @@
   bool is_type_app() const { return type_ == TYPE_APP; }
   bool is_type_app_popup() const { return type_ == TYPE_APP_POPUP; }
   bool is_type_devtools() const { return type_ == TYPE_DEVTOOLS; }
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   bool is_type_custom_tab() const { return type_ == TYPE_CUSTOM_TAB; }
 #endif
   // TODO(crbug.com/990158): |deprecated_is_app()| is added for backwards
@@ -1038,7 +1039,7 @@
   bool AppBrowserSupportsWindowFeature(WindowFeature feature,
                                        bool check_can_support) const;
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   bool CustomTabBrowserSupportsWindowFeature(WindowFeature feature) const;
 #endif
 
diff --git a/chrome/browser/ui/browser_browsertest.cc b/chrome/browser/ui/browser_browsertest.cc
index 72fd3d8d..97c05d0 100644
--- a/chrome/browser/ui/browser_browsertest.cc
+++ b/chrome/browser/ui/browser_browsertest.cc
@@ -27,6 +27,7 @@
 #include "base/test/metrics/user_action_tester.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
@@ -1024,7 +1025,7 @@
   // everything but ChromeOS allows unload handlers to block exit. On that
   // platform, though, it exits unconditionally. See the comment and bug ID
   // in AttemptUserExit() in application_lifetime.cc.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   chrome::AttemptExit();
 #else
   chrome::ExecuteCommand(second_window, IDC_EXIT);
@@ -1461,7 +1462,7 @@
 
 // Chromeos defaults to restoring the last session, so this test isn't
 // applicable.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 // Makes sure pinned tabs are restored correctly on start.
 IN_PROC_BROWSER_TEST_F(BrowserTest, RestorePinnedTabs) {
   ASSERT_TRUE(embedded_test_server()->Start());
@@ -1517,13 +1518,13 @@
   EXPECT_TRUE(new_model->IsTabPinned(1));
   EXPECT_FALSE(new_model->IsTabPinned(2));
 }
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 // TODO(1126339): fix the way how exo creates accelerated widgets. At the
 // moment, they are created only after the client attaches a buffer to a surface,
 // which is incorrect and results in the "[destroyed object]: error 1: popup
 // parent not constructed" error.
-#if BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
 #define MAYBE_CloseWithAppMenuOpen DISABLED_CloseWithAppMenuOpen
 #else
 #define MAYBE_CloseWithAppMenuOpen CloseWithAppMenuOpen
@@ -1693,7 +1694,7 @@
   EXPECT_TRUE(new_command_updater->IsCommandEnabled(IDC_NEW_INCOGNITO_WINDOW));
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_F(BrowserTest, ArcBrowserWindowFeaturesSetCorrectly) {
   Browser* new_browser = Browser::Create(
       Browser::CreateParams(Browser::TYPE_CUSTOM_TAB, browser()->profile(),
@@ -2039,13 +2040,15 @@
 
 // TODO(linux_aura) http://crbug.com/163931
 // Mac disabled: http://crbug.com/169820
-#if !defined(OS_MAC) && !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if !defined(OS_MAC) && !(defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
 IN_PROC_BROWSER_TEST_F(BrowserTest, FullscreenBookmarkBar) {
   chrome::ToggleBookmarkBar(browser());
   EXPECT_EQ(BookmarkBar::SHOW, browser()->bookmark_bar_state());
   chrome::ToggleFullscreenMode(browser());
   EXPECT_TRUE(browser()->window()->IsFullscreen());
-#if defined(OS_MAC) || defined(OS_CHROMEOS)
+#if defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
   // Mac and Chrome OS both have an "immersive style" fullscreen where the
   // bookmark bar is visible when the top views slide down.
   EXPECT_EQ(BookmarkBar::SHOW, browser()->bookmark_bar_state());
@@ -2077,7 +2080,9 @@
   }
 };
 
-#if defined(OS_MAC) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_MAC) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
 // Mac: http://crbug.com/103912
 // Linux: http://crbug.com/163931
 #define MAYBE_EnableKioskModeTest DISABLED_EnableKioskModeTest
@@ -2212,7 +2217,7 @@
 
 // Chromeos needs to track app windows because it considers them to be part of
 // session state.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_F(NoStartupWindowTest, DontInitSessionServiceForApps) {
   Profile* profile = ProfileManager::GetActiveUserProfile();
 
@@ -2226,7 +2231,7 @@
 
   ASSERT_FALSE(ProcessedAnyCommands(command_storage_manager));
 }
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 // This test needs to be placed outside the anonymous namespace because we
 // need to access private type of Browser.
diff --git a/chrome/browser/ui/browser_close_unittest.cc b/chrome/browser/ui/browser_close_unittest.cc
index b2e2d8e..bba07bfd 100644
--- a/chrome/browser/ui/browser_close_unittest.cc
+++ b/chrome/browser/ui/browser_close_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/macros.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/download/chrome_download_manager_delegate.h"
 #include "chrome/browser/download/download_core_service.h"
 #include "chrome/browser/download/download_core_service_factory.h"
@@ -264,7 +265,7 @@
   EXPECT_EQ(Browser::DownloadCloseType::kBrowserShutdown,
             browser->OkToCloseWithInProgressDownloads(&num_downloads_blocking));
   EXPECT_EQ(num_downloads_blocking, 1);
-#if defined(OS_MAC) || defined(OS_CHROMEOS)
+#if defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
   EXPECT_EQ(true, browser->CanCloseWithInProgressDownloads());
 #else
   EXPECT_EQ(false, browser->CanCloseWithInProgressDownloads());
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index 0ee1abae..5fb8de1 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -17,6 +17,7 @@
 #include "base/stl_util.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
@@ -76,14 +77,16 @@
 #include "content/public/browser/gpu_data_manager.h"
 #endif
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_context_menu.h"
 #include "chrome/browser/ui/browser_commands_chromeos.h"
 #include "components/session_manager/core/session_manager.h"
 #endif
 
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h"
 #endif
 
@@ -211,7 +214,7 @@
   if (browser_->is_type_app() || browser_->is_type_app_popup())
     return false;
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // On Chrome OS, the top row of keys are mapped to browser actions like
   // back/forward or refresh. We don't want web pages to be able to change the
   // behavior of these keys.  Ash handles F4 and up; this leaves us needing to
@@ -247,7 +250,9 @@
 #endif
   }
 
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   // If this key was registered by the user as a content editing hotkey, then
   // it is not reserved.
   ui::TextEditKeyBindingsDelegateAuraLinux* delegate =
@@ -280,7 +285,7 @@
   UpdateCommandsForFullscreenMode();
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 void BrowserCommandController::LockedFullscreenStateChanged() {
   UpdateCommandsForLockedFullscreenMode();
 }
@@ -468,7 +473,7 @@
       PromptToNameWindow(browser_);
       break;
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     case IDC_VISIT_DESKTOP_OF_LRU_USER_2:
     case IDC_VISIT_DESKTOP_OF_LRU_USER_3:
     case IDC_VISIT_DESKTOP_OF_LRU_USER_4:
@@ -477,7 +482,9 @@
       break;
 #endif
 
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
     case IDC_MINIMIZE_WINDOW:
       browser_->window()->Minimize();
       break;
@@ -661,7 +668,7 @@
     case IDC_TASK_MANAGER:
       OpenTaskManager(browser_);
       break;
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     case IDC_TAKE_SCREENSHOT:
       TakeScreenshot();
       break;
@@ -739,7 +746,7 @@
     case IDC_TOGGLE_COMMANDER:
       ToggleCommander(browser_);
       break;
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
     case IDC_SHOW_SIGNIN:
       ShowBrowserSigninOrSettings(
           browser_, signin_metrics::AccessPoint::ACCESS_POINT_MENU);
@@ -935,7 +942,7 @@
   command_updater_.UpdateCommandEnabled(IDC_EXIT, true);
   command_updater_.UpdateCommandEnabled(IDC_DEBUG_FRAME_TOGGLE, true);
   command_updater_.UpdateCommandEnabled(IDC_NAME_WINDOW, true);
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   command_updater_.UpdateCommandEnabled(IDC_MINIMIZE_WINDOW, true);
   // The VisitDesktop command is only supported for up to 5 logged in users
   // because that's the max number of user sessions. If that number is increased
@@ -951,7 +958,9 @@
   command_updater_.UpdateCommandEnabled(IDC_VISIT_DESKTOP_OF_LRU_USER_4, true);
   command_updater_.UpdateCommandEnabled(IDC_VISIT_DESKTOP_OF_LRU_USER_5, true);
 #endif
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   command_updater_.UpdateCommandEnabled(IDC_MINIMIZE_WINDOW, true);
   command_updater_.UpdateCommandEnabled(IDC_MAXIMIZE_WINDOW, true);
   command_updater_.UpdateCommandEnabled(IDC_RESTORE_WINDOW, true);
@@ -1004,7 +1013,7 @@
       IDC_CLEAR_BROWSING_DATA,
       (!profile()->IsGuestSession() && !profile()->IsSystemProfile() &&
        !profile()->IsIncognitoProfile()));
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   command_updater_.UpdateCommandEnabled(IDC_TAKE_SCREENSHOT, true);
   // Chrome OS uses the system tray menu to handle multi-profiles. Avatar menu
   // is only required in incognito mode.
@@ -1027,7 +1036,7 @@
       IDC_HOME, normal_window || browser_->deprecated_is_app());
 
   const bool is_web_app_or_custom_tab =
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
       browser_->is_type_custom_tab() ||
 #endif
       web_app::AppBrowserController::IsForWebAppBrowser(browser_);
@@ -1371,7 +1380,7 @@
   command_updater_.UpdateCommandEnabled(IDC_SHOW_APP_MENU, has_toolbar);
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 namespace {
 
 #if DCHECK_IS_ON()
diff --git a/chrome/browser/ui/browser_command_controller.h b/chrome/browser/ui/browser_command_controller.h
index 381be31e..b249f01 100644
--- a/chrome/browser/ui/browser_command_controller.h
+++ b/chrome/browser/ui/browser_command_controller.h
@@ -9,6 +9,7 @@
 
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/command_updater.h"
 #include "chrome/browser/command_updater_delegate.h"
 #include "chrome/browser/command_updater_impl.h"
@@ -50,7 +51,7 @@
   void ZoomStateChanged();
   void ContentRestrictionsChanged();
   void FullscreenStateChanged();
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Called when the browser goes in or out of the special locked fullscreen
   // mode. In this mode the user is basically locked into the current browser
   // window and tab hence we disable most keyboard shortcuts and we also
@@ -156,7 +157,7 @@
   // app windows.
   void UpdateCommandsForHostedAppAvailability();
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Update commands whose state depends on whether the window is in locked
   // fullscreen mode or not.
   void UpdateCommandsForLockedFullscreenMode();
diff --git a/chrome/browser/ui/browser_command_controller_browsertest.cc b/chrome/browser/ui/browser_command_controller_browsertest.cc
index 67780d1..7da7d58 100644
--- a/chrome/browser/ui/browser_command_controller_browsertest.cc
+++ b/chrome/browser/ui/browser_command_controller_browsertest.cc
@@ -9,6 +9,7 @@
 #include "base/run_loop.h"
 #include "base/stl_util.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -33,7 +34,7 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_utils.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/ui/base/window_pin_type.h"
 #include "chromeos/ui/base/window_properties.h"
@@ -48,7 +49,7 @@
   ~BrowserCommandControllerBrowserTest() override {}
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     command_line->AppendSwitch(
         chromeos::switches::kIgnoreUserProfileMappingForTests);
 #endif
@@ -86,7 +87,7 @@
 }
 
 // TODO(https://crbug.com/1125474): Expand to cover ChromeOS.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 class GuestBrowserCommandControllerBrowserTest
     : public BrowserCommandControllerBrowserTest,
       public testing::WithParamInterface<bool> {
@@ -116,7 +117,7 @@
                          /*is_ephemeral=*/testing::Bool());
 #endif
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_F(BrowserCommandControllerBrowserTest, LockedFullscreen) {
   CommandUpdaterImpl* command_updater =
       &browser()->command_controller()->command_updater_;
diff --git a/chrome/browser/ui/browser_command_controller_interactive_browsertest.cc b/chrome/browser/ui/browser_command_controller_interactive_browsertest.cc
index 3d6ce45c..5fc3ec48 100644
--- a/chrome/browser/ui/browser_command_controller_interactive_browsertest.cc
+++ b/chrome/browser/ui/browser_command_controller_interactive_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include "base/macros.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/fullscreen_keyboard_browsertest_base.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -149,7 +150,7 @@
 // the page to exit fullscreen mode. So we need to maintain a list of exiting /
 // non-exiting commands, which is not the goal of this test.
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // This test is flaky on ChromeOS, see http://crbug.com/754878.
 #define MAYBE_ShortcutsShouldTakeEffectInJsFullscreen \
   DISABLED_ShortcutsShouldTakeEffectInJsFullscreen
diff --git a/chrome/browser/ui/browser_command_controller_unittest.cc b/chrome/browser/ui/browser_command_controller_unittest.cc
index 18ad928..d108766 100644
--- a/chrome/browser/ui/browser_command_controller_unittest.cc
+++ b/chrome/browser/ui/browser_command_controller_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/stl_util.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/command_updater.h"
@@ -52,7 +53,7 @@
 };
 
 TEST_F(BrowserCommandControllerTest, IsReservedCommandOrKey) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // F1-3 keys are reserved Chrome accelerators on Chrome OS.
   EXPECT_TRUE(browser()->command_controller()->IsReservedCommandOrKey(
       IDC_BACK, content::NativeWebKeyboardEvent(
@@ -109,7 +110,7 @@
       -1, content::NativeWebKeyboardEvent(ui::KeyEvent(
               ui::ET_KEY_PRESSED, ui::VKEY_F3, ui::DomCode::F3,
               ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN))));
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(USE_AURA)
   // Ctrl+n, Ctrl+w are reserved while Ctrl+f is not.
@@ -142,7 +143,7 @@
   ASSERT_TRUE(browser()->is_type_app());
 
   // When is_type_app(), no keys are reserved.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   EXPECT_FALSE(browser()->command_controller()->IsReservedCommandOrKey(
       IDC_BACK, content::NativeWebKeyboardEvent(ui::KeyEvent(
                     ui::ET_KEY_PRESSED, ui::VKEY_F1, ui::DomCode::F1, 0))));
@@ -155,7 +156,7 @@
   EXPECT_FALSE(browser()->command_controller()->IsReservedCommandOrKey(
       -1, content::NativeWebKeyboardEvent(ui::KeyEvent(
               ui::ET_KEY_PRESSED, ui::VKEY_F4, ui::DomCode::F4, 0))));
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(USE_AURA)
   // The content::NativeWebKeyboardEvent constructor is available only when
@@ -228,7 +229,7 @@
 
   bool enabled = true;
   size_t profiles_count = 1U;
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Chrome OS uses system tray menu to handle multi-profiles.
   enabled = false;
   profiles_count = 2U;
diff --git a/chrome/browser/ui/browser_dialogs.h b/chrome/browser/ui/browser_dialogs.h
index c95dcf7..bf5394f 100644
--- a/chrome/browser/ui/browser_dialogs.h
+++ b/chrome/browser/ui/browser_dialogs.h
@@ -14,6 +14,7 @@
 #include "base/optional.h"
 #include "base/strings/string16.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/ui/bookmarks/bookmark_editor.h"
 #include "chrome/common/buildflags.h"
 #include "content/public/browser/content_browser_client.h"
@@ -149,7 +150,7 @@
 // user interaction.
 void SetAutoAcceptPWAInstallConfirmationForTesting(bool auto_accept);
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Shows the print job confirmation dialog bubble anchored to the toolbar icon
 // for the extension.
@@ -164,7 +165,7 @@
                                     const base::string16& printer_name,
                                     base::OnceCallback<void(bool)> callback);
 
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(OS_MAC)
 
diff --git a/chrome/browser/ui/browser_finder.cc b/chrome/browser/ui/browser_finder.cc
index 818076d..ecace6de 100644
--- a/chrome/browser/ui/browser_finder.cc
+++ b/chrome/browser/ui/browser_finder.cc
@@ -9,6 +9,7 @@
 #include <algorithm>
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -20,7 +21,7 @@
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/public/cpp/multi_user_window_manager.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
@@ -39,7 +40,7 @@
 const uint32_t kMatchCanSupportWindowFeature = 1 << 1;
 const uint32_t kMatchNormal = 1 << 2;
 const uint32_t kMatchDisplayId = 1 << 3;
-#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
 const uint32_t kMatchCurrentWorkspace = 1 << 4;
 #endif
 
@@ -61,7 +62,7 @@
     return false;
   }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Get the profile on which the window is currently shown.
   // MultiUserWindowManagerHelper might be NULL under test scenario.
   ash::MultiUserWindowManager* const multi_user_window_manager =
@@ -82,7 +83,7 @@
     if (browser->profile()->GetOriginalProfile() !=
         profile->GetOriginalProfile())
       return false;
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     if (shown_profile &&
         shown_profile->GetOriginalProfile() != profile->GetOriginalProfile()) {
       return false;
@@ -91,7 +92,7 @@
   } else {
     if (browser->profile() != profile)
       return false;
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     if (shown_profile && shown_profile != profile)
       return false;
 #endif
@@ -100,7 +101,7 @@
   if ((match_types & kMatchNormal) && !browser->is_type_normal())
     return false;
 
-#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
   // Note that |browser->window()| might be nullptr in tests.
   if ((match_types & kMatchCurrentWorkspace) &&
       (!browser->window() || !browser->window()->IsOnCurrentWorkspace())) {
@@ -150,7 +151,7 @@
     match_types |= kMatchOriginalProfile;
   if (display_id != display::kInvalidDisplayId)
     match_types |= kMatchDisplayId;
-#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
   if (match_current_workspace)
     match_types |= kMatchCurrentWorkspace;
 #endif
diff --git a/chrome/browser/ui/browser_navigator.cc b/chrome/browser/ui/browser_navigator.cc
index 9e33691..6f46f6b 100644
--- a/chrome/browser/ui/browser_navigator.cc
+++ b/chrome/browser/ui/browser_navigator.cc
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_about_handler.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/platform_util.h"
@@ -49,7 +50,7 @@
 #include "extensions/buildflags/buildflags.h"
 #include "url/url_constants.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/public/cpp/multi_user_window_manager.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
@@ -540,7 +541,7 @@
     ShowSingletonTabOverwritingNTP(params->browser, std::move(*params));
     return;
   }
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   if (source_browser) {
     // Open OS settings in PWA, even when user types in URL bar.
     if (params->url.GetOrigin() ==
@@ -765,7 +766,7 @@
   // chrome://settings.
   return host != chrome::kChromeUIAppLauncherPageHost &&
          host != chrome::kChromeUISettingsHost &&
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
          host != chrome::kChromeUIOSSettingsHost &&
 #endif
          host != chrome::kChromeUIHelpHost &&
diff --git a/chrome/browser/ui/browser_navigator_browsertest.cc b/chrome/browser/ui/browser_navigator_browsertest.cc
index 52d65d7..9949e00c 100644
--- a/chrome/browser/ui/browser_navigator_browsertest.cc
+++ b/chrome/browser/ui/browser_navigator_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
@@ -1409,7 +1410,7 @@
 }
 
 // TODO(1024166): Timing out on linux-chromeos-dbg.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #define MAYBE_NavigateFromBlankToOptionsInSameTab \
   DISABLED_NavigateFromBlankToOptionsInSameTab
 #else
@@ -1435,7 +1436,7 @@
 }
 
 // TODO(1024166): Timing out on linux-chromeos-dbg.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #define MAYBE_NavigateFromNTPToOptionsInSameTab \
   DISABLED_NavigateFromNTPToOptionsInSameTab
 #else
@@ -1514,7 +1515,7 @@
 }
 
 // TODO(1024166): Timing out on linux-chromeos-dbg.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #define MAYBE_NavigateFromNTPToOptionsPageInSameTab \
   DISABLED_NavigateFromNTPToOptionsPageInSameTab
 #else
@@ -1605,7 +1606,7 @@
 }
 
 // TODO(1024166): Timing out on linux-chromeos-dbg.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #define MAYBE_CloseSingletonTab DISABLED_CloseSingletonTab
 #else
 #define MAYBE_CloseSingletonTab CloseSingletonTab
@@ -1651,7 +1652,7 @@
 }
 
 // TODO(linux_aura) http://crbug.com/163931
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA)
+#if (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) && defined(USE_AURA)
 #define MAYBE_NavigateFromDefaultToBookmarksInSameTab \
   DISABLED_NavigateFromDefaultToBookmarksInSameTab
 #else
diff --git a/chrome/browser/ui/browser_ui_prefs.cc b/chrome/browser/ui/browser_ui_prefs.cc
index e4fbbfc4..1964ef6 100644
--- a/chrome/browser/ui/browser_ui_prefs.cc
+++ b/chrome/browser/ui/browser_ui_prefs.cc
@@ -8,6 +8,7 @@
 
 #include "base/numerics/safe_conversions.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/upgrade_detector/upgrade_detector.h"
@@ -21,7 +22,7 @@
 #include "media/media_buildflags.h"
 #include "third_party/blink/public/common/peerconnection/webrtc_ip_handling_policy.h"
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ui/accessibility/accessibility_features.h"
 #endif
 
@@ -82,7 +83,7 @@
   registry->RegisterBooleanPref(
       prefs::kEnableDoNotTrack, false,
       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
   registry->RegisterBooleanPref(prefs::kPrintPreviewUseSystemDefaultPrinter,
                                 false);
 #endif
@@ -146,7 +147,7 @@
   registry->RegisterBooleanPref(prefs::kShowCaretBrowsingDialog, true);
 #endif
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   registry->RegisterBooleanPref(prefs::kAccessibilityFocusHighlightEnabled,
                                 false);
 #endif
diff --git a/chrome/browser/ui/browser_unittest.cc b/chrome/browser/ui/browser_unittest.cc
index 518d29f..e4dc414 100644
--- a/chrome/browser/ui/browser_unittest.cc
+++ b/chrome/browser/ui/browser_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/browser.h"
 
 #include "base/macros.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
@@ -247,7 +248,7 @@
   EXPECT_TRUE(otr_browser);
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 TEST_F(BrowserUnitTest, CreateBrowserDuringKioskSplashScreen) {
   session_manager::SessionManager session_manager;
 
diff --git a/chrome/browser/ui/browser_view_prefs.cc b/chrome/browser/ui/browser_view_prefs.cc
index 94773fc5..e2e59680 100644
--- a/chrome/browser/ui/browser_view_prefs.cc
+++ b/chrome/browser/ui/browser_view_prefs.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/browser_view_prefs.h"
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/common/pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -26,7 +27,9 @@
 // Old values: 0 = SHRINK (default), 1 = STACKED.
 const char kTabStripLayoutType[] = "tab_strip_layout_type";
 
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
 bool GetCustomFramePrefDefault() {
 #if defined(USE_OZONE)
   if (features::IsUsingOzonePlatform()) {
@@ -51,10 +54,13 @@
 
 void RegisterBrowserViewProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   registry->RegisterBooleanPref(prefs::kUseCustomChromeFrame,
                                 GetCustomFramePrefDefault());
-#endif  // defined(OS_LINUX) && defined(!OS_CHROMEOS)
+#endif  // (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) &&
+        // defined(!OS_CHROMEOS)
 }
 
 void MigrateBrowserTabStripPrefs(PrefService* prefs) {
diff --git a/chrome/browser/ui/browser_window_state.cc b/chrome/browser/ui/browser_window_state.cc
index 6a677ad..40c23a4 100644
--- a/chrome/browser/ui/browser_window_state.cc
+++ b/chrome/browser/ui/browser_window_state.cc
@@ -11,6 +11,7 @@
 #include "base/command_line.h"
 #include "base/macros.h"
 #include "base/strings/string_number_conversions.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/defaults.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sessions/session_service.h"
@@ -78,7 +79,7 @@
 std::string GetWindowName(const Browser* browser) {
   switch (browser->type()) {
     case Browser::TYPE_NORMAL:
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     case Browser::TYPE_CUSTOM_TAB:
 #endif
       return prefs::kBrowserWindowPlacement;
diff --git a/chrome/browser/ui/chrome_pages.cc b/chrome/browser/ui/chrome_pages.cc
index 75a43628..6561b096 100644
--- a/chrome/browser/ui/chrome_pages.cc
+++ b/chrome/browser/ui/chrome_pages.cc
@@ -18,6 +18,7 @@
 #include "base/strings/stringprintf.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
@@ -59,7 +60,7 @@
 #include "ui/base/window_open_disposition.h"
 #include "url/url_util.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "base/metrics/histogram_functions.h"
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
 #include "chrome/browser/ui/webui/settings/chromeos/app_management/app_management_uma.h"
@@ -106,7 +107,7 @@
   ShowSingletonTabIgnorePathOverwriteNTP(browser, url);
 }
 
-#if defined(OS_CHROMEOS) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+#if BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
 void LaunchReleaseNotesImpl(Profile* profile,
                             apps::mojom::LaunchSource source) {
@@ -126,7 +127,7 @@
 // is created.
 void ShowHelpImpl(Browser* browser, Profile* profile, HelpSource source) {
   base::RecordAction(UserMetricsAction("ShowHelpTab"));
-#if defined(OS_CHROMEOS) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+#if BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
   auto app_launch_source = apps::mojom::LaunchSource::kUnknown;
   switch (source) {
     case HELP_SOURCE_KEYBOARD:
@@ -155,7 +156,7 @@
     case HELP_SOURCE_MENU:
       url = GURL(kChromeHelpViaMenuURL);
       break;
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     case HELP_SOURCE_WEBUI:
       url = GURL(kChromeHelpViaWebUIURL);
       break;
@@ -280,7 +281,7 @@
 }
 
 void LaunchReleaseNotes(Profile* profile, apps::mojom::LaunchSource source) {
-#if defined(OS_CHROMEOS) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+#if BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
   LaunchReleaseNotesImpl(profile, source);
 #endif
 }
@@ -294,7 +295,7 @@
 }
 
 void ShowSlow(Browser* browser) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   ShowSingletonTab(browser, GURL(kChromeUISlowURL));
 #endif
 }
@@ -322,7 +323,7 @@
 }
 
 void ShowSettingsSubPage(Browser* browser, const std::string& sub_page) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   ShowSettingsSubPageForProfile(browser->profile(), sub_page);
 #else
   ShowSettingsSubPageInTabbedBrowser(browser, sub_page);
@@ -331,7 +332,7 @@
 
 void ShowSettingsSubPageForProfile(Profile* profile,
                                    const std::string& sub_page) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // OS settings sub-pages are handled else where and should never be
   // encountered here.
   DCHECK(!chromeos::settings::IsOSSettingsSubPage(sub_page)) << sub_page;
@@ -419,7 +420,7 @@
   ShowSettingsSubPage(browser, kSearchEnginesSubPage);
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 void ShowEnterpriseManagementPageInTabbedBrowser(Browser* browser) {
   // Management shows in a tab because it has a "back" arrow that takes the
   // user to the Chrome browser about page, which is part of browser settings.
@@ -478,7 +479,7 @@
 }
 #endif
 
-#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+#if !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
 void ShowBrowserSignin(Browser* browser,
                        signin_metrics::AccessPoint access_point,
                        signin::ConsentLevel consent_level) {
diff --git a/chrome/browser/ui/chrome_pages.h b/chrome/browser/ui/chrome_pages.h
index 9b04495..ea710d9c 100644
--- a/chrome/browser/ui/chrome_pages.h
+++ b/chrome/browser/ui/chrome_pages.h
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 #include "url/gurl.h"
@@ -18,7 +19,7 @@
 #include "chrome/browser/signin/signin_promo.h"
 #endif
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/printing/print_management/print_management_uma.h"
 #include "chrome/browser/ui/webui/settings/chromeos/app_management/app_management_uma.h"
 #endif
@@ -43,7 +44,7 @@
   // WebUI (the "About" page).
   HELP_SOURCE_WEBUI,
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // WebUI (the OS "About" page).
   HELP_SOURCE_WEBUI_CHROME_OS,
 #endif
@@ -144,7 +145,7 @@
 void ShowAboutChrome(Browser* browser);
 void ShowSearchEngineSettings(Browser* browser);
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // Shows the enterprise management info page in a browser tab.
 void ShowEnterpriseManagementPageInTabbedBrowser(Browser* browser);
 
@@ -163,7 +164,7 @@
 void ShowScanningApp(Profile* profile);
 #endif
 
-#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+#if !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
 // Initiates signin in a new browser tab.
 void ShowBrowserSignin(Browser* browser,
                        signin_metrics::AccessPoint access_point,
diff --git a/chrome/browser/ui/content_settings/framebust_block_browsertest.cc b/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
index 05ca8e5..217ee1a 100644
--- a/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
+++ b/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
@@ -13,6 +13,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/blocked_content/framebust_block_tab_helper.h"
@@ -41,7 +42,7 @@
 #include "ui/events/event.h"
 #include "url/gurl.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/web_applications/system_web_app_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #endif
@@ -223,7 +224,7 @@
 #define MAYBE_ManageButtonClicked ManageButtonClicked
 #endif
 IN_PROC_BROWSER_TEST_F(FramebustBlockBrowserTest, MAYBE_ManageButtonClicked) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   web_app::WebAppProvider::Get(browser()->profile())
       ->system_web_app_manager()
       .InstallSystemAppsForTesting();
diff --git a/chrome/browser/ui/exclusive_access/fullscreen_controller_browsertest.cc b/chrome/browser/ui/exclusive_access/fullscreen_controller_browsertest.cc
index 4989ed2..057f619 100644
--- a/chrome/browser/ui/exclusive_access/fullscreen_controller_browsertest.cc
+++ b/chrome/browser/ui/exclusive_access/fullscreen_controller_browsertest.cc
@@ -7,6 +7,7 @@
 #include "base/test/test_mock_time_task_runner.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -428,7 +429,7 @@
   // Test Normal state <--> Browser fullscreen mode <--> Tab fullscreen mode.
   ToggleBrowserFullscreen();
   EXPECT_TRUE(context->IsFullscreen());
-#if defined(OS_MAC) || defined(OS_CHROMEOS)
+#if defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
   bool should_show_top_ui = true;
 #else
   bool should_show_top_ui = false;
@@ -437,7 +438,7 @@
 
   EnterActiveTabFullscreen();
   EXPECT_TRUE(context->IsFullscreen());
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   EXPECT_TRUE(browser()->window()->IsToolbarVisible());
 #else
   EXPECT_FALSE(browser()->window()->IsToolbarVisible());
diff --git a/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc b/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
index 5bb0a4d..612bbdd 100644
--- a/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
+++ b/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
@@ -5,6 +5,7 @@
 #include "base/command_line.h"
 #include "base/feature_list.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -30,10 +31,10 @@
 #include "ui/display/screen_base.h"
 #include "ui/display/test/test_screen.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/shell.h"
 #include "ui/display/test/display_manager_test_api.h"
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 using url::kAboutBlankURL;
 using content::WebContents;
@@ -465,7 +466,7 @@
   ASSERT_TRUE(IsMouseLocked());
 }
 
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA)
+#if (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) && defined(USE_AURA)
 // These are flaky on linux_aura.
 // http://crbug.com/163931
 #define MAYBE_TestTabExitsMouseLockOnNavigation \
@@ -518,7 +519,8 @@
   ASSERT_FALSE(IsMouseLocked());
 }
 
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA) || \
+#if (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) && \
+        defined(USE_AURA) ||                                \
     defined(OS_WIN) && defined(NDEBUG)
 // TODO(erg): linux_aura bringup: http://crbug.com/163931
 // Test is flaky on Windows: https://crbug.com/1124492
@@ -641,7 +643,7 @@
 // where the window server's async handling of the fullscreen window state may
 // transition the window into fullscreen on the actual (non-mocked) display
 // bounds before or after the window bounds checks, yielding flaky results.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 #define MAYBE_FullscreenOnSecondDisplay DISABLED_FullscreenOnSecondDisplay
 #else
 #define MAYBE_FullscreenOnSecondDisplay FullscreenOnSecondDisplay
@@ -651,7 +653,7 @@
 IN_PROC_BROWSER_TEST_F(ExperimentalFullscreenControllerInteractiveTest,
                        MAYBE_FullscreenOnSecondDisplay) {
   // Updates the display configuration to add a secondary display.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
       .UpdateDisplay("100+100-801x802,901+100-801x802");
 #else
@@ -662,7 +664,7 @@
   screen.display_list().AddDisplay({2, gfx::Rect(901, 100, 801, 802)},
                                    display::DisplayList::Type::NOT_PRIMARY);
   display::Screen::SetScreenInstance(&screen);
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   ASSERT_EQ(2, display::Screen::GetScreen()->GetNumDisplays());
 
   // Move the window to the first display (on the left).
@@ -694,11 +696,11 @@
   EXPECT_EQ(true, EvalJs(tab, request_fullscreen_script));
   enter_fullscreen_observer.Wait();
   EXPECT_TRUE(browser()->window()->IsFullscreen());
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   EXPECT_EQ(gfx::Rect(801, 0, 801, 802), browser()->window()->GetBounds());
 #else
   EXPECT_EQ(gfx::Rect(901, 100, 801, 802), browser()->window()->GetBounds());
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   // Execute JS to exit fullscreen.
   FullscreenNotificationObserver exit_fullscreen_observer(browser());
@@ -713,7 +715,7 @@
   EXPECT_FALSE(browser()->window()->IsFullscreen());
   EXPECT_EQ(original_bounds, browser()->window()->GetBounds());
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   display::Screen::SetScreenInstance(original_screen);
 #endif  // !OS_CHROMEOS
 }
@@ -725,7 +727,7 @@
 // transition the window into fullscreen on the actual (non-mocked) display
 // bounds before or after the window bounds checks, yielding flaky results.
 // TODO(msw): Parameterize the maximized state and combine with the test above.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 #define MAYBE_FullscreenOnSecondDisplayMaximized \
   DISABLED_FullscreenOnSecondDisplayMaximized
 #else
@@ -738,7 +740,7 @@
 IN_PROC_BROWSER_TEST_F(ExperimentalFullscreenControllerInteractiveTest,
                        MAYBE_FullscreenOnSecondDisplayMaximized) {
   // Updates the display configuration to add a secondary display.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
       .UpdateDisplay("100+100-801x802,901+100-801x802");
 #else
@@ -749,7 +751,7 @@
   screen.display_list().AddDisplay({2, gfx::Rect(901, 100, 801, 802)},
                                    display::DisplayList::Type::NOT_PRIMARY);
   display::Screen::SetScreenInstance(&screen);
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   ASSERT_EQ(2, display::Screen::GetScreen()->GetNumDisplays());
 
   // Move the window to the first display (on the left) and maximize it.
@@ -783,11 +785,11 @@
   EXPECT_EQ(true, EvalJs(tab, request_fullscreen_script));
   enter_fullscreen_observer.Wait();
   EXPECT_TRUE(browser()->window()->IsFullscreen());
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   EXPECT_EQ(gfx::Rect(801, 0, 801, 802), browser()->window()->GetBounds());
 #else
   EXPECT_EQ(gfx::Rect(901, 100, 801, 802), browser()->window()->GetBounds());
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   // Execute JS to exit fullscreen.
   FullscreenNotificationObserver exit_fullscreen_observer(browser());
@@ -803,7 +805,7 @@
   EXPECT_EQ(maximized_bounds, browser()->window()->GetBounds());
   EXPECT_TRUE(browser()->window()->IsMaximized());
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   display::Screen::SetScreenInstance(original_screen);
 #endif  // !OS_CHROMEOS
 }
@@ -818,14 +820,14 @@
 #endif
 IN_PROC_BROWSER_TEST_F(ExperimentalFullscreenControllerInteractiveTest,
                        MAYBE_FullscreenOnScreensChange) {
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   // Install a mock screen object to be monitored by a new web contents.
   display::Screen* original_screen = display::Screen::GetScreen();
   display::ScreenBase screen;
   screen.display_list().AddDisplay({1, gfx::Rect(100, 100, 801, 802)},
                                    display::DisplayList::Type::PRIMARY);
   display::Screen::SetScreenInstance(&screen);
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   // Open a new foreground tab that will observe the mock screen object.
   ASSERT_TRUE(embedded_test_server()->Start());
@@ -852,18 +854,18 @@
   FullscreenNotificationObserver fullscreen_observer(browser());
 
   // Update the display configuration to trigger window.onscreenschange.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
       .UpdateDisplay("100+100-801x802,901+100-801x802");
 #else
   screen.display_list().AddDisplay({2, gfx::Rect(901, 100, 801, 802)},
                                    display::DisplayList::Type::NOT_PRIMARY);
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   fullscreen_observer.Wait();
   EXPECT_TRUE(browser()->window()->IsFullscreen());
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   display::Screen::SetScreenInstance(original_screen);
 #endif  // !OS_CHROMEOS
 }
diff --git a/chrome/browser/ui/extensions/app_launch_params.cc b/chrome/browser/ui/extensions/app_launch_params.cc
index ee3bfb5..2e708fa 100644
--- a/chrome/browser/ui/extensions/app_launch_params.cc
+++ b/chrome/browser/ui/extensions/app_launch_params.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/extensions/app_launch_params.h"
 
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/launch_utils.h"
 #include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/profiles/profile.h"
@@ -11,7 +12,7 @@
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/arc/arc_util.h"
 #include "components/arc/arc_util.h"
 #endif
diff --git a/chrome/browser/ui/extensions/application_launch.cc b/chrome/browser/ui/extensions/application_launch.cc
index b827295..e6bd675 100644
--- a/chrome/browser/ui/extensions/application_launch.cc
+++ b/chrome/browser/ui/extensions/application_launch.cc
@@ -16,6 +16,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/app_mode/app_mode_utils.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/launch_utils.h"
@@ -192,7 +193,7 @@
   if (chrome::IsRunningInForcedAppMode())
     return ui::SHOW_STATE_FULLSCREEN;
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // In ash, LAUNCH_TYPE_FULLSCREEN launches in a maximized app window and
   // LAUNCH_TYPE_WINDOW launches in a default app window.
   extensions::LaunchType launch_type =
@@ -281,7 +282,7 @@
     tab_helper->SetAppId(extension->id());
   }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // In ash, LAUNCH_FULLSCREEN launches in the OpenApplicationWindow function
   // i.e. it should not reach here.
   DCHECK(launch_type != extensions::LAUNCH_TYPE_FULLSCREEN);
@@ -294,7 +295,7 @@
       !browser->window()->IsFullscreen()) {
     chrome::ToggleFullscreenMode(browser);
   }
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   return contents;
 }
 
diff --git a/chrome/browser/ui/extensions/application_launch_browsertest.cc b/chrome/browser/ui/extensions/application_launch_browsertest.cc
index 5705825..4d17902 100644
--- a/chrome/browser/ui/extensions/application_launch_browsertest.cc
+++ b/chrome/browser/ui/extensions/application_launch_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
@@ -14,7 +15,7 @@
 #include "content/public/test/browser_test_utils.h"
 #include "url/gurl.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/shell.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/extensions/application_launch.h"
@@ -24,7 +25,7 @@
 #include "ui/display/screen.h"
 #include "ui/display/test/display_manager_test_api.h"
 #include "ui/gfx/native_widget_types.h"
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 class ApplicationLaunchBrowserTest : public InProcessBrowserTest {
  public:
@@ -83,7 +84,7 @@
   EXPECT_TRUE(app_browser->is_focus_mode());
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_F(ApplicationLaunchBrowserTest, CreateWindowInDisplay) {
   display::Screen* screen = display::Screen::GetScreen();
   // Create 2 displays.
@@ -110,4 +111,4 @@
   gfx::NativeWindow window2 = browser2->window()->GetNativeWindow();
   EXPECT_EQ(display2, screen->GetDisplayNearestWindow(window2).id());
 }
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/extensions/extension_enable_flow.cc b/chrome/browser/ui/extensions/extension_enable_flow.cc
index 7c1ea16..e18e0f1 100644
--- a/chrome/browser/ui/extensions/extension_enable_flow.cc
+++ b/chrome/browser/ui/extensions/extension_enable_flow.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/bind.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_util.h"
@@ -18,9 +19,9 @@
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_system.h"
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ui/user_manager.h"
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
 #include "chrome/browser/supervised_user/supervised_user_service.h"
@@ -131,10 +132,10 @@
   }
 
   if (profiles::IsProfileLocked(profile_->GetPath())) {
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
     UserManager::Show(base::FilePath(),
                       profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
     return;
   }
 
diff --git a/chrome/browser/ui/extensions/extension_install_ui_default.cc b/chrome/browser/ui/extensions/extension_install_ui_default.cc
index 6b740f0..71626e35 100644
--- a/chrome/browser/ui/extensions/extension_install_ui_default.cc
+++ b/chrome/browser/ui/extensions/extension_install_ui_default.cc
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
@@ -30,7 +31,7 @@
 #include "extensions/browser/install/crx_install_error.h"
 #include "extensions/common/extension.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ui/extensions/extension_installed_notification.h"
 #else
 #include "chrome/common/url_constants.h"
@@ -89,11 +90,11 @@
       return;
     }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     ExtensionInstalledNotification::Show(extension.get(), current_profile);
-#else  // defined(OS_CHROMEOS)
+#else   // BUILDFLAG(IS_CHROMEOS_ASH)
     OpenAppInstalledUI(extension->id());
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
     return;
   }
 
@@ -118,7 +119,7 @@
 }
 
 void ExtensionInstallUIDefault::OpenAppInstalledUI(const std::string& app_id) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Notification always enabled on ChromeOS, so always handled in
   // OnInstallSuccess.
   NOTREACHED();
diff --git a/chrome/browser/ui/extensions/hosted_app_browser_controller.cc b/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
index ec3184e..cb11a3d 100644
--- a/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/extensions/hosted_app_browser_controller.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/extensions/extension_util.h"
@@ -71,7 +72,7 @@
 gfx::ImageSkia HostedAppBrowserController::GetWindowAppIcon() const {
   // TODO(calamity): Use the app name to retrieve the app icon without using the
   // extensions tab helper to make icon load more immediate.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon) &&
       apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(
           browser()->profile())) {
diff --git a/chrome/browser/ui/extensions/hosted_app_browsertest.cc b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
index 2e5fb06..6cad0b1 100644
--- a/chrome/browser/ui/extensions/hosted_app_browsertest.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
@@ -22,6 +22,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/apps/app_service/app_service_test.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
@@ -463,7 +464,7 @@
   EXPECT_FALSE(app->from_bookmark());
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_P(HostedAppTest, LoadIcon) {
   if (!base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon))
     return;
diff --git a/chrome/browser/ui/extensions/settings_api_bubble_helpers.cc b/chrome/browser/ui/extensions/settings_api_bubble_helpers.cc
index bfe4304..f6ad4d8 100644
--- a/chrome/browser/ui/extensions/settings_api_bubble_helpers.cc
+++ b/chrome/browser/ui/extensions/settings_api_bubble_helpers.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/extensions/ntp_overridden_bubble_delegate.h"
 #include "chrome/browser/extensions/settings_api_bubble_delegate.h"
 #include "chrome/browser/extensions/settings_api_helpers.h"
@@ -31,7 +32,7 @@
 
 // Whether the NTP post-install UI is enabled. By default, this is limited to
 // Windows, Mac, and ChromeOS, but can be overridden for testing.
-#if defined(OS_WIN) || defined(OS_MAC) || defined(OS_CHROMEOS)
+#if defined(OS_WIN) || defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
 bool g_ntp_post_install_ui_enabled = true;
 #else
 bool g_ntp_post_install_ui_enabled = false;
diff --git a/chrome/browser/ui/find_bar/find_bar_host_browsertest.cc b/chrome/browser/ui/find_bar/find_bar_host_browsertest.cc
index 2238f85..b4ccbc2 100644
--- a/chrome/browser/ui/find_bar/find_bar_host_browsertest.cc
+++ b/chrome/browser/ui/find_bar/find_bar_host_browsertest.cc
@@ -12,6 +12,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "build/buildflag.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -538,7 +539,7 @@
 
 // Search Back and Forward on a single occurrence.
 // TODO(crbug.com/1119361): Test is flaky on ChromeOS.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #define MAYBE_SingleOccurrence DISABLED_SingleOccurrence
 #else
 #define MAYBE_SingleOccurrence SingleOccurrence
diff --git a/chrome/browser/ui/fullscreen_keyboard_browsertest_base.cc b/chrome/browser/ui/fullscreen_keyboard_browsertest_base.cc
index 4af1d9e..78fb886 100644
--- a/chrome/browser/ui/fullscreen_keyboard_browsertest_base.cc
+++ b/chrome/browser/ui/fullscreen_keyboard_browsertest_base.cc
@@ -10,6 +10,7 @@
 #include "base/macros.h"
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -187,7 +188,7 @@
   // On MACOSX, Command + Control + F is used.
   ASSERT_TRUE(ui_test_utils::SendKeyPressSync(GetActiveBrowser(), ui::VKEY_F,
                                               true, false, false, true));
-#elif defined(OS_CHROMEOS)
+#elif BUILDFLAG(IS_CHROMEOS_ASH)
   // A dedicated fullscreen key is used on Chrome OS, so send a fullscreen
   // command directly instead, to avoid constructing the key press.
   ASSERT_TRUE(chrome::ExecuteCommand(GetActiveBrowser(), IDC_FULLSCREEN));
diff --git a/chrome/browser/ui/global_error/global_error_browsertest.cc b/chrome/browser/ui/global_error/global_error_browsertest.cc
index d144578..46c8fb2 100644
--- a/chrome/browser/ui/global_error/global_error_browsertest.cc
+++ b/chrome/browser/ui/global_error/global_error_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_disabled_ui.h"
@@ -41,7 +42,7 @@
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/feature_switch.h"
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/signin/signin_global_error.h"
 #include "chrome/browser/signin/signin_global_error_factory.h"
 #endif
@@ -201,7 +202,7 @@
         prefs::kRecoveryComponentNeedsElevation, true);
     waiter.Wait();
     ShowPendingError(browser());
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   } else if (name == "SigninGlobalError") {
     SigninGlobalErrorFactory::GetForProfile(profile)->ShowBubbleView(browser());
 #endif
@@ -246,7 +247,7 @@
 #endif
 
 // Signin global errors never happon on ChromeOS.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_F(GlobalErrorBubbleTest, InvokeUi_SigninGlobalError) {
   ShowAndVerifyUi();
 }
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service_factory.cc b/chrome/browser/ui/global_media_controls/media_notification_service_factory.cc
index 6b287ea..31dd097 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service_factory.cc
+++ b/chrome/browser/ui/global_media_controls/media_notification_service_factory.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/memory/singleton.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/global_media_controls/media_notification_service.h"
@@ -35,7 +36,7 @@
 KeyedService* MediaNotificationServiceFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
   bool show_from_all_profiles = false;
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   show_from_all_profiles = true;
 #endif
   return new MediaNotificationService(Profile::FromBrowserContext(context),
diff --git a/chrome/browser/ui/keyboard_lock_interactive_browsertest.cc b/chrome/browser/ui/keyboard_lock_interactive_browsertest.cc
index 3608b25b..decba42 100644
--- a/chrome/browser/ui/keyboard_lock_interactive_browsertest.cc
+++ b/chrome/browser/ui/keyboard_lock_interactive_browsertest.cc
@@ -5,6 +5,7 @@
 #include "base/macros.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/fullscreen_keyboard_browsertest_base.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -261,7 +262,7 @@
 
 // https://crbug.com/1108391 Flakey on ChromeOS.
 // https://crbug.com/1121172 Also flaky on Lacros.
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #define MAYBE_SubsequentLockCallSupersedesPreviousCall \
   DISABLED_SubsequentLockCallSupersedesPreviousCall
 #else
diff --git a/chrome/browser/ui/managed_ui.cc b/chrome/browser/ui/managed_ui.cc
index bdbb289d..60931ee 100644
--- a/chrome/browser/ui/managed_ui.cc
+++ b/chrome/browser/ui/managed_ui.cc
@@ -5,13 +5,14 @@
 #include "chrome/browser/ui/managed_ui.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/enterprise/util/managed_browser_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/management_ui_handler.h"
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
@@ -22,7 +23,7 @@
 namespace chrome {
 
 bool ShouldDisplayManagedUi(Profile* profile) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Don't show the UI in demo mode.
   if (chromeos::DemoSession::IsDeviceInDemoMode())
     return false;
@@ -63,7 +64,7 @@
   return l10n_util::GetStringFUTF16(string_id, replacements, nullptr);
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 base::string16 GetDeviceManagedUiWebUILabel() {
   policy::BrowserPolicyConnectorChromeOS* connector =
       g_browser_process->platform_part()->browser_policy_connector_chromeos();
diff --git a/chrome/browser/ui/managed_ui.h b/chrome/browser/ui/managed_ui.h
index f4a85cbd..21cd65d 100644
--- a/chrome/browser/ui/managed_ui.h
+++ b/chrome/browser/ui/managed_ui.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_MANAGED_UI_H_
 
 #include "base/strings/string16.h"
+#include "build/chromeos_buildflags.h"
 
 class Profile;
 
@@ -30,7 +31,7 @@
 // is managed. These strings contain HTML for an <a> element.
 base::string16 GetManagedUiWebUILabel(Profile* profile);
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // The label for the WebUI footnote for Managed UI indicating that the device
 // is mananged. These strings contain HTML for an <a> element.
 base::string16 GetDeviceManagedUiWebUILabel();
diff --git a/chrome/browser/ui/managed_ui_browsertest.cc b/chrome/browser/ui/managed_ui_browsertest.cc
index 44a9fc56e..640745b 100644
--- a/chrome/browser/ui/managed_ui_browsertest.cc
+++ b/chrome/browser/ui/managed_ui_browsertest.cc
@@ -6,6 +6,7 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -15,7 +16,7 @@
 #include "content/public/test/browser_test.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
 #endif
 
@@ -51,7 +52,7 @@
                  base::Value("hello world"), nullptr);
   provider()->UpdateChromePolicy(policy_map);
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   EXPECT_FALSE(chrome::ShouldDisplayManagedUi(browser()->profile()));
 #else
   EXPECT_TRUE(chrome::ShouldDisplayManagedUi(browser()->profile()));
@@ -94,7 +95,7 @@
       chrome::GetManagedUiWebUILabel(profile_with_domain.get()));
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 using ManagedUiTestCros = policy::DevicePolicyCrosBrowserTest;
 IN_PROC_BROWSER_TEST_F(ManagedUiTestCros, GetManagedUiWebUILabel) {
   EXPECT_EQ(base::ASCIIToUTF16("Your <a target=\"_blank\" "
diff --git a/chrome/browser/ui/omnibox/omnibox_pedals_unittest.cc b/chrome/browser/ui/omnibox/omnibox_pedals_unittest.cc
index aa4f66c4..e176bd1 100644
--- a/chrome/browser/ui/omnibox/omnibox_pedals_unittest.cc
+++ b/chrome/browser/ui/omnibox/omnibox_pedals_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "base/environment.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/chromeos_buildflags.h"
 #include "components/omnibox/browser/autocomplete_input.h"
 #include "components/omnibox/browser/mock_autocomplete_provider_client.h"
 #include "components/omnibox/browser/omnibox_pedal_provider.h"
@@ -561,7 +562,7 @@
     OmniboxPedalProvider provider(client);
 
     EXPECT_EQ(provider.FindPedalMatch(input, base::UTF8ToUTF16("")), nullptr);
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     // TODO(orinj): Get ChromeOS to use the right dataset, but for now make this
     //  a soft failure so as to not block all other platforms. To ensure this
     //  is not going to cause failure in production, still test that English
diff --git a/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc b/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
index 7ba657d..e1142f3 100644
--- a/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
+++ b/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
@@ -17,6 +17,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/history/history_service_factory.h"
@@ -432,7 +433,7 @@
   ASSERT_NO_FATAL_FAILURE(SendKey(ui::VKEY_X, kCtrlOrCmdMask));
   EXPECT_EQ(ASCIIToUTF16("Hello "), omnibox_view->GetText());
 
-#if !defined(OS_CHROMEOS) && !defined(OS_MAC)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_MAC)
   // Try alt-f4 to close the browser.
   ExpectBrowserClosed(browser(), ui::VKEY_F4, ui::EF_ALT_DOWN);
 #endif
@@ -472,7 +473,7 @@
   EXPECT_EQ(ASCIIToUTF16("Hello world"), omnibox_view->GetText());
   EXPECT_TRUE(omnibox_view->IsSelectAll());
 
-#if !defined(OS_CHROMEOS) && !defined(OS_MAC)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_MAC)
   // Try alt-f4 to close the popup.
   ExpectBrowserClosed(popup, ui::VKEY_F4, ui::EF_ALT_DOWN);
 #endif
diff --git a/chrome/browser/ui/page_info/page_info_unittest.cc b/chrome/browser/ui/page_info/page_info_unittest.cc
index f48de8b..6e8af4b1 100644
--- a/chrome/browser/ui/page_info/page_info_unittest.cc
+++ b/chrome/browser/ui/page_info/page_info_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/infobars/mock_infobar_service.h"
@@ -897,7 +898,7 @@
             page_info()->site_identity_status());
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 TEST_F(PageInfoTest, HTTPSPolicyCertConnection) {
   security_level_ = security_state::SECURE_WITH_POLICY_INSTALLED_CERT;
   visible_security_state_.url = GURL("https://scheme-is-cryptographic.test");
diff --git a/chrome/browser/ui/passwords/bubble_controllers/save_update_bubble_controller_unittest.cc b/chrome/browser/ui/passwords/bubble_controllers/save_update_bubble_controller_unittest.cc
index d8ae8d0..54daf8d 100644
--- a/chrome/browser/ui/passwords/bubble_controllers/save_update_bubble_controller_unittest.cc
+++ b/chrome/browser/ui/passwords/bubble_controllers/save_update_bubble_controller_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/password_manager/password_store_factory.h"
 #include "chrome/browser/ui/passwords/passwords_model_delegate_mock.h"
 #include "chrome/test/base/testing_profile.h"
@@ -393,14 +394,14 @@
                                         pending_password().password_value));
   controller()->OnSaveClicked();
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   EXPECT_FALSE(controller()->ReplaceToShowPromotionIfNeeded());
 #else
   EXPECT_TRUE(controller()->ReplaceToShowPromotionIfNeeded());
 #endif
 }
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 TEST_F(SaveUpdateBubbleControllerTest, SignInPromoCancel) {
   base::HistogramTester histogram_tester;
   PretendPasswordWaiting();
@@ -432,7 +433,7 @@
   EXPECT_FALSE(prefs()->GetBoolean(
       password_manager::prefs::kWasSignInPasswordPromoClicked));
 }
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Verify that URL keyed metrics are properly recorded.
 TEST_F(SaveUpdateBubbleControllerTest, RecordUKMs) {
diff --git a/chrome/browser/ui/passwords/bubble_controllers/sign_in_promo_bubble_controller_unittest.cc b/chrome/browser/ui/passwords/bubble_controllers/sign_in_promo_bubble_controller_unittest.cc
index bf7fb019..d4af1694 100644
--- a/chrome/browser/ui/passwords/bubble_controllers/sign_in_promo_bubble_controller_unittest.cc
+++ b/chrome/browser/ui/passwords/bubble_controllers/sign_in_promo_bubble_controller_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/passwords/bubble_controllers/sign_in_promo_bubble_controller.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/ui/passwords/passwords_model_delegate_mock.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/password_manager/core/browser/password_form.h"
@@ -66,7 +67,7 @@
       new SignInPromoBubbleController(mock_delegate_->AsWeakPtr()));
 }
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 TEST_F(SignInPromoBubbleControllerTest, SignInPromoOK) {
   Init();
   AccountInfo account;
diff --git a/chrome/browser/ui/prefs/pref_watcher.cc b/chrome/browser/ui/prefs/pref_watcher.cc
index 0b4aece..919746a 100644
--- a/chrome/browser/ui/prefs/pref_watcher.cc
+++ b/chrome/browser/ui/prefs/pref_watcher.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
@@ -16,7 +17,7 @@
 #include "components/language/core/browser/pref_names.h"
 #include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/public/cpp/ash_pref_names.h"
 #endif
 
@@ -55,7 +56,7 @@
     prefs::kWebkitTabsToLinks,
     prefs::kWebKitTextAreasAreResizable,
     prefs::kWebKitWebSecurityEnabled,
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     ash::prefs::kAccessibilityFocusHighlightEnabled,
 #else
     prefs::kAccessibilityFocusHighlightEnabled,
diff --git a/chrome/browser/ui/prefs/prefs_tab_helper.cc b/chrome/browser/ui/prefs/prefs_tab_helper.cc
index 1d2032c..45f928b 100644
--- a/chrome/browser/ui/prefs/prefs_tab_helper.cc
+++ b/chrome/browser/ui/prefs/prefs_tab_helper.cc
@@ -19,6 +19,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/font_pref_change_notifier_factory.h"
@@ -135,7 +136,7 @@
     {prefs::kWebKitCursiveFontFamily, IDS_CURSIVE_FONT_FAMILY},
     {prefs::kWebKitFantasyFontFamily, IDS_FANTASY_FONT_FAMILY},
     {prefs::kWebKitPictographFontFamily, IDS_PICTOGRAPH_FONT_FAMILY},
-#if defined(OS_CHROMEOS) || defined(OS_MAC) || defined(OS_WIN)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_MAC) || defined(OS_WIN)
     {prefs::kWebKitStandardFontFamilyJapanese,
      IDS_STANDARD_FONT_FAMILY_JAPANESE},
     {prefs::kWebKitFixedFontFamilyJapanese, IDS_FIXED_FONT_FAMILY_JAPANESE},
@@ -165,7 +166,7 @@
     {prefs::kWebKitCursiveFontFamilyTraditionalHan,
      IDS_CURSIVE_FONT_FAMILY_TRADITIONAL_HAN},
 #endif
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     {prefs::kWebKitStandardFontFamilyArabic, IDS_STANDARD_FONT_FAMILY_ARABIC},
     {prefs::kWebKitSerifFontFamilyArabic, IDS_SERIF_FONT_FAMILY_ARABIC},
     {prefs::kWebKitSansSerifFontFamilyArabic,
diff --git a/chrome/browser/ui/profile_error_browsertest.cc b/chrome/browser/ui/profile_error_browsertest.cc
index 86905b0f..44f3b59 100644
--- a/chrome/browser/ui/profile_error_browsertest.cc
+++ b/chrome/browser/ui/profile_error_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/strings/string_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/simple_message_box_internal.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -71,7 +72,7 @@
   const bool do_corrupt_;
 };
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // Disable the test on chromos since kernel controls the user profile thus we
 // won't be able to corrupt it.
 #define MAYBE_CorruptedProfile DISABLED_CorruptedProfile
diff --git a/chrome/browser/ui/renderer_event_injection_browsertest.cc b/chrome/browser/ui/renderer_event_injection_browsertest.cc
index c41ccd8..fb82d886 100644
--- a/chrome/browser/ui/renderer_event_injection_browsertest.cc
+++ b/chrome/browser/ui/renderer_event_injection_browsertest.cc
@@ -7,6 +7,7 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "build/chromeos_buildflags.h"
 #include "cc/base/switches.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -52,7 +53,7 @@
     command_line->AppendSwitch(switches::kDisableRendererBackgrounding);
     command_line->AppendSwitch(cc::switches::kEnableGpuBenchmarking);
     // kHostWindowBounds is unique to ChromeOS.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     command_line->AppendSwitchASCII(switches::kHostWindowBounds, GetParam());
 #endif
     embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
@@ -123,7 +124,7 @@
   rwh->RemoveInputEventObserver(&touch_observer);
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // This configures the display in various interesting ways for ChromeOS. In
 // particular, it tests rotation "/r" and a scale factor of 2 "*2".
 INSTANTIATE_TEST_SUITE_P(
diff --git a/chrome/browser/ui/sad_tab.cc b/chrome/browser/ui/sad_tab.cc
index d8dc3b0e..a5c6d700 100644
--- a/chrome/browser/ui/sad_tab.cc
+++ b/chrome/browser/ui/sad_tab.cc
@@ -26,7 +26,7 @@
 #include "content/public/browser/web_contents.h"
 #include "ui/base/l10n/l10n_util.h"
 
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chrome/browser/memory/oom_memory_details.h"
 #endif
 
@@ -91,7 +91,7 @@
   switch (status) {
     case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
     case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
     case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
 #endif
     case base::TERMINATION_STATUS_PROCESS_CRASHED:
@@ -117,7 +117,7 @@
   if (!is_repeatedly_crashing_)
     return IDS_SAD_TAB_TITLE;
   switch (kind_) {
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
     case SAD_TAB_KIND_KILLED_BY_OOM:
       return IDS_SAD_TAB_RELOAD_TITLE;
 #endif
@@ -139,7 +139,7 @@
 
 int SadTab::GetInfoMessage() {
   switch (kind_) {
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
     case SAD_TAB_KIND_KILLED_BY_OOM:
       return IDS_KILLED_TAB_BY_OOM_MESSAGE;
 #endif
@@ -176,7 +176,7 @@
     return std::vector<int>();
 
   switch (kind_) {
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
     case SAD_TAB_KIND_KILLED_BY_OOM:
       return std::vector<int>();
 #endif
@@ -218,7 +218,7 @@
     case SAD_TAB_KIND_OOM:
       UMA_SAD_TAB_COUNTER("Tabs.SadTab.OomDisplayed");
       break;
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
     case SAD_TAB_KIND_KILLED_BY_OOM:
       UMA_SAD_TAB_COUNTER("Tabs.SadTab.KillDisplayed.OOM");
       FALLTHROUGH;
@@ -280,7 +280,7 @@
     case SAD_TAB_KIND_OOM:
       UMA_SAD_TAB_COUNTER("Tabs.SadTab.OomCreated");
       break;
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
     case SAD_TAB_KIND_KILLED_BY_OOM:
       UMA_SAD_TAB_COUNTER("Tabs.SadTab.KillCreated.OOM");
       {
diff --git a/chrome/browser/ui/sad_tab_helper.cc b/chrome/browser/ui/sad_tab_helper.cc
index 589e3a5..3f264455 100644
--- a/chrome/browser/ui/sad_tab_helper.cc
+++ b/chrome/browser/ui/sad_tab_helper.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/sad_tab_helper.h"
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/lifetime/browser_shutdown.h"
 #include "chrome/browser/ui/sad_tab.h"
 #include "content/public/browser/navigation_handle.h"
@@ -15,7 +16,7 @@
 
 SadTabKind SadTabKindFromTerminationStatus(base::TerminationStatus status) {
   switch (status) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
       return SAD_TAB_KIND_KILLED_BY_OOM;
 #endif
diff --git a/chrome/browser/ui/sad_tab_types.h b/chrome/browser/ui/sad_tab_types.h
index 4a688c44..106f779 100644
--- a/chrome/browser/ui/sad_tab_types.h
+++ b/chrome/browser/ui/sad_tab_types.h
@@ -10,7 +10,7 @@
 
 enum SadTabKind {
   SAD_TAB_KIND_CRASHED,  // Tab crashed.
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
   SAD_TAB_KIND_KILLED_BY_OOM,  // Tab killed by oom killer.
 #endif
   SAD_TAB_KIND_OOM,    // Tab ran out of memory.
diff --git a/chrome/browser/ui/startup/bad_flags_prompt.cc b/chrome/browser/ui/startup/bad_flags_prompt.cc
index 3b3bb4b..2d18213 100644
--- a/chrome/browser/ui/startup/bad_flags_prompt.cc
+++ b/chrome/browser/ui/startup/bad_flags_prompt.cc
@@ -14,6 +14,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/trace_event/memory_dump_manager.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/ui/simple_message_box.h"
 #include "chrome/common/chrome_paths.h"
@@ -88,7 +89,9 @@
     extensions::switches::kExtensionsOnChromeURLs,
 #endif
 
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
     // Speech dispatcher is buggy, it can crash and it can make Chrome freeze.
     // http://crbug.com/327295
     switches::kEnableSpeechDispatcher,
diff --git a/chrome/browser/ui/startup/credential_provider_signin_info_fetcher_win.cc b/chrome/browser/ui/startup/credential_provider_signin_info_fetcher_win.cc
index 6516a76..9dead78c 100644
--- a/chrome/browser/ui/startup/credential_provider_signin_info_fetcher_win.cc
+++ b/chrome/browser/ui/startup/credential_provider_signin_info_fetcher_win.cc
@@ -12,10 +12,10 @@
 #include "base/strings/string_split.h"
 #include "base/syslog_logging.h"
 #include "chrome/credential_provider/common/gcp_strings.h"
+#include "google_apis/gaia/gaia_access_token_fetcher.h"
 #include "google_apis/gaia/gaia_oauth_client.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "google_apis/gaia/oauth2_access_token_fetcher.h"
-#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
 #include "google_apis/google_api_keys.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
@@ -23,9 +23,11 @@
     const std::string& refresh_token,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
     : scoped_access_token_fetcher_(
-          std::make_unique<OAuth2AccessTokenFetcherImpl>(this,
-                                                         url_loader_factory,
-                                                         refresh_token)),
+          GaiaAccessTokenFetcher::
+              CreateExchangeRefreshTokenForAccessTokenInstance(
+                  this,
+                  url_loader_factory,
+                  refresh_token)),
       user_info_fetcher_(
           std::make_unique<gaia::GaiaOAuthClient>(url_loader_factory)),
       token_handle_fetcher_(
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index 27f6fb8..4d7c99a 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -31,6 +31,7 @@
 #include "base/trace_event/trace_event.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/app_mode/app_mode_utils.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
@@ -79,7 +80,7 @@
 #include "extensions/common/switches.h"
 #include "printing/buildflags/buildflags.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/app_mode/app_launch_utils.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_app_launcher.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
@@ -253,7 +254,7 @@
 // Returns whether |profile| can be opened during Chrome startup without
 // explicit user action.
 bool CanOpenProfileOnStartup(Profile* profile) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // On ChromeOS, the user has already chosen and logged into the profile before
   // Chrome starts up.
   return true;
@@ -278,10 +279,10 @@
 }
 
 void ShowUserManagerOnStartup() {
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   UserManager::Show(base::FilePath(),
                     profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 bool IsSilentLaunchEnabled(const base::CommandLine& command_line,
@@ -294,10 +295,10 @@
   if (command_line.HasSwitch(switches::kSilentLaunch))
     return true;
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   return profile->GetPrefs()->GetBoolean(
       prefs::kStartupBrowserWindowLaunchSuppressed);
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   return false;
 }
@@ -514,7 +515,7 @@
   // is starting Chrome for the first time. On Chrome OS, the sentinel is stored
   // in a location shared by all users and the check is meaningless. Query the
   // UserManager instead to determine whether the user is new.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   const bool is_first_run =
       user_manager::UserManager::Get()->IsCurrentUserNew();
   // On ChromeOS restarts force the user to login again. The expectation is that
@@ -577,7 +578,7 @@
 // static
 void StartupBrowserCreator::RegisterLocalStatePrefs(
     PrefRegistrySimple* registry) {
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   registry->RegisterBooleanPref(prefs::kPromotionalTabsEnabled, true);
   registry->RegisterBooleanPref(prefs::kCommandLineFlagSecurityWarningsEnabled,
                                 true);
@@ -650,7 +651,7 @@
 
     const GURL settings_url = GURL(chrome::kChromeUISettingsURL);
     bool url_points_to_an_approved_settings_page = false;
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     // In ChromeOS, allow any settings page to be specified on the command line.
     url_points_to_an_approved_settings_page =
         url.GetOrigin() == settings_url.GetOrigin();
@@ -669,7 +670,7 @@
         url_points_to_an_approved_settings_page ||
         url == reset_settings_url_with_cct_hash;
 #endif  // defined(OS_WIN)
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
     ChildProcessSecurityPolicy* policy =
         ChildProcessSecurityPolicy::GetInstance();
@@ -733,7 +734,7 @@
     return false;
   }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 
   // The browser will be launched after the user logs in.
   if (command_line.HasSwitch(chromeos::switches::kLoginManager))
@@ -773,7 +774,7 @@
     chrome::AttemptUserExit();
     return false;
   }
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(TOOLKIT_VIEWS) && defined(USE_X11)
   if (!features::IsUsingOzonePlatform()) {
@@ -803,7 +804,7 @@
     silent_launch = true;
   }
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   if (base::FeatureList::IsEnabled(features::kOnConnectNative) &&
       command_line.HasSwitch(switches::kNativeMessagingConnectHost) &&
       command_line.HasSwitch(switches::kNativeMessagingConnectExtension)) {
@@ -961,7 +962,7 @@
     bool process_startup,
     Profile* last_used_profile,
     const Profiles& last_opened_profiles) {
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   const std::vector<GURL> urls_to_launch =
       StartupBrowserCreator::GetURLsFromCommandLine(command_line, cur_dir,
                                                     last_used_profile);
@@ -972,7 +973,7 @@
             : ProfilePicker::EntryPoint::kNewSessionOnExistingProcess);
     return true;
   }
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
   chrome::startup::IsProcessStartup is_process_startup =
       process_startup ? chrome::startup::IS_PROCESS_STARTUP
@@ -1034,7 +1035,7 @@
   for (Profile* profile : last_opened_profiles) {
     DCHECK(!profile->IsGuestSession() && !profile->IsEphemeralGuestProfile());
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
     // Skip any locked profile.
     if (!CanOpenProfileOnStartup(profile))
       continue;
@@ -1080,7 +1081,7 @@
 // Note that this must be done after all profiles have
 // been launched so the observer knows about all profiles to wait before
 // activation this one.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   if (is_process_startup == chrome::startup::IS_PROCESS_STARTUP)
     ShowUserManagerOnStartup();
   else
@@ -1155,13 +1156,13 @@
   }
   StartupBrowserCreator startup_browser_creator;
   Profiles last_opened_profiles;
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   // On ChromeOS multiple profiles doesn't apply.
   // If no browser windows are open, i.e. the browser is being kept alive in
   // background mode or for other processing, restore |last_opened_profiles|.
   if (chrome::GetTotalBrowserCount() == 0)
     last_opened_profiles = profile_manager->GetLastOpenedProfiles();
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   startup_browser_creator.ProcessCmdLineImpl(command_line, cur_dir,
                                              /*process_startup=*/false, profile,
                                              last_opened_profiles);
@@ -1223,7 +1224,7 @@
       user_data_dir);
 }
 
-#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
 Profile* GetStartupProfile(const base::FilePath& user_data_dir,
                            const base::CommandLine& command_line) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
@@ -1294,4 +1295,4 @@
 
   return nullptr;
 }
-#endif  // !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
diff --git a/chrome/browser/ui/startup/startup_browser_creator.h b/chrome/browser/ui/startup/startup_browser_creator.h
index 36747ee..9fc735d 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.h
+++ b/chrome/browser/ui/startup/startup_browser_creator.h
@@ -10,6 +10,7 @@
 
 #include "base/files/file_path.h"
 #include "base/gtest_prod_util.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/prefs/session_startup_pref.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/startup/startup_types.h"
@@ -213,7 +214,7 @@
 base::FilePath GetStartupProfilePath(const base::FilePath& user_data_dir,
                                      const base::CommandLine& command_line);
 
-#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
 // Returns the profile that should be loaded on process startup. This is either
 // the profile returned by GetStartupProfilePath, or the guest profile if the
 // above profile is locked. The guest profile denotes that we should open the
@@ -228,6 +229,6 @@
 // guest profile means the caller should open the user manager. This may return
 // null if neither any profile nor the user manager can be opened.
 Profile* GetFallbackStartupProfile();
-#endif  // !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
 
 #endif  // CHROME_BROWSER_UI_STARTUP_STARTUP_BROWSER_CREATOR_H_
diff --git a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
index 7c6c1b2..3513ba2 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
@@ -21,6 +21,7 @@
 #include "base/threading/thread_restrictions.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
@@ -90,7 +91,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 #include "base/callback.h"
 #include "base/run_loop.h"
 #include "base/values.h"
@@ -100,7 +101,7 @@
 #include "components/policy/core/common/policy_types.h"
 
 using testing::Return;
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
 #include "chrome/browser/supervised_user/supervised_user_constants.h"
@@ -122,7 +123,7 @@
 
 namespace {
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 
 const char kAppId[] = "dofnemchnjfeendjmdhaldenaiabpiad";
 const char kAppName[] = "Test App";
@@ -169,7 +170,7 @@
   return OpenNewBrowser(profile);
 }
 
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 typedef base::Optional<policy::PolicyLevel> PolicyVariant;
 
@@ -253,7 +254,7 @@
   void SetUpCommandLine(base::CommandLine* command_line) override {
     extensions::ExtensionBrowserTest::SetUpCommandLine(command_line);
     command_line->AppendSwitchASCII(switches::kHomePage, url::kAboutBlankURL);
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     // TODO(nkostylev): Investigate if we can remove this switch.
     command_line->AppendSwitch(switches::kCreateBrowserOnStartupForTests);
 #endif
@@ -344,7 +345,7 @@
 // We don't do non-process-startup browser launches on ChromeOS.
 // Session restore for process-startup browser launches is tested
 // in session_restore_uitest.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 // Verify that startup URLs are honored when the process already exists but has
 // no tabbed browser windows (eg. as if the process is running only due to a
 // background application.
@@ -542,7 +543,7 @@
       << browser()->app_name_;
 }
 
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(OS_WIN)
 IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorTest, ValidNotificationLaunchId) {
@@ -642,7 +643,7 @@
   EXPECT_FALSE(StartupBrowserCreator::WasRestarted());
 }
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorTest, StartupURLsForTwoProfiles) {
   Profile* default_profile = browser()->profile();
 
@@ -1367,7 +1368,7 @@
   EXPECT_EQ("/title2.html", tab_strip->GetWebContentsAt(0)->GetURL().path());
 }
 
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 class StartupBrowserCreatorExtensionsCheckupExperimentTest
     : public extensions::ExtensionBrowserTest {
@@ -1471,7 +1472,7 @@
 
 // These tests are not applicable to Chrome OS as neither initial preferences
 // nor the onboarding promos exist there.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 
 class StartupBrowserCreatorFirstRunTest : public InProcessBrowserTest {
  public:
@@ -1754,7 +1755,7 @@
             tab_strip->GetWebContentsAt(0)->GetURL().ExtractFileName());
 }
 
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 class StartupBrowserCreatorWelcomeBackTest : public InProcessBrowserTest {
  protected:
@@ -1870,7 +1871,7 @@
 }
 
 // The kCommandLineFlagSecurityWarningsEnabled policy doesn't exist on ChromeOS.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 enum class CommandLineFlagSecurityWarningsPolicy {
   kNoPolicy,
   kEnabled,
@@ -2208,4 +2209,4 @@
 INSTANTIATE_TEST_SUITE_P(All,
                          GuestStartupBrowserCreatorPickerTest,
                          /*ephemeral_guest_profile_enabled=*/testing::Bool());
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/startup/startup_browser_creator_impl.cc b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
index f1a9ede..61a6cb50 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_impl.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
@@ -78,7 +78,7 @@
 #include "components/rlz/rlz_tracker.h"  // nogncheck
 #endif
 
-#if BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chrome/grit/generated_resources.h"
 #include "components/infobars/core/simple_alert_infobar_delegate.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -112,7 +112,7 @@
 }
 
 bool ShouldShowBadFlagsSecurityWarnings() {
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   PrefService* local_state = g_browser_process->local_state();
   if (!local_state)
     return true;
@@ -315,11 +315,11 @@
   // administrative policy.
   bool promotional_tabs_enabled = true;
   const PrefService::Preference* enabled_pref = nullptr;
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   PrefService* local_state = g_browser_process->local_state();
   if (local_state)
     enabled_pref = local_state->FindPreference(prefs::kPromotionalTabsEnabled);
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
   if (enabled_pref && enabled_pref->IsManaged()) {
     // Presentation is managed; obey the policy setting.
     promotional_tabs_enabled = enabled_pref->GetValue()->GetBool();
@@ -332,10 +332,10 @@
   }
 
   bool welcome_enabled = true;
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   welcome_enabled =
       welcome::IsEnabled(profile_) && welcome::HasModulesToShow(profile_);
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
   bool serve_extensions_page =
       extensions::ShouldShowExtensionsCheckupOnStartup(profile_);
@@ -573,7 +573,7 @@
     InfoBarService* infobar_service =
         InfoBarService::FromWebContents(web_contents);
 
-#if BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
     // Show the experimental lacros info bar. auto_expire must be set to false,
     // since otherwise an automated navigation [which can happen at launch] will
     // cause the info bar to disappear.
@@ -600,7 +600,7 @@
       MacSystemInfoBarDelegate::Create(infobar_service);
 #endif
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
     if (!command_line_.HasSwitch(switches::kNoDefaultBrowserCheck)) {
       // The default browser prompt should only be shown after the first run.
       if (!is_first_run_)
diff --git a/chrome/browser/ui/startup/startup_browser_creator_interactive_uitest.cc b/chrome/browser/ui/startup/startup_browser_creator_interactive_uitest.cc
index 9ca878f..986fb45a 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_interactive_uitest.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_interactive_uitest.cc
@@ -10,6 +10,7 @@
 #include "base/run_loop.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_browser_main.h"
 #include "chrome/browser/chrome_browser_main_extra_parts.h"
@@ -36,7 +37,7 @@
 // Chrome OS doesn't support multiprofile.
 // And BrowserWindow::IsActive() always returns false in tests on MAC.
 // And this test is useless without that functionality.
-#if !defined(OS_CHROMEOS) && !defined(OS_MAC)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_MAC)
 IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorTest, LastUsedProfileActivated) {
   base::ScopedAllowBlockingForTesting allow_blocking;
   ProfileManager* profile_manager = g_browser_process->profile_manager();
diff --git a/chrome/browser/ui/startup/startup_tab_provider.cc b/chrome/browser/ui/startup/startup_tab_provider.cc
index ab549af..b16e6b9 100644
--- a/chrome/browser/ui/startup/startup_tab_provider.cc
+++ b/chrome/browser/ui/startup/startup_tab_provider.cc
@@ -6,6 +6,7 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "build/branding_buildflags.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/profile_resetter/triggered_profile_resetter.h"
 #include "chrome/browser/profile_resetter/triggered_profile_resetter_factory.h"
@@ -47,7 +48,7 @@
 
 StartupTabs StartupTabProviderImpl::GetOnboardingTabs(Profile* profile) const {
 // Chrome OS has its own welcome flow provided by OOBE.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   return StartupTabs();
 #else
   if (!profile)
@@ -67,7 +68,7 @@
   standard_params.is_force_signin_enabled = signin_util::IsForceSigninEnabled();
 
   return GetStandardOnboardingTabsForState(standard_params);
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 StartupTabs StartupTabProviderImpl::GetWelcomeBackTabs(
diff --git a/chrome/browser/ui/sync/profile_signin_confirmation_helper_browsertest.cc b/chrome/browser/ui/sync/profile_signin_confirmation_helper_browsertest.cc
index 9667f7e..0041eb457 100644
--- a/chrome/browser/ui/sync/profile_signin_confirmation_helper_browsertest.cc
+++ b/chrome/browser/ui/sync/profile_signin_confirmation_helper_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/run_loop.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/common/chrome_switches.h"
@@ -40,7 +41,7 @@
 #endif
 IN_PROC_BROWSER_TEST_F(ProfileSigninConfirmationHelperBrowserTest,
                        MAYBE_HasNotBeenShutdown) {
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   EXPECT_TRUE(first_run::auto_import_state() & first_run::AUTO_IMPORT_CALLED);
 #endif
   EXPECT_FALSE(ui::HasBeenShutdown(browser()->profile()));
diff --git a/chrome/browser/ui/sync/profile_signin_confirmation_helper_unittest.cc b/chrome/browser/ui/sync/profile_signin_confirmation_helper_unittest.cc
index e810f46..1134ce4 100644
--- a/chrome/browser/ui/sync/profile_signin_confirmation_helper_unittest.cc
+++ b/chrome/browser/ui/sync/profile_signin_confirmation_helper_unittest.cc
@@ -19,6 +19,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/prefs/browser_prefs.h"
@@ -37,7 +38,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h"
 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
 #endif
@@ -179,7 +180,7 @@
   TestingPrefStoreWithCustomReadError* user_prefs_;
   BookmarkModel* model_;
 
-#if defined OS_CHROMEOS
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   chromeos::ScopedCrosSettingsTestHelper cros_settings_test_helper_;
   chromeos::ScopedTestUserManager test_user_manager_;
 #endif
diff --git a/chrome/browser/ui/sync/sync_promo_ui_unittest.cc b/chrome/browser/ui/sync/sync_promo_ui_unittest.cc
index 6221d5e..d027d10f 100644
--- a/chrome/browser/ui/sync/sync_promo_ui_unittest.cc
+++ b/chrome/browser/ui/sync/sync_promo_ui_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/sync/driver/sync_driver_switches.h"
@@ -51,7 +52,7 @@
 // Verifies that ShouldShowSyncPromo returns true if all conditions to
 // show the promo are met.
 TEST_F(SyncPromoUITest, ShouldShowSyncPromoSyncEnabled) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // No sync promo on CrOS.
   EXPECT_FALSE(SyncPromoUI::ShouldShowSyncPromo(profile_.get()));
 #else
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index 4fb7af3b..78bf9b43 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -12,6 +12,7 @@
 #include "base/time/default_tick_clock.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/buildflags.h"
@@ -142,7 +143,7 @@
 #include "components/zoom/zoom_controller.h"
 #endif  // defined(OS_ANDROID)
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/child_accounts/time_limits/web_time_navigation_observer.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_content_tab_helper.h"
 #include "chrome/browser/ui/app_list/search/cros_action_history/cros_action_recorder_tab_tracker.h"
@@ -398,15 +399,17 @@
   web_modal::WebContentsModalDialogManager::CreateForWebContents(web_contents);
 #endif
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   app_list::CrOSActionRecorderTabTracker::CreateForWebContents(web_contents);
   chromeos::app_time::WebTimeNavigationObserver::MaybeCreateForWebContents(
       web_contents);
   policy::DlpContentTabHelper::CreateForWebContents(web_contents);
 #endif
 
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
 #if defined(OS_WIN) || defined(OS_MAC) || \
-    (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+    (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
   metrics::DesktopSessionDurationObserver::CreateForWebContents(web_contents);
 #endif
 
diff --git a/chrome/browser/ui/tabs/tab_renderer_data.cc b/chrome/browser/ui/tabs/tab_renderer_data.cc
index d70e049..01df75e 100644
--- a/chrome/browser/ui/tabs/tab_renderer_data.cc
+++ b/chrome/browser/ui/tabs/tab_renderer_data.cc
@@ -72,7 +72,7 @@
 
 bool TabRendererData::IsCrashed() const {
   return (crashed_status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED ||
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
           crashed_status ==
               base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM ||
 #endif
diff --git a/chrome/browser/ui/task_manager/task_manager_columns.cc b/chrome/browser/ui/task_manager/task_manager_columns.cc
index 2a97d6c..50bd9ed 100644
--- a/chrome/browser/ui/task_manager/task_manager_columns.cc
+++ b/chrome/browser/ui/task_manager/task_manager_columns.cc
@@ -7,6 +7,7 @@
 #include "base/notreached.h"
 #include "base/stl_util.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/nacl/common/buildflags.h"
 
@@ -30,7 +31,7 @@
      base::size("800 MiB") * kCharWidth,
      base::size("Memory Footprint") * 1.5 * kCharWidth, true, false, true},
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     {IDS_TASK_MANAGER_SWAPPED_MEM_COLUMN, ui::TableColumn::RIGHT, -1, 0,
      base::size("800 MiB") * kCharWidth, -1, true, false, false},
 #endif
diff --git a/chrome/browser/ui/test/test_browser_dialog.cc b/chrome/browser/ui/test/test_browser_dialog.cc
index 1863e4e..d6efd49 100644
--- a/chrome/browser/ui/test/test_browser_dialog.cc
+++ b/chrome/browser/ui/test/test_browser_dialog.cc
@@ -11,9 +11,10 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/shell.h"
 #endif
 
@@ -114,7 +115,9 @@
 
   views::Widget* dialog_widget = *(added.begin());
 // TODO(https://crbug.com/958242) support Mac for pixel tests.
-#if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_WIN) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
   dialog_widget->SetBlockCloseForTesting(true);
   // Deactivate before taking screenshot. Deactivated dialog pixel outputs
   // is more predictable than activated dialog.
@@ -195,7 +198,7 @@
 
 void TestBrowserDialog::UpdateWidgets() {
   widgets_.clear();
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   for (aura::Window* root_window : ash::Shell::GetAllRootWindows())
     views::Widget::GetAllChildWidgets(root_window, &widgets_);
 #elif defined(TOOLKIT_VIEWS)
diff --git a/chrome/browser/ui/test/test_browser_ui.cc b/chrome/browser/ui/test/test_browser_ui.cc
index 6deede6..35ef2c5 100644
--- a/chrome/browser/ui/test/test_browser_ui.cc
+++ b/chrome/browser/ui/test/test_browser_ui.cc
@@ -8,9 +8,12 @@
 #include "base/test/gtest_util.h"
 #include "base/test/test_switches.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
 #if defined(OS_WIN) || defined(OS_MAC) || \
-    (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+    (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
 #include "chrome/test/pixel/browser_skia_gold_pixel_diff.h"
 #include "ui/base/test/skia_gold_matching_algorithm.h"
 #include "ui/compositor/test/draw_waiter_for_test.h"
@@ -32,7 +35,9 @@
 }  // namespace
 
 TestBrowserUi::TestBrowserUi() {
-#if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_WIN) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
   // Default to fuzzy diff. The magic number is chosen based on
   // past experiments.
   SetPixelMatchAlgorithm(
@@ -43,7 +48,9 @@
 TestBrowserUi::~TestBrowserUi() = default;
 
 // TODO(https://crbug.com/958242) support Mac for pixel tests.
-#if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_WIN) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
 bool TestBrowserUi::VerifyPixelUi(views::Widget* widget,
                                   const std::string& screenshot_prefix,
                                   const std::string& screenshot_name) {
diff --git a/chrome/browser/ui/test/test_browser_ui.h b/chrome/browser/ui/test/test_browser_ui.h
index 8658d5c..6d9ce3f 100644
--- a/chrome/browser/ui/test/test_browser_ui.h
+++ b/chrome/browser/ui/test/test_browser_ui.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/macros.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/test/base/in_process_browser_test.h"
 
 namespace views {
@@ -91,7 +92,9 @@
   // successfully shown.
   virtual bool VerifyUi() = 0;
 
-#if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_WIN) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
   // Can be called by VerifyUi() to ensure pixel correctness.
   bool VerifyPixelUi(views::Widget* widget,
                      const std::string& screenshot_prefix,
@@ -120,8 +123,10 @@
   void ShowAndVerifyUi();
 
  private:
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
 #if defined(OS_WIN) || defined(OS_MAC) || \
-    (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+    (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
   std::unique_ptr<ui::test::SkiaGoldMatchingAlgorithm> algorithm_;
 #endif
 
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index 3b462a9..4aa93919 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -19,6 +19,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/banners/app_banner_manager.h"
@@ -83,11 +84,11 @@
 #include "ui/gfx/text_elider.h"
 #include "ui/native_theme/native_theme.h"
 
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING) || defined(OS_CHROMEOS)
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING) || BUILDFLAG(IS_CHROMEOS_ASH)
 #include "base/feature_list.h"
 #endif
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/public/cpp/tablet_mode.h"
 #include "chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -178,12 +179,12 @@
 
  private:
   void Build(Browser* browser) {
-#if defined(OS_CHROMEOS) && defined(OFFICIAL_BUILD)
+#if BUILDFLAG(IS_CHROMEOS_ASH) && defined(OFFICIAL_BUILD)
     int help_string_id = IDS_GET_HELP;
 #else
     int help_string_id = IDS_HELP_PAGE;
 #endif
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
 #else
     AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
@@ -226,7 +227,7 @@
   AddItemWithStringId(IDC_MANAGE_EXTENSIONS, IDS_SHOW_EXTENSIONS);
   if (chrome::CanOpenTaskManager())
     AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   AddItemWithStringId(IDC_TAKE_SCREENSHOT, IDS_TAKE_SCREENSHOT);
 #endif
   AddSeparator(ui::NORMAL_SEPARATOR);
@@ -274,7 +275,7 @@
   Observe(tab_strip_model->GetActiveWebContents());
   UpdateZoomControls();
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   PrefService* const local_state = g_browser_process->local_state();
   if (local_state) {
     local_state_pref_change_registrar_.Init(local_state);
@@ -284,7 +285,7 @@
                             base::Unretained(this)));
     UpdateSettingsItemState();
   }
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 bool AppMenuModel::DoesCommandIdDismissMenu(int command_id) const {
@@ -834,7 +835,7 @@
     }
   }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Always show this option if we're in tablet mode on Chrome OS.
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           chromeos::switches::kEnableRequestTabletSite) ||
@@ -859,7 +860,7 @@
   sub_menus_.push_back(std::make_unique<HelpMenuModel>(this, browser_));
   AddSubMenuWithStringId(IDC_HELP_MENU, IDS_HELP_MENU, sub_menus_.back().get());
 #else
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
 #else
   AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
@@ -873,7 +874,7 @@
 
   // On Chrome OS, similar UI is displayed in the system tray menu, instead of
   // this menu.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   if (chrome::ShouldDisplayManagedUi(browser_->profile())) {
     AddSeparator(ui::LOWER_SEPARATOR);
     const int kIconSize = 18;
@@ -885,7 +886,7 @@
             ui::NativeTheme::kColorId_HighlightedMenuItemForegroundColor,
             kIconSize));
   }
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
   uma_action_recorded_ = false;
 }
@@ -981,7 +982,7 @@
   UpdateZoomControls();
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 void AppMenuModel::UpdateSettingsItemState() {
   const base::ListValue* system_features_disable_list_pref = nullptr;
   PrefService* const local_state = g_browser_process->local_state();
@@ -999,4 +1000,4 @@
   if (index != -1)
     SetEnabledAt(index, is_enabled);
 }
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/toolbar/app_menu_model.h b/chrome/browser/ui/toolbar/app_menu_model.h
index d75ba9a..b2d303fe 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.h
+++ b/chrome/browser/ui/toolbar/app_menu_model.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "content/public/browser/host_zoom_map.h"
@@ -198,11 +199,11 @@
   // took to select the command.
   void LogMenuMetrics(int command_id);
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Disables/Enables the settings item based on kSystemFeaturesDisableList
   // pref.
   void UpdateSettingsItemState();
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   // Time menu has been open. Used by LogMenuMetrics() to record the time
   // to action when the user selects a menu item.
diff --git a/chrome/browser/ui/toolbar/app_menu_model_unittest.cc b/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
index 94e10115..da961d1 100644
--- a/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/defaults.h"
 #include "chrome/browser/prefs/browser_prefs.h"
@@ -24,10 +25,10 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/color_palette.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h"
 #include "components/policy/core/common/policy_pref_names.h"
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 namespace {
 
@@ -223,7 +224,7 @@
   EXPECT_EQ(1, error1->execute_count());
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // Tests settings menu items is disabled in the app menu when
 // kSystemFeaturesDisableList is set.
 TEST_F(AppMenuModelTest, DisableSettingsItem) {
@@ -248,4 +249,4 @@
   }
   EXPECT_TRUE(model.IsEnabledAt(index));
 }
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/toolbar/bookmark_sub_menu_model.cc b/chrome/browser/ui/toolbar/bookmark_sub_menu_model.cc
index 6f1d426..90788de 100644
--- a/chrome/browser/ui/toolbar/bookmark_sub_menu_model.cc
+++ b/chrome/browser/ui/toolbar/bookmark_sub_menu_model.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/toolbar/bookmark_sub_menu_model.h"
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/grit/generated_resources.h"
 
@@ -40,7 +41,7 @@
   }
   AddCheckItemWithStringId(IDC_SHOW_BOOKMARK_BAR, IDS_SHOW_BOOKMARK_BAR);
   AddItemWithStringId(IDC_SHOW_BOOKMARK_MANAGER, IDS_BOOKMARK_MANAGER);
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   AddItemWithStringId(IDC_IMPORT_SETTINGS, IDS_IMPORT_SETTINGS_MENU_LABEL);
 #endif
 }
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index 5924106..f8ff53e 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/ui_features.h"
 
+#include "build/chromeos_buildflags.h"
+
 namespace features {
 
 // Enables showing the EV certificate details in the Page Info bubble.
@@ -127,7 +129,7 @@
 const base::Feature kWebUITabStrip{"WebUITabStrip",
                                    base::FEATURE_DISABLED_BY_DEFAULT};
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // Enables a warning about connecting to hidden WiFi networks.
 // https://crbug.com/903908
 const base::Feature kHiddenNetworkWarning{"HiddenNetworkWarning",
@@ -137,5 +139,5 @@
 // for pointing sticks (such as TrackPoints).
 const base::Feature kSeparatePointingStickSettings{
     "SeparatePointingStickSettings", base::FEATURE_DISABLED_BY_DEFAULT};
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }  // namespace features
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h
index 8bc3e162..a53e9f1c 100644
--- a/chrome/browser/ui/ui_features.h
+++ b/chrome/browser/ui/ui_features.h
@@ -11,6 +11,7 @@
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/common/buildflags.h"
 #include "extensions/buildflags/buildflags.h"
 
@@ -91,10 +92,10 @@
 
 extern const base::Feature kWebUITabStrip;
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 extern const base::Feature kHiddenNetworkWarning;
 extern const base::Feature kSeparatePointingStickSettings;
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }  // namespace features
 
 #endif  // CHROME_BROWSER_UI_UI_FEATURES_H_
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view.cc b/chrome/browser/ui/views/profiles/profile_menu_view.cc
index 3a5697d..3a9de32 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view.cc
@@ -680,9 +680,11 @@
                              ->GetProfileAttributesStorage()
                              .GetAllProfilesAttributesSortedByName();
   for (ProfileAttributesEntry* profile_entry : profile_entries) {
-    // The current profile is excluded.
-    if (profile_entry->GetPath() == browser()->profile()->GetPath())
+    // Guest profile and the current profile are excluded.
+    if (profile_entry->IsGuest() ||
+        profile_entry->GetPath() == browser()->profile()->GetPath()) {
       continue;
+    }
 
     AddSelectableProfile(
         ui::ImageModel::FromImage(
diff --git a/chrome/browser/ui/views/profiles/profile_picker_interactive_uitest.cc b/chrome/browser/ui/views/profiles/profile_picker_interactive_uitest.cc
deleted file mode 100644
index 0410261..0000000
--- a/chrome/browser/ui/views/profiles/profile_picker_interactive_uitest.cc
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/profiles/profile_picker_view.h"
-
-#include "base/check.h"
-#include "base/run_loop.h"
-#include "base/test/mock_callback.h"
-#include "build/build_config.h"
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/lifetime/browser_shutdown.h"
-#include "chrome/browser/ui/profile_picker.h"
-#include "chrome/browser/ui/views/profiles/profile_picker_test_base.h"
-#include "chrome/test/base/interactive_test_utils.h"
-#include "content/public/browser/notification_source.h"
-#include "content/public/test/browser_test.h"
-#include "content/public/test/browser_test_utils.h"
-#include "content/public/test/test_utils.h"
-#include "google_apis/gaia/gaia_urls.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/events/keycodes/dom/dom_key.h"
-#include "ui/views/view.h"
-#include "ui/views/view_observer.h"
-#include "ui/views/widget/widget.h"
-#include "ui/views/widget/widget_observer.h"
-
-namespace {
-
-// Waits until a view is deleted.
-class ViewDeletedWaiter : public views::ViewObserver {
- public:
-  explicit ViewDeletedWaiter(views::View* view) {
-    DCHECK(view);
-    observation_.Observe(view);
-  }
-  ~ViewDeletedWaiter() override = default;
-
-  // Waits until the view is deleted.
-  void Wait() { run_loop_.Run(); }
-
- private:
-  // ViewObserver:
-  void OnViewIsDeleting(views::View* observed_view) override {
-    run_loop_.Quit();
-  }
-
-  base::RunLoop run_loop_;
-  base::ScopedObservation<views::View, views::ViewObserver> observation_{this};
-};
-
-// Waits until the widget bounds change.
-class WidgetBoundsChangeWaiter : public views::WidgetObserver {
- public:
-  explicit WidgetBoundsChangeWaiter(views::Widget* widget) {
-    DCHECK(widget);
-    observation_.Observe(widget);
-  }
-
-  // Waits until the widget bounds change.
-  void Wait() { run_loop_.Run(); }
-
- private:
-  // WidgetObserver:
-  void OnWidgetBoundsChanged(views::Widget* widget,
-                             const gfx::Rect& new_bounds) override {
-    run_loop_.Quit();
-  }
-
-  base::RunLoop run_loop_;
-  base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{
-      this};
-};
-
-}  // namespace
-
-class ProfilePickerInteractiveUiTest : public ProfilePickerTestBase {
- public:
-  ProfilePickerInteractiveUiTest() = default;
-  ~ProfilePickerInteractiveUiTest() override = default;
-
-  void SendCloseWindowKeyboardCommand() {
-    // Close window using keyboard.
-#if defined(OS_MAC)
-    // Use Cmd-W on Mac.
-    bool control = false;
-    bool shift = false;
-    bool command = true;
-#else
-    // Use Ctrl-Shift-W on other platforms.
-    bool control = true;
-    bool shift = true;
-    bool command = false;
-#endif
-    ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-        widget()->GetNativeWindow(), ui::VKEY_W, control, shift, /*alt=*/false,
-        command));
-  }
-
-  void WaitForPickerClosed() {
-    if (!ProfilePicker::IsOpen())
-      return;
-    ViewDeletedWaiter(view()).Wait();
-    ASSERT_FALSE(ProfilePicker::IsOpen());
-  }
-};
-
-// Checks that the main picker view can be closed with keyboard shortcut.
-IN_PROC_BROWSER_TEST_F(ProfilePickerInteractiveUiTest, CloseWithKeyboard) {
-  // Open a new picker.
-  ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileMenuManageProfiles);
-  WaitForNewWebView();
-  WaitForFirstPaint(web_contents(), GURL("chrome://profile-picker"));
-  EXPECT_TRUE(ProfilePicker::IsOpen());
-  SendCloseWindowKeyboardCommand();
-  WaitForPickerClosed();
-  // Closing the picker does not exit Chrome.
-  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
-}
-
-#if defined(OS_MAC)
-// Checks that Chrome be closed with keyboard shortcut. Only MacOS has a
-// keyboard shortcut to exit Chrome.
-IN_PROC_BROWSER_TEST_F(ProfilePickerInteractiveUiTest, ExitWithKeyboard) {
-  // Open a new picker.
-  ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileMenuManageProfiles);
-  WaitForNewWebView();
-  WaitForFirstPaint(web_contents(), GURL("chrome://profile-picker"));
-  EXPECT_TRUE(ProfilePicker::IsOpen());
-
-  content::WindowedNotificationObserver terminate_observer(
-      chrome::NOTIFICATION_APP_TERMINATING,
-      content::NotificationService::AllSources());
-  // Send Cmd-Q.
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      widget()->GetNativeWindow(), ui::VKEY_Q, /*control=*/false,
-      /*shift=*/false, /*alt=*/false, /*command=*/true));
-  // Check that Chrome is quitting.
-  terminate_observer.Wait();
-  WaitForPickerClosed();
-  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
-}
-#endif
-
-// Checks that the main picker view can switch to full screen.
-IN_PROC_BROWSER_TEST_F(ProfilePickerInteractiveUiTest, FullscreenWithKeyboard) {
-  // Open a new picker.
-  ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileMenuManageProfiles);
-  WaitForNewWebView();
-  WaitForFirstPaint(web_contents(), GURL("chrome://profile-picker"));
-  EXPECT_TRUE(ProfilePicker::IsOpen());
-
-  EXPECT_FALSE(widget()->IsFullscreen());
-  WidgetBoundsChangeWaiter bounds_waiter(widget());
-
-  // Toggle fullscreen with keyboard.
-#if defined(OS_MAC)
-  // Use Cmd-Ctrl-F on Mac.
-  bool control = true;
-  bool command = true;
-  ui::KeyboardCode key_code = ui::VKEY_F;
-#else
-  // Use F11 on other platforms.
-  bool control = false;
-  bool command = false;
-  ui::KeyboardCode key_code = ui::VKEY_F11;
-#endif
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      widget()->GetNativeWindow(), key_code, control, /*shift=*/false,
-      /*alt=*/false, command));
-  // Fullscreen causes the bounds of the widget to change.
-  bounds_waiter.Wait();
-  EXPECT_TRUE(widget()->IsFullscreen());
-}
-
-// Checks that the signin web view is able to process keyboard events.
-IN_PROC_BROWSER_TEST_F(ProfilePickerInteractiveUiTest,
-                       CloseSigninWithKeyboard) {
-  ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileMenuAddNewProfile);
-  WaitForNewWebView();
-
-  // Simulate a click on the signin button.
-  base::MockCallback<base::OnceClosure> switch_failure_callback;
-  EXPECT_CALL(switch_failure_callback, Run()).Times(0);
-  ProfilePicker::SwitchToSignIn(SK_ColorRED, switch_failure_callback.Get());
-
-  // Switch to the signin webview.
-  WaitForNewWebView();
-  WaitForFirstPaint(web_contents(),
-                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
-
-  // Close the picker with the keyboard.
-  EXPECT_TRUE(ProfilePicker::IsOpen());
-  SendCloseWindowKeyboardCommand();
-  WaitForPickerClosed();
-}
diff --git a/chrome/browser/ui/views/profiles/profile_picker_test_base.cc b/chrome/browser/ui/views/profiles/profile_picker_test_base.cc
deleted file mode 100644
index ac452a3..0000000
--- a/chrome/browser/ui/views/profiles/profile_picker_test_base.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/profiles/profile_picker_test_base.h"
-
-#include "base/callback.h"
-#include "base/run_loop.h"
-#include "base/scoped_observation.h"
-#include "chrome/browser/ui/profile_picker.h"
-#include "chrome/browser/ui/ui_features.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_contents_observer.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/views/controls/webview/webview.h"
-#include "ui/views/view.h"
-#include "ui/views/view_observer.h"
-#include "url/gurl.h"
-
-namespace {
-
-// Waits until a web view is added as a child view of the given view.
-class WebViewAddedWaiter : public views::ViewObserver {
- public:
-  WebViewAddedWaiter(
-      views::View* top_view,
-      base::RepeatingCallback<views::WebView*()> current_web_view_getter)
-      : current_web_view_getter_(current_web_view_getter) {
-    observation_.Observe(top_view);
-  }
-  ~WebViewAddedWaiter() override = default;
-
-  void Wait() { run_loop_.Run(); }
-
- private:
-  // ViewObserver:
-  void OnChildViewAdded(views::View* observed_view,
-                        views::View* child) override {
-    if (child == current_web_view_getter_.Run()) {
-      ASSERT_TRUE(child);
-      run_loop_.Quit();
-    }
-  }
-
-  base::RunLoop run_loop_;
-  base::RepeatingCallback<views::WebView*()> current_web_view_getter_;
-  base::ScopedObservation<views::View, views::ViewObserver> observation_{this};
-};
-
-// Waits until a first non empty paint for given `url`.
-class FirstVisuallyNonEmptyPaintObserver : public content::WebContentsObserver {
- public:
-  explicit FirstVisuallyNonEmptyPaintObserver(content::WebContents* contents,
-                                              const GURL& url)
-      : content::WebContentsObserver(contents), url_(url) {}
-
-  // Waits for the first paint.
-  void Wait() {
-    if (IsExitConditionSatisfied()) {
-      return;
-    }
-    run_loop_.Run();
-    EXPECT_TRUE(IsExitConditionSatisfied())
-        << web_contents()->GetVisibleURL() << " != " << url_;
-  }
-
- private:
-  // WebContentsObserver:
-  void DidFirstVisuallyNonEmptyPaint() override {
-    if (web_contents()->GetVisibleURL() == url_)
-      run_loop_.Quit();
-  }
-
-  bool IsExitConditionSatisfied() {
-    return (web_contents()->GetVisibleURL() == url_ &&
-            web_contents()->CompletedFirstVisuallyNonEmptyPaint());
-  }
-
-  base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
-  GURL url_;
-};
-
-}  // namespace
-
-ProfilePickerTestBase::ProfilePickerTestBase() {
-  feature_list_.InitAndEnableFeature(features::kNewProfilePicker);
-}
-
-ProfilePickerTestBase::~ProfilePickerTestBase() = default;
-
-views::View* ProfilePickerTestBase::view() {
-  return ProfilePicker::GetViewForTesting();
-}
-
-views::Widget* ProfilePickerTestBase::widget() {
-  return view() ? view()->GetWidget() : nullptr;
-}
-
-views::WebView* ProfilePickerTestBase::web_view() {
-  return ProfilePicker::GetWebViewForTesting();
-}
-
-void ProfilePickerTestBase::WaitForNewWebView() {
-  ASSERT_TRUE(view());
-  WebViewAddedWaiter(view(),
-                     base::BindRepeating(&ProfilePickerTestBase::web_view,
-                                         base::Unretained(this)))
-      .Wait();
-  EXPECT_TRUE(web_view());
-}
-
-void ProfilePickerTestBase::WaitForFirstPaint(content::WebContents* contents,
-                                              const GURL& url) {
-  DCHECK(contents);
-  FirstVisuallyNonEmptyPaintObserver(contents, url).Wait();
-}
-
-content::WebContents* ProfilePickerTestBase::web_contents() {
-  if (!web_view())
-    return nullptr;
-  return web_view()->GetWebContents();
-}
diff --git a/chrome/browser/ui/views/profiles/profile_picker_test_base.h b/chrome/browser/ui/views/profiles/profile_picker_test_base.h
deleted file mode 100644
index 7176045..0000000
--- a/chrome/browser/ui/views/profiles/profile_picker_test_base.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_PICKER_TEST_BASE_H_
-#define CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_PICKER_TEST_BASE_H_
-
-#include "base/test/scoped_feature_list.h"
-#include "chrome/test/base/in_process_browser_test.h"
-
-namespace content {
-class WebContents;
-}
-
-namespace views {
-class View;
-class WebView;
-class Widget;
-}  // namespace views
-
-class GURL;
-
-class ProfilePickerTestBase : public InProcessBrowserTest {
- public:
-  ProfilePickerTestBase();
-  ~ProfilePickerTestBase() override;
-
-  // Returns the ProfilePickerView that is currently displayed.
-  views::View* view();
-
-  // Returns the widget associated with the profile picker.
-  views::Widget* widget();
-
-  // Returns the internal web view for the profile picker.
-  views::WebView* web_view();
-
-  // Waits until a new internal web view has been added to the main picker view.
-  void WaitForNewWebView();
-
-  // Waits until the web contents does the first non-empty paint for `url`.
-  void WaitForFirstPaint(content::WebContents* contents, const GURL& url);
-
-  // Gets the picker's web contents.
-  content::WebContents* web_contents();
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_PICKER_TEST_BASE_H_
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view.cc b/chrome/browser/ui/views/profiles/profile_picker_view.cc
index 3c0a000..dcf67b1 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view.cc
@@ -10,10 +10,8 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
-#include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
-#include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
@@ -26,7 +24,6 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
-#include "chrome/browser/ui/views/accelerator_table.h"
 #include "chrome/browser/ui/views/profiles/profile_picker_view_sync_delegate.h"
 #include "chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h"
 #include "chrome/browser/ui/webui/signin/profile_picker_ui.h"
@@ -55,10 +52,6 @@
 #include "ui/views/win/hwnd_util.h"
 #endif
 
-#if defined(OS_MAC)
-#include "chrome/browser/global_keyboard_shortcuts_mac.h"
-#endif
-
 namespace {
 
 ProfilePickerView* g_profile_picker_view = nullptr;
@@ -69,10 +62,6 @@
 constexpr base::TimeDelta kExtendedAccountInfoTimeout =
     base::TimeDelta::FromSeconds(10);
 
-constexpr int kSupportedAcceleratorCommands[] = {
-    IDC_CLOSE_TAB, IDC_CLOSE_WINDOW, IDC_EXIT, IDC_FULLSCREEN,
-    IDC_MINIMIZE_WINDOW};
-
 GURL CreateURLForEntryPoint(ProfilePicker::EntryPoint entry_point) {
   GURL base_url = GURL(chrome::kChromeUIProfilePickerUrl);
   switch (entry_point) {
@@ -156,7 +145,6 @@
   SetButtons(ui::DIALOG_BUTTON_NONE);
   SetTitle(IDS_PRODUCT_NAME);
   set_use_custom_frame(false);
-  ConfigureAccelerators();
   // TODO(crbug.com/1063856): Add |RecordDialogCreation|.
 }
 
@@ -214,11 +202,16 @@
 void ProfilePickerView::Init(ProfilePicker::EntryPoint entry_point,
                              Profile* system_profile) {
   DCHECK_EQ(state_, kInitializing);
-  CreateWebView(system_profile);
-  DCHECK(web_view_);
+  auto web_view = std::make_unique<views::WebView>(system_profile);
+  web_view->GetWebContents()->SetDelegate(this);
   // To record metrics using javascript, extensions are needed.
   extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
-      web_view_->GetWebContents());
+      web_view->GetWebContents());
+  // Set the member before adding to the hieararchy to make it easier for tests
+  // to detect that a new WebView has been created.
+  web_view_ = web_view.get();
+  AddChildView(std::move(web_view));
+  SetLayoutManager(std::make_unique<views::FillLayout>());
 
   CreateDialogWidget(this, nullptr, nullptr);
 
@@ -317,7 +310,13 @@
   // Rebuild the view.
   // TODO(crbug.com/1126913): Add the simple toolbar with the back button.
   RemoveAllChildViews(true);
-  CreateWebView(profile);
+  auto web_view = std::make_unique<views::WebView>(profile);
+  web_view->GetWebContents()->SetDelegate(this);
+  // Set the member before adding to the hieararchy to make it easier for tests
+  // to detect that a new WebView has been created.
+  web_view_ = web_view.get();
+  AddChildView(std::move(web_view));
+  SetLayoutManager(std::make_unique<views::FillLayout>());
   web_view_->LoadInitialURL(GaiaUrls::GetInstance()->signin_chrome_sync_dice());
   web_view_->RequestFocus();
 }
@@ -361,43 +360,6 @@
   return minimum_size;
 }
 
-bool ProfilePickerView::AcceleratorPressed(const ui::Accelerator& accelerator) {
-  // Ignore presses of the Escape key. The profile picker may be Chrome's only
-  // top-level window, in which case we don't want presses of Esc to maybe quit
-  // the entire browser. This has higher priority than the default dialog Esc
-  // accelerator (which would otherwise close the window).
-  if (accelerator.key_code() == ui::VKEY_ESCAPE &&
-      accelerator.modifiers() == ui::EF_NONE) {
-    return true;
-  }
-
-  const auto& iter = accelerator_table_.find(accelerator);
-  DCHECK(iter != accelerator_table_.end());
-  int command_id = iter->second;
-  switch (command_id) {
-    case IDC_CLOSE_TAB:
-    case IDC_CLOSE_WINDOW:
-      // kEscKeyPressed is used although that shortcut is disabled (this is
-      // Ctrl-Shift-W instead).
-      GetWidget()->CloseWithReason(views::Widget::ClosedReason::kEscKeyPressed);
-      break;
-    case IDC_EXIT:
-      chrome::AttemptUserExit();
-      break;
-    case IDC_FULLSCREEN:
-      GetWidget()->SetFullscreen(!GetWidget()->IsFullscreen());
-      break;
-    case IDC_MINIMIZE_WINDOW:
-      GetWidget()->Minimize();
-      break;
-    default:
-      NOTREACHED() << "Unexpected command_id: " << command_id;
-      break;
-  }
-
-  return true;
-}
-
 bool ProfilePickerView::HandleContextMenu(
     content::RenderFrameHost* render_frame_host,
     const content::ContextMenuParams& params) {
@@ -405,15 +367,6 @@
   return true;
 }
 
-bool ProfilePickerView::HandleKeyboardEvent(
-    content::WebContents* source,
-    const content::NativeWebKeyboardEvent& event) {
-  // Forward the keyboard event to AcceleratorPressed() through the
-  // FocusManager.
-  return unhandled_keyboard_event_handler_.HandleKeyboardEvent(
-      event, GetFocusManager());
-}
-
 void ProfilePickerView::AddNewContents(
     content::WebContents* source,
     std::unique_ptr<content::WebContents> new_contents,
@@ -573,44 +526,3 @@
   DCHECK(browser);
   std::move(finish_flow_callback).Run(browser);
 }
-
-void ProfilePickerView::ConfigureAccelerators() {
-  // By default, dialog views close when pressing escape. Override this
-  // behavior as the profile picker should not close in that case.
-  AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
-
-  const std::vector<AcceleratorMapping> accelerator_list(GetAcceleratorList());
-  for (const auto& entry : accelerator_list) {
-    if (!base::Contains(kSupportedAcceleratorCommands, entry.command_id))
-      continue;
-    ui::Accelerator accelerator(entry.keycode, entry.modifiers);
-    accelerator_table_[accelerator] = entry.command_id;
-    AddAccelerator(accelerator);
-  }
-
-#if defined(OS_MAC)
-  // Check Mac-specific accelerators. Note: Chrome does not support dynamic or
-  // user-configured accelerators on Mac. Default static accelerators are used
-  // instead.
-  for (int command_id : kSupportedAcceleratorCommands) {
-    ui::Accelerator accelerator;
-    bool mac_accelerator_found =
-        GetDefaultMacAcceleratorForCommandId(command_id, &accelerator);
-    if (mac_accelerator_found) {
-      accelerator_table_[accelerator] = command_id;
-      AddAccelerator(accelerator);
-    }
-  }
-#endif  // OS_MAC
-}
-
-void ProfilePickerView::CreateWebView(Profile* profile) {
-  auto web_view = std::make_unique<views::WebView>(profile);
-  web_view->GetWebContents()->SetDelegate(this);
-  web_view->set_allow_accelerators(true);
-  // Set the member before adding to the hieararchy to make it easier for tests
-  // to detect that a new WebView has been created.
-  web_view_ = web_view.get();
-  AddChildView(std::move(web_view));
-  SetLayoutManager(std::make_unique<views::FillLayout>());
-}
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view.h b/chrome/browser/ui/views/profiles/profile_picker_view.h
index 9141802..7c470c2 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view.h
+++ b/chrome/browser/ui/views/profiles/profile_picker_view.h
@@ -11,7 +11,6 @@
 #include "components/keep_alive_registry/scoped_keep_alive.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "content/public/browser/web_contents_delegate.h"
-#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
 #include "ui/views/controls/webview/webview.h"
 #include "ui/views/window/dialog_delegate.h"
 
@@ -73,7 +72,6 @@
 
   // views::View;
   gfx::Size GetMinimumSize() const override;
-  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
 
   // content::WebContentsDelegate:
   bool HandleContextMenu(content::RenderFrameHost* render_frame_host,
@@ -85,9 +83,6 @@
                       const gfx::Rect& initial_rect,
                       bool user_gesture,
                       bool* was_blocked) override;
-  bool HandleKeyboardEvent(
-      content::WebContents* source,
-      const content::NativeWebKeyboardEvent& event) override;
 
   // IdentityManager::Observer:
   void OnRefreshTokenUpdatedForAccount(
@@ -109,22 +104,9 @@
                        Profile* profile,
                        Profile::CreateStatus profile_create_status);
 
-  // Register basic keyboard accelerators such as closing the window (Alt-F4
-  // on Windows).
-  void ConfigureAccelerators();
-
-  // Creates and configures the internal web view, and adds it as a child view.
-  void CreateWebView(Profile* profile);
-
   ScopedKeepAlive keep_alive_;
   State state_ = State::kNotStarted;
 
-  // A mapping between accelerators and command IDs.
-  std::map<ui::Accelerator, int> accelerator_table_;
-
-  // Handler for unhandled key events from renderer.
-  views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
-
   // The current WebView object, owned by the view hierarchy.
   views::WebView* web_view_ = nullptr;
 
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
index f7f4af1..c22da81 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
@@ -25,7 +25,6 @@
 #include "chrome/browser/ui/sync/profile_signin_confirmation_helper.h"
 #include "chrome/browser/ui/tab_dialogs.h"
 #include "chrome/browser/ui/ui_features.h"
-#include "chrome/browser/ui/views/profiles/profile_picker_test_base.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -39,12 +38,12 @@
 #include "components/sync/driver/sync_driver_switches.h"
 #include "components/sync/driver/sync_service.h"
 #include "components/sync/driver/sync_user_settings.h"
-#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "third_party/skia/include/core/SkColor.h"
-#include "url/gurl.h"
+#include "ui/views/view_observer.h"
 
 namespace {
 
@@ -70,6 +69,64 @@
   return account_info;
 }
 
+// Waits until a first non empty paint for given `url`.
+class FirstVisuallyNonEmptyPaintObserver : public content::WebContentsObserver {
+ public:
+  explicit FirstVisuallyNonEmptyPaintObserver(content::WebContents* contents,
+                                              const GURL& url)
+      : content::WebContentsObserver(contents), url_(url) {}
+
+  void DidFirstVisuallyNonEmptyPaint() override {
+    if (web_contents()->GetVisibleURL() == url_)
+      run_loop_.Quit();
+  }
+
+  void Wait() {
+    if (IsExitConditionSatisfied()) {
+      return;
+    }
+    run_loop_.Run();
+    EXPECT_TRUE(IsExitConditionSatisfied())
+        << web_contents()->GetVisibleURL() << " != " << url_;
+  }
+
+ private:
+  bool IsExitConditionSatisfied() {
+    return (web_contents()->GetVisibleURL() == url_ &&
+            web_contents()->CompletedFirstVisuallyNonEmptyPaint());
+  }
+
+  base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
+  GURL url_;
+};
+
+class WebViewAddedWaiter : public views::ViewObserver {
+ public:
+  WebViewAddedWaiter(
+      views::View* top_view,
+      base::RepeatingCallback<views::WebView*()> current_web_view_getter)
+      : current_web_view_getter_(current_web_view_getter) {
+    observation_.Observe(top_view);
+  }
+  ~WebViewAddedWaiter() override = default;
+
+  void Wait() { run_loop_.Run(); }
+
+ private:
+  // ViewObserver:
+  void OnChildViewAdded(views::View* observed_view,
+                        views::View* child) override {
+    if (child == current_web_view_getter_.Run()) {
+      ASSERT_TRUE(child);
+      run_loop_.Quit();
+    }
+  }
+
+  base::RunLoop run_loop_;
+  base::RepeatingCallback<views::WebView*()> current_web_view_getter_;
+  base::ScopedObservation<views::View, views::ViewObserver> observation_{this};
+};
+
 class BrowserAddedWaiter : public BrowserListObserver {
  public:
   explicit BrowserAddedWaiter(size_t total_count) : total_count_(total_count) {
@@ -199,14 +256,15 @@
   base::RunLoop* run_loop_;
 };
 
-class ProfilePickerCreationFlowBrowserTest : public ProfilePickerTestBase {
+class ProfilePickerCreationFlowBrowserTest : public InProcessBrowserTest {
  public:
   ProfilePickerCreationFlowBrowserTest() {
-    feature_list_.InitAndEnableFeature(features::kProfilesUIRevamp);
+    feature_list_.InitWithFeatures(
+        {features::kProfilesUIRevamp, features::kNewProfilePicker}, {});
   }
 
   void SetUpInProcessBrowserTestFixture() override {
-    ProfilePickerTestBase::SetUpInProcessBrowserTestFixture();
+    InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
     create_services_subscription_ =
         BrowserContextDependencyManager::GetInstance()
             ->RegisterCreateServicesCallbackForTesting(
@@ -221,6 +279,26 @@
         context, base::BindRepeating(&FakeUserPolicySigninService::Build));
   }
 
+  views::View* view() { return ProfilePicker::GetViewForTesting(); }
+
+  views::WebView* web_view() { return ProfilePicker::GetWebViewForTesting(); }
+
+  void WaitForNewWebView() {
+    ASSERT_TRUE(view());
+    WebViewAddedWaiter(
+        view(),
+        base::BindRepeating(&ProfilePickerCreationFlowBrowserTest::web_view,
+                            base::Unretained(this)))
+        .Wait();
+    EXPECT_TRUE(web_view());
+  }
+
+  content::WebContents* web_contents() {
+    if (!web_view())
+      return nullptr;
+    return web_view()->GetWebContents();
+  }
+
  private:
   base::CallbackListSubscription create_services_subscription_;
   base::test::ScopedFeatureList feature_list_;
@@ -246,8 +324,9 @@
   ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileMenuAddNewProfile);
   WaitForNewWebView();
   EXPECT_TRUE(ProfilePicker::IsOpen());
-  WaitForFirstPaint(web_contents(),
-                    GURL("chrome://profile-picker/new-profile"));
+  FirstVisuallyNonEmptyPaintObserver(
+      web_contents(), GURL("chrome://profile-picker/new-profile"))
+      .Wait();
 }
 
 IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest,
@@ -265,8 +344,9 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  WaitForFirstPaint(web_contents(),
-                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
+  FirstVisuallyNonEmptyPaintObserver(
+      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
+      .Wait();
 
   // Add an account - simulate a successful Gaia sign-in.
   Profile* profile_being_created =
@@ -285,14 +365,18 @@
 
   // Wait for the sign-in to propagate to the flow, resulting in sync
   // confirmation screen getting displayed.
-  WaitForFirstPaint(web_contents(), GURL("chrome://sync-confirmation/"));
+  FirstVisuallyNonEmptyPaintObserver(web_contents(),
+                                     GURL("chrome://sync-confirmation/"))
+      .Wait();
 
   // Simulate closing the UI with "Yes, I'm in".
   LoginUIServiceFactory::GetForProfile(profile_being_created)
       ->SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
-  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
-                    GURL("chrome://newtab/"));
+  FirstVisuallyNonEmptyPaintObserver(
+      new_browser->tab_strip_model()->GetActiveWebContents(),
+      GURL("chrome://newtab/"))
+      .Wait();
 
   // Check expectations when the profile creation flow is done.
   EXPECT_FALSE(ProfilePicker::IsOpen());
@@ -327,8 +411,9 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  WaitForFirstPaint(web_contents(),
-                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
+  FirstVisuallyNonEmptyPaintObserver(
+      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
+      .Wait();
 
   // Disable sync by setting the device as managed in prefs.
   Profile* profile_being_created =
@@ -352,8 +437,10 @@
   // Wait for the sign-in to propagate to the flow, resulting in new browser
   // getting opened.
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
-  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
-                    GURL("chrome://newtab/"));
+  FirstVisuallyNonEmptyPaintObserver(
+      new_browser->tab_strip_model()->GetActiveWebContents(),
+      GURL("chrome://newtab/"))
+      .Wait();
 
   EXPECT_FALSE(ProfilePicker::IsOpen());
 
@@ -391,8 +478,9 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  WaitForFirstPaint(web_contents(),
-                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
+  FirstVisuallyNonEmptyPaintObserver(
+      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
+      .Wait();
 
   // Add an account - simulate a successful Gaia sign-in.
   Profile* profile_being_created =
@@ -409,14 +497,18 @@
 
   // Wait for the sign-in to propagate to the flow, resulting in sync
   // confirmation screen getting displayed.
-  WaitForFirstPaint(web_contents(), GURL("chrome://sync-confirmation/"));
+  FirstVisuallyNonEmptyPaintObserver(web_contents(),
+                                     GURL("chrome://sync-confirmation/"))
+      .Wait();
 
   // Simulate closing the UI with "Yes, I'm in".
   LoginUIServiceFactory::GetForProfile(profile_being_created)
       ->SyncConfirmationUIClosed(LoginUIService::CONFIGURE_SYNC_FIRST);
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
-  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
-                    GURL("chrome://settings/syncSetup"));
+  FirstVisuallyNonEmptyPaintObserver(
+      new_browser->tab_strip_model()->GetActiveWebContents(),
+      GURL("chrome://settings/syncSetup"))
+      .Wait();
 
   // Check expectations when the profile creation flow is done.
   EXPECT_FALSE(ProfilePicker::IsOpen());
@@ -452,8 +544,9 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  WaitForFirstPaint(web_contents(),
-                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
+  FirstVisuallyNonEmptyPaintObserver(
+      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
+      .Wait();
 
   // Simulate clicking on a link that opens in a new window.
   const GURL kURL("https://foo.google.com");
@@ -468,8 +561,9 @@
   // A new pppup browser is displayed (with the specified URL).
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
   EXPECT_EQ(new_browser->type(), Browser::TYPE_POPUP);
-  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
-                    kURL);
+  FirstVisuallyNonEmptyPaintObserver(
+      new_browser->tab_strip_model()->GetActiveWebContents(), kURL)
+      .Wait();
 }
 
 // TODO(crbug.com/1144065): Flaky on multiple platforms.
@@ -512,8 +606,9 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  WaitForFirstPaint(web_contents(),
-                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
+  FirstVisuallyNonEmptyPaintObserver(
+      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
+      .Wait();
 
   // Add an account - simulate a successful Gaia sign-in.
   Profile* profile_being_created =
@@ -530,8 +625,10 @@
 
   // Instead of sync confirmation, a browser is displayed (with a login error).
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
-  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
-                    GURL("chrome://newtab/"));
+  FirstVisuallyNonEmptyPaintObserver(
+      new_browser->tab_strip_model()->GetActiveWebContents(),
+      GURL("chrome://newtab/"))
+      .Wait();
 
   // Check expectations when the profile creation flow is done.
   EXPECT_FALSE(ProfilePicker::IsOpen());
@@ -562,8 +659,9 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  WaitForFirstPaint(web_contents(),
-                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
+  FirstVisuallyNonEmptyPaintObserver(
+      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
+      .Wait();
 
   Profile* profile_being_created =
       static_cast<Profile*>(web_view()->GetBrowserContext());
@@ -582,14 +680,18 @@
 
   // Wait for the sign-in to propagate to the flow, resulting in sync
   // confirmation screen getting displayed.
-  WaitForFirstPaint(web_contents(), GURL("chrome://sync-confirmation/"));
+  FirstVisuallyNonEmptyPaintObserver(web_contents(),
+                                     GURL("chrome://sync-confirmation/"))
+      .Wait();
 
   // Simulate closing the UI with "Yes, I'm in".
   LoginUIServiceFactory::GetForProfile(profile_being_created)
       ->SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
-  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
-                    GURL("chrome://newtab/"));
+  FirstVisuallyNonEmptyPaintObserver(
+      new_browser->tab_strip_model()->GetActiveWebContents(),
+      GURL("chrome://newtab/"))
+      .Wait();
 
   // Check expectations when the profile creation flow is done.
   EXPECT_FALSE(ProfilePicker::IsOpen());
@@ -638,8 +740,9 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  WaitForFirstPaint(web_contents(),
-                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
+  FirstVisuallyNonEmptyPaintObserver(
+      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
+      .Wait();
 
   // Add an account - simulate a successful Gaia sign-in.
   Profile* profile_being_created =
@@ -672,8 +775,10 @@
   // The picker should be closed even before the enterprise confirmation but it
   // is closed asynchronously after opening the browser so after the NTP
   // renders, it is safe to check.
-  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
-                    GURL("chrome://newtab/"));
+  FirstVisuallyNonEmptyPaintObserver(
+      new_browser->tab_strip_model()->GetActiveWebContents(),
+      GURL("chrome://newtab/"))
+      .Wait();
   EXPECT_FALSE(ProfilePicker::IsOpen());
 
   // Now the sync consent screen is shown, simulate closing the UI with "Yes,
@@ -709,8 +814,9 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  WaitForFirstPaint(web_contents(),
-                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
+  FirstVisuallyNonEmptyPaintObserver(
+      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
+      .Wait();
 
   // Add an account - simulate a successful Gaia sign-in.
   Profile* profile_being_created =
@@ -743,8 +849,10 @@
   // "Configure sync".
   LoginUIServiceFactory::GetForProfile(profile_being_created)
       ->SyncConfirmationUIClosed(LoginUIService::CONFIGURE_SYNC_FIRST);
-  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
-                    GURL("chrome://settings/syncSetup"));
+  FirstVisuallyNonEmptyPaintObserver(
+      new_browser->tab_strip_model()->GetActiveWebContents(),
+      GURL("chrome://settings/syncSetup"))
+      .Wait();
 
   // Check expectations when the profile creation flow is done.
   EXPECT_FALSE(ProfilePicker::IsOpen());
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index 2a1a0cd..475acf66 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -2459,7 +2459,9 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-#if defined(OS_LINUX)
+// TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is
+// complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #define MAYBE_DraggingRightExpandsTabStripSize \
   DISABLED_DraggingRightExpandsTabStripSize
 #else
diff --git a/chrome/browser/ui/views/toolbar/app_menu.cc b/chrome/browser/ui/views/toolbar/app_menu.cc
index 23d02c8..c71703c 100644
--- a/chrome/browser/ui/views/toolbar/app_menu.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu.cc
@@ -474,7 +474,7 @@
         InMenuButtonBackground::LEADING_BORDER));
     fullscreen_button_->SetAccessibleName(GetAccessibleNameForAppMenuItem(
         menu_model, fullscreen_index, IDS_ACCNAME_FULLSCREEN,
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
         // ChromeOS uses a dedicated "fullscreen" media key for fullscreen
         // mode on most ChromeOS devices which cannot be specified in the
         // standard way here, so omit the accelerator to avoid providing
diff --git a/chrome/browser/ui/web_applications/app_browser_controller.cc b/chrome/browser/ui/web_applications/app_browser_controller.cc
index 6838d1f..718206a 100644
--- a/chrome/browser/ui/web_applications/app_browser_controller.cc
+++ b/chrome/browser/ui/web_applications/app_browser_controller.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "base/strings/string_piece.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/installable/installable_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
@@ -51,7 +52,7 @@
 #include "url/gurl.h"
 #include "url/origin.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/crostini/crostini_terminal.h"
 #include "chrome/browser/ui/app_list/icon_standardizer.h"
 #endif
@@ -377,7 +378,7 @@
 
 bool AppBrowserController::ShouldShowTabContextMenuShortcut(
     int command_id) const {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // TODO(crbug.com/1061822): Generalize ShouldShowTabContextMenuShortcut as
   // a SystemWebApp capability.
   if (system_app_type_ == SystemAppType::TERMINAL &&
@@ -508,7 +509,7 @@
 gfx::ImageSkia AppBrowserController::GetFallbackAppIcon() const {
   gfx::ImageSkia page_icon = browser()->GetCurrentPageIcon().AsImageSkia();
   if (!page_icon.isNull()) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon))
       return app_list::CreateStandardIconImage(page_icon);
 #endif
diff --git a/chrome/browser/ui/web_applications/app_browser_controller_browsertest.cc b/chrome/browser/ui/web_applications/app_browser_controller_browsertest.cc
index 030ce983..aa94f8d 100644
--- a/chrome/browser/ui/web_applications/app_browser_controller_browsertest.cc
+++ b/chrome/browser/ui/web_applications/app_browser_controller_browsertest.cc
@@ -8,6 +8,7 @@
 
 #include "base/run_loop.h"
 #include "base/test/bind.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/installable/installable_metrics.h"
 #include "chrome/browser/themes/custom_theme_supplier.h"
@@ -245,7 +246,7 @@
 }
 
 // App Popups are only used on Chrome OS. See https://crbug.com/1060917.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_F(AppBrowserControllerBrowserTest,
                        WhiteThemeForSystemAppPopup) {
   InstallAndLaunchMockPopup();
@@ -268,7 +269,7 @@
 }
 #endif
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_F(AppBrowserControllerBrowserTest, InitialBounds) {
   InstallAndLaunchMockApp();
   EXPECT_EQ(app_browser_->window()->GetBounds(), gfx::Rect(64, 64, 652, 484));
diff --git a/chrome/browser/ui/web_applications/pwa_mixed_content_browsertest.cc b/chrome/browser/ui/web_applications/pwa_mixed_content_browsertest.cc
index 1f8dd96..b8692634 100644
--- a/chrome/browser/ui/web_applications/pwa_mixed_content_browsertest.cc
+++ b/chrome/browser/ui/web_applications/pwa_mixed_content_browsertest.cc
@@ -5,6 +5,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -234,7 +235,7 @@
 // Tests that iframes can't dynamically load mixed content in a regular browser
 // tab, when the iframe was created in a PWA window.
 // https://crbug.com/1087382: Flaky on Windows, CrOS and ASAN
-#if defined(OS_WIN) || defined(OS_CHROMEOS) || defined(ADDRESS_SANITIZER)
+#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH) || defined(ADDRESS_SANITIZER)
 #define MAYBE_IFrameDynamicMixedContentInPWAOpenInChrome \
   DISABLED_IFrameDynamicMixedContentInPWAOpenInChrome
 #else
diff --git a/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc b/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
index dffb5f3c..0a81b98 100644
--- a/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
+++ b/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
@@ -14,6 +14,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/optional.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/app_service_metrics.h"
 #include "chrome/browser/apps/app_service/launch_utils.h"
@@ -36,7 +37,7 @@
 #include "ui/base/window_open_disposition.h"
 #include "ui/display/types/display_constants.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
 #endif
 
@@ -51,7 +52,7 @@
   // alternative.
   if (profile->IsSystemProfile())
     return nullptr;
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   if (chromeos::ProfileHelper::IsSigninProfile(profile))
     return nullptr;
 #endif
@@ -258,7 +259,7 @@
     }
   }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // LaunchSystemWebApp may be called with a profile associated with an
   // inactive (background) desktop (e.g. when multiple users are logged in).
   // Here we move the newly created browser window (or the existing one on the
diff --git a/chrome/browser/ui/web_applications/test/system_web_app_ui_browsertest.cc b/chrome/browser/ui/web_applications/test/system_web_app_ui_browsertest.cc
index 5f5d9fe..a63c595 100644
--- a/chrome/browser/ui/web_applications/test/system_web_app_ui_browsertest.cc
+++ b/chrome/browser/ui/web_applications/test/system_web_app_ui_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/gtest_util.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
@@ -29,7 +30,7 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_navigation_observer.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/chromeos/login/login_manager_test.h"
@@ -442,7 +443,7 @@
   EXPECT_FALSE(app_browser->app_controller()->ShouldShowCustomTabBar());
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // Use LoginManagerTest here instead of SystemWebAppManagerBrowserTest, because
 // it's less complicated to add SWA to LoginManagerTest than adding multi-logins
 // to SWA browsertest.
@@ -567,14 +568,14 @@
       MultiUserWindowManagerHelper::GetInstance()->IsWindowOnDesktopOfUser(
           browser2->window()->GetNativeWindow(), account_id2_));
 }
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // The following tests are disabled in DCHECK builds. LaunchSystemWebApp DCHECKs
 // if the wrong profile is used. EXPECT_DCHECK_DEATH (or its variants) aren't
 // reliable in browsertests, so we don't test this. This is okay because these
 // tests are used to verify that in release builds, LaunchSystemWebApp doesn't
 // crash and behaves reasonably (pick an appropriate profile).
-#if defined(OS_CHROMEOS) && !DCHECK_IS_ON()
+#if BUILDFLAG(IS_CHROMEOS_ASH) && !DCHECK_IS_ON()
 using SystemWebAppLaunchProfileBrowserTest = SystemWebAppManagerBrowserTest;
 
 IN_PROC_BROWSER_TEST_P(SystemWebAppLaunchProfileBrowserTest,
@@ -646,12 +647,12 @@
   EXPECT_TRUE(result_browser->profile()->IsGuestSession());
   EXPECT_TRUE(result_browser->profile()->IsPrimaryOTRProfile());
 }
-#endif  // defined(OS_CHROMEOS) && !DCHECK_IS_ON()
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) && !DCHECK_IS_ON()
 
 INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P(
     SystemWebAppLinkCaptureBrowserTest);
 
-#if defined(OS_CHROMEOS) && !DCHECK_IS_ON()
+#if BUILDFLAG(IS_CHROMEOS_ASH) && !DCHECK_IS_ON()
 INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P(
     SystemWebAppLaunchProfileBrowserTest);
 
diff --git a/chrome/browser/ui/web_applications/web_app_browser_controller.cc b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
index 1a9aba7..668ec1d 100644
--- a/chrome/browser/ui/web_applications/web_app_browser_controller.cc
+++ b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
@@ -7,6 +7,7 @@
 #include "base/callback_helpers.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -27,7 +28,7 @@
 #include "ui/gfx/image/image.h"
 #include "url/gurl.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/apps/apk_web_app_service.h"
 
 namespace {
@@ -68,7 +69,7 @@
   return display == DisplayMode::kWindowControlsOverlay;
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 bool WebAppBrowserController::ShouldShowCustomTabBar() const {
   if (AppBrowserController::ShouldShowCustomTabBar())
     return true;
@@ -92,7 +93,7 @@
   browser()->window()->UpdateCustomTabBarVisibility(should_show_cct,
                                                     false /* animate */);
 }
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 void WebAppBrowserController::OnWebAppUninstalled(const AppId& app_id) {
   if (HasAppId() && app_id == GetAppId())
@@ -113,7 +114,7 @@
     return *app_icon_;
   app_icon_ = GetFallbackAppIcon();
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon) &&
       apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(
           browser()->profile())) {
@@ -279,7 +280,7 @@
 
 void WebAppBrowserController::PerformDigitalAssetLinkVerification(
     Browser* browser) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   asset_link_handler_ =
       std::make_unique<digital_asset_links::DigitalAssetLinksHandler>(
           browser->profile()->GetURLLoaderFactory());
diff --git a/chrome/browser/ui/web_applications/web_app_browser_controller.h b/chrome/browser/ui/web_applications/web_app_browser_controller.h
index da81bcf..5f0b88d2 100644
--- a/chrome/browser/ui/web_applications/web_app_browser_controller.h
+++ b/chrome/browser/ui/web_applications/web_app_browser_controller.h
@@ -13,6 +13,7 @@
 #include "base/optional.h"
 #include "base/scoped_observer.h"
 #include "base/strings/string16.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/web_applications/components/app_registrar.h"
 #include "chrome/browser/web_applications/components/app_registrar_observer.h"
@@ -21,7 +22,7 @@
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/image/image_skia.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/installable/digital_asset_links/digital_asset_links_handler.h"
 #endif
 
@@ -69,9 +70,9 @@
   bool IsHostedApp() const override;
   bool IsWindowControlsOverlayEnabled() const override;
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   bool ShouldShowCustomTabBar() const override;
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   // AppRegistrarObserver:
   void OnWebAppUninstalled(const AppId& app_id) override;
@@ -95,22 +96,22 @@
   void OnReadIcon(const SkBitmap& bitmap);
   void PerformDigitalAssetLinkVerification(Browser* browser);
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   void OnRelationshipCheckComplete(
       digital_asset_links::RelationshipCheckResult result);
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   WebAppProvider& provider_;
   mutable base::Optional<gfx::ImageSkia> app_icon_;
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // The result of digital asset link verification of the web app.
   // Only used for web-only TWAs installed through the Play Store.
   base::Optional<bool> is_verified_;
 
   std::unique_ptr<digital_asset_links::DigitalAssetLinksHandler>
       asset_link_handler_;
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   ScopedObserver<AppRegistrar, AppRegistrarObserver> registrar_observer_{this};
 
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index 09e6e2e..d844d5c5 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -11,6 +11,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/sessions/tab_restore_service_factory.h"
 #include "chrome/browser/themes/custom_theme_supplier.h"
@@ -56,7 +57,7 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #endif
 
@@ -698,7 +699,7 @@
   int index = -1;
   const bool found = app_menu_model->GetModelAndIndexForCommandId(
       WebAppMenuModel::kUninstallAppCommandId, &model, &index);
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   EXPECT_FALSE(found);
 #else
   EXPECT_TRUE(found);
@@ -711,7 +712,7 @@
                             MENU_ACTION_UNINSTALL_APP, 1);
   tester.ExpectUniqueSample("WrenchMenu.MenuAction", MENU_ACTION_UNINSTALL_APP,
                             1);
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 // Tests that both installing a PWA and creating a shortcut app are disabled for
@@ -800,7 +801,7 @@
   EXPECT_EQ(1, user_action_tester.GetActionCount("InstallWebAppFromMenu"));
   EXPECT_EQ(0, user_action_tester.GetActionCount("CreateShortcut"));
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Apps on Chrome OS should not be pinned after install.
   EXPECT_FALSE(ChromeLauncherController::instance()->IsAppPinned(app_id));
 #endif
diff --git a/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc b/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc
index 217caea..40997f7 100644
--- a/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc
@@ -11,6 +11,7 @@
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
@@ -33,7 +34,7 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/web_launch/file_handling_expiry.mojom-test-utils.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/chromeos/file_manager/file_manager_test_util.h"
 #endif
 
@@ -557,7 +558,7 @@
             content::EvalJs(web_content, "window.launchParams.files[0].name"));
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 
 // End-to-end test to ensure the file handler is registered on ChromeOS when the
 // extension system is initialized. Gives more coverage than the unit tests for
@@ -598,4 +599,4 @@
   EXPECT_EQ(0u, tasks.size());
 }
 
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/web_applications/web_app_menu_model.cc b/chrome/browser/ui/web_applications/web_app_menu_model.cc
index abccc4e7..a380027 100644
--- a/chrome/browser/ui/web_applications/web_app_menu_model.cc
+++ b/chrome/browser/ui/web_applications/web_app_menu_model.cc
@@ -6,6 +6,7 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/profiles/profile.h"
@@ -67,7 +68,7 @@
 
 // Chrome OS's app list is prominent enough to not need a separate uninstall
 // option in the app menu.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   DCHECK(browser()->app_controller());
   if (browser()->app_controller()->IsInstalled()) {
     AddSeparator(ui::NORMAL_SEPARATOR);
@@ -77,7 +78,7 @@
                 ui::EscapeMenuLabelAmpersands(
                     browser()->app_controller()->GetAppShortName())));
   }
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
   AddSeparator(ui::LOWER_SEPARATOR);
 
   CreateZoomMenu();
diff --git a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
index af0d2c3..8acc7d7 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
+++ b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
@@ -8,6 +8,7 @@
 
 #include "base/callback.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/extensions/launch_util.h"
@@ -27,7 +28,7 @@
 #include "extensions/browser/app_sorting.h"
 #include "extensions/browser/extension_system.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/public/cpp/shelf_model.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
@@ -92,7 +93,7 @@
 
 // static
 bool WebAppUiManager::ShouldHideAppFromUser(const AppId& app_id) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   return app_list::HideInLauncherById(app_id);
 #else
   return false;
@@ -183,7 +184,7 @@
       continue;
 
     if (!has_migrated) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
       // Grid position in app list.
       auto* app_list_syncable_service =
           app_list::AppListSyncableServiceFactory::GetForProfile(profile_);
@@ -233,7 +234,7 @@
 }
 
 bool WebAppUiManagerImpl::CanAddAppToQuickLaunchBar() const {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   return true;
 #else
   return false;
@@ -242,13 +243,13 @@
 
 void WebAppUiManagerImpl::AddAppToQuickLaunchBar(const AppId& app_id) {
   DCHECK(CanAddAppToQuickLaunchBar());
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // ChromeLauncherController does not exist in unit tests.
   if (auto* controller = ChromeLauncherController::instance()) {
     controller->PinAppWithID(app_id);
     controller->UpdateV1AppState(app_id);
   }
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 bool WebAppUiManagerImpl::IsInAppWindow(content::WebContents* web_contents,
diff --git a/chrome/browser/ui/web_applications/web_app_ui_manager_impl_browsertest.cc b/chrome/browser/ui/web_applications/web_app_ui_manager_impl_browsertest.cc
index 5b18af7..1f618c7 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_manager_impl_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_ui_manager_impl_browsertest.cc
@@ -6,6 +6,7 @@
 
 #include "base/barrier_closure.h"
 #include "base/test/bind.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/built_in_chromeos_apps.h"
@@ -20,7 +21,7 @@
 #include "content/public/test/browser_test.h"
 #include "url/gurl.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/public/cpp/app_list/internal_app_id_constants.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
@@ -147,7 +148,7 @@
   }
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // Tests that app migrations use the UI preferences of the replaced app but only
 // if it's present.
 IN_PROC_BROWSER_TEST_F(WebAppUiManagerImplBrowserTest, DoubleMigration) {
@@ -187,6 +188,6 @@
                 ->item_pin_ordinal.ToDebugString(),
             "positionnew");
 }
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 }  // namespace web_app
diff --git a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
index f20f17c..fa7db71 100644
--- a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
@@ -1094,6 +1094,14 @@
     return;
   }
 
+  // We need a special case for USB printers here. We cannot query them
+  // directly, so we have to fall back to manual configuration here.
+  if (printer->IsUsbProtocol()) {
+    RejectJavascriptCallback(base::Value(callback_id),
+                             *GetCupsPrinterInfo(*printer));
+    return;
+  }
+
   // The mDNS record doesn't guarantee we can setup the printer.  Query it to
   // see if we want to try IPP.
   auto address = printer->GetHostAndPort();
diff --git a/chrome/browser/ui/webui/settings/people_handler.cc b/chrome/browser/ui/webui/settings/people_handler.cc
index b31cbad..e265dbd2 100644
--- a/chrome/browser/ui/webui/settings/people_handler.cc
+++ b/chrome/browser/ui/webui/settings/people_handler.cc
@@ -655,14 +655,21 @@
 void PeopleHandler::HandleSignout(const base::ListValue* args) {
   bool delete_profile = false;
   args->GetBoolean(0, &delete_profile);
+  base::FilePath profile_path = profile_->GetPath();
 
   if (!signin_util::IsUserSignoutAllowedForProfile(profile_)) {
     // If the user cannot signout, the profile must be destroyed.
     DCHECK(delete_profile);
   } else {
+    Browser* browser =
+        chrome::FindBrowserWithWebContents(web_ui()->GetWebContents());
+    if (browser) {
+      browser->signin_view_controller()->ShowGaiaLogoutTab(
+          signin_metrics::SourceForRefreshTokenOperation::kSettings_Signout);
+    }
+
     auto* identity_manager = IdentityManagerFactory::GetForProfile(profile_);
     if (identity_manager->HasPrimaryAccount()) {
-
       signin_metrics::SignoutDelete delete_metric =
           delete_profile ? signin_metrics::SignoutDelete::DELETED
                          : signin_metrics::SignoutDelete::KEEPING;
@@ -672,6 +679,8 @@
       // (see http://crbug.com/1068978). If the account is already invalid, drop
       // the token now (because it's already invalid on the web, so the Gaia
       // logout tab won't affect it, see http://crbug.com/1114646).
+      // This operation may delete the current browser that owns |this| if force
+      // signin is enabled. See https://crbug.com/1153120.
       identity_manager->GetPrimaryAccountMutator()->ClearPrimaryAccount(
           signin::PrimaryAccountMutator::ClearAccountsAction::kDefault,
           signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS, delete_metric);
@@ -679,18 +688,12 @@
       DCHECK(!delete_profile)
           << "Deleting the profile should only be offered the user is syncing.";
     }
-
-    Browser* browser =
-        chrome::FindBrowserWithWebContents(web_ui()->GetWebContents());
-    if (!browser)
-      return;
-
-    browser->signin_view_controller()->ShowGaiaLogoutTab(
-        signin_metrics::SourceForRefreshTokenOperation::kSettings_Signout);
   }
 
+  // CAUTION: |this| may be deleted at this point.
+
   if (delete_profile) {
-    webui::DeleteProfileAtPath(profile_->GetPath(),
+    webui::DeleteProfileAtPath(profile_path,
                                ProfileMetrics::DELETE_PROFILE_SETTINGS);
   }
 }
diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.cc b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
index 81f0c0cd..47fe176 100644
--- a/chrome/browser/ui/webui/signin/profile_picker_handler.cc
+++ b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
@@ -552,6 +552,8 @@
   const int avatar_icon_size =
       kProfileCardAvatarSize * web_ui()->GetDeviceScaleFactor();
   for (const ProfileAttributesEntry* entry : entries) {
+    if (entry->IsGuest())
+      continue;
     auto profile_entry = std::make_unique<base::DictionaryValue>();
     profile_entry->SetKey("profilePath",
                           util::FilePathToValue(entry->GetPath()));
diff --git a/chrome/browser/ui/window_sizer/window_sizer.cc b/chrome/browser/ui/window_sizer/window_sizer.cc
index 00062ca..c1cc1ac 100644
--- a/chrome/browser/ui/window_sizer/window_sizer.cc
+++ b/chrome/browser/ui/window_sizer/window_sizer.cc
@@ -23,7 +23,7 @@
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chrome/browser/ui/window_sizer/window_sizer_chromeos.h"
 #endif
 
@@ -113,7 +113,7 @@
     }
 
     if (window) {
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
       if (window->IsVisible())
         *bounds = window->GetRestoredBounds();
 #else
@@ -163,7 +163,7 @@
     ui::WindowShowState* show_state) {
   DCHECK(bounds);
   DCHECK(show_state);
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
   WindowSizerChromeOS sizer(std::move(state_provider), browser);
 #else
   WindowSizer sizer(std::move(state_provider), browser);
@@ -363,10 +363,10 @@
 
 // static
 display::Display WindowSizer::GetDisplayForNewWindow(const gfx::Rect& bounds) {
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
   // Prefer the display where the user last activated a window.
   return display::Screen::GetScreen()->GetDisplayForNewWindows();
 #else
   return display::Screen::GetScreen()->GetDisplayMatching(bounds);
-#endif  // defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 }
diff --git a/chrome/browser/ui/window_sizer/window_sizer_chromeos.cc b/chrome/browser/ui/window_sizer/window_sizer_chromeos.cc
index f7cc74f..e8ffe58 100644
--- a/chrome/browser/ui/window_sizer/window_sizer_chromeos.cc
+++ b/chrome/browser/ui/window_sizer/window_sizer_chromeos.cc
@@ -15,7 +15,7 @@
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 
-#if !BUILDFLAG(IS_LACROS)
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chrome/common/pref_names.h"
 #endif
 
@@ -26,7 +26,7 @@
 constexpr int kForceMaximizeWidthLimit = 1366;
 
 bool ShouldForceMaximizeOnFirstRun(Profile* profile) {
-#if BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
   // TODO(https://crbug.com/1110548): Support the ForceMaximizeOnFirstRun policy
   // in lacros-chrome.
   return false;
diff --git a/chrome/browser/ui/window_sizer/window_sizer_common_unittest.cc b/chrome/browser/ui/window_sizer/window_sizer_common_unittest.cc
index e9a9e04..42021ff5 100644
--- a/chrome/browser/ui/window_sizer/window_sizer_common_unittest.cc
+++ b/chrome/browser/ui/window_sizer/window_sizer_common_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/testing_profile.h"
@@ -121,7 +122,7 @@
 
 #if !defined(OS_MAC)
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 // Passing null for the browser parameter of GetWindowBounds makes the test skip
 // all Ash-specific logic, so there's no point running this on Chrome OS.
 TEST(WindowSizerTestCommon,
@@ -309,7 +310,7 @@
     EXPECT_EQ("50,368 500x400", window_bounds.ToString());
   }
 }
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Test that the window is sized appropriately for the first run experience
 // where the default window bounds calculation is invoked.
diff --git a/chrome/browser/ui/zoom/zoom_controller_browsertest.cc b/chrome/browser/ui/zoom/zoom_controller_browsertest.cc
index 9322098..c8a1d98 100644
--- a/chrome/browser/ui/zoom/zoom_controller_browsertest.cc
+++ b/chrome/browser/ui/zoom/zoom_controller_browsertest.cc
@@ -7,6 +7,7 @@
 #include "base/macros.h"
 #include "base/process/kill.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -243,7 +244,7 @@
   TestResetOnNavigation(ZoomController::ZOOM_MODE_MANUAL);
 }
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 // Regression test: crbug.com/438979.
 IN_PROC_BROWSER_TEST_F(ZoomControllerBrowserTest,
                        SettingsZoomAfterSigninWorks) {
@@ -297,4 +298,4 @@
   zoom_controller->SetZoomLevel(new_zoom_level);
   zoom_change_watcher.Wait();
 }
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index a7756c46..d7083bf 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1606434328-b11de2c42e4be1904f18040e42ebddef022e94a9.profdata
+chrome-linux-master-1606477302-0eef9df5e342eba3f05f76047fdfe0ee5f85aae6.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 6deb4a5..8d1f476 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1606434328-d20b89cbb7113eb50f7c91d68d6760cac88c7639.profdata
+chrome-mac-master-1606477302-630adcad5e4cdcdcf2ef51c7b233ac57a3e03250.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 4a409ad..29b5c65 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1606423711-20b7d89425d1187d62c0bed2d0c51ba670083d7c.profdata
+chrome-win32-master-1606467421-254a879f9aaee24c27bff0d45dc43b60000cc185.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index b008469..3a8b5c2ed 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1606445966-4a9759f3f64c32ba89411261d898a8870ebcc34b.profdata
+chrome-win64-master-1606467421-5bc9c94caac3d987cc9eb0508bb0c4216e88e158.profdata
diff --git a/chrome/common/service_process_util_mac.mm b/chrome/common/service_process_util_mac.mm
index 2b5c5c3..11147c9 100644
--- a/chrome/common/service_process_util_mac.mm
+++ b/chrome/common/service_process_util_mac.mm
@@ -281,7 +281,7 @@
       return false;
     }
     if (!executable_watcher.Watch(
-            executable_path, false,
+            executable_path, base::FilePathWatcher::Type::kNonRecursive,
             base::BindRepeating(&ExecFilePathWatcherCallback::NotifyPathChanged,
                                 base::Owned(callback.release())))) {
       DLOG(ERROR) << "executable_watcher.watch " << executable_path.value();
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 569cddb..da0f325 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3183,7 +3183,8 @@
           [ "../browser/site_isolation/spellcheck_per_process_browsertest.cc" ]
     }
 
-    if (is_win || is_mac || (is_linux && !is_chromeos_lacros)) {
+    if (is_win || is_mac ||
+        ((is_linux || is_chromeos_lacros) && !is_chromeos_lacros)) {
       sources +=
           [ "../browser/ui/views/profiles/profile_picker_view_browsertest.cc" ]
     }
@@ -6319,12 +6320,6 @@
         "../browser/password_manager/password_manager_signin_intercept_test_helper.h",
       ]
     }
-    if (is_win || is_mac || (is_linux && !is_chromeos_lacros)) {
-      sources += [
-        "../browser/ui/views/profiles/profile_picker_test_base.cc",
-        "../browser/ui/views/profiles/profile_picker_test_base.h",
-      ]
-    }
   }
 
   proto_library("test_proto") {
@@ -6808,12 +6803,6 @@
       sources +=
           [ "../browser/ui/views/frame/webui_tab_strip_interactive_uitest.cc" ]
     }
-
-    if (is_win || is_mac || (is_linux && !is_chromeos_lacros)) {
-      sources += [
-        "../browser/ui/views/profiles/profile_picker_interactive_uitest.cc",
-      ]
-    }
   }
 }
 
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
index ec2abf63..be13b4b 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
@@ -5,31 +5,30 @@
 package org.chromium.chrome.test;
 
 import android.app.Activity;
+import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.internal.runner.listener.InstrumentationResultPrinter;
+import android.support.test.rule.ActivityTestRule;
 import android.view.Menu;
 
-import androidx.annotation.NonNull;
-
 import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
+import org.chromium.base.ActivityState;
 import org.chromium.base.ApplicationStatus;
+import org.chromium.base.ApplicationStatus.ActivityStateListener;
 import org.chromium.base.CommandLine;
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.test.BaseActivityTestRule;
+import org.chromium.base.Log;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.ScalableTimeout;
-import org.chromium.chrome.browser.DeferredStartupHandler;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -42,9 +41,7 @@
 import org.chromium.chrome.browser.ui.appmenu.AppMenuTestSupport;
 import org.chromium.chrome.test.util.ChromeApplicationTestUtils;
 import org.chromium.chrome.test.util.ChromeTabUtils;
-import org.chromium.chrome.test.util.NewTabPageTestUtils;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.infobars.InfoBar;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.WebContents;
@@ -62,13 +59,14 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Custom  {@link ActivityTestRule} for test using  {@link ChromeActivity}.
  *
  * @param <T> The {@link Activity} class under test.
  */
-public class ChromeActivityTestRule<T extends ChromeActivity> extends BaseActivityTestRule<T> {
+public class ChromeActivityTestRule<T extends ChromeActivity> extends ActivityTestRule<T> {
     private static final String TAG = "ChromeATR";
 
     // The number of ms to wait for the rendering activity to be started.
@@ -77,13 +75,20 @@
     private static final long OMNIBOX_FIND_SUGGESTION_TIMEOUT_MS = 10 * 1000;
 
     private Thread.UncaughtExceptionHandler mDefaultUncaughtExceptionHandler;
+    private Class<T> mChromeActivityClass;
+    private T mSetActivity;
     private String mCurrentTestName;
 
     @Rule
     private EmbeddedTestServerRule mTestServerRule = new EmbeddedTestServerRule();
 
     protected ChromeActivityTestRule(Class<T> activityClass) {
-        super(activityClass);
+        this(activityClass, false);
+    }
+
+    protected ChromeActivityTestRule(Class<T> activityClass, boolean initialTouchMode) {
+        super(activityClass, initialTouchMode, false);
+        mChromeActivityClass = activityClass;
     }
 
     @Override
@@ -130,11 +135,13 @@
         return ACTIVITY_START_TIMEOUT_MS;
     }
 
-    // This has to be here or getActivity will return a T that extends Activity, not a T that
-    // extends ChromeActivity.
+    // TODO(yolandyan): remove this once startActivityCompletely is refactored out of
+    // ChromeActivityTestRule
     @Override
-    @SuppressWarnings("RedundantOverride")
     public T getActivity() {
+        if (mSetActivity != null) {
+            return mSetActivity;
+        }
         return super.getActivity();
     }
 
@@ -145,14 +152,6 @@
     }
 
     /**
-     * TODO(https://crbug.com/1146574): This only exists here because legacy ActivityTestRule
-     * inherited from UiThreadTestRule. This function should be removed.
-     */
-    public void runOnUiThread(Runnable r) {
-        ThreadUtils.runOnUiThreadBlocking(r);
-    }
-
-    /**
      * @return The {@link AppMenuCoordinator} for the activity.
      */
     public AppMenuCoordinator getAppMenuCoordinator() {
@@ -197,37 +196,49 @@
     }
 
     /**
-     * Similar to #launchActivity(Intent), but waits for the Activity tab to be initialized.
+     * Invokes {@link Instrumentation#startActivitySync(Intent)} and sets the
+     * test case's activity to the result. See the documentation for
+     * {@link Instrumentation#startActivitySync(Intent)} on the timing of the
+     * return, but generally speaking the activity's "onCreate" has completed
+     * and the activity's main looper has become idle.
+     *
+     * TODO(yolandyan): very similar to ActivityTestRule#launchActivity(Intent),
+     * yet small differences remains (e.g. launchActivity() uses FLAG_ACTIVITY_NEW_TASK while
+     * startActivityCompletely doesn't), need to refactor and use only launchActivity
+     * after the JUnit4 migration
      */
     public void startActivityCompletely(Intent intent) {
-        DeferredStartupHandler.setExpectingActivityStartupForTesting();
-        launchActivity(intent);
-        waitForActivityNativeInitializationComplete();
-
-        CriteriaHelper.pollUiThread(
-                () -> getActivity().getActivityTab() != null, "Tab never selected/initialized.");
-        Tab tab = getActivity().getActivityTab();
-
-        ChromeTabUtils.waitForTabPageLoaded(tab, (String) null);
-
-        if (tab != null && UrlUtilities.isNTPUrl(ChromeTabUtils.getUrlStringOnUiThread(tab))
-                && !getActivity().isInOverviewMode()) {
-            NewTabPageTestUtils.waitForNtpLoaded(tab);
-        }
-
-        Assert.assertTrue("Deferred startup never completed. Did you try to start an Activity "
-                        + "that was already started?",
-                DeferredStartupHandler.waitForDeferredStartupCompleteForTesting(
-                        ScalableTimeout.scaleTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)));
-
-        Assert.assertNotNull(tab);
-        Assert.assertNotNull(tab.getView());
-    }
-
-    @Override
-    public void launchActivity(@NonNull Intent startIntent) {
         Features.ensureCommandLineIsUpToDate();
-        super.launchActivity(startIntent);
+
+        final CallbackHelper activityCallback = new CallbackHelper();
+        final AtomicReference<T> activityRef = new AtomicReference<>();
+        ActivityStateListener stateListener = new ActivityStateListener() {
+            @SuppressWarnings("unchecked")
+            @Override
+            public void onActivityStateChange(Activity activity, int newState) {
+                if (newState == ActivityState.RESUMED) {
+                    if (!mChromeActivityClass.isAssignableFrom(activity.getClass())) return;
+
+                    activityRef.set((T) activity);
+                    activityCallback.notifyCalled();
+                    ApplicationStatus.unregisterActivityStateListener(this);
+                }
+            }
+        };
+        ApplicationStatus.registerStateListenerForAllActivities(stateListener);
+
+        try {
+            InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+            activityCallback.waitForCallback("Activity did not start as expected", 0);
+            T activity = activityRef.get();
+            Assert.assertNotNull("Activity reference is null.", activity);
+            setActivity(activity);
+            Log.d(TAG, "startActivityCompletely <<");
+        } catch (TimeoutException e) {
+            throw new RuntimeException(e);
+        } finally {
+            ApplicationStatus.unregisterActivityStateListener(stateListener);
+        }
     }
 
     /**
@@ -461,6 +472,10 @@
         return getActivity().getWindowAndroid().getKeyboardDelegate();
     }
 
+    public void setActivity(T chromeActivity) {
+        mSetActivity = chromeActivity;
+    }
+
     /**
      * Waits for an Activity of the given class to be started.
      * @return The Activity.
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java
index 65e683d..cc708d7 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java
@@ -20,8 +20,11 @@
 import org.chromium.base.Log;
 import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.DeferredStartupHandler;
 import org.chromium.chrome.browser.omnibox.UrlBar;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabCreationState;
@@ -33,6 +36,7 @@
 import org.chromium.chrome.test.util.MenuUtils;
 import org.chromium.chrome.test.util.NewTabPageTestUtils;
 import org.chromium.chrome.test.util.WaitForFocusHelper;
+import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.concurrent.TimeoutException;
@@ -109,7 +113,31 @@
      */
     public void startMainActivityFromIntent(Intent intent, String url) {
         prepareUrlIntent(intent, url);
+
+        DeferredStartupHandler.setExpectingActivityStartupForTesting();
         startActivityCompletely(intent);
+        waitForActivityNativeInitializationComplete();
+
+        CriteriaHelper.pollUiThread(
+                () -> getActivity().getActivityTab() != null, "Tab never selected/initialized.");
+        Tab tab = getActivity().getActivityTab();
+
+        ChromeTabUtils.waitForTabPageLoaded(tab, (String) null);
+
+        if (tab != null && UrlUtilities.isNTPUrl(ChromeTabUtils.getUrlStringOnUiThread(tab))
+                && !getActivity().isInOverviewMode()) {
+            NewTabPageTestUtils.waitForNtpLoaded(tab);
+        }
+
+        Assert.assertTrue("Deferred startup never completed. Did you try to start an Activity "
+                        + "that was already started?",
+                DeferredStartupHandler.waitForDeferredStartupCompleteForTesting(
+                        ScalableTimeout.scaleTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)));
+
+        Assert.assertNotNull(tab);
+        Assert.assertNotNull(tab.getView());
+
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 
     /**
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java
index dba985d0..e401694 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java
@@ -39,7 +39,6 @@
         super();
         mActivityTestRule = activityTestRule;
         mClearAllTabState = clearAllTabState;
-        mActivityTestRule.setFinishActivity(false);
     }
 
     @Override
diff --git a/chromecast/media/cma/backend/cast_audio_json.cc b/chromecast/media/cma/backend/cast_audio_json.cc
index c2b2780..b38e0b7b 100644
--- a/chromecast/media/cma/backend/cast_audio_json.cc
+++ b/chromecast/media/cma/backend/cast_audio_json.cc
@@ -98,7 +98,8 @@
 void CastAudioJsonProviderImpl::FileWatcher::SetTuningChangedCallback(
     TuningChangedCallback callback) {
   watcher_.Watch(
-      CastAudioJson::GetFilePathForTuning(), false /* recursive */,
+      CastAudioJson::GetFilePathForTuning(),
+      base::FilePathWatcher::Type::kNonRecursive,
       base::BindRepeating(&ReadFileRunCallback, std::move(callback)));
 }
 
diff --git a/chromeos/components/account_manager/account_manager.cc b/chromeos/components/account_manager/account_manager.cc
index aa28a6c..d33563e 100644
--- a/chromeos/components/account_manager/account_manager.cc
+++ b/chromeos/components/account_manager/account_manager.cc
@@ -27,11 +27,11 @@
 #include "chromeos/constants/chromeos_pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
+#include "google_apis/gaia/gaia_access_token_fetcher.h"
 #include "google_apis/gaia/gaia_auth_consumer.h"
 #include "google_apis/gaia/gaia_auth_fetcher.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_constants.h"
-#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "third_party/protobuf/src/google/protobuf/message_lite.h"
 
@@ -661,8 +661,9 @@
     return nullptr;
   }
 
-  return std::make_unique<OAuth2AccessTokenFetcherImpl>(
-      consumer, url_loader_factory_, it->second.token);
+  return GaiaAccessTokenFetcher::
+      CreateExchangeRefreshTokenForAccessTokenInstance(
+          consumer, url_loader_factory_, it->second.token);
 }
 
 bool AccountManager::IsTokenAvailable(
diff --git a/components/autofill_assistant/browser/BUILD.gn b/components/autofill_assistant/browser/BUILD.gn
index 2a28417..12fb2b5 100644
--- a/components/autofill_assistant/browser/BUILD.gn
+++ b/components/autofill_assistant/browser/BUILD.gn
@@ -49,12 +49,16 @@
     "actions/highlight_element_action.h",
     "actions/navigate_action.cc",
     "actions/navigate_action.h",
+    "actions/perform_on_single_element_action.cc",
+    "actions/perform_on_single_element_action.h",
     "actions/popup_message_action.cc",
     "actions/popup_message_action.h",
     "actions/presave_generated_password_action.cc",
     "actions/presave_generated_password_action.h",
     "actions/prompt_action.cc",
     "actions/prompt_action.h",
+    "actions/release_elements_action.cc",
+    "actions/release_elements_action.h",
     "actions/save_generated_password_action.cc",
     "actions/save_generated_password_action.h",
     "actions/select_option_action.cc",
@@ -284,6 +288,8 @@
     "mock_website_login_manager.h",
     "service/mock_service.cc",
     "service/mock_service.h",
+    "web/fake_element_store.cc",
+    "web/fake_element_store.h",
     "web/mock_web_controller.cc",
     "web/mock_web_controller.h",
   ]
@@ -314,9 +320,11 @@
     "actions/generate_password_for_form_field_action_unittest.cc",
     "actions/get_element_status_action_unittest.cc",
     "actions/highlight_element_action_unittest.cc",
+    "actions/perform_on_single_element_action_unittest.cc",
     "actions/popup_message_action_unittest.cc",
     "actions/presave_generated_password_action_unittest.cc",
     "actions/prompt_action_unittest.cc",
+    "actions/release_elements_action_unittest.cc",
     "actions/save_generated_password_action_unittest.cc",
     "actions/select_option_action_unittest.cc",
     "actions/set_attribute_action_unittest.cc",
diff --git a/components/autofill_assistant/browser/actions/action.cc b/components/autofill_assistant/browser/actions/action.cc
index c2a842a..7e657f36 100644
--- a/components/autofill_assistant/browser/actions/action.cc
+++ b/components/autofill_assistant/browser/actions/action.cc
@@ -170,6 +170,48 @@
     case ActionProto::ActionInfoCase::kGetElementStatus:
       out << "GetElementStatus";
       break;
+    case ActionProto::ActionInfoCase::kScrollIntoView:
+      out << "ScrollIntoView";
+      break;
+    case ActionProto::ActionInfoCase::kWaitForDocumentToBecomeInteractive:
+      out << "WaitForDocumentToBecomeInteractive";
+      break;
+    case ActionProto::ActionInfoCase::kWaitForDocumentToBecomeComplete:
+      out << "WaitForDocumentToBecomeComplete";
+      break;
+    case ActionProto::ActionInfoCase::kSendClickEvent:
+      out << "SendClickEvent";
+      break;
+    case ActionProto::ActionInfoCase::kSendTapEvent:
+      out << "SendTapEvent";
+      break;
+    case ActionProto::ActionInfoCase::kJsClick:
+      out << "JsClick";
+      break;
+    case ActionProto::ActionInfoCase::kSendKeystrokeEvents:
+      out << "SendKeystrokeEvents";
+      break;
+    case ActionProto::ActionInfoCase::kSetFieldValue:
+      out << "SetFieldValue";
+      break;
+    case ActionProto::ActionInfoCase::kSetElementAttribute:
+      out << "SetElementAttribute";
+      break;
+    case ActionProto::ActionInfoCase::kSelectFieldValue:
+      out << "SelectFieldValue";
+      break;
+    case ActionProto::ActionInfoCase::kFocusField:
+      out << "FocusField";
+      break;
+    case ActionProto::ActionInfoCase::kWaitForElementToBecomeStable:
+      out << "WaitForElementToBecomeStable";
+      break;
+    case ActionProto::ActionInfoCase::kCheckElementIsOnTop:
+      out << "CheckElementIsOnTop";
+      break;
+    case ActionProto::ActionInfoCase::kReleaseElements:
+      out << "ReleaseElements";
+      break;
     case ActionProto::ActionInfoCase::ACTION_INFO_NOT_SET:
       out << "ACTION_INFO_NOT_SET";
       break;
diff --git a/components/autofill_assistant/browser/actions/action_delegate.h b/components/autofill_assistant/browser/actions/action_delegate.h
index 237bbb3..6a1b882 100644
--- a/components/autofill_assistant/browser/actions/action_delegate.h
+++ b/components/autofill_assistant/browser/actions/action_delegate.h
@@ -43,6 +43,7 @@
 struct CollectUserDataOptions;
 class ElementStore;
 class UserAction;
+class WebController;
 class WebsiteLoginManager;
 
 // Action delegate called when processing actions.
@@ -284,8 +285,8 @@
       const ElementFinder::Result& element,
       base::OnceCallback<void(const ClientStatus&)> callback) = 0;
 
-  // Sets the keyboard focus to |element| and inputs the specified codepoints.
-  // Returns the result through |callback|.
+  // Inputs the specified codepoints into |element|. Expects the |element| to
+  // have focus. Returns the result through |callback|.
   virtual void SendKeyboardInput(
       const std::vector<UChar32>& codepoints,
       int key_press_delay_in_millisecond,
@@ -381,6 +382,9 @@
   // Get the ElementStore.
   virtual ElementStore* GetElementStore() const = 0;
 
+  // Get the WebController.
+  virtual WebController* GetWebController() const = 0;
+
   // Returns the e-mail address that corresponds to the access token or an empty
   // string.
   virtual std::string GetEmailAddressForAccessTokenAccount() = 0;
diff --git a/components/autofill_assistant/browser/actions/action_delegate_util.cc b/components/autofill_assistant/browser/actions/action_delegate_util.cc
index 727e07aa..cc0ba7d 100644
--- a/components/autofill_assistant/browser/actions/action_delegate_util.cc
+++ b/components/autofill_assistant/browser/actions/action_delegate_util.cc
@@ -11,6 +11,7 @@
 #include "components/autofill_assistant/browser/selector.h"
 #include "components/autofill_assistant/browser/service.pb.h"
 #include "components/autofill_assistant/browser/string_conversions_util.h"
+#include "components/autofill_assistant/browser/user_data_util.h"
 #include "components/autofill_assistant/browser/web/element_finder.h"
 
 namespace autofill_assistant {
@@ -159,6 +160,37 @@
       std::move(done), OkClientStatus());
 }
 
+void PerformWithTextValue(
+    const ActionDelegate* delegate,
+    const TextValue& text_value,
+    base::OnceCallback<void(const std::string&,
+                            const ElementFinder::Result&,
+                            base::OnceCallback<void(const ClientStatus&)>)>
+        perform,
+    const ElementFinder::Result& element,
+    base::OnceCallback<void(const ClientStatus&)> done) {
+  std::string value;
+  switch (text_value.value_case()) {
+    case TextValue::kText:
+      value = text_value.text();
+      break;
+    case TextValue::kAutofillValue: {
+      ClientStatus autofill_status = GetFormattedAutofillValue(
+          text_value.autofill_value(), delegate->GetUserData(), &value);
+      if (!autofill_status.ok()) {
+        std::move(done).Run(autofill_status);
+        return;
+      }
+      break;
+    }
+    case TextValue::VALUE_NOT_SET:
+      std::move(done).Run(ClientStatus(INVALID_ACTION));
+      return;
+  }
+
+  std::move(perform).Run(value, element, std::move(done));
+}
+
 void AddOptionalStep(OptionalStep optional_step,
                      ElementActionCallback step,
                      ElementActionVector* actions) {
diff --git a/components/autofill_assistant/browser/actions/action_delegate_util.h b/components/autofill_assistant/browser/actions/action_delegate_util.h
index 5aa27bb..1c1a916 100644
--- a/components/autofill_assistant/browser/actions/action_delegate_util.h
+++ b/components/autofill_assistant/browser/actions/action_delegate_util.h
@@ -78,10 +78,25 @@
                           std::move(element_result), std::move(done)));
 }
 
+// Run all |perform_actions| sequentially. Breaks the execution on any error
+// and executes the |done| callback with the final status.
 void PerformAll(std::unique_ptr<ElementActionVector> perform_actions,
                 const ElementFinder::Result& element,
                 base::OnceCallback<void(const ClientStatus&)> done);
 
+// Resolve the |text_value| and run the |perform| callback. Run the |done|
+// callback with an error status if the |text_value| could not be resolved.
+// Run the |done| callback with the result status of |perform| otherwise.
+void PerformWithTextValue(
+    const ActionDelegate* delegate,
+    const TextValue& text_value,
+    base::OnceCallback<void(const std::string&,
+                            const ElementFinder::Result&,
+                            base::OnceCallback<void(const ClientStatus&)>)>
+        perform,
+    const ElementFinder::Result& element,
+    base::OnceCallback<void(const ClientStatus&)> done);
+
 // Adds an optional step to the |actions|. If the step is |SKIP_STEP|, it does
 // not add it. For |REPORT_STEP_RESULT| it adds the step ignoring a potential
 // failure. For |REQUIRE_STEP_SUCCESS| it binds the step as is.
diff --git a/components/autofill_assistant/browser/actions/collect_user_data_action.cc b/components/autofill_assistant/browser/actions/collect_user_data_action.cc
index c4d5c5c..87910d2 100644
--- a/components/autofill_assistant/browser/actions/collect_user_data_action.cc
+++ b/components/autofill_assistant/browser/actions/collect_user_data_action.cc
@@ -507,6 +507,10 @@
         login_option.preselection_priority(),
         login_option.has_info_popup()
             ? base::make_optional(login_option.info_popup())
+            : base::nullopt,
+        login_option.has_edit_button_content_description()
+            ? base::make_optional(
+                  login_option.edit_button_content_description())
             : base::nullopt);
     login_details_map_.emplace(
         identifier, std::make_unique<LoginDetails>(
@@ -807,6 +811,10 @@
                 : -1,
             login_option.has_info_popup()
                 ? base::make_optional(login_option.info_popup())
+                : base::nullopt,
+            login_option.has_edit_button_content_description()
+                ? base::make_optional(
+                      login_option.edit_button_content_description())
                 : base::nullopt};
         collect_user_data_options_->login_choices.emplace_back(
             std::move(choice));
diff --git a/components/autofill_assistant/browser/actions/mock_action_delegate.h b/components/autofill_assistant/browser/actions/mock_action_delegate.h
index ec06bfe..2eca1de 100644
--- a/components/autofill_assistant/browser/actions/mock_action_delegate.h
+++ b/components/autofill_assistant/browser/actions/mock_action_delegate.h
@@ -21,6 +21,8 @@
 #include "components/autofill_assistant/browser/user_data.h"
 #include "components/autofill_assistant/browser/web/element_finder.h"
 #include "components/autofill_assistant/browser/web/element_store.h"
+#include "components/autofill_assistant/browser/web/fake_element_store.h"
+#include "components/autofill_assistant/browser/web/web_controller.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace autofill_assistant {
@@ -269,6 +271,7 @@
   MOCK_METHOD0(GetPersonalDataManager, autofill::PersonalDataManager*());
   MOCK_METHOD0(GetWebsiteLoginManager, WebsiteLoginManager*());
   MOCK_METHOD0(GetWebContents, content::WebContents*());
+  MOCK_CONST_METHOD0(GetWebController, WebController*());
   MOCK_METHOD0(GetEmailAddressForAccessTokenAccount, std::string());
   MOCK_METHOD0(GetLocale, std::string());
   MOCK_METHOD1(SetDetails, void(std::unique_ptr<Details> details));
@@ -378,7 +381,7 @@
 
   ElementStore* GetElementStore() const override {
     if (!element_store_) {
-      element_store_ = std::make_unique<ElementStore>(nullptr);
+      element_store_ = std::make_unique<FakeElementStore>();
     }
     return element_store_.get();
   }
diff --git a/components/autofill_assistant/browser/actions/perform_on_single_element_action.cc b/components/autofill_assistant/browser/actions/perform_on_single_element_action.cc
new file mode 100644
index 0000000..166ad88
--- /dev/null
+++ b/components/autofill_assistant/browser/actions/perform_on_single_element_action.cc
@@ -0,0 +1,122 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/actions/perform_on_single_element_action.h"
+
+#include "base/callback_helpers.h"
+#include "base/memory/ptr_util.h"
+#include "base/time/time.h"
+#include "components/autofill_assistant/browser/dom_action.pb.h"
+#include "components/autofill_assistant/browser/web/element_store.h"
+
+namespace autofill_assistant {
+
+PerformOnSingleElementAction::PerformOnSingleElementAction(
+    ActionDelegate* delegate,
+    const ActionProto& proto,
+    const ClientIdProto& client_id,
+    bool element_is_optional,
+    PerformAction perform,
+    PerformTimedAction perform_timed)
+    : Action(delegate, proto),
+      client_id_(client_id.identifier()),
+      element_is_optional_(element_is_optional),
+      perform_(std::move(perform)),
+      perform_timed_(std::move(perform_timed)) {}
+
+PerformOnSingleElementAction::~PerformOnSingleElementAction() = default;
+
+// static
+std::unique_ptr<PerformOnSingleElementAction>
+PerformOnSingleElementAction::WithClientId(ActionDelegate* delegate,
+                                           const ActionProto& proto,
+                                           const ClientIdProto& client_id,
+                                           PerformAction perform) {
+  return base::WrapUnique(new PerformOnSingleElementAction(
+      delegate, proto, client_id,
+      /* element_is_optional= */ false, std::move(perform),
+      /* perform_timed= */ base::NullCallback()));
+}
+
+// static
+std::unique_ptr<PerformOnSingleElementAction>
+PerformOnSingleElementAction::WithOptionalClientId(
+    ActionDelegate* delegate,
+    const ActionProto& proto,
+    const ClientIdProto& client_id,
+    PerformAction perform) {
+  return base::WrapUnique(new PerformOnSingleElementAction(
+      delegate, proto, client_id,
+      /* element_is_optional= */ true, std::move(perform),
+      /* perform_timed= */ base::NullCallback()));
+}
+
+// static
+std::unique_ptr<PerformOnSingleElementAction>
+PerformOnSingleElementAction::WithClientIdTimed(
+    ActionDelegate* delegate,
+    const ActionProto& proto,
+    const ClientIdProto& client_id,
+    PerformTimedAction perform_timed) {
+  return base::WrapUnique(new PerformOnSingleElementAction(
+      delegate, proto, client_id,
+      /* element_is_optional= */ false,
+      /* perform= */ base::NullCallback(), std::move(perform_timed)));
+}
+
+// static
+std::unique_ptr<PerformOnSingleElementAction>
+PerformOnSingleElementAction::WithOptionalClientIdTimed(
+    ActionDelegate* delegate,
+    const ActionProto& proto,
+    const ClientIdProto& client_id,
+    PerformTimedAction perform_timed) {
+  return base::WrapUnique(new PerformOnSingleElementAction(
+      delegate, proto, client_id,
+      /* element_is_optional= */ true,
+      /* perform= */ base::NullCallback(), std::move(perform_timed)));
+}
+
+void PerformOnSingleElementAction::InternalProcessAction(
+    ProcessActionCallback callback) {
+  callback_ = std::move(callback);
+  if (client_id_.empty() && !element_is_optional_) {
+    EndAction(ClientStatus(INVALID_ACTION));
+    return;
+  }
+
+  if (!client_id_.empty()) {
+    ClientStatus status =
+        delegate_->GetElementStore()->GetElement(client_id_, &element_);
+    if (!status.ok()) {
+      EndAction(status);
+      return;
+    }
+  }
+
+  if (perform_) {
+    std::move(perform_).Run(
+        element_, base::BindOnce(&PerformOnSingleElementAction::EndAction,
+                                 weak_ptr_factory_.GetWeakPtr()));
+    return;
+  } else if (perform_timed_) {
+    std::move(perform_timed_)
+        .Run(element_,
+             base::BindOnce(
+                 &PerformOnSingleElementAction::OnWaitForElementTimed,
+                 weak_ptr_factory_.GetWeakPtr(),
+                 base::BindOnce(&PerformOnSingleElementAction::EndAction,
+                                weak_ptr_factory_.GetWeakPtr())));
+    return;
+  }
+  NOTREACHED();
+  EndAction(ClientStatus(INVALID_ACTION));
+}
+
+void PerformOnSingleElementAction::EndAction(const ClientStatus& status) {
+  UpdateProcessedAction(status);
+  std::move(callback_).Run(std::move(processed_action_proto_));
+}
+
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/actions/perform_on_single_element_action.h b/components/autofill_assistant/browser/actions/perform_on_single_element_action.h
new file mode 100644
index 0000000..f8238058
--- /dev/null
+++ b/components/autofill_assistant/browser/actions/perform_on_single_element_action.h
@@ -0,0 +1,82 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_PERFORM_ON_SINGLE_ELEMENT_ACTION_H_
+#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_PERFORM_ON_SINGLE_ELEMENT_ACTION_H_
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "components/autofill_assistant/browser/actions/action.h"
+#include "components/autofill_assistant/browser/actions/action_delegate.h"
+#include "components/autofill_assistant/browser/dom_action.pb.h"
+#include "components/autofill_assistant/browser/web/element_finder.h"
+
+namespace autofill_assistant {
+
+// An action that runs the registered execution callback on a single element
+// previously stored. No additional parameters are provided when running the
+// callback.
+class PerformOnSingleElementAction : public Action {
+ public:
+  using PerformAction =
+      base::OnceCallback<void(const ElementFinder::Result&,
+                              base::OnceCallback<void(const ClientStatus&)>)>;
+  using PerformTimedAction = base::OnceCallback<void(
+      const ElementFinder::Result&,
+      base::OnceCallback<void(const ClientStatus&, base::TimeDelta)>)>;
+
+  ~PerformOnSingleElementAction() override;
+
+  PerformOnSingleElementAction(const PerformOnSingleElementAction&) = delete;
+  PerformOnSingleElementAction& operator=(const PerformOnSingleElementAction&) =
+      delete;
+
+  static std::unique_ptr<PerformOnSingleElementAction> WithClientId(
+      ActionDelegate* delegate,
+      const ActionProto& proto,
+      const ClientIdProto& client_id,
+      PerformAction perform);
+  static std::unique_ptr<PerformOnSingleElementAction> WithOptionalClientId(
+      ActionDelegate* delegate,
+      const ActionProto& proto,
+      const ClientIdProto& client_id,
+      PerformAction perform);
+  static std::unique_ptr<PerformOnSingleElementAction> WithClientIdTimed(
+      ActionDelegate* delegate,
+      const ActionProto& proto,
+      const ClientIdProto& client_id,
+      PerformTimedAction perform);
+  static std::unique_ptr<PerformOnSingleElementAction>
+  WithOptionalClientIdTimed(ActionDelegate* delegate,
+                            const ActionProto& proto,
+                            const ClientIdProto& client_id,
+                            PerformTimedAction perform);
+
+ private:
+  PerformOnSingleElementAction(ActionDelegate* delegate,
+                               const ActionProto& proto,
+                               const ClientIdProto& client_id,
+                               bool element_is_optional,
+                               PerformAction perform,
+                               PerformTimedAction perform_timed);
+
+  // Overrides Action:
+  void InternalProcessAction(ProcessActionCallback callback) override;
+
+  void EndAction(const ClientStatus& status);
+
+  ElementFinder::Result element_;
+  ProcessActionCallback callback_;
+
+  std::string client_id_;
+  bool element_is_optional_ = false;
+  PerformAction perform_;
+  PerformTimedAction perform_timed_;
+
+  base::WeakPtrFactory<PerformOnSingleElementAction> weak_ptr_factory_{this};
+};
+
+}  // namespace autofill_assistant
+#endif  // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_PERFORM_ON_SINGLE_ELEMENT_ACTION_H_
diff --git a/components/autofill_assistant/browser/actions/perform_on_single_element_action_unittest.cc b/components/autofill_assistant/browser/actions/perform_on_single_element_action_unittest.cc
new file mode 100644
index 0000000..45b98e3
--- /dev/null
+++ b/components/autofill_assistant/browser/actions/perform_on_single_element_action_unittest.cc
@@ -0,0 +1,136 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/actions/perform_on_single_element_action.h"
+
+#include "base/test/gmock_callback_support.h"
+#include "base/test/mock_callback.h"
+#include "components/autofill_assistant/browser/action_value.pb.h"
+#include "components/autofill_assistant/browser/actions/action_test_utils.h"
+#include "components/autofill_assistant/browser/actions/mock_action_delegate.h"
+#include "components/autofill_assistant/browser/client_status.h"
+#include "components/autofill_assistant/browser/dom_action.pb.h"
+#include "components/autofill_assistant/browser/web/element_finder.h"
+#include "components/autofill_assistant/browser/web/element_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace autofill_assistant {
+namespace {
+
+using ::base::test::RunOnceCallback;
+using ::testing::_;
+using ::testing::Pointee;
+using ::testing::Property;
+using ::testing::SaveArgPointee;
+
+const char kClientId[] = "1";
+
+class PerformOnSingleElementActionTest : public testing::Test {
+ public:
+  PerformOnSingleElementActionTest() {}
+
+  void SetUp() override { client_id_.set_identifier(kClientId); }
+
+ protected:
+  MockActionDelegate mock_action_delegate_;
+  base::MockCallback<Action::ProcessActionCallback> callback_;
+  base::MockCallback<PerformOnSingleElementAction::PerformAction> perform_;
+  base::MockCallback<PerformOnSingleElementAction::PerformTimedAction>
+      perform_timed_;
+  UserData user_data_;
+  ActionProto action_proto_;
+  ClientIdProto client_id_;
+};
+
+TEST_F(PerformOnSingleElementActionTest, EmptyClientIdFails) {
+  EXPECT_CALL(
+      callback_,
+      Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION))));
+  EXPECT_CALL(perform_, Run(_, _)).Times(0);
+
+  ClientIdProto client_id;
+  auto action = PerformOnSingleElementAction::WithClientId(
+      &mock_action_delegate_, action_proto_, client_id, perform_.Get());
+  action->ProcessAction(callback_.Get());
+}
+
+TEST_F(PerformOnSingleElementActionTest, OptionalEmptyClientIdDoesNotFail) {
+  EXPECT_CALL(
+      callback_,
+      Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+  EXPECT_CALL(perform_, Run(_, _))
+      .WillOnce(RunOnceCallback<1>(OkClientStatus()));
+
+  ClientIdProto client_id;
+  auto action = PerformOnSingleElementAction::WithOptionalClientId(
+      &mock_action_delegate_, action_proto_, client_id, perform_.Get());
+  action->ProcessAction(callback_.Get());
+}
+
+TEST_F(PerformOnSingleElementActionTest,
+       OptionalEmptyClientIdDoesNotFailForTimed) {
+  ProcessedActionProto capture;
+  EXPECT_CALL(callback_, Run(_)).WillOnce(testing::SaveArgPointee<0>(&capture));
+  EXPECT_CALL(perform_timed_, Run(_, _))
+      .WillOnce(RunOnceCallback<1>(OkClientStatus(),
+                                   base::TimeDelta::FromSeconds(1)));
+
+  ClientIdProto client_id;
+  auto action = PerformOnSingleElementAction::WithOptionalClientIdTimed(
+      &mock_action_delegate_, action_proto_, client_id, perform_timed_.Get());
+  action->ProcessAction(callback_.Get());
+
+  EXPECT_EQ(ACTION_APPLIED, capture.status());
+  EXPECT_EQ(1000, capture.timing_stats().wait_time_ms());
+}
+
+TEST_F(PerformOnSingleElementActionTest, FailsIfElementDoesNotExist) {
+  EXPECT_CALL(callback_, Run(Pointee(Property(&ProcessedActionProto::status,
+                                              CLIENT_ID_RESOLUTION_FAILED))));
+  EXPECT_CALL(perform_, Run(_, _)).Times(0);
+
+  auto action = PerformOnSingleElementAction::WithClientId(
+      &mock_action_delegate_, action_proto_, client_id_, perform_.Get());
+  action->ProcessAction(callback_.Get());
+}
+
+TEST_F(PerformOnSingleElementActionTest, PerformsAndEnds) {
+  ElementFinder::Result element;
+  element.dom_object.object_data.object_id = "id";
+  mock_action_delegate_.GetElementStore()->AddElement(kClientId,
+                                                      element.dom_object);
+
+  EXPECT_CALL(
+      callback_,
+      Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+  EXPECT_CALL(perform_, Run(EqualsElement(element), _))
+      .WillOnce(RunOnceCallback<1>(OkClientStatus()));
+
+  auto action = PerformOnSingleElementAction::WithClientId(
+      &mock_action_delegate_, action_proto_, client_id_, perform_.Get());
+  action->ProcessAction(callback_.Get());
+}
+
+TEST_F(PerformOnSingleElementActionTest, PerformsTimedAndEnds) {
+  ElementFinder::Result element;
+  element.dom_object.object_data.object_id = "id";
+  mock_action_delegate_.GetElementStore()->AddElement(kClientId,
+                                                      element.dom_object);
+
+  ProcessedActionProto capture;
+  EXPECT_CALL(callback_, Run(_)).WillOnce(testing::SaveArgPointee<0>(&capture));
+  EXPECT_CALL(perform_timed_, Run(EqualsElement(element), _))
+      .WillOnce(RunOnceCallback<1>(OkClientStatus(),
+                                   base::TimeDelta::FromSeconds(1)));
+
+  auto action = PerformOnSingleElementAction::WithClientIdTimed(
+      &mock_action_delegate_, action_proto_, client_id_, perform_timed_.Get());
+  action->ProcessAction(callback_.Get());
+
+  EXPECT_EQ(ACTION_APPLIED, capture.status());
+  EXPECT_EQ(1000, capture.timing_stats().wait_time_ms());
+}
+
+}  // namespace
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/actions/release_elements_action.cc b/components/autofill_assistant/browser/actions/release_elements_action.cc
new file mode 100644
index 0000000..558dda78
--- /dev/null
+++ b/components/autofill_assistant/browser/actions/release_elements_action.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/actions/release_elements_action.h"
+
+#include "base/callback.h"
+#include "components/autofill_assistant/browser/action_value.pb.h"
+#include "components/autofill_assistant/browser/actions/action_delegate.h"
+#include "components/autofill_assistant/browser/client_status.h"
+#include "components/autofill_assistant/browser/web/element_store.h"
+
+namespace autofill_assistant {
+
+ReleaseElementsAction::ReleaseElementsAction(ActionDelegate* delegate,
+                                             const ActionProto& proto)
+    : Action(delegate, proto) {
+  DCHECK(proto_.has_release_elements());
+}
+
+ReleaseElementsAction::~ReleaseElementsAction() {}
+
+void ReleaseElementsAction::InternalProcessAction(
+    ProcessActionCallback callback) {
+  process_action_callback_ = std::move(callback);
+
+  for (const auto& client_id : proto_.release_elements().client_ids()) {
+    delegate_->GetElementStore()->RemoveElement(client_id.identifier());
+  }
+
+  EndAction(ClientStatus(ACTION_APPLIED));
+}
+
+void ReleaseElementsAction::EndAction(const ClientStatus& status) {
+  UpdateProcessedAction(status);
+  std::move(process_action_callback_).Run(std::move(processed_action_proto_));
+}
+
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/actions/release_elements_action.h b/components/autofill_assistant/browser/actions/release_elements_action.h
new file mode 100644
index 0000000..9f0c29d4
--- /dev/null
+++ b/components/autofill_assistant/browser/actions/release_elements_action.h
@@ -0,0 +1,37 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_RELEASE_ELEMENTS_ACTION_H_
+#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_RELEASE_ELEMENTS_ACTION_H_
+
+#include "base/memory/weak_ptr.h"
+#include "components/autofill_assistant/browser/actions/action.h"
+#include "components/autofill_assistant/browser/actions/action_delegate.h"
+#include "components/autofill_assistant/browser/client_status.h"
+
+namespace autofill_assistant {
+
+// An action to release elements from the store.
+class ReleaseElementsAction : public Action {
+ public:
+  explicit ReleaseElementsAction(ActionDelegate* delegate,
+                                 const ActionProto& proto);
+  ~ReleaseElementsAction() override;
+
+  ReleaseElementsAction(const ReleaseElementsAction&) = delete;
+  ReleaseElementsAction& operator=(const ReleaseElementsAction&) = delete;
+
+ private:
+  // Overrides Action:
+  void InternalProcessAction(ProcessActionCallback callback) override;
+
+  void EndAction(const ClientStatus& status);
+
+  ProcessActionCallback process_action_callback_;
+
+  base::WeakPtrFactory<ReleaseElementsAction> weak_ptr_factory_{this};
+};
+
+}  // namespace autofill_assistant
+#endif  // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_RELEASE_ELEMEMENTS_ACTION_H_
diff --git a/components/autofill_assistant/browser/actions/release_elements_action_unittest.cc b/components/autofill_assistant/browser/actions/release_elements_action_unittest.cc
new file mode 100644
index 0000000..700aad9f1
--- /dev/null
+++ b/components/autofill_assistant/browser/actions/release_elements_action_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/actions/release_elements_action.h"
+
+#include "base/test/gmock_callback_support.h"
+#include "base/test/mock_callback.h"
+#include "components/autofill_assistant/browser/action_value.pb.h"
+#include "components/autofill_assistant/browser/actions/mock_action_delegate.h"
+#include "components/autofill_assistant/browser/client_status.h"
+#include "components/autofill_assistant/browser/web/element_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace autofill_assistant {
+namespace {
+
+using ::base::test::RunOnceCallback;
+using ::testing::_;
+using ::testing::Pointee;
+using ::testing::Property;
+
+const char kClientId[] = "1";
+const char kOtherClientId[] = "2";
+
+class ReleaseElementsActionTest : public testing::Test {
+ public:
+  ReleaseElementsActionTest() {}
+
+  void SetUp() override {
+    ClientIdProto client_id;
+    client_id.set_identifier(kClientId);
+    *proto_.add_client_ids() = client_id;
+  }
+
+  void Run() {
+    ActionProto action_proto;
+    *action_proto.mutable_release_elements() = proto_;
+    ReleaseElementsAction action(&mock_action_delegate_, action_proto);
+    action.ProcessAction(callback_.Get());
+  }
+
+ protected:
+  MockActionDelegate mock_action_delegate_;
+  base::MockCallback<Action::ProcessActionCallback> callback_;
+  ReleaseElementsProto proto_;
+};
+
+TEST_F(ReleaseElementsActionTest, ReleasesSingleElement) {
+  ElementFinder::Result element;
+  element.dom_object.object_data.object_id = "id";
+  mock_action_delegate_.GetElementStore()->AddElement(kClientId,
+                                                      element.dom_object);
+
+  EXPECT_TRUE(mock_action_delegate_.GetElementStore()->HasElement(kClientId));
+
+  EXPECT_CALL(
+      callback_,
+      Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+
+  Run();
+
+  EXPECT_FALSE(mock_action_delegate_.GetElementStore()->HasElement(kClientId));
+}
+
+TEST_F(ReleaseElementsActionTest, ReleasesMultipleElements) {
+  ClientIdProto client_id_2;
+  client_id_2.set_identifier(kOtherClientId);
+  *proto_.add_client_ids() = client_id_2;
+
+  ElementFinder::Result element;
+  element.dom_object.object_data.object_id = "id";
+  mock_action_delegate_.GetElementStore()->AddElement(kClientId,
+                                                      element.dom_object);
+  mock_action_delegate_.GetElementStore()->AddElement(kOtherClientId,
+                                                      element.dom_object);
+
+  EXPECT_TRUE(mock_action_delegate_.GetElementStore()->HasElement(kClientId));
+  EXPECT_TRUE(
+      mock_action_delegate_.GetElementStore()->HasElement(kOtherClientId));
+
+  EXPECT_CALL(
+      callback_,
+      Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+
+  Run();
+
+  EXPECT_FALSE(mock_action_delegate_.GetElementStore()->HasElement(kClientId));
+  EXPECT_FALSE(
+      mock_action_delegate_.GetElementStore()->HasElement(kOtherClientId));
+}
+
+}  // namespace
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/dom_action.proto b/components/autofill_assistant/browser/dom_action.proto
index 5b0f1813..3533297 100644
--- a/components/autofill_assistant/browser/dom_action.proto
+++ b/components/autofill_assistant/browser/dom_action.proto
@@ -30,13 +30,13 @@
   optional int32 timeout_in_ms = 2;
 }
 
-message WaitForElementToBecomeStable {
+message WaitForElementToBecomeStableProto {
   optional ClientIdProto client_id = 1;
   optional int32 stable_check_max_rounds = 2 [default = 50];
   optional int32 stable_check_interval_ms = 3 [default = 200];
 }
 
-message CheckElementIsOnTop {
+message CheckElementIsOnTopProto {
   optional ClientIdProto client_id = 1;
 }
 
diff --git a/components/autofill_assistant/browser/protocol_utils.cc b/components/autofill_assistant/browser/protocol_utils.cc
index 7ea264e..712fc85 100644
--- a/components/autofill_assistant/browser/protocol_utils.cc
+++ b/components/autofill_assistant/browser/protocol_utils.cc
@@ -8,6 +8,7 @@
 
 #include "base/feature_list.h"
 #include "base/logging.h"
+#include "components/autofill_assistant/browser/actions/action_delegate_util.h"
 #include "components/autofill_assistant/browser/actions/click_action.h"
 #include "components/autofill_assistant/browser/actions/collect_user_data_action.h"
 #include "components/autofill_assistant/browser/actions/configure_bottom_sheet_action.h"
@@ -17,9 +18,11 @@
 #include "components/autofill_assistant/browser/actions/get_element_status_action.h"
 #include "components/autofill_assistant/browser/actions/highlight_element_action.h"
 #include "components/autofill_assistant/browser/actions/navigate_action.h"
+#include "components/autofill_assistant/browser/actions/perform_on_single_element_action.h"
 #include "components/autofill_assistant/browser/actions/popup_message_action.h"
 #include "components/autofill_assistant/browser/actions/presave_generated_password_action.h"
 #include "components/autofill_assistant/browser/actions/prompt_action.h"
+#include "components/autofill_assistant/browser/actions/release_elements_action.h"
 #include "components/autofill_assistant/browser/actions/save_generated_password_action.h"
 #include "components/autofill_assistant/browser/actions/select_option_action.h"
 #include "components/autofill_assistant/browser/actions/set_attribute_action.h"
@@ -40,6 +43,7 @@
 #include "components/autofill_assistant/browser/actions/wait_for_dom_action.h"
 #include "components/autofill_assistant/browser/actions/wait_for_navigation_action.h"
 #include "components/autofill_assistant/browser/service.pb.h"
+#include "components/autofill_assistant/browser/web/web_controller.h"
 #include "url/gurl.h"
 
 namespace autofill_assistant {
@@ -233,6 +237,102 @@
       return std::make_unique<PresaveGeneratedPasswordAction>(delegate, action);
     case ActionProto::ActionInfoCase::kGetElementStatus:
       return std::make_unique<GetElementStatusAction>(delegate, action);
+    case ActionProto::ActionInfoCase::kScrollIntoView:
+      return PerformOnSingleElementAction::WithClientId(
+          delegate, action, action.scroll_into_view().client_id(),
+          base::BindOnce(&WebController::ScrollIntoView,
+                         delegate->GetWebController()->GetWeakPtr()));
+    case ActionProto::ActionInfoCase::kWaitForDocumentToBecomeInteractive:
+      return PerformOnSingleElementAction::WithOptionalClientIdTimed(
+          delegate, action,
+          action.wait_for_document_to_become_interactive().client_id(),
+          base::BindOnce(&ActionDelegate::WaitUntilDocumentIsInReadyState,
+                         delegate->GetWeakPtr(),
+                         base::TimeDelta::FromMilliseconds(
+                             action.wait_for_document_to_become_interactive()
+                                 .timeout_in_ms()),
+                         DOCUMENT_INTERACTIVE));
+    case ActionProto::ActionInfoCase::kWaitForDocumentToBecomeComplete:
+      return PerformOnSingleElementAction::WithOptionalClientIdTimed(
+          delegate, action,
+          action.wait_for_document_to_become_complete().client_id(),
+          base::BindOnce(&ActionDelegate::WaitUntilDocumentIsInReadyState,
+                         delegate->GetWeakPtr(),
+                         base::TimeDelta::FromMilliseconds(
+                             action.wait_for_document_to_become_complete()
+                                 .timeout_in_ms()),
+                         DOCUMENT_COMPLETE));
+    case ActionProto::ActionInfoCase::kSendClickEvent:
+      return PerformOnSingleElementAction::WithClientId(
+          delegate, action, action.send_click_event().client_id(),
+          base::BindOnce(&ActionDelegate::ClickOrTapElement,
+                         delegate->GetWeakPtr(), ClickType::CLICK));
+    case ActionProto::ActionInfoCase::kSendTapEvent:
+      return PerformOnSingleElementAction::WithClientId(
+          delegate, action, action.send_tap_event().client_id(),
+          base::BindOnce(&ActionDelegate::ClickOrTapElement,
+                         delegate->GetWeakPtr(), ClickType::TAP));
+    case ActionProto::ActionInfoCase::kJsClick:
+      return PerformOnSingleElementAction::WithClientId(
+          delegate, action, action.js_click().client_id(),
+          base::BindOnce(&ActionDelegate::ClickOrTapElement,
+                         delegate->GetWeakPtr(), ClickType::JAVASCRIPT));
+    case ActionProto::ActionInfoCase::kSendKeystrokeEvents:
+      return PerformOnSingleElementAction::WithClientId(
+          delegate, action, action.send_keystroke_events().client_id(),
+          base::BindOnce(
+              &action_delegate_util::PerformWithTextValue, delegate,
+              action.send_keystroke_events().value(),
+              base::BindOnce(&WebController::SendTextInput,
+                             delegate->GetWebController()->GetWeakPtr(),
+                             action.send_keystroke_events().delay_in_ms())));
+    case ActionProto::ActionInfoCase::kSetFieldValue:
+      return PerformOnSingleElementAction::WithClientId(
+          delegate, action, action.set_field_value().client_id(),
+          base::BindOnce(&action_delegate_util::PerformWithTextValue, delegate,
+                         action.set_field_value().value(),
+                         base::BindOnce(&ActionDelegate::SetValueAttribute,
+                                        delegate->GetWeakPtr())));
+    case ActionProto::ActionInfoCase::kSetElementAttribute: {
+      std::vector<std::string> attributes;
+      for (const auto& attribute : action.set_element_attribute().attribute()) {
+        attributes.emplace_back(attribute);
+      }
+      return PerformOnSingleElementAction::WithClientId(
+          delegate, action, action.set_element_attribute().client_id(),
+          base::BindOnce(&action_delegate_util::PerformWithTextValue, delegate,
+                         action.set_element_attribute().value(),
+                         base::BindOnce(&ActionDelegate::SetAttribute,
+                                        delegate->GetWeakPtr(), attributes)));
+    }
+    case ActionProto::ActionInfoCase::kSelectFieldValue:
+      return PerformOnSingleElementAction::WithClientId(
+          delegate, action, action.select_field_value().client_id(),
+          base::BindOnce(&WebController::SelectFieldValue,
+                         delegate->GetWebController()->GetWeakPtr()));
+    case ActionProto::ActionInfoCase::kFocusField:
+      return PerformOnSingleElementAction::WithClientId(
+          delegate, action, action.focus_field().client_id(),
+          base::BindOnce(&WebController::FocusField,
+                         delegate->GetWebController()->GetWeakPtr()));
+    case ActionProto::ActionInfoCase::kWaitForElementToBecomeStable:
+      return PerformOnSingleElementAction::WithClientIdTimed(
+          delegate, action,
+          action.wait_for_element_to_become_stable().client_id(),
+          base::BindOnce(&ActionDelegate::WaitUntilElementIsStable,
+                         delegate->GetWeakPtr(),
+                         action.wait_for_element_to_become_stable()
+                             .stable_check_max_rounds(),
+                         base::TimeDelta::FromMilliseconds(
+                             action.wait_for_element_to_become_stable()
+                                 .stable_check_interval_ms())));
+    case ActionProto::ActionInfoCase::kCheckElementIsOnTop:
+      return PerformOnSingleElementAction::WithClientId(
+          delegate, action, action.check_element_is_on_top().client_id(),
+          base::BindOnce(&WebController::CheckOnTop,
+                         delegate->GetWebController()->GetWeakPtr()));
+    case ActionProto::ActionInfoCase::kReleaseElements:
+      return std::make_unique<ReleaseElementsAction>(delegate, action);
     case ActionProto::ActionInfoCase::ACTION_INFO_NOT_SET: {
       VLOG(1) << "Encountered action with ACTION_INFO_NOT_SET";
       return std::make_unique<UnsupportedAction>(delegate, action);
diff --git a/components/autofill_assistant/browser/script_executor.cc b/components/autofill_assistant/browser/script_executor.cc
index b053823..bf75ab4 100644
--- a/components/autofill_assistant/browser/script_executor.cc
+++ b/components/autofill_assistant/browser/script_executor.cc
@@ -215,6 +215,20 @@
     case ActionProto::ActionInfoCase::kConfigureUiState:
     case ActionProto::ActionInfoCase::kPresaveGeneratedPassword:
     case ActionProto::ActionInfoCase::kGetElementStatus:
+    case ActionProto::ActionInfoCase::kScrollIntoView:
+    case ActionProto::ActionInfoCase::kWaitForDocumentToBecomeInteractive:
+    case ActionProto::ActionInfoCase::kWaitForDocumentToBecomeComplete:
+    case ActionProto::ActionInfoCase::kSendClickEvent:
+    case ActionProto::ActionInfoCase::kSendTapEvent:
+    case ActionProto::ActionInfoCase::kJsClick:
+    case ActionProto::ActionInfoCase::kSendKeystrokeEvents:
+    case ActionProto::ActionInfoCase::kSetFieldValue:
+    case ActionProto::ActionInfoCase::kSetElementAttribute:
+    case ActionProto::ActionInfoCase::kSelectFieldValue:
+    case ActionProto::ActionInfoCase::kFocusField:
+    case ActionProto::ActionInfoCase::kWaitForElementToBecomeStable:
+    case ActionProto::ActionInfoCase::kCheckElementIsOnTop:
+    case ActionProto::ActionInfoCase::kReleaseElements:
     case ActionProto::ActionInfoCase::ACTION_INFO_NOT_SET:
       return false;
   }
@@ -781,6 +795,10 @@
   return delegate_->GetElementStore();
 }
 
+WebController* ScriptExecutor::GetWebController() const {
+  return delegate_->GetWebController();
+}
+
 std::string ScriptExecutor::GetEmailAddressForAccessTokenAccount() {
   return delegate_->GetEmailAddressForAccessTokenAccount();
 }
diff --git a/components/autofill_assistant/browser/script_executor.h b/components/autofill_assistant/browser/script_executor.h
index 6389fd6..89931c0 100644
--- a/components/autofill_assistant/browser/script_executor.h
+++ b/components/autofill_assistant/browser/script_executor.h
@@ -31,6 +31,7 @@
 class ElementStore;
 class UserModel;
 class WaitForDocumentOperation;
+class WebController;
 
 // Class to execute an assistant script.
 class ScriptExecutor : public ActionDelegate,
@@ -263,6 +264,7 @@
   WebsiteLoginManager* GetWebsiteLoginManager() override;
   content::WebContents* GetWebContents() override;
   ElementStore* GetElementStore() const override;
+  WebController* GetWebController() const override;
   std::string GetEmailAddressForAccessTokenAccount() override;
   std::string GetLocale() override;
   void SetDetails(std::unique_ptr<Details> details) override;
diff --git a/components/autofill_assistant/browser/service.proto b/components/autofill_assistant/browser/service.proto
index a1c3a13..096e5f8 100644
--- a/components/autofill_assistant/browser/service.proto
+++ b/components/autofill_assistant/browser/service.proto
@@ -619,6 +619,22 @@
     ConfigureUiStateProto configure_ui_state = 54;
     PresaveGeneratedPasswordProto presave_generated_password = 55;
     GetElementStatusProto get_element_status = 56;
+    ScrollIntoViewProto scroll_into_view = 57;
+    WaitForDocumentToBecomeInteractiveProto
+        wait_for_document_to_become_interactive = 58;
+    WaitForDocumentToBecomeCompleteProto wait_for_document_to_become_complete =
+        59;
+    SendClickEventProto send_click_event = 60;
+    SendTapEventProto send_tap_event = 61;
+    JsClickProto js_click = 62;
+    SendKeystrokeEventsProto send_keystroke_events = 63;
+    SetFieldValueProto set_field_value = 64;
+    SetElementAttributeProto set_element_attribute = 65;
+    SelectFieldValueProto select_field_value = 66;
+    FocusFieldProto focus_field = 67;
+    WaitForElementToBecomeStableProto wait_for_element_to_become_stable = 68;
+    CheckElementIsOnTopProto check_element_is_on_top = 69;
+    ReleaseElementsProto release_elements = 70;
   }
 
   // Set to true to make the client remove any contextual information if the
@@ -1942,6 +1958,14 @@
     optional string sublabel = 7;
     optional string sublabel_accessibility_hint = 8;
 
+    // The optional content description of the edit button. There are three
+    // possible states:
+    // - unset: the content description will be inferred from the button.
+    // - set to empty string: this button is not important for a11y.
+    // - set to non-empty string: the button will have the specified content
+    // description.
+    optional string edit_button_content_description = 10;
+
     // If the option was chosen, this payload will be returned to the server.
     optional bytes payload = 1;
 
@@ -1956,6 +1980,8 @@
       LoginOptionCustomProto custom = 4;
       LoginOptionPasswordManagerProto password_manager = 5;
     }
+
+    reserved 9;
   }
   // The title for the login selection (e.g., 'Login details for <domain>').
   optional string section_title = 1;
@@ -2775,3 +2801,7 @@
     optional bool expected_empty_match = 7;
   }
 }
+
+message ReleaseElementsProto {
+  repeated ClientIdProto client_ids = 1;
+}
diff --git a/components/autofill_assistant/browser/service/service_request_sender_impl.cc b/components/autofill_assistant/browser/service/service_request_sender_impl.cc
index f147669d..eabeb73 100644
--- a/components/autofill_assistant/browser/service/service_request_sender_impl.cc
+++ b/components/autofill_assistant/browser/service/service_request_sender_impl.cc
@@ -74,7 +74,8 @@
 #ifdef DEBUG
   loader->SetAllowHttpErrorResults(true);
 #endif
-  loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+  auto* const loader_ptr = loader.get();
+  loader_ptr->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
       content::BrowserContext::GetDefaultStoragePartition(context)
           ->GetURLLoaderFactoryForBrowserProcess()
           .get(),
diff --git a/components/autofill_assistant/browser/user_data.cc b/components/autofill_assistant/browser/user_data.cc
index 9e9c18e..6267839 100644
--- a/components/autofill_assistant/browser/user_data.cc
+++ b/components/autofill_assistant/browser/user_data.cc
@@ -15,13 +15,15 @@
     const std::string& _sublabel,
     const base::Optional<std::string>& _sublabel_accessibility_hint,
     int _preselect_priority,
-    const base::Optional<InfoPopupProto>& _info_popup)
+    const base::Optional<InfoPopupProto>& _info_popup,
+    const base::Optional<std::string>& _edit_button_content_description)
     : identifier(_identifier),
       label(_label),
       sublabel(_sublabel),
       sublabel_accessibility_hint(_sublabel_accessibility_hint),
       preselect_priority(_preselect_priority),
-      info_popup(_info_popup) {}
+      info_popup(_info_popup),
+      edit_button_content_description(_edit_button_content_description) {}
 LoginChoice::LoginChoice(const LoginChoice& another) = default;
 LoginChoice::~LoginChoice() = default;
 
diff --git a/components/autofill_assistant/browser/user_data.h b/components/autofill_assistant/browser/user_data.h
index 582cc682..e3fc8f0 100644
--- a/components/autofill_assistant/browser/user_data.h
+++ b/components/autofill_assistant/browser/user_data.h
@@ -52,12 +52,14 @@
 // Represents a concrete login choice in the UI, e.g., 'Guest checkout' or
 // a particular Chrome PWM login account.
 struct LoginChoice {
-  LoginChoice(const std::string& id,
-              const std::string& label,
-              const std::string& sublabel,
-              const base::Optional<std::string>& sublabel_accessibility_hint,
-              int priority,
-              const base::Optional<InfoPopupProto>& info_popup);
+  LoginChoice(
+      const std::string& id,
+      const std::string& label,
+      const std::string& sublabel,
+      const base::Optional<std::string>& sublabel_accessibility_hint,
+      int priority,
+      const base::Optional<InfoPopupProto>& info_popup,
+      const base::Optional<std::string>& edit_button_content_description);
   LoginChoice(const LoginChoice& another);
   ~LoginChoice();
 
@@ -73,6 +75,8 @@
   int preselect_priority = -1;
   // The popup to show to provide more information about this login choice.
   base::Optional<InfoPopupProto> info_popup;
+  // The a11y hint for the edit button.
+  base::Optional<std::string> edit_button_content_description;
 };
 
 // Tuple for holding credit card and billing address;
diff --git a/components/autofill_assistant/browser/web/element.cc b/components/autofill_assistant/browser/web/element.cc
index 716570f..b105044 100644
--- a/components/autofill_assistant/browser/web/element.cc
+++ b/components/autofill_assistant/browser/web/element.cc
@@ -18,6 +18,9 @@
 content::RenderFrameHost* FindCorrespondingRenderFrameHost(
     const std::string& frame_id,
     content::WebContents* web_contents) {
+  if (frame_id.empty()) {
+    return web_contents->GetMainFrame();
+  }
   const auto& all_frames = web_contents->GetAllFrames();
   const auto& it = std::find_if(
       all_frames.begin(), all_frames.end(), [&](const auto& frame) {
diff --git a/components/autofill_assistant/browser/web/element_store.h b/components/autofill_assistant/browser/web/element_store.h
index f371cfac..665f8e1 100644
--- a/components/autofill_assistant/browser/web/element_store.h
+++ b/components/autofill_assistant/browser/web/element_store.h
@@ -23,7 +23,7 @@
  public:
   // |web_contents| must outlive this instance.
   ElementStore(content::WebContents* web_contents);
-  ~ElementStore();
+  virtual ~ElementStore();
 
   ElementStore(const ElementStore&) = delete;
   ElementStore& operator=(const ElementStore&) = delete;
@@ -35,8 +35,8 @@
 
   // Get an element from the store. If the element does not exist or cannot be
   // reconstructed this returns an error status.
-  ClientStatus GetElement(const std::string& client_id,
-                          ElementFinder::Result* out_element) const;
+  virtual ClientStatus GetElement(const std::string& client_id,
+                                  ElementFinder::Result* out_element) const;
 
   // Removes an element. Returns true if the element was removed.
   bool RemoveElement(const std::string& client_id);
@@ -48,6 +48,8 @@
   void Clear();
 
  private:
+  friend class FakeElementStore;
+
   content::WebContents* web_contents_;
 
   base::flat_map<std::string, DomObjectFrameStack> object_map_;
diff --git a/components/autofill_assistant/browser/web/element_store_unittest.cc b/components/autofill_assistant/browser/web/element_store_unittest.cc
index bcbbe079..23b61827 100644
--- a/components/autofill_assistant/browser/web/element_store_unittest.cc
+++ b/components/autofill_assistant/browser/web/element_store_unittest.cc
@@ -71,6 +71,7 @@
 TEST_F(ElementStoreTest, GetElementFromStoreWithBadFrameHost) {
   auto element = std::make_unique<ElementFinder::Result>();
   element->dom_object.object_data.object_id = "1";
+  element->dom_object.object_data.node_frame_id = "unknown";
   AddElement("1", std::move(element));
 
   ElementFinder::Result result;
@@ -78,6 +79,17 @@
             element_store_->GetElement("1", &result).proto_status());
 }
 
+TEST_F(ElementStoreTest, GetElementFromStoreWithNoFrameId) {
+  auto element = std::make_unique<ElementFinder::Result>();
+  element->dom_object.object_data.object_id = "1";
+  AddElement("1", std::move(element));
+
+  ElementFinder::Result result;
+  EXPECT_EQ(ACTION_APPLIED,
+            element_store_->GetElement("1", &result).proto_status());
+  EXPECT_EQ(web_contents()->GetMainFrame(), result.container_frame_host);
+}
+
 TEST_F(ElementStoreTest, AddElementToStoreOverwrites) {
   auto element_1 = CreateElement("1");
   auto element_2 = CreateElement("2");
diff --git a/components/autofill_assistant/browser/web/fake_element_store.cc b/components/autofill_assistant/browser/web/fake_element_store.cc
new file mode 100644
index 0000000..bdfff4e
--- /dev/null
+++ b/components/autofill_assistant/browser/web/fake_element_store.cc
@@ -0,0 +1,26 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/web/fake_element_store.h"
+#include "components/autofill_assistant/browser/client_status.h"
+
+namespace autofill_assistant {
+
+FakeElementStore::FakeElementStore() : ElementStore(nullptr) {}
+
+FakeElementStore::~FakeElementStore() = default;
+
+ClientStatus FakeElementStore::GetElement(
+    const std::string& client_id,
+    ElementFinder::Result* out_element) const {
+  auto it = object_map_.find(client_id);
+  if (it == object_map_.end()) {
+    return ClientStatus(CLIENT_ID_RESOLUTION_FAILED);
+  }
+
+  out_element->dom_object = it->second;
+  return OkClientStatus();
+}
+
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/web/fake_element_store.h b/components/autofill_assistant/browser/web/fake_element_store.h
new file mode 100644
index 0000000..5e3a333
--- /dev/null
+++ b/components/autofill_assistant/browser/web/fake_element_store.h
@@ -0,0 +1,28 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_FAKE_ELEMENT_STORE_H_
+#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_FAKE_ELEMENT_STORE_H_
+
+#include "components/autofill_assistant/browser/client_status.h"
+#include "components/autofill_assistant/browser/web/element_finder.h"
+#include "components/autofill_assistant/browser/web/element_store.h"
+
+namespace autofill_assistant {
+
+class FakeElementStore : public ElementStore {
+ public:
+  FakeElementStore();
+  ~FakeElementStore() override;
+
+  FakeElementStore(const FakeElementStore&) = delete;
+  FakeElementStore& operator=(const FakeElementStore&) = delete;
+
+  ClientStatus GetElement(const std::string& client_id,
+                          ElementFinder::Result* out_element) const override;
+};
+
+}  // namespace autofill_assistant
+
+#endif  // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_FAKE_ELEMENT_STORE_H_
diff --git a/components/autofill_assistant/browser/web/web_controller.cc b/components/autofill_assistant/browser/web/web_controller.cc
index 74f1048..a3a025f3 100644
--- a/components/autofill_assistant/browser/web/web_controller.cc
+++ b/components/autofill_assistant/browser/web/web_controller.cc
@@ -1155,10 +1155,19 @@
                                     std::move(callback))));
 }
 
+void WebController::SendTextInput(
+    int key_press_delay_in_millisecond,
+    const std::string& value,
+    const ElementFinder::Result& element,
+    base::OnceCallback<void(const ClientStatus&)> callback) {
+  SendKeyboardInput(element, UTF8ToUnicode(value),
+                    key_press_delay_in_millisecond, std::move(callback));
+}
+
 void WebController::SendKeyboardInput(
     const ElementFinder::Result& element,
     const std::vector<UChar32>& codepoints,
-    const int delay_in_millisecond,
+    const int key_press_delay_in_millisecond,
     base::OnceCallback<void(const ClientStatus&)> callback) {
   if (VLOG_IS_ON(3)) {
     std::string input_str;
@@ -1174,7 +1183,7 @@
 
   DispatchKeyboardTextDownEvent(
       element.node_frame_id(), codepoints, 0,
-      /* delay= */ false, delay_in_millisecond,
+      /* delay= */ false, key_press_delay_in_millisecond,
       base::BindOnce(&DecorateWebControllerStatus,
                      WebControllerErrorInfoProto::SEND_KEYBOARD_INPUT,
                      std::move(callback)));
diff --git a/components/autofill_assistant/browser/web/web_controller.h b/components/autofill_assistant/browser/web/web_controller.h
index 6af7e7f..1d1399d 100644
--- a/components/autofill_assistant/browser/web/web_controller.h
+++ b/components/autofill_assistant/browser/web/web_controller.h
@@ -198,14 +198,24 @@
       const ElementFinder::Result& element,
       base::OnceCallback<void(const ClientStatus&)> callback);
 
-  // Sets the keyboard focus to |element| and inputs |codepoints|, one
-  // character at a time. Key presses will have a delay of |delay_in_milli|
-  // between them.
-  // Returns the result through |callback|.
+  // Inputs the specified codepoints into |element|. Expects the |element| to
+  // have focus. Key presses will have a delay of
+  // |key_press_delay_in_millisecond| between them. Returns the result through
+  // |callback|.
   virtual void SendKeyboardInput(
       const ElementFinder::Result& element,
       const std::vector<UChar32>& codepoints,
-      int delay_in_milli,
+      int key_press_delay_in_millisecond,
+      base::OnceCallback<void(const ClientStatus&)> callback);
+
+  // Inputs the specified |value| into |element| with keystrokes per character.
+  // Expects the |element| to have focus. Key presses will have a delay of
+  // |key_press_delay_in_millisecond| between them. Returns the result through
+  // |callback|.
+  virtual void SendTextInput(
+      int key_press_delay_in_millisecond,
+      const std::string& value,
+      const ElementFinder::Result& element,
       base::OnceCallback<void(const ClientStatus&)> callback);
 
   // Return the outerHTML of |element|.
diff --git a/components/autofill_assistant/browser/web/web_controller_browsertest.cc b/components/autofill_assistant/browser/web/web_controller_browsertest.cc
index 8977dbb3..3d0b368 100644
--- a/components/autofill_assistant/browser/web/web_controller_browsertest.cc
+++ b/components/autofill_assistant/browser/web/web_controller_browsertest.cc
@@ -185,8 +185,9 @@
       std::unique_ptr<ElementFinder::Result> element_result) {
     EXPECT_EQ(ACTION_APPLIED, status.proto_status());
     ASSERT_TRUE(element_result != nullptr);
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     PerformClickOrTap(
-        click_type, *element_result,
+        click_type, *element_result_ptr,
         base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback,
                        base::Unretained(this), std::move(element_result),
                        std::move(done_callback), result_output));
@@ -267,8 +268,9 @@
     }
 
     ASSERT_TRUE(element_result != nullptr);
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     web_controller_->ScrollToElementPosition(
-        *element_result, top_padding,
+        *element_result_ptr, top_padding,
         base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback,
                        base::Unretained(this), std::move(element_result),
                        std::move(done_callback), result_output));
@@ -308,8 +310,9 @@
     }
 
     ASSERT_TRUE(element_result != nullptr);
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     web_controller_->SelectOption(
-        *element_result, re2, case_sensitive, option_comparison_attribute,
+        *element_result_ptr, re2, case_sensitive, option_comparison_attribute,
         base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback,
                        base::Unretained(this), std::move(element_result),
                        std::move(done_callback), result_output));
@@ -359,8 +362,9 @@
     }
 
     ASSERT_TRUE(element_result != nullptr);
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     web_controller_->HighlightElement(
-        *element_result,
+        *element_result_ptr,
         base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback,
                        base::Unretained(this), std::move(element_result),
                        std::move(done_callback), result_output));
@@ -391,8 +395,9 @@
       std::unique_ptr<ElementFinder::Result> element_result) {
     EXPECT_EQ(ACTION_APPLIED, element_status.proto_status());
     ASSERT_TRUE(element_result != nullptr);
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     web_controller_->GetOuterHtml(
-        *element_result,
+        *element_result_ptr,
         base::BindOnce(
             &WebControllerBrowserTest::ElementRetainingStringCallback,
             base::Unretained(this), std::move(element_result),
@@ -421,11 +426,13 @@
     EXPECT_EQ(ACTION_APPLIED, client_status.proto_status());
     ASSERT_TRUE(elements);
 
+    const ElementFinder::Result* elements_ptr = elements.get();
     web_controller_->GetOuterHtmls(
-        *elements, base::BindOnce(&WebControllerBrowserTest::OnGetOuterHtmls,
-                                  base::Unretained(this), std::move(elements),
-                                  std::move(done_callback),
-                                  client_status_output, htmls_output));
+        *elements_ptr,
+        base::BindOnce(&WebControllerBrowserTest::OnGetOuterHtmls,
+                       base::Unretained(this), std::move(elements),
+                       std::move(done_callback), client_status_output,
+                       htmls_output));
   }
 
   void OnGetOuterHtmls(std::unique_ptr<ElementFinder::Result> elements,
@@ -464,8 +471,9 @@
       std::unique_ptr<ElementFinder::Result> element_result) {
     EXPECT_EQ(ACTION_APPLIED, element_status.proto_status());
     ASSERT_TRUE(element_result != nullptr);
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     web_controller_->GetElementTag(
-        *element_result,
+        *element_result_ptr,
         base::BindOnce(
             &WebControllerBrowserTest::ElementRetainingStringCallback,
             base::Unretained(this), std::move(element_result),
@@ -570,8 +578,9 @@
       std::unique_ptr<ElementFinder::Result> element_result) {
     EXPECT_EQ(ACTION_APPLIED, element_status.proto_status());
     ASSERT_TRUE(element_result != nullptr);
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     web_controller_->GetStringAttribute(
-        *element_result, attributes,
+        *element_result_ptr, attributes,
         base::BindOnce(
             &WebControllerBrowserTest::ElementRetainingStringCallback,
             base::Unretained(this), std::move(element_result),
@@ -606,8 +615,9 @@
                       element_status, std::string());
       return;
     }
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     web_controller_->GetFieldValue(
-        *element_result,
+        *element_result_ptr,
         base::BindOnce(&WebControllerBrowserTest::OnGetFieldValue,
                        base::Unretained(this), std::move(element_result),
                        std::move(done_callback),
@@ -659,8 +669,9 @@
 
     EXPECT_EQ(ACTION_APPLIED, element_status.proto_status());
     ASSERT_TRUE(element_result != nullptr);
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     PerformSetFieldValue(
-        value, fill_strategy, *element_result,
+        value, fill_strategy, *element_result_ptr,
         base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback,
                        base::Unretained(this), std::move(element_result),
                        std::move(done_callback), result_output));
@@ -769,8 +780,9 @@
       std::unique_ptr<ElementFinder::Result> element_result) {
     EXPECT_EQ(ACTION_APPLIED, element_status.proto_status());
     ASSERT_TRUE(element_result != nullptr);
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     PerformSendKeyboardInput(
-        codepoints, delay_in_milli, use_js_focus, *element_result,
+        codepoints, delay_in_milli, use_js_focus, *element_result_ptr,
         base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback,
                        base::Unretained(this), std::move(element_result),
                        std::move(done_callback), result_output));
@@ -846,8 +858,9 @@
     }
 
     ASSERT_TRUE(element_result != nullptr);
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     web_controller_->SetAttribute(
-        *element_result, attributes, value,
+        *element_result_ptr, attributes, value,
         base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback,
                        base::Unretained(this), std::move(element_result),
                        std::move(done_callback), result_output));
@@ -880,8 +893,9 @@
     }
 
     ASSERT_TRUE(element_result != nullptr);
+    const ElementFinder::Result* element_result_ptr = element_result.get();
     web_controller_->GetElementRect(
-        *element_result,
+        *element_result_ptr,
         base::BindOnce(&WebControllerBrowserTest::OnGetElementRect,
                        base::Unretained(this), std::move(element_result),
                        std::move(done_callback), result_output, rect_output));
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
index 0cf0c1c..8051bdf 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
@@ -7,6 +7,7 @@
 import static org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge.SITE_WILDCARD;
 
 import android.app.Activity;
+import android.app.Dialog;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
@@ -170,6 +171,8 @@
     // Map from preference key to ContentSettingsType.
     private Map<String, Integer> mPreferenceMap;
 
+    private Dialog mConfirmationDialog;
+
     private class SingleWebsitePermissionsPopulator
             implements WebsitePermissionsFetcher.WebsitePermissionsCallback {
         private final WebsiteAddress mSiteAddress;
@@ -232,6 +235,14 @@
         super.onActivityCreated(savedInstanceState);
     }
 
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        if (mConfirmationDialog != null) {
+            mConfirmationDialog.dismiss();
+        }
+    }
+
     private void init() {
         Object extraSite = getArguments().getSerializable(EXTRA_SITE);
         Object extraSiteAddress = getArguments().getSerializable(EXTRA_SITE_ADDRESS);
@@ -1044,6 +1055,10 @@
 
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
+        // It is possible that this UI is destroyed while a ListPreference dialog is open because
+        // incognito mode is closed through the system notification.
+        if (getView() == null) return true;
+
         @ContentSettingValues
         int permission = ContentSetting.fromString((String) newValue);
         BrowserContextHandle browserContextHandle =
@@ -1076,23 +1091,26 @@
                 : R.string.website_reset_confirmation;
         int buttonResId = mHideNonPermissionPreferences ? R.string.reset : titleResId;
         // Handle the Clear & Reset preference click by showing a confirmation.
-        new AlertDialog.Builder(getContext(), R.style.Theme_Chromium_AlertDialog)
-                .setTitle(titleResId)
-                .setMessage(confirmationResId)
-                .setPositiveButton(buttonResId,
-                        (dialog, which) -> {
-                            if (mHideNonPermissionPreferences) {
-                                mSiteDataCleaner.resetPermissions(
-                                        getSiteSettingsClient().getBrowserContextHandle(), mSite);
-                            } else {
-                                resetSite();
-                            }
-                            if (mWebsiteSettingsObserver != null) {
-                                mWebsiteSettingsObserver.onPermissionsReset();
-                            }
-                        })
-                .setNegativeButton(R.string.cancel, null)
-                .show();
+        mConfirmationDialog =
+                new AlertDialog.Builder(getContext(), R.style.Theme_Chromium_AlertDialog)
+                        .setTitle(titleResId)
+                        .setMessage(confirmationResId)
+                        .setPositiveButton(buttonResId,
+                                (dialog, which) -> {
+                                    if (mHideNonPermissionPreferences) {
+                                        mSiteDataCleaner.resetPermissions(
+                                                getSiteSettingsClient().getBrowserContextHandle(),
+                                                mSite);
+                                    } else {
+                                        resetSite();
+                                    }
+                                    if (mWebsiteSettingsObserver != null) {
+                                        mWebsiteSettingsObserver.onPermissionsReset();
+                                    }
+                                })
+                        .setNegativeButton(
+                                R.string.cancel, (dialog, which) -> mConfirmationDialog = null)
+                        .show();
         return true;
     }
 
diff --git a/components/chromeos_camera/jpeg_encode_accelerator_unittest.cc b/components/chromeos_camera/jpeg_encode_accelerator_unittest.cc
index e57704f..3a52635 100644
--- a/components/chromeos_camera/jpeg_encode_accelerator_unittest.cc
+++ b/components/chromeos_camera/jpeg_encode_accelerator_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/macros.h"
 #include "base/memory/unsafe_shared_memory_region.h"
 #include "base/path_service.h"
+#include "base/rand_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
@@ -271,7 +272,8 @@
  public:
   JpegClient(const std::vector<TestImage*>& test_aligned_images,
              const std::vector<TestImage*>& test_images,
-             media::test::ClientStateNotification<ClientState>* note);
+             media::test::ClientStateNotification<ClientState>* note,
+             size_t exif_size);
   ~JpegClient() override;
   void CreateJpegEncoder();
   void DestroyJpegEncoder();
@@ -325,6 +327,10 @@
   // this.
   media::test::ClientStateNotification<ClientState>* note_;
 
+  // EXIF data size for testing.
+  size_t exif_size_;
+  // Input buffer for EXIF data.
+  media::BitstreamBuffer exif_buffer_;
   // Output buffer prepared for JpegEncodeAccelerator.
   media::BitstreamBuffer encoded_buffer_;
 
@@ -348,11 +354,13 @@
 
 JpegClient::JpegClient(const std::vector<TestImage*>& test_aligned_images,
                        const std::vector<TestImage*>& test_images,
-                       media::test::ClientStateNotification<ClientState>* note)
+                       media::test::ClientStateNotification<ClientState>* note,
+                       size_t exif_size)
     : test_aligned_images_(test_aligned_images),
       test_unaligned_images_(test_images),
       state_(ClientState::CREATED),
       note_(note),
+      exif_size_(exif_size),
       gpu_memory_buffer_manager_(new media::LocalGpuMemoryBufferManager()) {}
 
 JpegClient::~JpegClient() {}
@@ -547,6 +555,14 @@
 void JpegClient::PrepareMemory(int32_t bitstream_buffer_id) {
   TestImage* test_image = GetTestImage(bitstream_buffer_id);
 
+  if (exif_size_ > 0) {
+    auto shm = base::UnsafeSharedMemoryRegion::Create(exif_size_);
+    auto shm_mapping = shm.Map();
+    base::RandBytes(shm_mapping.memory(), exif_size_);
+    exif_buffer_ =
+        media::BitstreamBuffer(bitstream_buffer_id, std::move(shm), exif_size_);
+  }
+
   size_t input_size = test_image->image_data.size();
   if (!in_mapping_.IsValid() || input_size > in_mapping_.size()) {
     in_shm_ = base::UnsafeSharedMemoryRegion::Create(input_size);
@@ -632,7 +648,8 @@
   input_frame_->BackWithSharedMemory(&in_shm_);
 
   buffer_id_to_start_time_[bitstream_buffer_id] = base::TimeTicks::Now();
-  encoder_->Encode(input_frame_, kJpegDefaultQuality, nullptr,
+  encoder_->Encode(input_frame_, kJpegDefaultQuality,
+                   exif_size_ > 0 ? &exif_buffer_ : nullptr,
                    std::move(encoded_buffer_));
 }
 
@@ -673,14 +690,17 @@
 
   buffer_id_to_start_time_[bitstream_buffer_id] = base::TimeTicks::Now();
   encoder_->EncodeWithDmaBuf(input_frame, hw_out_frame_, kJpegDefaultQuality,
-                             bitstream_buffer_id, nullptr);
+                             bitstream_buffer_id,
+                             exif_size_ > 0 ? &exif_buffer_ : nullptr);
 }
 
 class JpegEncodeAcceleratorTest : public ::testing::Test {
  protected:
   JpegEncodeAcceleratorTest() {}
 
-  void TestEncode(size_t num_concurrent_encoders, bool is_dma);
+  void TestEncode(size_t num_concurrent_encoders,
+                  bool is_dma,
+                  size_t exif_size);
 
   // This is needed to allow the usage of methods in post_task.h in
   // JpegEncodeAccelerator implementations.
@@ -696,7 +716,8 @@
 };
 
 void JpegEncodeAcceleratorTest::TestEncode(size_t num_concurrent_encoders,
-                                           bool is_dma) {
+                                           bool is_dma,
+                                           size_t exif_size) {
   base::Thread encoder_thread("EncoderThread");
   ASSERT_TRUE(encoder_thread.Start());
 
@@ -710,7 +731,8 @@
     notes.push_back(
         std::make_unique<media::test::ClientStateNotification<ClientState>>());
     clients.push_back(std::make_unique<JpegClient>(
-        test_aligned_images_, test_unaligned_images_, notes.back().get()));
+        test_aligned_images_, test_unaligned_images_, notes.back().get(),
+        exif_size));
     encoder_thread.task_runner()->PostTask(
         FROM_HERE, base::BindOnce(&JpegClient::CreateJpegEncoder,
                                   base::Unretained(clients.back().get())));
@@ -801,32 +823,37 @@
       test_aligned_images_.push_back(image.get());
     }
   }
-  TestEncode(1, false);
+  TestEncode(/*num_concurrent_encoders=*/1u, /*is_dma=*/false,
+             /*exif_size=*/0u);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, MultipleEncoders) {
   for (auto& image : g_env->image_data_user_) {
     test_aligned_images_.push_back(image.get());
   }
-  TestEncode(3, false);
+  TestEncode(/*num_concurrent_encoders=*/3u, /*is_dma=*/false,
+             /*exif_size=*/0u);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, ResolutionChange) {
   test_aligned_images_.push_back(g_env->image_data_640x368_black_.get());
   test_aligned_images_.push_back(g_env->image_data_1280x720_white_.get());
-  TestEncode(1, false);
+  TestEncode(/*num_concurrent_encoders=*/1u, /*is_dma=*/false,
+             /*exif_size=*/0u);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, AlignedSizes) {
   test_aligned_images_.push_back(g_env->image_data_2560x1920_white_.get());
   test_aligned_images_.push_back(g_env->image_data_1280x720_white_.get());
   test_aligned_images_.push_back(g_env->image_data_640x480_black_.get());
-  TestEncode(1, false);
+  TestEncode(/*num_concurrent_encoders=*/1u, /*is_dma=*/false,
+             /*exif_size=*/0u);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, CodedSizeAlignment) {
   test_unaligned_images_.push_back(g_env->image_data_640x360_black_.get());
-  TestEncode(1, false);
+  TestEncode(/*num_concurrent_encoders=*/1u, /*is_dma=*/false,
+             /*exif_size=*/0u);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, SimpleDmaEncode) {
@@ -835,32 +862,49 @@
       test_aligned_images_.push_back(image.get());
     }
   }
-  TestEncode(1, true);
+  TestEncode(/*num_concurrent_encoders=*/1u, /*is_dma=*/true, /*exif_size=*/0u);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, MultipleDmaEncoders) {
   for (auto& image : g_env->image_data_user_) {
     test_aligned_images_.push_back(image.get());
   }
-  TestEncode(3, true);
+  TestEncode(/*num_concurrent_encoders=*/3u, /*is_dma=*/true, /*exif_size=*/0u);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, ResolutionChangeDma) {
   test_aligned_images_.push_back(g_env->image_data_640x368_black_.get());
   test_aligned_images_.push_back(g_env->image_data_1280x720_white_.get());
-  TestEncode(1, true);
+  TestEncode(/*num_concurrent_encoders=*/1u, /*is_dma=*/true, /*exif_size=*/0u);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, AlignedSizesDma) {
   test_aligned_images_.push_back(g_env->image_data_2560x1920_white_.get());
   test_aligned_images_.push_back(g_env->image_data_1280x720_white_.get());
   test_aligned_images_.push_back(g_env->image_data_640x480_black_.get());
-  TestEncode(1, true);
+  TestEncode(/*num_concurrent_encoders=*/1u, /*is_dma=*/true, /*exif_size=*/0u);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, CodedSizeAlignmentDma) {
   test_unaligned_images_.push_back(g_env->image_data_640x360_black_.get());
-  TestEncode(1, true);
+  TestEncode(/*num_concurrent_encoders=*/1u, /*is_dma=*/true, /*exif_size=*/0u);
+}
+
+TEST_F(JpegEncodeAcceleratorTest, ExifSizesDma) {
+  for (size_t i = 0; i < g_env->repeat_; i++) {
+    for (auto& image : g_env->image_data_user_) {
+      test_aligned_images_.push_back(image.get());
+    }
+  }
+  // Intel iHD driver is known to fail when |exif_size| % 1020 == 411, so we
+  // sample more around these numbers.
+  constexpr size_t kTestExifSizes[] = {
+      8000u,  8571u,  9000u,  9591u,  10000u,
+      10609u, 10610u, 10611u, 10612u, 11111u,
+  };
+  for (size_t exif_size : kTestExifSizes) {
+    TestEncode(/*num_concurrent_encoders=*/1u, /*is_dma=*/true, exif_size);
+  }
 }
 
 }  // namespace
diff --git a/components/crash/content/browser/BUILD.gn b/components/crash/content/browser/BUILD.gn
index 7649900..af6c561 100644
--- a/components/crash/content/browser/BUILD.gn
+++ b/components/crash/content/browser/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/chromeos/ui_mode.gni")
+
 if (is_android) {
   import("//build/config/android/config.gni")
 }
@@ -25,9 +27,11 @@
       "crash_handler_host_linux.cc",
       "crash_handler_host_linux.h",
     ]
+
+    deps += [ "//build:chromeos_buildflags" ]
   }
 
-  if (!is_chromeos) {
+  if (!is_chromeos_ash) {
     deps += [ "//third_party/crashpad/crashpad/client" ]
   }
 
diff --git a/components/crash/content/browser/crash_handler_host_linux.h b/components/crash/content/browser/crash_handler_host_linux.h
index b40c795c..7728a1f 100644
--- a/components/crash/content/browser/crash_handler_host_linux.h
+++ b/components/crash/content/browser/crash_handler_host_linux.h
@@ -20,6 +20,7 @@
 #include "base/synchronization/lock.h"
 #include "base/task/current_thread.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
 #if !defined(OS_ANDROID)
 #include "components/crash/core/app/breakpad_linux_impl.h"
@@ -119,7 +120,7 @@
 
 #endif  // !defined(OS_ANDROID)
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 
 namespace crashpad {
 
@@ -178,6 +179,6 @@
 
 }  // namespace crashpad
 
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 #endif  // COMPONENTS_CRASH_CONTENT_BROWSER_CRASH_HANDLER_HOST_LINUX_H_
diff --git a/components/crash/core/app/BUILD.gn b/components/crash/core/app/BUILD.gn
index 4eb4173..6176836 100644
--- a/components/crash/core/app/BUILD.gn
+++ b/components/crash/core/app/BUILD.gn
@@ -70,6 +70,7 @@
   deps = [
     "//base",
     "//build:branding_buildflags",
+    "//build:chromeos_buildflags",
     "//content/public/common:content_descriptors",
   ]
 
@@ -152,6 +153,7 @@
 
     deps = [
       "//base",
+      "//build:chromeos_buildflags",
       "//components/browser_watcher:activity_report",
       "//components/gwp_asan/buildflags",
       "//third_party/crashpad/crashpad/client",
diff --git a/components/crash/core/app/breakpad_linux.cc b/components/crash/core/app/breakpad_linux.cc
index 9d8370e..9e850c0 100644
--- a/components/crash/core/app/breakpad_linux.cc
+++ b/components/crash/core/app/breakpad_linux.cc
@@ -61,9 +61,10 @@
 #include "base/android/path_utils.h"
 #include "base/debug/leak_annotations.h"
 #endif
+#include "build/chromeos_buildflags.h"
 #include "third_party/lss/linux_syscall_support.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "components/crash/core/app/crash_switches.h"
 #endif
 
@@ -93,7 +94,7 @@
 
 namespace {
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // An optional UNIX timestamp passed to us from session_manager. If set,
 // session_manager thinks we are in a possible crash-loop and will log the user
 // out if we crash again before the indicated time. We don't actually do much
@@ -244,7 +245,7 @@
     output[index - 1] = '0' + (i % 10);
 }
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 bool my_isxdigit(char c) {
   return base::IsAsciiDigit(c) || ((c | 0x20) >= 'a' && (c | 0x20) <= 'f');
 }
@@ -291,7 +292,7 @@
 }
 
 // MIME substrings.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 const char g_sep[] = ":";
 #endif
 const char g_rn[] = "\r\n";
@@ -471,7 +472,7 @@
                                             size));
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // This subclass is used on Chromium OS to report crashes in a format easy for
 // the central crash reporting facility to understand.
 // Format is <name>:<data length in decimal>:<data>
@@ -584,7 +585,7 @@
   AddItem(file_data, file_size);
   Flush();
 }
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(OS_ANDROID)
 // Writes the "package" field, which is in the format:
@@ -1141,7 +1142,7 @@
 
 void SetCrashLoopBeforeTime(const std::string& process_type,
                             const base::CommandLine& parsed_command_line) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   std::string crash_loop_before = parsed_command_line.GetSwitchValueASCII(
       crash_reporter::switches::kCrashLoopBefore);
   if (crash_loop_before.empty()) {
@@ -1153,7 +1154,7 @@
                  << crash_loop_before << " to integer";
     g_crash_loop_before_time = 0;
   }
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 // Miscellaneous initialization functions to call after Breakpad has been
@@ -1262,7 +1263,7 @@
                                   const char* exe_buf,
                                   int upload_status_fd,
                                   google_breakpad::PageAllocator* allocator) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // CrOS uses crash_reporter instead of wget to report crashes,
   // it needs to know where the crash dump lives and the pid and uid of the
   // crashing process.
@@ -1311,7 +1312,7 @@
   static const char msg[] = "Cannot upload crash dump: cannot exec "
                             "/sbin/crash_reporter\n";
 
-#else  // defined(OS_CHROMEOS)
+#else   // BUILDFLAG(IS_CHROMEOS_ASH)
 
   // Compress |dumpfile| with gzip.
   const pid_t gzip_child = sys_fork();
@@ -1401,7 +1402,7 @@
   };
   static const char msg[] = "Cannot upload crash dump: cannot exec "
                             "/usr/bin/wget\n";
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   execve(args[0], const_cast<char**>(args), environ);
   WriteLog(msg, sizeof(msg) - 1);
@@ -1452,7 +1453,7 @@
     WriteLog(msg, sizeof(msg) - 1);
     return false;
   }
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // See kSuccessMagic in platform2/crash-reporter/chrome_collector.cc.
   return my_strcmp(buf, "_sys_cr_finished") == 0;
 #else
@@ -1469,7 +1470,7 @@
                          size_t expected_len) {
   WriteNewline();
   if (!IsValidCrashReportId(buf, bytes_read, expected_len)) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     static const char msg[] =
         "System crash_reporter failed to process crash report.";
 #else
@@ -1485,7 +1486,7 @@
     return;
   }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   static const char msg[] = "Crash dump received by crash_reporter\n";
   WriteLog(msg, sizeof(msg) - 1);
 #else
@@ -1516,7 +1517,7 @@
 #endif
 }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 const char* GetCrashingProcessName(const BreakpadInfo& info,
                                    google_breakpad::PageAllocator* allocator) {
   // Symlink to process binary is at /proc/###/exe.
@@ -1544,7 +1545,7 @@
   // Either way too long, or a read error.
   return "chrome-crash-unknown-process";
 }
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Attempts to close all open file descriptors other than stdin, stdout and
 // stderr (0, 1, and 2).
@@ -1579,7 +1580,7 @@
     return;
   }
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Grab the crashing process' name now, when it should still be available.
   // If we try to do this later in our grandchild the crashing process has
   // already terminated.
@@ -1720,7 +1721,7 @@
   //   <dump contents>
   //   \r\n BOUNDARY -- \r\n
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   CrashReporterWriter writer(temp_file_fd);
 #else
   MimeWriter writer(temp_file_fd, mime_boundary);
@@ -2023,7 +2024,7 @@
 #endif
       process_type.empty();
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   SetUploadURL(GetCrashReporterClient()->GetUploadUrl());
 #endif
 
diff --git a/components/crash/core/app/crash_switches.cc b/components/crash/core/app/crash_switches.cc
index d221ff6..dfcb6bd 100644
--- a/components/crash/core/app/crash_switches.cc
+++ b/components/crash/core/app/crash_switches.cc
@@ -4,6 +4,8 @@
 
 #include "components/crash/core/app/crash_switches.h"
 
+#include "build/chromeos_buildflags.h"
+
 namespace crash_reporter {
 namespace switches {
 
@@ -20,7 +22,7 @@
 const char kCrashpadHandlerPid[] = "crashpad-handler-pid";
 #endif
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 // A time_t. Passed by session_manager into the Chrome user session, indicating
 // that if Chrome crashes before the indicated time, session_manager will
 // consider this to be a crash-loop situation and log the user out. Chrome
diff --git a/components/crash/core/app/crash_switches.h b/components/crash/core/app/crash_switches.h
index c989fb1..1855b80 100644
--- a/components/crash/core/app/crash_switches.h
+++ b/components/crash/core/app/crash_switches.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_CRASH_CORE_APP_CRASH_SWITCHES_H_
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
 namespace crash_reporter {
 namespace switches {
@@ -16,7 +17,7 @@
 extern const char kCrashpadHandlerPid[];
 #endif  // defined(OS_LINUX) || defined(OS_CHROMEOS)
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 extern const char kCrashLoopBefore[];
 #endif
 
diff --git a/components/crash/core/app/crashpad.cc b/components/crash/core/app/crashpad.cc
index e1c66df..64ca383 100644
--- a/components/crash/core/app/crashpad.cc
+++ b/components/crash/core/app/crashpad.cc
@@ -27,6 +27,7 @@
 #include "base/system/sys_info.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/crash/core/app/crash_reporter_client.h"
 #include "third_party/crashpad/crashpad/client/annotation.h"
 #include "third_party/crashpad/crashpad/client/annotation_list.h"
@@ -200,7 +201,7 @@
     g_database =
         crashpad::CrashReportDatabase::Initialize(database_path).release();
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
     CrashReporterClient* crash_reporter_client = GetCrashReporterClient();
     SetUploadConsent(crash_reporter_client->GetCollectStatsConsent());
 #endif
@@ -240,7 +241,7 @@
   return *client;
 }
 
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 void SetUploadConsent(bool consent) {
   if (!g_database)
     return;
@@ -272,7 +273,7 @@
 
   return false;
 }
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if !defined(OS_ANDROID)
 void DumpWithoutCrashing() {
diff --git a/components/crash/core/app/crashpad.h b/components/crash/core/app/crashpad.h
index 15c16b3..f661a154 100644
--- a/components/crash/core/app/crashpad.h
+++ b/components/crash/core/app/crashpad.h
@@ -13,6 +13,7 @@
 
 #include "base/files/file_path.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
 #if defined(OS_APPLE)
 #include "base/mac/scoped_mach_port.h"
@@ -107,7 +108,7 @@
 
 // ChromeOS has its own, OS-level consent system; Chrome does not maintain a
 // separate Upload Consent on ChromeOS.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Enables or disables crash report upload, taking the given consent to upload
 // into account. Consent may be ignored, uploads may not be enabled even with
@@ -122,7 +123,7 @@
 // Determines whether uploads are enabled or disabled. This information is only
 // available in the browser process.
 bool GetUploadsEnabled();
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 enum class ReportUploadState {
   NotUploaded,
diff --git a/components/crash/core/app/crashpad_linux.cc b/components/crash/core/app/crashpad_linux.cc
index 2c9f505..8e020bf 100644
--- a/components/crash/core/app/crashpad_linux.cc
+++ b/components/crash/core/app/crashpad_linux.cc
@@ -16,6 +16,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "build/branding_buildflags.h"
+#include "build/chromeos_buildflags.h"
 #include "components/crash/core/app/crash_reporter_client.h"
 #include "components/crash/core/app/crash_switches.h"
 #include "content/public/common/content_descriptors.h"
@@ -89,7 +90,7 @@
   DCHECK(exe_path.empty());
 
   crashpad::CrashpadClient client;
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   std::string crash_loop_before =
       base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
           switches::kCrashLoopBefore);
@@ -120,7 +121,7 @@
     // to ChromeOS's /sbin/crash_reporter which in turn passes the dump to
     // crash_sender which handles the upload.
     std::string url;
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
     url = crash_reporter_client->GetUploadUrl();
 #else
     url = std::string();
@@ -156,7 +157,7 @@
     // contain these annotations.
     arguments.push_back("--monitor-self-annotation=ptype=crashpad-handler");
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     arguments.push_back("--use-cros-crash-reporter");
 
     if (crash_reporter_client->IsRunningUnattended()) {
diff --git a/components/enterprise/BUILD.gn b/components/enterprise/BUILD.gn
index 430910e..9acb9dc 100644
--- a/components/enterprise/BUILD.gn
+++ b/components/enterprise/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/chromeos/ui_mode.gni")
+
 static_library("enterprise") {
   sources = [
     "browser/controller/browser_dm_token_storage.cc",
@@ -16,6 +18,7 @@
 
   deps = [
     "//base",
+    "//build:chromeos_buildflags",
     "//components/policy/core/common",
     "//components/prefs",
     "//services/network/public/cpp",
@@ -52,7 +55,7 @@
     ]
   }
 
-  if (!is_android && !is_chromeos) {
+  if (!is_android && !is_chromeos_ash) {
     sources += [
       "browser/controller/chrome_browser_cloud_management_controller.cc",
       "browser/controller/chrome_browser_cloud_management_controller.h",
@@ -89,6 +92,9 @@
   if (!is_android) {
     sources += [ "browser/reporting/report_uploader_unittest.cc" ]
 
-    deps += [ "//components/policy/core/common:test_support" ]
+    deps += [
+      "//build:chromeos_buildflags",
+      "//components/policy/core/common:test_support",
+    ]
   }
 }
diff --git a/components/enterprise/browser/enterprise_switches.cc b/components/enterprise/browser/enterprise_switches.cc
index 17046ca..d7787a1 100644
--- a/components/enterprise/browser/enterprise_switches.cc
+++ b/components/enterprise/browser/enterprise_switches.cc
@@ -5,10 +5,11 @@
 #include "components/enterprise/browser/enterprise_switches.h"
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
 namespace switches {
 
-#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
 // Enables the Chrome Browser Cloud Management integration on Chromium builds.
 // CBCM is always enabled in branded builds.
 const char kEnableChromeBrowserCloudManagement[] =
diff --git a/components/enterprise/browser/enterprise_switches.h b/components/enterprise/browser/enterprise_switches.h
index 2a9b434..902e1f2 100644
--- a/components/enterprise/browser/enterprise_switches.h
+++ b/components/enterprise/browser/enterprise_switches.h
@@ -9,10 +9,11 @@
 #define COMPONENTS_ENTERPRISE_BROWSER_ENTERPRISE_SWITCHES_H_
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
 namespace switches {
 
-#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
 extern const char kEnableChromeBrowserCloudManagement[];
 #endif
 
diff --git a/components/enterprise/browser/reporting/browser_report_generator.cc b/components/enterprise/browser/reporting/browser_report_generator.cc
index e16dda6..67a02cc 100644
--- a/components/enterprise/browser/reporting/browser_report_generator.cc
+++ b/components/enterprise/browser/reporting/browser_report_generator.cc
@@ -9,6 +9,7 @@
 
 #include "base/version.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
 #include "components/policy/core/common/cloud/cloud_policy_util.h"
 #include "components/version_info/version_info.h"
@@ -40,7 +41,7 @@
 }
 
 void BrowserReportGenerator::GenerateBasicInfo(em::BrowserReport* report) {
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   report->set_browser_version(version_info::GetVersionNumber());
   report->set_channel(policy::ConvertToProtoChannel(delegate_->GetChannel()));
   delegate_->GenerateBuildStateInfo(report);
diff --git a/components/enterprise/browser/reporting/policy_info.cc b/components/enterprise/browser/reporting/policy_info.cc
index 580b525a..5727814 100644
--- a/components/enterprise/browser/reporting/policy_info.cc
+++ b/components/enterprise/browser/reporting/policy_info.cc
@@ -8,6 +8,7 @@
 
 #include "base/json/json_writer.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/policy/core/browser/policy_conversions.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
@@ -135,7 +136,7 @@
 void AppendMachineLevelUserCloudPolicyFetchTimestamp(
     em::ChromeUserProfileInfo* profile_info,
     policy::MachineLevelUserCloudPolicyManager* manager) {
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   if (!manager || !manager->IsClientRegistered())
     return;
   auto* timestamp = profile_info->add_policy_fetched_timestamps();
@@ -143,7 +144,7 @@
       policy::dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
   timestamp->set_timestamp(
       manager->core()->client()->last_policy_timestamp().ToJavaTime());
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 }  // namespace enterprise_reporting
diff --git a/components/enterprise/browser/reporting/profile_report_generator.cc b/components/enterprise/browser/reporting/profile_report_generator.cc
index 0e641ea..4488f9a 100644
--- a/components/enterprise/browser/reporting/profile_report_generator.cc
+++ b/components/enterprise/browser/reporting/profile_report_generator.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/files/file_path.h"
+#include "build/chromeos_buildflags.h"
 #include "components/enterprise/browser/reporting/policy_info.h"
 #include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
 #include "components/policy/core/browser/policy_conversions.h"
@@ -43,11 +44,11 @@
   if (report_type == ReportType::kExtensionRequest) {
     delegate_->GetExtensionRequest(report_.get());
     report_->set_is_detail_available(true);
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     // Extension request is aggregated at the user level on CrOS.
     report_->set_name(name);
     delegate_->GetSigninUserInfo(report_.get());
-#endif  // defined(OS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   } else {
     report_->set_name(name);
     report_->set_is_detail_available(true);
@@ -83,10 +84,10 @@
 }
 
 void ProfileReportGenerator::GetPolicyFetchTimestampInfo() {
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   AppendMachineLevelUserCloudPolicyFetchTimestamp(
       report_.get(), delegate_->GetCloudPolicyManager());
-#endif  // !defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 }  // namespace enterprise_reporting
diff --git a/components/enterprise/browser/reporting/report_generator.cc b/components/enterprise/browser/reporting/report_generator.cc
index a881a05..ab701ab 100644
--- a/components/enterprise/browser/reporting/report_generator.cc
+++ b/components/enterprise/browser/reporting/report_generator.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/enterprise/browser/reporting/browser_report_generator.h"
 #include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
 #include "components/policy/core/common/cloud/cloud_policy_util.h"
@@ -46,7 +47,7 @@
     basic_request->add_partial_report_types(
         em::PartialReportType::EXTENSION_REQUEST);
   } else {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     delegate_->SetAndroidAppInfos(basic_request.get());
 #else
     basic_request->set_computer_name(this->GetMachineName());
diff --git a/components/enterprise/browser/reporting/report_request_definition.h b/components/enterprise/browser/reporting/report_request_definition.h
index 5fdcc22..0775104 100644
--- a/components/enterprise/browser/reporting/report_request_definition.h
+++ b/components/enterprise/browser/reporting/report_request_definition.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_ENTERPRISE_BROWSER_REPORTING_REPORT_REQUEST_DEFINITION_H_
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 
 namespace enterprise_reporting {
@@ -16,7 +17,7 @@
 // upload usage data to DM Server. By the reference to this macro, most classes
 // in enterprise_reporting namespace can share the same logic for various
 // operation systems.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 using ReportRequest = enterprise_management::ChromeOsUserReportRequest;
 #else
 using ReportRequest = enterprise_management::ChromeDesktopReportRequest;
diff --git a/components/enterprise/browser/reporting/report_request_queue_generator.cc b/components/enterprise/browser/reporting/report_request_queue_generator.cc
index a719a505..3614627 100644
--- a/components/enterprise/browser/reporting/report_request_queue_generator.cc
+++ b/components/enterprise/browser/reporting/report_request_queue_generator.cc
@@ -8,6 +8,7 @@
 #include "base/callback.h"
 #include "base/files/file_path.h"
 #include "base/metrics/histogram_functions.h"
+#include "build/chromeos_buildflags.h"
 
 namespace enterprise_reporting {
 
@@ -35,7 +36,7 @@
     ReportingDelegateFactory* delegate_factory)
     : maximum_report_size_(kMaximumReportSize),
       profile_report_generator_(delegate_factory) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // For Chrome OS, policy information needn't be uploaded to DM server.
   profile_report_generator_.set_policies_enabled(false);
 #endif
diff --git a/components/enterprise/browser/reporting/report_scheduler.cc b/components/enterprise/browser/reporting/report_scheduler.cc
index 63dbdd9..214b27e 100644
--- a/components/enterprise/browser/reporting/report_scheduler.cc
+++ b/components/enterprise/browser/reporting/report_scheduler.cc
@@ -13,6 +13,7 @@
 #include "base/task/post_task.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/enterprise/browser/controller/browser_dm_token_storage.h"
 #include "components/enterprise/browser/reporting/common_pref_names.h"
 #include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
@@ -124,7 +125,7 @@
   // For Chrome OS, it needn't register the cloud policy client here. The
   // |dm_token| and |client_id| should have already existed after the client is
   // initialized, and will keep valid during whole life-cycle.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   if (!SetupBrowserPolicyClientRegistration()) {
     Stop();
     return;
diff --git a/components/enterprise/browser/reporting/report_uploader.cc b/components/enterprise/browser/reporting/report_uploader.cc
index 85fef3d..1f0e0fb 100644
--- a/components/enterprise/browser/reporting/report_uploader.cc
+++ b/components/enterprise/browser/reporting/report_uploader.cc
@@ -8,6 +8,7 @@
 
 #include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
+#include "build/chromeos_buildflags.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
 
 namespace em = enterprise_management;
@@ -51,7 +52,7 @@
   auto callback = base::BindRepeating(&ReportUploader::OnRequestFinished,
                                       weak_ptr_factory_.GetWeakPtr());
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   client_->UploadChromeOsUserReport(std::move(request), std::move(callback));
 #else
   client_->UploadChromeDesktopReport(std::move(request), std::move(callback));
diff --git a/components/enterprise/browser/reporting/report_uploader_unittest.cc b/components/enterprise/browser/reporting/report_uploader_unittest.cc
index 2c55f149..c144a10 100644
--- a/components/enterprise/browser/reporting/report_uploader_unittest.cc
+++ b/components/enterprise/browser/reporting/report_uploader_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -33,7 +34,7 @@
   // Different CloudPolicyClient proxy function will be used in test cases based
   // on the current operation system. They share same retry and error handling
   // behaviors provided by ReportUploader.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #define UploadReportProxy UploadChromeOsUserReportProxy
 #else
 #define UploadReportProxy UploadChromeDesktopReportProxy
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java
index 3e69919e..ba8390b2 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java
@@ -6,6 +6,7 @@
 
 import android.app.Activity;
 
+import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
 import org.chromium.components.navigation_interception.NavigationParams;
 import org.chromium.content_public.browser.WebContents;
 
@@ -54,4 +55,10 @@
     /* Invoked when a navigation has begun in the InterceptNavigationDelegateImpl instance
      * associated with this instance. */
     void onNavigationStarted(NavigationParams params);
+
+    /* Invoked when the InterceptNavigationDelegateImpl instance
+     * associated with this instance has reached a decision for the navigation specified by
+     * |params|. |overrideUrlLoadingResult| specifies the decision. */
+    void onDecisionReachedForNavigation(
+            NavigationParams params, OverrideUrlLoadingResult overrideUrlLoadingResult);
 }
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
index 817f638..b36ce00 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
@@ -10,6 +10,7 @@
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.base.task.PostTask;
+import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
 import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResultType;
 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
 import org.chromium.components.navigation_interception.NavigationParams;
@@ -131,11 +132,12 @@
         ExternalNavigationParams params =
                 buildExternalNavigationParams(navigationParams, redirectHandler, shouldCloseTab)
                         .build();
-        @OverrideUrlLoadingResultType
-        int result = mExternalNavHandler.shouldOverrideUrlLoading(params).getResultType();
-        mLastOverrideUrlLoadingResultType = result;
+        OverrideUrlLoadingResult result = mExternalNavHandler.shouldOverrideUrlLoading(params);
+        mLastOverrideUrlLoadingResultType = result.getResultType();
 
-        switch (result) {
+        mClient.onDecisionReachedForNavigation(navigationParams, result);
+
+        switch (mLastOverrideUrlLoadingResultType) {
             case OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT:
                 assert mExternalNavHandler.canExternalAppHandleUrl(url);
                 if (navigationParams.isMainFrame) {
diff --git a/components/metrics/BUILD.gn b/components/metrics/BUILD.gn
index cbc77b4..d5c9bd8e 100644
--- a/components/metrics/BUILD.gn
+++ b/components/metrics/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/chromeos/ui_mode.gni")
 import("//testing/test.gni")
 
 if (is_android) {
@@ -127,7 +128,7 @@
     "//url",
   ]
 
-  if (is_chromeos) {
+  if (is_chromeos_ash) {
     deps += [ ":serialization" ]
   }
 
@@ -477,7 +478,7 @@
     deps += [ ":serialization" ]
   }
 
-  if (is_chromeos) {
+  if (is_chromeos_ash) {
     deps += [
       "//chromeos/dbus:test_support",
       "//chromeos/network:test_support",
diff --git a/components/metrics/metrics_log.cc b/components/metrics/metrics_log.cc
index ed6b3db..245eb18 100644
--- a/components/metrics/metrics_log.cc
+++ b/components/metrics/metrics_log.cc
@@ -210,11 +210,11 @@
 #endif
 
   metrics::SystemProfileProto::OS* os = system_profile->mutable_os();
-#if BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
   // The Lacros browser runs on Chrome OS, but reports a special OS name to
   // differentiate itself from the built-in ash browser + window manager binary.
   os->set_name("Lacros");
-#elif defined(OS_CHROMEOS)
+#elif BUILDFLAG(IS_CHROMEOS_ASH)
   os->set_name("CrOS");
 #else
   os->set_name(base::SysInfo::OperatingSystemName());
@@ -223,9 +223,9 @@
 
 // On ChromeOS, KernelVersion refers to the Linux kernel version and
 // OperatingSystemVersion refers to the ChromeOS release version.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   os->set_kernel_version(base::SysInfo::KernelVersion());
-#elif defined(OS_LINUX)
+#elif defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   // Linux operating system version is copied over into kernel version to be
   // consistent.
   os->set_kernel_version(base::SysInfo::OperatingSystemVersion());
diff --git a/components/metrics/metrics_log_unittest.cc b/components/metrics/metrics_log_unittest.cc
index ac659478..9a3d1145 100644
--- a/components/metrics/metrics_log_unittest.cc
+++ b/components/metrics/metrics_log_unittest.cc
@@ -77,7 +77,7 @@
 
 // Returns the expected hardware class for a metrics log.
 std::string GetExpectedHardwareClass() {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Currently, we are relying on base/ implementation for functionality on our
   // side which can be fragile if in the future someone decides to change that.
   // This replicates the logic to get the hardware class for ChromeOS and this
@@ -187,19 +187,19 @@
   hardware->set_dll_base(reinterpret_cast<uint64_t>(CURRENT_MODULE()));
 #endif
 
-#if BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
   system_profile->mutable_os()->set_name("Lacros");
-#elif defined(OS_CHROMEOS)
+#elif BUILDFLAG(IS_CHROMEOS_ASH)
   system_profile->mutable_os()->set_name("CrOS");
 #else
   system_profile->mutable_os()->set_name(base::SysInfo::OperatingSystemName());
 #endif
   system_profile->mutable_os()->set_version(
       base::SysInfo::OperatingSystemVersion());
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   system_profile->mutable_os()->set_kernel_version(
       base::SysInfo::KernelVersion());
-#elif defined(OS_LINUX)
+#elif defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   system_profile->mutable_os()->set_kernel_version(
       base::SysInfo::OperatingSystemVersion());
 #elif defined(OS_ANDROID)
diff --git a/components/metrics/net/network_metrics_provider_unittest.cc b/components/metrics/net/network_metrics_provider_unittest.cc
index a25ed76f..e462bee 100644
--- a/components/metrics/net/network_metrics_provider_unittest.cc
+++ b/components/metrics/net/network_metrics_provider_unittest.cc
@@ -12,14 +12,15 @@
 #include "base/run_loop.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "services/network/test/test_network_connection_tracker.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/metrics_proto/system_profile.pb.h"
 
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/network/network_handler.h"
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(OS_IOS)
 #include "ios/web/public/test/web_task_environment.h"
@@ -39,17 +40,17 @@
   ~NetworkMetricsProviderTest() override {}
 
   void SetUp() override {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     chromeos::DBusThreadManager::Initialize();
     chromeos::NetworkHandler::Initialize();
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   }
 
   void TearDown() override {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
     chromeos::NetworkHandler::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
-#endif  // OS_CHROMEOS
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   }
 
  private:
diff --git a/components/metrics/persistent_histograms.cc b/components/metrics/persistent_histograms.cc
index 7d2cc36..7ea2a0f 100644
--- a/components/metrics/persistent_histograms.cc
+++ b/components/metrics/persistent_histograms.cc
@@ -18,6 +18,7 @@
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/metrics/persistent_system_profile.h"
 #include "components/variations/variations_associated_data.h"
 
@@ -130,7 +131,9 @@
   static const char kMappedFile[] = "MappedFile";
   static const char kLocalMemory[] = "LocalMemory";
 
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   // Linux kernel 4.4.0.* shows a huge number of SIGBUS crashes with persistent
   // histograms enabled using a mapped file.  Change this to use local memory.
   // https://bugs.chromium.org/p/chromium/issues/detail?id=753741
diff --git a/components/metrics/stability_metrics_helper.cc b/components/metrics/stability_metrics_helper.cc
index 5ea06444..0c39ac9 100644
--- a/components/metrics/stability_metrics_helper.cc
+++ b/components/metrics/stability_metrics_helper.cc
@@ -27,7 +27,7 @@
 #include <windows.h>  // Needed for STATUS_* codes
 #endif
 
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "components/metrics/system_memory_stats_recorder.h"
 #endif
 
@@ -250,7 +250,7 @@
       // TODO(wfh): Check if this should be a Kill or a Crash on Android.
       break;
 #endif
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
     case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
       RecordChildKills(histogram_type);
       base::UmaHistogramExactLinear("BrowserRenderProcessHost.ChildKills.OOM",
diff --git a/components/metrics/system_memory_stats_recorder_linux.cc b/components/metrics/system_memory_stats_recorder_linux.cc
index 1d46bc70..b15a05e 100644
--- a/components/metrics/system_memory_stats_recorder_linux.cc
+++ b/components/metrics/system_memory_stats_recorder_linux.cc
@@ -31,7 +31,7 @@
   UMA_HISTOGRAM_LINEAR(name, sample, 2500, 50)
 
 void RecordMemoryStats(RecordMemoryStatsType type) {
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
   // Record graphics GEM object size in a histogram with 50 MB buckets.
   int mem_gpu_mb = 0;
   bool mem_gpu_result = false;
@@ -58,7 +58,7 @@
     // On Intel, graphics objects are in anonymous pages, but on ARM they are
     // not. For a total "allocated count" add in graphics pages on ARM.
     int mem_allocated_mb = (memory.active_anon + memory.inactive_anon) / 1024;
-#if (defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)) && \
+#if (BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)) && \
     defined(ARCH_CPU_ARM_FAMILY)
     mem_allocated_mb += mem_gpu_mb;
 #endif
@@ -83,7 +83,7 @@
       }
     }
 
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
     // Record shared memory (used by renderer/GPU buffers).
     int mem_shmem_mb = memory.shmem / 1024;
     switch (type) {
diff --git a/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/NavigationParams.java b/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/NavigationParams.java
index e02aa54..14dac2c 100644
--- a/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/NavigationParams.java
+++ b/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/NavigationParams.java
@@ -21,6 +21,12 @@
     /** The referrer URL for the navigation. */
     public final String referrer;
 
+    /**
+     * The ID of the C++-side NavigationHandle that this instance corresponds to, or 0 if
+     * this instance was not constructed from a NavigationHandle.
+     */
+    public final long navigationId;
+
     /** True if the the navigation method is "POST". */
     public final boolean isPost;
 
@@ -51,12 +57,13 @@
     /** Initiator origin of the request, could be null. */
     public final Origin initiatorOrigin;
 
-    public NavigationParams(String url, String referrer, boolean isPost, boolean hasUserGesture,
-            int pageTransitionType, boolean isRedirect, boolean isExternalProtocol,
-            boolean isMainFrame, boolean isRendererInitiated, boolean hasUserGestureCarryover,
-            @Nullable Origin initiatorOrigin) {
+    public NavigationParams(String url, String referrer, long navigationId, boolean isPost,
+            boolean hasUserGesture, int pageTransitionType, boolean isRedirect,
+            boolean isExternalProtocol, boolean isMainFrame, boolean isRendererInitiated,
+            boolean hasUserGestureCarryover, @Nullable Origin initiatorOrigin) {
         this.url = url;
         this.referrer = TextUtils.isEmpty(referrer) ? null : referrer;
+        this.navigationId = navigationId;
         this.isPost = isPost;
         this.hasUserGesture = hasUserGesture;
         this.pageTransitionType = pageTransitionType;
@@ -69,12 +76,12 @@
     }
 
     @CalledByNative
-    public static NavigationParams create(String url, String referrer, boolean isPost,
-            boolean hasUserGesture, int pageTransitionType, boolean isRedirect,
+    public static NavigationParams create(String url, String referrer, long navigationId,
+            boolean isPost, boolean hasUserGesture, int pageTransitionType, boolean isRedirect,
             boolean isExternalProtocol, boolean isMainFrame, boolean isRendererInitiated,
             boolean hasUserGestureCarryover, @Nullable Origin initiatorOrigin) {
-        return new NavigationParams(url, referrer, isPost, hasUserGesture, pageTransitionType,
-                isRedirect, isExternalProtocol, isMainFrame, isRendererInitiated,
-                hasUserGestureCarryover, initiatorOrigin);
+        return new NavigationParams(url, referrer, navigationId, isPost, hasUserGesture,
+                pageTransitionType, isRedirect, isExternalProtocol, isMainFrame,
+                isRendererInitiated, hasUserGestureCarryover, initiatorOrigin);
     }
 }
diff --git a/components/navigation_interception/intercept_navigation_throttle.cc b/components/navigation_interception/intercept_navigation_throttle.cc
index 3953e16..44cbcab4 100644
--- a/components/navigation_interception/intercept_navigation_throttle.cc
+++ b/components/navigation_interception/intercept_navigation_throttle.cc
@@ -117,6 +117,7 @@
     bool is_redirect) const {
   return NavigationParams(navigation_handle()->GetURL(),
                           content::Referrer(navigation_handle()->GetReferrer()),
+                          navigation_handle()->GetNavigationId(),
                           navigation_handle()->HasUserGesture(),
                           navigation_handle()->IsPost(),
                           navigation_handle()->GetPageTransition(), is_redirect,
diff --git a/components/navigation_interception/navigation_params.cc b/components/navigation_interception/navigation_params.cc
index de7a3f2..fb3d766 100644
--- a/components/navigation_interception/navigation_params.cc
+++ b/components/navigation_interception/navigation_params.cc
@@ -9,6 +9,7 @@
 NavigationParams::NavigationParams(
     const GURL& url,
     const content::Referrer& referrer,
+    int64_t navigation_id,
     bool has_user_gesture,
     bool is_post,
     ui::PageTransition transition_type,
@@ -20,6 +21,7 @@
     const base::Optional<url::Origin>& initiator_origin)
     : url_(url),
       referrer_(referrer),
+      navigation_id_(navigation_id),
       has_user_gesture_(has_user_gesture),
       is_post_(is_post),
       transition_type_(transition_type),
diff --git a/components/navigation_interception/navigation_params.h b/components/navigation_interception/navigation_params.h
index bbd5fa0..bd6ae63d 100644
--- a/components/navigation_interception/navigation_params.h
+++ b/components/navigation_interception/navigation_params.h
@@ -15,6 +15,7 @@
  public:
   NavigationParams(const GURL& url,
                    const content::Referrer& referrer,
+                   int64_t navigation_id,
                    bool has_user_gesture,
                    bool is_post,
                    ui::PageTransition page_transition_type,
@@ -31,6 +32,10 @@
   const GURL& url() const { return url_; }
   GURL& url() { return url_; }
   const content::Referrer& referrer() const { return referrer_; }
+
+  // The ID of the NavigationHandle that this instance corresponds to, or 0 if
+  // this instance was not constructed from a NavigationHandle.
+  int64_t navigation_id() const { return navigation_id_; }
   bool has_user_gesture() const { return has_user_gesture_; }
   bool is_post() const { return is_post_; }
   ui::PageTransition transition_type() const { return transition_type_; }
@@ -47,6 +52,7 @@
 
   GURL url_;
   content::Referrer referrer_;
+  int64_t navigation_id_;
   bool has_user_gesture_;
   bool is_post_;
   ui::PageTransition transition_type_;
diff --git a/components/navigation_interception/navigation_params_android.cc b/components/navigation_interception/navigation_params_android.cc
index 2563534..b84d3e9 100644
--- a/components/navigation_interception/navigation_params_android.cc
+++ b/components/navigation_interception/navigation_params_android.cc
@@ -26,10 +26,11 @@
       ConvertUTF8ToJavaString(env, params.referrer().url.spec());
 
   return Java_NavigationParams_create(
-      env, jstring_url, jstring_referrer, params.is_post(),
-      params.has_user_gesture(), params.transition_type(), params.is_redirect(),
-      params.is_external_protocol(), params.is_main_frame(),
-      params.is_renderer_initiated(), has_user_gesture_carryover,
+      env, jstring_url, jstring_referrer, params.navigation_id(),
+      params.is_post(), params.has_user_gesture(), params.transition_type(),
+      params.is_redirect(), params.is_external_protocol(),
+      params.is_main_frame(), params.is_renderer_initiated(),
+      has_user_gesture_carryover,
       params.initiator_origin() ? params.initiator_origin()->CreateJavaObject()
                                 : nullptr);
 }
diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java
index b0cb261..562a65d 100644
--- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java
+++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java
@@ -488,6 +488,10 @@
             mPendingRunAfterDismissTask.run();
             mPendingRunAfterDismissTask = null;
         }
+        if (mSubpageController != null) {
+            mSubpageController.onSubpageRemoved();
+            mSubpageController = null;
+        }
         mWebContentsObserver.destroy();
         mWebContentsObserver = null;
         PageInfoControllerJni.get().destroy(mNativePageInfoController, PageInfoController.this);
@@ -612,6 +616,9 @@
     public void exitSubpage() {
         if (mSubpageController == null) return;
         mContainer.showPage(mView, null, () -> {
+            // The PageInfo dialog can get dismissed during the page change animation.
+            // In that case mSubpageController will already be null.
+            if (mSubpageController == null) return;
             mSubpageController.onSubpageRemoved();
             mSubpageController = null;
         });
diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesController.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesController.java
index 5b170292..bd2aa59 100644
--- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesController.java
+++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesController.java
@@ -122,10 +122,11 @@
     public void onSubpageRemoved() {
         assert mSubPage != null;
         AppCompatActivity host = (AppCompatActivity) mRowView.getContext();
-        PageInfoCookiesPreference subBage = mSubPage;
+        PageInfoCookiesPreference subPage = mSubPage;
         mSubPage = null;
-        if (host.isFinishing()) return;
-        host.getSupportFragmentManager().beginTransaction().remove(subBage).commitNow();
+        // If the activity is getting destroyed or saved, it is not allowed to modify fragments.
+        if (host.isFinishing() || host.getSupportFragmentManager().isStateSaved()) return;
+        host.getSupportFragmentManager().beginTransaction().remove(subPage).commitNow();
     }
 
     @Override
diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesPreference.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesPreference.java
index bfa3f84..0dc23a4 100644
--- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesPreference.java
+++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesPreference.java
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 package org.chromium.components.page_info;
 
+import android.app.Dialog;
 import android.os.Bundle;
 import android.text.format.Formatter;
 
@@ -30,6 +31,7 @@
     private ChromeSwitchPreference mCookieSwitch;
     private ChromeImageViewPreference mCookieInUse;
     private Runnable mOnClearCallback;
+    private Dialog mConfirmationDialog;
 
     /**  Parameters to configure the cookie controls view. */
     public static class PageInfoCookiesViewParams {
@@ -48,6 +50,14 @@
         mCookieInUse = findPreference(COOKIE_IN_USE_PREFERENCE);
     }
 
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        if (mConfirmationDialog != null) {
+            mConfirmationDialog.dismiss();
+        }
+    }
+
     public void setParams(PageInfoCookiesViewParams params) {
         Preference cookieSummary = findPreference(COOKIE_SUMMARY_PREFERENCE);
         NoUnderlineClickableSpan linkSpan = new NoUnderlineClickableSpan(
@@ -81,13 +91,15 @@
     }
 
     private void showClearCookiesConfirmation() {
-        new AlertDialog.Builder(getActivity(), R.style.Theme_Chromium_AlertDialog)
-                .setTitle(R.string.page_info_cookies_clear)
-                .setMessage(R.string.page_info_cookies_clear_confirmation)
-                .setPositiveButton(R.string.page_info_cookies_clear_confirmation_button,
-                        (dialog, which) -> mOnClearCallback.run())
-                .setNegativeButton(R.string.cancel, null)
-                .show();
+        mConfirmationDialog =
+                new AlertDialog.Builder(getActivity(), R.style.Theme_Chromium_AlertDialog)
+                        .setTitle(R.string.page_info_cookies_clear)
+                        .setMessage(R.string.page_info_cookies_clear_confirmation)
+                        .setPositiveButton(R.string.page_info_cookies_clear_confirmation_button,
+                                (dialog, which) -> mOnClearCallback.run())
+                        .setNegativeButton(
+                                R.string.cancel, (dialog, which) -> mConfirmationDialog = null)
+                        .show();
     }
 
     public void setCookieBlockingStatus(@CookieControlsStatus int status, boolean isEnforced) {
diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoPermissionsController.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoPermissionsController.java
index fb30cfa..8b8f166 100644
--- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoPermissionsController.java
+++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoPermissionsController.java
@@ -27,7 +27,7 @@
     private PageInfoControllerDelegate mDelegate;
     private String mTitle;
     private String mPageUrl;
-    private SingleWebsiteSettings mSubpageFragment;
+    private SingleWebsiteSettings mSubPage;
 
     public PageInfoPermissionsController(PageInfoMainController mainController,
             PageInfoRowView view, PageInfoControllerDelegate delegate, String pageUrl) {
@@ -49,26 +49,27 @@
 
     @Override
     public View createViewForSubpage(ViewGroup parent) {
-        assert mSubpageFragment == null;
+        assert mSubPage == null;
         Bundle fragmentArgs = SingleWebsiteSettings.createFragmentArgsForSite(mPageUrl);
-        mSubpageFragment = (SingleWebsiteSettings) Fragment.instantiate(
+        mSubPage = (SingleWebsiteSettings) Fragment.instantiate(
                 mRowView.getContext(), SingleWebsiteSettings.class.getName(), fragmentArgs);
-        mSubpageFragment.setSiteSettingsClient(mDelegate.getSiteSettingsClient());
-        mSubpageFragment.setHideNonPermissionPreferences(true);
-        mSubpageFragment.setWebsiteSettingsObserver(this);
+        mSubPage.setSiteSettingsClient(mDelegate.getSiteSettingsClient());
+        mSubPage.setHideNonPermissionPreferences(true);
+        mSubPage.setWebsiteSettingsObserver(this);
         AppCompatActivity host = (AppCompatActivity) mRowView.getContext();
-        host.getSupportFragmentManager().beginTransaction().add(mSubpageFragment, null).commitNow();
-        return mSubpageFragment.requireView();
+        host.getSupportFragmentManager().beginTransaction().add(mSubPage, null).commitNow();
+        return mSubPage.requireView();
     }
 
     @Override
     public void onSubpageRemoved() {
-        assert mSubpageFragment != null;
+        assert mSubPage != null;
         AppCompatActivity host = (AppCompatActivity) mRowView.getContext();
-        SingleWebsiteSettings fragment = mSubpageFragment;
-        mSubpageFragment = null;
-        if (host.isFinishing()) return;
-        host.getSupportFragmentManager().beginTransaction().remove(fragment).commitNow();
+        SingleWebsiteSettings subPage = mSubPage;
+        mSubPage = null;
+        // If the activity is getting destroyed or saved, it is not allowed to modify fragments.
+        if (host.isFinishing() || host.getSupportFragmentManager().isStateSaved()) return;
+        host.getSupportFragmentManager().beginTransaction().remove(subPage).commitNow();
     }
 
     public void setPermissions(PageInfoView.PermissionParams params) {
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/BrowserPaymentRequest.java b/components/payments/content/android/java/src/org/chromium/components/payments/BrowserPaymentRequest.java
index 28ed87a0..565f679 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/BrowserPaymentRequest.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/BrowserPaymentRequest.java
@@ -207,11 +207,6 @@
         return null;
     }
 
-    /** @return Whether any payment UI is being shown. */
-    default boolean isShowingUi() {
-        return false;
-    }
-
     /**
      * Continues the unfinished part of show() that was blocked for the payment details that was
      * pending to be updated.
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
index a6a8ab34a..14969ea 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
@@ -97,7 +97,7 @@
     private PaymentRequestSpec mSpec;
     private boolean mHasClosed;
     private boolean mIsFinishedQueryingPaymentApps;
-    private boolean mIsCurrentPaymentRequestShowing;
+    private boolean mIsShowCalled;
     private boolean mIsShowWaitingForUpdatedDetails;
 
     /** If not empty, use this error message for rejecting PaymentRequest.show(). */
@@ -282,6 +282,11 @@
                 String appLocale) {
             return new PaymentRequestSpec(options, details, methodData, appLocale);
         }
+
+        /** @return The PaymentAppService that is used to create payment app for this service. */
+        default PaymentAppService getPaymentAppService() {
+            return PaymentAppService.getInstance();
+        }
     }
 
     /**
@@ -376,15 +381,15 @@
 
         if (renderFrameHost.getLastCommittedOrigin() == null
                 || renderFrameHost.getLastCommittedURL() == null) {
-            abortBeforeInstantiation(/*client=*/null, /*journeyLogger=*/null, ErrorStrings.NO_FRAME,
-                    AbortReason.INVALID_DATA_FROM_RENDERER);
+            abortForInvalidDataFromRenderer(
+                    /*client=*/null, /*journeyLogger=*/null, ErrorStrings.NO_FRAME);
             return null;
         }
 
         WebContents webContents = delegate.getLiveWebContents(renderFrameHost);
         if (webContents == null || webContents.isDestroyed()) {
-            abortBeforeInstantiation(/*client=*/null, /*journeyLogger=*/null,
-                    ErrorStrings.NO_WEB_CONTENTS, AbortReason.INVALID_DATA_FROM_RENDERER);
+            abortForInvalidDataFromRenderer(
+                    /*client=*/null, /*journeyLogger=*/null, ErrorStrings.NO_WEB_CONTENTS);
             return null;
         }
 
@@ -392,45 +397,43 @@
         JourneyLogger journeyLogger = delegate.createJourneyLogger(isOffTheRecord, webContents);
 
         if (client == null) {
-            abortBeforeInstantiation(/*client=*/null, journeyLogger, ErrorStrings.INVALID_STATE,
-                    AbortReason.INVALID_DATA_FROM_RENDERER);
+            abortForInvalidDataFromRenderer(
+                    /*client=*/null, journeyLogger, ErrorStrings.INVALID_STATE);
             return null;
         }
 
         if (!delegate.isOriginSecure(webContents.getLastCommittedUrl())) {
-            abortBeforeInstantiation(client, journeyLogger, ErrorStrings.NOT_IN_A_SECURE_ORIGIN,
-                    AbortReason.INVALID_DATA_FROM_RENDERER);
+            abortForInvalidDataFromRenderer(
+                    client, journeyLogger, ErrorStrings.NOT_IN_A_SECURE_ORIGIN);
             return null;
         }
 
         if (methodData == null) {
-            abortBeforeInstantiation(client, journeyLogger,
-                    ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
-                    AbortReason.INVALID_DATA_FROM_RENDERER);
+            abortForInvalidDataFromRenderer(
+                    client, journeyLogger, ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA);
             return null;
         }
 
         for (PaymentMethodData datum : methodData) {
             if (datum == null || TextUtils.isEmpty(datum.supportedMethod)) {
-                abortBeforeInstantiation(client, journeyLogger,
-                        ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
-                        AbortReason.INVALID_DATA_FROM_RENDERER);
+                abortForInvalidDataFromRenderer(
+                        client, journeyLogger, ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA);
                 return null;
             }
         }
 
         // details has default value, so could never be null, according to payment_request.idl.
         if (details == null || details.id == null || details.total == null) {
-            abortBeforeInstantiation(client, journeyLogger, ErrorStrings.INVALID_PAYMENT_DETAILS,
-                    AbortReason.INVALID_DATA_FROM_RENDERER);
+            abortForInvalidDataFromRenderer(
+                    client, journeyLogger, ErrorStrings.INVALID_PAYMENT_DETAILS);
             return null;
         }
 
         // options has default value, so could never be null, according to
         // payment_request.idl.
         if (options == null) {
-            abortBeforeInstantiation(client, journeyLogger, ErrorStrings.INVALID_PAYMENT_OPTIONS,
-                    AbortReason.INVALID_DATA_FROM_RENDERER);
+            abortForInvalidDataFromRenderer(
+                    client, journeyLogger, ErrorStrings.INVALID_PAYMENT_OPTIONS);
             return null;
         }
 
@@ -447,7 +450,7 @@
     }
 
     private void startPaymentAppService() {
-        PaymentAppService service = PaymentAppService.getInstance();
+        PaymentAppService service = mDelegate.getPaymentAppService();
         mBrowserPaymentRequest.addPaymentAppFactories(service, /*delegate=*/this);
         service.create(/*delegate=*/this);
     }
@@ -492,12 +495,13 @@
         }
     }
 
-    /** Abort the request, used before this class's instantiation. */
-    private static void abortBeforeInstantiation(@Nullable PaymentRequestClient client,
-            @Nullable JourneyLogger journeyLogger, String debugMessage, int reason) {
+    private static void abortForInvalidDataFromRenderer(@Nullable PaymentRequestClient client,
+            @Nullable JourneyLogger journeyLogger, String debugMessage) {
         Log.d(TAG, debugMessage);
-        if (client != null) client.onError(reason, debugMessage);
-        if (journeyLogger != null) journeyLogger.setAborted(reason);
+        if (client != null) {
+            client.onError(PaymentErrorReason.INVALID_DATA_FROM_RENDERER, debugMessage);
+        }
+        if (journeyLogger != null) journeyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
         if (sNativeObserverForTest != null) sNativeObserverForTest.onConnectionTerminated();
     }
 
@@ -588,8 +592,8 @@
         mBrowserPaymentRequest.modifyMethodDataIfNeeded(methodData);
         if (methodData == null) {
             mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
-            disconnectFromClientWithDebugMessage(
-                    ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA, PaymentErrorReason.USER_CANCEL);
+            disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
+                    PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
             return false;
         }
         methodData = Collections.unmodifiableMap(methodData);
@@ -600,8 +604,8 @@
         if (details == null || details.id == null || details.total == null
                 || !mDelegate.validatePaymentDetails(details)) {
             mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
-            disconnectFromClientWithDebugMessage(
-                    ErrorStrings.INVALID_PAYMENT_DETAILS, PaymentErrorReason.USER_CANCEL);
+            disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_PAYMENT_DETAILS,
+                    PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
             return false;
         }
 
@@ -615,7 +619,7 @@
         if (spec.getRawTotal() == null) {
             mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
             disconnectFromClientWithDebugMessage(
-                    ErrorStrings.TOTAL_REQUIRED, PaymentErrorReason.USER_CANCEL);
+                    ErrorStrings.TOTAL_REQUIRED, PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
             return false;
         }
         mSpec = spec;
@@ -762,7 +766,7 @@
 
         mBrowserPaymentRequest.notifyPaymentUiOfPendingApps(mPendingApps);
         mPendingApps.clear();
-        if (isCurrentPaymentRequestShowing()) {
+        if (mIsShowCalled) {
             String error = mBrowserPaymentRequest.showAppSelector(mIsShowWaitingForUpdatedDetails,
                     mSpec.getRawTotal(), mSpec.getPaymentOptions(), mIsUserGestureShow);
             if (error != null) {
@@ -796,7 +800,7 @@
      * @return Whether client has been disconnected.
      */
     private boolean disconnectIfNoPaymentMethodsSupported(boolean hasAvailableApps) {
-        if (!mIsFinishedQueryingPaymentApps || !isCurrentPaymentRequestShowing()) return false;
+        if (!mIsFinishedQueryingPaymentApps || !mIsShowCalled) return false;
         if (!mCanMakePayment || (mPendingApps.isEmpty() && !hasAvailableApps)) {
             // All factories have responded, but none of them have apps. It's possible to add credit
             // cards, but the merchant does not support them either. The payment request must be
@@ -862,11 +866,6 @@
         return mIsUserGestureShow;
     }
 
-    /** @return Whether the current payment request service has called show(). */
-    public boolean isCurrentPaymentRequestShowing() {
-        return mIsCurrentPaymentRequestShowing;
-    }
-
     /**
      * @return Whether all payment apps have been queried of canMakePayment() and
      *         hasEnrolledInstrument().
@@ -1007,6 +1006,11 @@
         return result;
     }
 
+    @VisibleForTesting
+    public static void resetShowingPaymentRequestForTest() {
+        sShowingPaymentRequest = null;
+    }
+
     /**
      * The component part of the {@link PaymentRequest#show} implementation. Check {@link
      * PaymentRequest#show} for the parameters' specification.
@@ -1016,7 +1020,7 @@
         assert mSpec != null;
         assert !mSpec.isDestroyed() : "mSpec is destroyed only after close().";
 
-        if (mBrowserPaymentRequest.isShowingUi()) {
+        if (mIsShowCalled) {
             // Can be triggered only by a compromised renderer. In normal operation, calling show()
             // twice on the same instance of PaymentRequest in JavaScript is rejected at the
             // renderer level.
@@ -1035,7 +1039,7 @@
         }
         sShowingPaymentRequest = this;
         mJourneyLogger.recordCheckoutStep(CheckoutFunnelStep.SHOW_CALLED);
-        mIsCurrentPaymentRequestShowing = true;
+        mIsShowCalled = true;
         mIsUserGestureShow = isUserGesture;
         mIsShowWaitingForUpdatedDetails = waitForUpdatedDetails;
 
@@ -1062,7 +1066,7 @@
     // Return the error if failed, null if success.
     @Nullable
     private String triggerPaymentAppUiSkipIfApplicable() {
-        if (!mIsFinishedQueryingPaymentApps || !mIsCurrentPaymentRequestShowing
+        if (mHasClosed || !mIsFinishedQueryingPaymentApps || !mIsShowCalled
                 || mIsShowWaitingForUpdatedDetails) {
             return null;
         }
@@ -1192,7 +1196,7 @@
             return;
         }
 
-        if (!mIsCurrentPaymentRequestShowing) {
+        if (!mIsShowCalled) {
             mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
             disconnectFromClientWithDebugMessage(
                     ErrorStrings.CANNOT_UPDATE_WITHOUT_SHOW, PaymentErrorReason.USER_CANCEL);
@@ -1237,7 +1241,7 @@
      */
     /* package */ void onPaymentDetailsNotUpdated() {
         if (mBrowserPaymentRequest == null) return;
-        if (!mIsCurrentPaymentRequestShowing) {
+        if (!mIsShowCalled) {
             mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
             disconnectFromClientWithDebugMessage(
                     ErrorStrings.CANNOT_UPDATE_WITHOUT_SHOW, PaymentErrorReason.USER_CANCEL);
@@ -1370,7 +1374,8 @@
      */
     /* package */ void abortForInvalidDataFromRenderer(String debugMessage) {
         mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
-        disconnectFromClientWithDebugMessage(debugMessage, PaymentErrorReason.USER_CANCEL);
+        disconnectFromClientWithDebugMessage(
+                debugMessage, PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
     }
 
     /**
@@ -1382,7 +1387,6 @@
         if (mHasClosed) return;
         mHasClosed = true;
 
-        mIsCurrentPaymentRequestShowing = false;
         sShowingPaymentRequest = null;
 
         if (mBrowserPaymentRequest != null) {
diff --git a/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceBuilder.java b/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceBuilder.java
index 15769f5f..b17c969 100644
--- a/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceBuilder.java
+++ b/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceBuilder.java
@@ -29,6 +29,7 @@
     private final Delegate mDelegate;
     private final RenderFrameHost mRenderFrameHost;
     private final Runnable mOnClosedListener;
+    private final PaymentAppService mPaymentAppService;
     private final Factory mBrowserPaymentRequestFactory;
     private WebContents mWebContents;
     private boolean mIsOffTheRecord = true;
@@ -48,12 +49,14 @@
     private PaymentRequestSpec mSpec;
 
     /* package */ static PaymentRequestServiceBuilder defaultBuilder(Runnable onClosedListener,
-            PaymentRequestClient client, BrowserPaymentRequest browserPaymentRequest) {
-        return new PaymentRequestServiceBuilder(onClosedListener, client, browserPaymentRequest);
+            PaymentRequestClient client, PaymentAppService appService,
+            BrowserPaymentRequest browserPaymentRequest) {
+        return new PaymentRequestServiceBuilder(
+                onClosedListener, client, appService, browserPaymentRequest);
     }
 
     private PaymentRequestServiceBuilder(Runnable onClosedListener, PaymentRequestClient client,
-            BrowserPaymentRequest browserPaymentRequest) {
+            PaymentAppService appService, BrowserPaymentRequest browserPaymentRequest) {
         mDelegate = this;
         mWebContents = Mockito.mock(WebContents.class);
         setTopLevelOrigin("https://top.level.origin");
@@ -76,6 +79,7 @@
         mBrowserPaymentRequestFactory = (paymentRequestService) -> browserPaymentRequest;
         mOnClosedListener = onClosedListener;
         mClient = client;
+        mPaymentAppService = appService;
     }
 
     @Override
@@ -141,6 +145,11 @@
         return mSpec;
     }
 
+    @Override
+    public PaymentAppService getPaymentAppService() {
+        return mPaymentAppService;
+    }
+
     /* package */ PaymentRequestServiceBuilder setRenderFrameHostLastCommittedOrigin(
             Origin origin) {
         Mockito.doReturn(origin).when(mRenderFrameHost).getLastCommittedOrigin();
diff --git a/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceTest.java b/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceTest.java
index bd801518..44c4c8f 100644
--- a/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceTest.java
+++ b/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceTest.java
@@ -4,7 +4,9 @@
 
 package org.chromium.components.payments;
 
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -20,17 +22,23 @@
 import org.chromium.mojo.system.MojoException;
 import org.chromium.payments.mojom.PayerDetail;
 import org.chromium.payments.mojom.PaymentAddress;
+import org.chromium.payments.mojom.PaymentDetails;
 import org.chromium.payments.mojom.PaymentErrorReason;
 import org.chromium.payments.mojom.PaymentMethodData;
 import org.chromium.payments.mojom.PaymentRequestClient;
 import org.chromium.payments.mojom.PaymentResponse;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** A test for PaymentRequestService. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class PaymentRequestServiceTest implements PaymentRequestClient {
     private static final int NO_PAYMENT_ERROR = PaymentErrorReason.MIN_VALUE;
     private final BrowserPaymentRequest mBrowserPaymentRequest;
+    private List<PaymentApp> mNotifiedPendingApps;
+
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.WARN);
 
@@ -50,9 +58,53 @@
     private boolean mWarnNoFaviconCalled;
     private boolean mIsClientClosed;
     private MojoException mConnectionError;
+    private boolean mIsUserGestureDefaultValue = true;
+    private boolean mWaitForUpdatedDetailsDefaultValue;
+    private PaymentAppService mPaymentAppService;
+    private PaymentAppFactoryDelegate mPaymentAppFactoryDelegate;
 
     public PaymentRequestServiceTest() {
+        mPaymentAppService = Mockito.mock(PaymentAppService.class);
+        Mockito.doAnswer((args) -> {
+                   mPaymentAppFactoryDelegate = args.getArgument(0);
+                   return null;
+               })
+                .when(mPaymentAppService)
+                .create(Mockito.any());
+
         mBrowserPaymentRequest = Mockito.mock(BrowserPaymentRequest.class);
+        Mockito.doReturn(true).when(mBrowserPaymentRequest).hasAvailableApps();
+        Mockito.doReturn(false)
+                .when(mBrowserPaymentRequest)
+                .disconnectIfExtraValidationFails(
+                        Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
+        Mockito.doReturn(true)
+                .when(mBrowserPaymentRequest)
+                .patchPaymentResponseIfNeeded(Mockito.any());
+        Mockito.doReturn(null)
+                .when(mBrowserPaymentRequest)
+                .showAppSelector(
+                        Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.anyBoolean());
+        Mockito.doReturn(true)
+                .when(mBrowserPaymentRequest)
+                .parseAndValidateDetailsFurtherIfNeeded(Mockito.any());
+        Mockito.doAnswer((args) -> {
+                   List<PaymentApp> pendingApps = args.getArgument(0);
+                   mNotifiedPendingApps = new ArrayList<>(pendingApps);
+                   return null;
+               })
+                .when(mBrowserPaymentRequest)
+                .notifyPaymentUiOfPendingApps(Mockito.any());
+    }
+
+    @Before
+    public void setUp() {
+        PaymentRequestService.resetShowingPaymentRequestForTest();
+    }
+
+    @After
+    public void tearDown() {
+        PaymentRequestService.resetShowingPaymentRequestForTest();
     }
 
     @Override
@@ -131,9 +183,37 @@
         Assert.assertEquals(errorReason, mSentErrorReason);
     }
 
+    private void assertClosed(boolean isClosed) {
+        Assert.assertEquals(mIsClientClosed, isClosed);
+        Assert.assertEquals(mIsOnCloseListenerInvoked, isClosed);
+    }
+
+    private void show(PaymentRequestService service) {
+        service.show(mIsUserGestureDefaultValue, mWaitForUpdatedDetailsDefaultValue);
+    }
+
     private PaymentRequestServiceBuilder defaultBuilder() {
         return PaymentRequestServiceBuilder.defaultBuilder(
-                () -> mIsOnCloseListenerInvoked = true, /*client=*/this, mBrowserPaymentRequest);
+                ()
+                        -> mIsOnCloseListenerInvoked = true,
+                /*client=*/this, mPaymentAppService, mBrowserPaymentRequest);
+    }
+
+    private PaymentApp createDefaultPaymentApp() {
+        PaymentApp app = Mockito.mock(PaymentApp.class);
+        Mockito.doReturn(true).when(app).canMakePayment();
+        Mockito.doReturn(false).when(app).isAutofillInstrument();
+        return app;
+    }
+
+    private void queryPaymentApps() {
+        mPaymentAppFactoryDelegate.onCanMakePaymentCalculated(true);
+        mPaymentAppFactoryDelegate.onPaymentAppCreated(createDefaultPaymentApp());
+        mPaymentAppFactoryDelegate.onDoneCreatingPaymentApps(null);
+    }
+
+    private PaymentDetails getDefaultDetails() {
+        return new PaymentDetails();
     }
 
     @Test
@@ -177,24 +257,32 @@
     @Feature({"Payments"})
     public void testInsecureOriginFailsCreation() {
         Assert.assertNull(defaultBuilder().setOriginSecure(false).build());
+        assertErrorAndReason(
+                ErrorStrings.NOT_IN_A_SECURE_ORIGIN, PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
     }
 
     @Test
     @Feature({"Payments"})
     public void testNullMethodDataFailsCreation() {
         Assert.assertNull(defaultBuilder().setMethodData(null).build());
+        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
+                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
     }
 
     @Test
     @Feature({"Payments"})
     public void testNullDetailsFailsCreation() {
         Assert.assertNull(defaultBuilder().setDetails(null).build());
+        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_DETAILS,
+                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
     }
 
     @Test
     @Feature({"Payments"})
     public void testNullOptionsFailsCreation() {
         Assert.assertNull(defaultBuilder().setOptions(null).build());
+        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_OPTIONS,
+                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
     }
 
     @Test
@@ -219,6 +307,8 @@
     public void testMethodDataNullElementFailsCreation() {
         PaymentMethodData[] methodData = new PaymentMethodData[1];
         Assert.assertNull(defaultBuilder().setMethodData(methodData).build());
+        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
+                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
     }
 
     @Test
@@ -228,6 +318,8 @@
         methodData[0] = new PaymentMethodData();
         methodData[0].supportedMethod = "";
         Assert.assertNull(defaultBuilder().setMethodData(methodData).build());
+        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
+                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
     }
 
     @Test
@@ -237,13 +329,16 @@
         methodData[0] = new PaymentMethodData();
         methodData[0].supportedMethod = null;
         Assert.assertNull(defaultBuilder().setMethodData(methodData).build());
+        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
+                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
     }
 
     @Test
     @Feature({"Payments"})
     public void testInvalidDetailsFailsCreation() {
         Assert.assertNull(defaultBuilder().setIsPaymentDetailsValid(false).build());
-        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_DETAILS, PaymentErrorReason.USER_CANCEL);
+        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_DETAILS,
+                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
     }
 
     @Test
@@ -252,15 +347,146 @@
         PaymentRequestSpec spec = Mockito.mock(PaymentRequestSpec.class);
         Mockito.doReturn(null).when(spec).getRawTotal();
         Assert.assertNull(defaultBuilder().setPaymentRequestSpec(spec).build());
-        assertErrorAndReason(ErrorStrings.TOTAL_REQUIRED, PaymentErrorReason.USER_CANCEL);
+        assertErrorAndReason(
+                ErrorStrings.TOTAL_REQUIRED, PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
+    }
+
+    @Test
+    @Feature({"Payments"})
+    public void testTwoShowsFailService() {
+        PaymentRequestService service = defaultBuilder().build();
+        show(service);
+        assertErrorAndReason(null, NO_PAYMENT_ERROR);
+        assertClosed(false);
+        show(service);
+        assertErrorAndReason(ErrorStrings.CANNOT_SHOW_TWICE, PaymentErrorReason.USER_CANCEL);
+        assertClosed(true);
     }
 
     @Test
     @Feature({"Payments"})
     public void testDefaultParamsMakeCreationSuccess() {
+        Assert.assertNull(mPaymentAppFactoryDelegate);
         PaymentRequestService service = defaultBuilder().build();
         Assert.assertNotNull(service);
         Mockito.verify(mBrowserPaymentRequest, Mockito.times(1)).onSpecValidated(Mockito.notNull());
         assertNoError();
+        Assert.assertNotNull(mPaymentAppFactoryDelegate);
+    }
+
+    @Test
+    @Feature({"Payments"})
+    public void testCanNotMakePaymentFailsPayment() {
+        PaymentRequestService service = defaultBuilder().build();
+        show(service);
+        mPaymentAppFactoryDelegate.onCanMakePaymentCalculated(false);
+        mPaymentAppFactoryDelegate.onPaymentAppCreated(createDefaultPaymentApp());
+        mPaymentAppFactoryDelegate.onDoneCreatingPaymentApps(null);
+        assertErrorAndReason(ErrorStrings.USER_CANCELLED, PaymentErrorReason.USER_CANCEL);
+        assertClosed(true);
+    }
+
+    @Test
+    @Feature({"Payments"})
+    public void testNoPaymentAppFailsPayment() {
+        PaymentRequestService service = defaultBuilder().build();
+        show(service);
+        mPaymentAppFactoryDelegate.onDoneCreatingPaymentApps(null);
+        assertErrorAndReason(ErrorStrings.USER_CANCELLED, PaymentErrorReason.USER_CANCEL);
+        assertClosed(true);
+    }
+
+    @Test
+    @Feature({"Payments"})
+    public void testAppSelectorIsTriggeredOnShownAndAppsQueried() {
+        PaymentRequestService service = defaultBuilder().build();
+        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
+                .showAppSelector(
+                        Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.anyBoolean());
+        show(service);
+        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
+                .showAppSelector(
+                        Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.anyBoolean());
+        queryPaymentApps();
+        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1))
+                .showAppSelector(
+                        Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.anyBoolean());
+    }
+
+    @Test
+    @Feature({"Payments"})
+    public void testUiIsNotifiedOfPendingAppsOnShownAndAppsQueried() {
+        PaymentRequestService service = defaultBuilder().build();
+        show(service);
+        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
+                .notifyPaymentUiOfPendingApps(Mockito.any());
+        Assert.assertNull(mNotifiedPendingApps);
+        queryPaymentApps();
+        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1))
+                .notifyPaymentUiOfPendingApps(Mockito.any());
+        Assert.assertEquals(1, mNotifiedPendingApps.size());
+    }
+
+    @Test
+    @Feature({"Payments"})
+    public void testAppSelectorIsNotTriggeredOnAppsQueriedOnly() {
+        PaymentRequestService service = defaultBuilder().build();
+        queryPaymentApps();
+        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
+                .showAppSelector(
+                        Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.anyBoolean());
+        show(service);
+        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1))
+                .showAppSelector(
+                        Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.anyBoolean());
+    }
+
+    @Test
+    @Feature({"Payments"})
+    public void testInvokeUiSkipMethodOnShownAndAppsQueried() {
+        PaymentRequestService service = defaultBuilder().build();
+        show(service);
+        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
+                .triggerPaymentAppUiSkipIfApplicable(Mockito.anyBoolean());
+        queryPaymentApps();
+        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1))
+                .triggerPaymentAppUiSkipIfApplicable(Mockito.anyBoolean());
+    }
+
+    @Test
+    @Feature({"Payments"})
+    public void testWaitingForUpdatedDetailsDeterUiSkipMethod() {
+        PaymentRequestService service = defaultBuilder().build();
+        service.show(mIsUserGestureDefaultValue, true);
+        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
+                .triggerPaymentAppUiSkipIfApplicable(Mockito.anyBoolean());
+        queryPaymentApps();
+        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
+                .triggerPaymentAppUiSkipIfApplicable(Mockito.anyBoolean());
+        service.updateWith(getDefaultDetails());
+        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1))
+                .triggerPaymentAppUiSkipIfApplicable(Mockito.anyBoolean());
+    }
+
+    @Test
+    @Feature({"Payments"})
+    public void testCloseTeardownResources() {
+        PaymentRequestService service = defaultBuilder().build();
+        Mockito.verify(mBrowserPaymentRequest, Mockito.never()).close();
+        assertClosed(false);
+        service.close();
+        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1)).close();
+        assertClosed(true);
+    }
+
+    @Test
+    @Feature({"Payments"})
+    public void testOnlyOneServiceCanBeShownGlobally() {
+        PaymentRequestService service1 = defaultBuilder().build();
+        show(service1);
+        assertNoError();
+        PaymentRequestService service2 = defaultBuilder().build();
+        show(service2);
+        assertErrorAndReason(ErrorStrings.ANOTHER_UI_SHOWING, PaymentErrorReason.ALREADY_SHOWING);
     }
 }
diff --git a/components/permissions/BUILD.gn b/components/permissions/BUILD.gn
index 4861029..9edc62b 100644
--- a/components/permissions/BUILD.gn
+++ b/components/permissions/BUILD.gn
@@ -63,6 +63,7 @@
   ]
   deps = [
     "//base",
+    "//build:chromeos_buildflags",
     "//components/autofill_assistant/browser/public:public",
     "//components/content_settings/browser",
     "//components/content_settings/core/browser",
@@ -159,6 +160,7 @@
     ":test_support",
     "//base",
     "//base/test:test_support",
+    "//build:chromeos_buildflags",
     "//components/content_settings/browser",
     "//components/content_settings/browser:test_support",
     "//components/content_settings/core/browser",
diff --git a/components/permissions/permission_context_base_unittest.cc b/components/permissions/permission_context_base_unittest.cc
index 21db013b..94c6811 100644
--- a/components/permissions/permission_context_base_unittest.cc
+++ b/components/permissions/permission_context_base_unittest.cc
@@ -19,6 +19,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
@@ -765,7 +766,7 @@
   TestRequestPermissionInvalidUrl(ContentSettingsType::GEOLOCATION);
   TestRequestPermissionInvalidUrl(ContentSettingsType::NOTIFICATIONS);
   TestRequestPermissionInvalidUrl(ContentSettingsType::MIDI_SYSEX);
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
   TestRequestPermissionInvalidUrl(
       ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER);
 #endif
@@ -795,7 +796,7 @@
   TestGlobalPermissionsKillSwitch(ContentSettingsType::NOTIFICATIONS);
   TestGlobalPermissionsKillSwitch(ContentSettingsType::MIDI_SYSEX);
   TestGlobalPermissionsKillSwitch(ContentSettingsType::DURABLE_STORAGE);
-#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
+#if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
   TestGlobalPermissionsKillSwitch(
       ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER);
 #endif
diff --git a/components/permissions/permission_manager.cc b/components/permissions/permission_manager.cc
index 40a9b555..6ff3c23 100644
--- a/components/permissions/permission_manager.cc
+++ b/components/permissions/permission_manager.cc
@@ -11,6 +11,7 @@
 #include "base/callback.h"
 #include "base/feature_list.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/permissions/features.h"
 #include "components/permissions/permission_context_base.h"
@@ -81,7 +82,7 @@
     case PermissionType::GEOLOCATION:
       return ContentSettingsType::GEOLOCATION;
     case PermissionType::PROTECTED_MEDIA_IDENTIFIER:
-#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
+#if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
       return ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER;
 #else
       break;
diff --git a/components/permissions/permission_manager_unittest.cc b/components/permissions/permission_manager_unittest.cc
index ce6ba4d..6ad48c0 100644
--- a/components/permissions/permission_manager_unittest.cc
+++ b/components/permissions/permission_manager_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/metrics/field_trial.h"
 #include "base/metrics/field_trial_params.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/permissions/features.h"
 #include "components/permissions/permission_context_base.h"
@@ -658,7 +659,7 @@
 
 TEST_F(PermissionManagerTest, MissingContextIsNotOverridable) {
   // Permissions that are not implemented should be denied overridability.
-#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
   EXPECT_FALSE(
       GetPermissionControllerDelegate()->IsPermissionOverridableByDevTools(
           PermissionType::PROTECTED_MEDIA_IDENTIFIER,
diff --git a/components/permissions/permission_request_impl.cc b/components/permissions/permission_request_impl.cc
index 8458de2..2c59219 100644
--- a/components/permissions/permission_request_impl.cc
+++ b/components/permissions/permission_request_impl.cc
@@ -6,6 +6,7 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/permissions/permission_util.h"
 #include "components/permissions/permissions_client.h"
 #include "components/strings/grit/components_strings.h"
@@ -199,7 +200,7 @@
     case ContentSettingsType::MIDI_SYSEX:
       message_id = IDS_MIDI_SYSEX_PERMISSION_FRAGMENT;
       break;
-#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
+#if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
     case ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER:
       message_id = IDS_PROTECTED_MEDIA_IDENTIFIER_PERMISSION_FRAGMENT;
       break;
diff --git a/components/permissions/permission_util.cc b/components/permissions/permission_util.cc
index 9bf867a3..9567d4bc 100644
--- a/components/permissions/permission_util.cc
+++ b/components/permissions/permission_util.cc
@@ -6,6 +6,7 @@
 
 #include "base/notreached.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "content/public/browser/permission_type.h"
 
 using content::PermissionType;
@@ -147,7 +148,7 @@
     *out = PermissionType::BACKGROUND_SYNC;
   } else if (type == ContentSettingsType::PLUGINS) {
     *out = PermissionType::FLASH;
-#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
+#if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
   } else if (type == ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER) {
     *out = PermissionType::PROTECTED_MEDIA_IDENTIFIER;
 #endif
@@ -199,7 +200,7 @@
     case ContentSettingsType::MEDIASTREAM_MIC:
     case ContentSettingsType::BACKGROUND_SYNC:
     case ContentSettingsType::PLUGINS:
-#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
+#if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
     case ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER:
 #endif
     case ContentSettingsType::SENSORS:
diff --git a/components/permissions/permissions_client.cc b/components/permissions/permissions_client.cc
index 19fa0bf..f6b8828 100644
--- a/components/permissions/permissions_client.cc
+++ b/components/permissions/permissions_client.cc
@@ -5,6 +5,7 @@
 #include "components/permissions/permissions_client.h"
 
 #include "base/callback.h"
+#include "build/chromeos_buildflags.h"
 #include "components/permissions/notification_permission_ui_selector.h"
 
 #if !defined(OS_ANDROID)
@@ -44,7 +45,7 @@
     entry.second = false;
 }
 
-#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
+#if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
 bool PermissionsClient::IsCookieDeletionDisabled(
     content::BrowserContext* browser_context,
     const GURL& origin) {
diff --git a/components/permissions/permissions_client.h b/components/permissions/permissions_client.h
index c8e8c1f..b73234c 100644
--- a/components/permissions/permissions_client.h
+++ b/components/permissions/permissions_client.h
@@ -9,6 +9,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/permissions/notification_permission_ui_selector.h"
 #include "components/permissions/permission_prompt.h"
@@ -95,7 +96,7 @@
       content::BrowserContext* browser_context,
       std::vector<std::pair<url::Origin, bool>>* origins);
 
-#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
+#if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
   // Returns whether cookie deletion is allowed for |browser_context| and
   // |origin|.
   // TODO(crbug.com/1081944): Remove this method and all code depending on it
diff --git a/components/permissions/prediction_service/BUILD.gn b/components/permissions/prediction_service/BUILD.gn
index 96b42b9..0a8f8ca 100644
--- a/components/permissions/prediction_service/BUILD.gn
+++ b/components/permissions/prediction_service/BUILD.gn
@@ -18,6 +18,7 @@
     "prediction_service_common.h",
   ]
   deps = [
+    "//build:chromeos_buildflags",
     "//components/keyed_service/content",
     "//components/permissions:permissions_common",
     "//services/network/public/cpp:cpp",
diff --git a/components/permissions/prediction_service/prediction_service_common.cc b/components/permissions/prediction_service/prediction_service_common.cc
index 02dbf47..448a333 100644
--- a/components/permissions/prediction_service/prediction_service_common.cc
+++ b/components/permissions/prediction_service/prediction_service_common.cc
@@ -5,12 +5,13 @@
 #include "components/permissions/prediction_service/prediction_service_common.h"
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
 namespace permissions {
 ClientFeatures_Platform GetCurrentPlatformProto() {
 #if defined(OS_WIN)
   return permissions::ClientFeatures_Platform_PLATFORM_WINDOWS;
-#elif defined(OS_LINUX)
+#elif defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   return permissions::ClientFeatures_Platform_PLATFORM_LINUX;
 #elif defined(OS_ANDROID)
   return permissions::ClientFeatures_Platform_PLATFORM_ANDROID;
diff --git a/components/policy/core/common/config_dir_policy_loader.cc b/components/policy/core/common/config_dir_policy_loader.cc
index dd39dfa..74d0a03 100644
--- a/components/policy/core/common/config_dir_policy_loader.cc
+++ b/components/policy/core/common/config_dir_policy_loader.cc
@@ -67,9 +67,11 @@
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
   base::FilePathWatcher::Callback callback = base::BindRepeating(
       &ConfigDirPolicyLoader::OnFileUpdated, base::Unretained(this));
-  mandatory_watcher_.Watch(config_dir_.Append(kMandatoryConfigDir), false,
+  mandatory_watcher_.Watch(config_dir_.Append(kMandatoryConfigDir),
+                           base::FilePathWatcher::Type::kNonRecursive,
                            callback);
-  recommended_watcher_.Watch(config_dir_.Append(kRecommendedConfigDir), false,
+  recommended_watcher_.Watch(config_dir_.Append(kRecommendedConfigDir),
+                             base::FilePathWatcher::Type::kNonRecursive,
                              callback);
 }
 
diff --git a/components/policy/core/common/policy_loader_mac.mm b/components/policy/core/common/policy_loader_mac.mm
index 35005d02..f56505d1 100644
--- a/components/policy/core/common/policy_loader_mac.mm
+++ b/components/policy/core/common/policy_loader_mac.mm
@@ -77,7 +77,8 @@
 
 void PolicyLoaderMac::InitOnBackgroundThread() {
   if (!managed_policy_path_.empty()) {
-    watcher_.Watch(managed_policy_path_, false,
+    watcher_.Watch(managed_policy_path_,
+                   base::FilePathWatcher::Type::kNonRecursive,
                    base::BindRepeating(&PolicyLoaderMac::OnFileUpdated,
                                        base::Unretained(this)));
   }
diff --git a/components/policy/tools/template_writers/writers/jamf_writer.py b/components/policy/tools/template_writers/writers/jamf_writer.py
index e00ac95..e87cd15 100755
--- a/components/policy/tools/template_writers/writers/jamf_writer.py
+++ b/components/policy/tools/template_writers/writers/jamf_writer.py
@@ -80,6 +80,7 @@
     policies = [policy for policy in policies if self.IsPolicySupported(policy)]
     output = {
         'title': self.config['bundle_id'],
+        'version': self.config['version'].split(".", 1)[0],
         'description': self.config['app_name'],
         'options': {
             'remove_empty_properties': True
diff --git a/components/policy/tools/template_writers/writers/jamf_writer_unittest.py b/components/policy/tools/template_writers/writers/jamf_writer_unittest.py
index 6e94821..1ab69c4 100755
--- a/components/policy/tools/template_writers/writers/jamf_writer_unittest.py
+++ b/components/policy/tools/template_writers/writers/jamf_writer_unittest.py
@@ -60,7 +60,7 @@
     return _JsonFormat(template)
 
   def _GetExpectedOutput(self, policy_name, policy_type, policy_caption,
-                         initial_type):
+                         initial_type, version):
     output = {
         'description': 'Google Chrome',
         'options': {
@@ -73,7 +73,8 @@
                 'type': policy_type
             }
         },
-        'title': 'com.google.chrome.ios'
+        'title': 'com.google.chrome.ios',
+        'version': version
     }
     if initial_type == 'int-enum' or initial_type == 'string-enum':
       output['properties'][policy_name]['enum'] = [1]
@@ -103,16 +104,22 @@
     policy_json = self._GetTestPolicyTemplate('stringPolicy', 'string', '',
                                               'A string policy')
     expected = self._GetExpectedOutput('stringPolicy', 'string',
-                                       'A string policy', 'string')
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+                                       'A string policy', 'string', '83')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), expected.strip())
 
   def testIntPolicy(self):
     policy_json = self._GetTestPolicyTemplate('intPolicy', 'int', '',
                                               'An int policy')
     expected = self._GetExpectedOutput('intPolicy', 'integer', 'An int policy',
-                                       'int')
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+                                       'int', '83')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), expected.strip())
 
   def testIntPolicyWithMinAndMax(self):
@@ -150,27 +157,38 @@
                 'type': 'integer'
             }
         },
-        'title': 'com.google.chrome.ios'
+        'title': 'com.google.chrome.ios',
+        'version': '83'
     }
     expected_json = _JsonFormat(expected)
 
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), expected_json.strip())
 
   def testIntEnumPolicy(self):
     policy_json = self._GetTestPolicyTemplate('intPolicy', 'int-enum', '',
                                               'An int-enum policy')
     expected = self._GetExpectedOutput('intPolicy', 'integer',
-                                       'An int-enum policy', 'int-enum')
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+                                       'An int-enum policy', 'int-enum', '83')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), expected.strip())
 
   def testStringEnumPolicy(self):
     policy_json = self._GetTestPolicyTemplate('stringPolicy', 'string-enum', '',
                                               'A string-enum policy')
     expected = self._GetExpectedOutput('stringPolicy', 'string',
-                                       'A string-enum policy', 'string-enum')
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+                                       'A string-enum policy', 'string-enum',
+                                       '83')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), expected.strip())
 
   def testStringEnumListPolicy(self):
@@ -179,40 +197,55 @@
                                               'A string-enum-list policy')
     expected = self._GetExpectedOutput('stringPolicy', 'array',
                                        'A string-enum-list policy',
-                                       'string-enum-list')
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+                                       'string-enum-list', '83')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), expected.strip())
 
   def testBooleanPolicy(self):
     policy_json = self._GetTestPolicyTemplate('booleanPolicy', 'main', '',
                                               'A boolean policy')
     expected = self._GetExpectedOutput('booleanPolicy', 'boolean',
-                                       'A boolean policy', 'main')
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+                                       'A boolean policy', 'main', '83')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), expected.strip())
 
   def testListPolicy(self):
     policy_json = self._GetTestPolicyTemplate('listPolicy', 'list', '',
                                               'A list policy')
     expected = self._GetExpectedOutput('listPolicy', 'array', 'A list policy',
-                                       'list')
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+                                       'list', '83')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), expected.strip())
 
   def testDictPolicy(self):
     policy_json = self._GetTestPolicyTemplate('dictPolicy', 'dict', 'object',
                                               'A dict policy')
     expected = self._GetExpectedOutput('dictPolicy', 'object', 'A dict policy',
-                                       'dict')
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+                                       'dict', '83')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), expected.strip())
 
   def testArrayDictPolicy(self):
     policy_json = self._GetTestPolicyTemplate('dictPolicy', 'dict', 'array',
                                               'A dict policy')
     expected = self._GetExpectedOutput('dictPolicy', 'array', 'A dict policy',
-                                       'dict')
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+                                       'dict', '83')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), expected.strip())
 
   def testNestedArrayDict(self):
@@ -268,7 +301,8 @@
                 }
             }
         },
-        'title': 'com.google.chrome.ios'
+        'title': 'com.google.chrome.ios',
+        'version': '83'
     }
 
     for i in range(0, 5):
@@ -278,15 +312,21 @@
       }
 
     output_expected = _JsonFormat(expected)
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), output_expected.strip())
 
   def testExternalPolicy(self):
     policy_json = self._GetTestPolicyTemplate('externalPolicy', 'external', '',
                                               'A external policy')
     expected = self._GetExpectedOutput('externalPolicy', 'object',
-                                       'A external policy', 'external')
-    output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'jamf')
+                                       'A external policy', 'external', '83')
+    output = self.GetOutput(policy_json, {
+        '_google_chrome': '1',
+        'version': '83.0.4089.0'
+    }, 'jamf')
     self.assertEquals(output.strip(), expected.strip())
 
 
diff --git a/components/security_interstitials/content/BUILD.gn b/components/security_interstitials/content/BUILD.gn
index f722af2..629d27a 100644
--- a/components/security_interstitials/content/BUILD.gn
+++ b/components/security_interstitials/content/BUILD.gn
@@ -77,6 +77,7 @@
     ":proto",
     "//base",
     "//build:branding_buildflags",
+    "//build:chromeos_buildflags",
     "//components/captive_portal/core",
     "//components/content_settings/core/browser",
     "//components/content_settings/core/common",
@@ -151,6 +152,7 @@
     ":security_interstitial_page",
     "//base",
     "//base/test:test_support",
+    "//build:chromeos_buildflags",
     "//components/captive_portal/content",
     "//components/captive_portal/core:test_support",
     "//components/embedder_support",
diff --git a/components/security_interstitials/content/certificate_error_report_unittest.cc b/components/security_interstitials/content/certificate_error_report_unittest.cc
index 7940f40..8433e169 100644
--- a/components/security_interstitials/content/certificate_error_report_unittest.cc
+++ b/components/security_interstitials/content/certificate_error_report_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/time/default_clock.h"
 #include "base/time/default_tick_clock.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/network_time/network_time_test_utils.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/security_interstitials/content/cert_logger.pb.h"
@@ -286,7 +287,7 @@
   }
 }
 
-#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
 // Tests that the SetIsEnterpriseManaged() function populates
 // is_enterprise_managed correctly on Windows, and that value is correctly
 // extracted from the parsed report.
diff --git a/components/security_interstitials/content/utils.cc b/components/security_interstitials/content/utils.cc
index cbafd50..906016f 100644
--- a/components/security_interstitials/content/utils.cc
+++ b/components/security_interstitials/content/utils.cc
@@ -8,6 +8,7 @@
 #include "base/files/file_util.h"
 #include "base/process/launch.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
 #if defined(OS_ANDROID)
 #include "base/android/jni_android.h"
@@ -24,7 +25,7 @@
 
 namespace security_interstitials {
 
-#if !defined(OS_CHROMEOS) && !defined(OS_FUCHSIA)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_FUCHSIA)
 void LaunchDateAndTimeSettings() {
 // The code for each OS is completely separate, in order to avoid bugs like
 // https://crbug.com/430877 .
diff --git a/components/security_interstitials/content/utils.h b/components/security_interstitials/content/utils.h
index e643e93..beb25a8 100644
--- a/components/security_interstitials/content/utils.h
+++ b/components/security_interstitials/content/utils.h
@@ -6,12 +6,13 @@
 #define COMPONENTS_SECURITY_INTERSTITIALS_CONTENT_UTILS_H_
 
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
 namespace security_interstitials {
 
 // Provides utilities for security interstitials on //content-based platforms.
 
-#if !defined(OS_CHROMEOS) && !defined(OS_FUCHSIA)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_FUCHSIA)
 // Launches date and time settings as appropriate based on the platform (not
 // supported on ChromeOS, where taking this action requires embedder-level
 // machinery, or Fuchsia, which simply doesn't require this functionality).
diff --git a/components/signin/internal/identity_manager/account_tracker_service_unittest.cc b/components/signin/internal/identity_manager/account_tracker_service_unittest.cc
index cad98d3..14fc8ca 100644
--- a/components/signin/internal/identity_manager/account_tracker_service_unittest.cc
+++ b/components/signin/internal/identity_manager/account_tracker_service_unittest.cc
@@ -40,6 +40,9 @@
 #endif
 
 namespace {
+
+using TokenResponseBuilder = OAuth2AccessTokenConsumer::TokenResponse::Builder;
+
 // Simple wrapper around a static string; used to avoid implicit conversion
 // of the account key to an std::string (which is the type used for account
 // identifier). This is a POD type so it can be used for static storage const
@@ -274,9 +277,11 @@
   void IssueAccessToken(AccountKey account_key) {
     fake_oauth2_token_service_.IssueAllTokensForAccount(
         AccountKeyToAccountId(account_key),
-        OAuth2AccessTokenConsumer::TokenResponse(
-            base::StringPrintf("access_token-%s", account_key.value),
-            base::Time::Max(), std::string()));
+        TokenResponseBuilder()
+            .WithAccessToken(
+                base::StringPrintf("access_token-%s", account_key.value))
+            .WithExpirationTime(base::Time::Max())
+            .build());
   }
 
   std::string GenerateValidTokenInfoResponse(AccountKey account_key) {
diff --git a/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc b/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc
index 2f24cb3..660b591 100644
--- a/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc
+++ b/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc
@@ -5,8 +5,8 @@
 #include "components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.h"
 
 #include "components/signin/internal/identity_manager/profile_oauth2_token_service.h"
+#include "google_apis/gaia/gaia_access_token_fetcher.h"
 #include "google_apis/gaia/gaia_constants.h"
-#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
 
 namespace {
 // Values used from |MutableProfileOAuth2TokenServiceDelegate|.
@@ -47,8 +47,9 @@
     OAuth2AccessTokenConsumer* consumer) {
   auto it = refresh_tokens_.find(account_id);
   DCHECK(it != refresh_tokens_.end());
-  return std::make_unique<OAuth2AccessTokenFetcherImpl>(
-      consumer, url_loader_factory, it->second->refresh_token);
+  return GaiaAccessTokenFetcher::
+      CreateExchangeRefreshTokenForAccessTokenInstance(
+          consumer, url_loader_factory, it->second->refresh_token);
 }
 
 bool FakeProfileOAuth2TokenServiceDelegate::RefreshTokenIsAvailable(
diff --git a/components/signin/internal/identity_manager/gaia_cookie_manager_service_unittest.cc b/components/signin/internal/identity_manager/gaia_cookie_manager_service_unittest.cc
index e135e8d..07e05e33 100644
--- a/components/signin/internal/identity_manager/gaia_cookie_manager_service_unittest.cc
+++ b/components/signin/internal/identity_manager/gaia_cookie_manager_service_unittest.cc
@@ -35,6 +35,8 @@
 
 namespace {
 
+using TokenResponseBuilder = OAuth2AccessTokenConsumer::TokenResponse::Builder;
+
 const char kAccountId1[] = "account_id1";
 const char kAccountId2[] = "account_id2";
 const char kAccountId3[] = "account_id3";
@@ -154,10 +156,10 @@
 
   void SimulateAccessTokenSuccess(OAuth2AccessTokenManager::Consumer* consumer,
                                   OAuth2AccessTokenManager::Request* request) {
-    OAuth2AccessTokenConsumer::TokenResponse token_response =
-        OAuth2AccessTokenConsumer::TokenResponse("AccessToken", base::Time(),
-                                                 "Idtoken");
-    consumer->OnGetTokenSuccess(request, token_response);
+    consumer->OnGetTokenSuccess(request, TokenResponseBuilder()
+                                             .WithAccessToken("AccessToken")
+                                             .WithIdToken("Idtoken")
+                                             .build());
   }
 
   void SimulateMergeSessionSuccess(GaiaAuthConsumer* consumer,
diff --git a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc
index 8e3a33f..4063a0b6 100644
--- a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc
+++ b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc
@@ -21,11 +21,11 @@
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/webdata/token_web_data.h"
 #include "components/webdata/common/web_data_service_base.h"
+#include "google_apis/gaia/gaia_access_token_fetcher.h"
 #include "google_apis/gaia/gaia_auth_fetcher.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h"
-#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace {
@@ -300,8 +300,9 @@
   }
   std::string refresh_token = GetRefreshToken(account_id);
   DCHECK(!refresh_token.empty());
-  return std::make_unique<OAuth2AccessTokenFetcherImpl>(
-      consumer, url_loader_factory, refresh_token);
+  return GaiaAccessTokenFetcher::
+      CreateExchangeRefreshTokenForAccessTokenInstance(
+          consumer, url_loader_factory, refresh_token);
 }
 
 GoogleServiceAuthError MutableProfileOAuth2TokenServiceDelegate::GetAuthError(
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service.cc
index 804adebb..426f92c 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service.cc
@@ -23,6 +23,9 @@
 using signin_metrics::SourceForRefreshTokenOperation;
 
 namespace {
+
+using TokenResponseBuilder = OAuth2AccessTokenConsumer::TokenResponse::Builder;
+
 std::string SourceToString(SourceForRefreshTokenOperation source) {
   switch (source) {
     case SourceForRefreshTokenOperation::kUnknown:
@@ -195,9 +198,10 @@
       new OAuth2AccessTokenManager::RequestImpl(account_id, consumer));
   // Create token response from token. Expiration time and id token do not
   // matter and should not be accessed.
-  OAuth2AccessTokenConsumer::TokenResponse token_response(
-      refresh_token, base::Time(), std::string());
-  // If we can get refresh token from the delegate, inform consumer right away.
+  // TODO(1151018): See bug description for why the refresh token is passed
+  // in the access token field.
+  OAuth2AccessTokenConsumer::TokenResponse token_response =
+      TokenResponseBuilder().WithAccessToken(refresh_token).build();
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(&OAuth2AccessTokenManager::RequestImpl::InformConsumer,
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc
index f7c4831b..2a43aaf 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc
@@ -29,6 +29,8 @@
 
 namespace {
 
+using TokenResponseBuilder = OAuth2AccessTokenConsumer::TokenResponse::Builder;
+
 // Callback from FetchOAuth2TokenWithUsername().
 // Arguments:
 // - the error, or NONE if the token fetch was successful.
@@ -113,8 +115,10 @@
     return;
   }
   if (error.state() == GoogleServiceAuthError::NONE) {
-    FireOnGetTokenSuccess(OAuth2AccessTokenConsumer::TokenResponse(
-        access_token, expiration_time, std::string()));
+    FireOnGetTokenSuccess(TokenResponseBuilder()
+                              .WithAccessToken(access_token)
+                              .WithExpirationTime(expiration_time)
+                              .build());
   } else {
     FireOnGetTokenFailure(error);
   }
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.mm b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.mm
index ea2e802..afa151d 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.mm
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.mm
@@ -30,6 +30,8 @@
 
 namespace {
 
+using TokenResponseBuilder = OAuth2AccessTokenConsumer::TokenResponse::Builder;
+
 // Match the way Chromium handles authentication errors in
 // google_apis/gaia/oauth2_access_token_fetcher.cc:
 GoogleServiceAuthError GetGoogleServiceAuthErrorFromNSError(
@@ -144,8 +146,10 @@
   if (auth_error.state() == GoogleServiceAuthError::NONE) {
     base::Time expiration_date =
         base::Time::FromDoubleT([expiration timeIntervalSince1970]);
-    FireOnGetTokenSuccess(OAuth2AccessTokenConsumer::TokenResponse(
-        base::SysNSStringToUTF8(token), expiration_date, std::string()));
+    FireOnGetTokenSuccess(TokenResponseBuilder()
+                              .WithAccessToken(base::SysNSStringToUTF8(token))
+                              .WithExpirationTime(expiration_date)
+                              .build());
   } else {
     FireOnGetTokenFailure(auth_error);
   }
diff --git a/components/signin/internal/identity_manager/ubertoken_fetcher_impl_unittest.cc b/components/signin/internal/identity_manager/ubertoken_fetcher_impl_unittest.cc
index 94b8215..1009c62 100644
--- a/components/signin/internal/identity_manager/ubertoken_fetcher_impl_unittest.cc
+++ b/components/signin/internal/identity_manager/ubertoken_fetcher_impl_unittest.cc
@@ -18,6 +18,8 @@
 
 namespace {
 
+using TokenResponseBuilder = OAuth2AccessTokenConsumer::TokenResponse::Builder;
+
 const char kTestAccountId[] = "test@gmail.com";
 
 class MockUbertokenConsumer {
@@ -76,9 +78,8 @@
 TEST_F(UbertokenFetcherImplTest, Basic) {}
 
 TEST_F(UbertokenFetcherImplTest, Success) {
-  fetcher_->OnGetTokenSuccess(nullptr,
-                              OAuth2AccessTokenConsumer::TokenResponse(
-                                  "accessToken", base::Time(), std::string()));
+  fetcher_->OnGetTokenSuccess(
+      nullptr, TokenResponseBuilder().WithAccessToken("accessToken").build());
   fetcher_->OnUberAuthTokenSuccess("uberToken");
 
   EXPECT_EQ(0, consumer_.nb_error_);
@@ -105,9 +106,8 @@
 
 TEST_F(UbertokenFetcherImplTest, TransientFailureEventualFailure) {
   GoogleServiceAuthError error(GoogleServiceAuthError::CONNECTION_FAILED);
-  fetcher_->OnGetTokenSuccess(nullptr,
-                              OAuth2AccessTokenConsumer::TokenResponse(
-                                  "accessToken", base::Time(), std::string()));
+  fetcher_->OnGetTokenSuccess(
+      nullptr, TokenResponseBuilder().WithAccessToken("accessToken").build());
 
   for (int i = 0; i < signin::UbertokenFetcherImpl::kMaxRetries; ++i) {
     fetcher_->OnUberAuthTokenFailure(error);
@@ -124,9 +124,8 @@
 
 TEST_F(UbertokenFetcherImplTest, TransientFailureEventualSuccess) {
   GoogleServiceAuthError error(GoogleServiceAuthError::CONNECTION_FAILED);
-  fetcher_->OnGetTokenSuccess(nullptr,
-                              OAuth2AccessTokenConsumer::TokenResponse(
-                                  "accessToken", base::Time(), std::string()));
+  fetcher_->OnGetTokenSuccess(
+      nullptr, TokenResponseBuilder().WithAccessToken("accessToken").build());
 
   for (int i = 0; i < signin::UbertokenFetcherImpl::kMaxRetries; ++i) {
     fetcher_->OnUberAuthTokenFailure(error);
@@ -142,9 +141,8 @@
 }
 
 TEST_F(UbertokenFetcherImplTest, PermanentFailureEventualFailure) {
-  fetcher_->OnGetTokenSuccess(nullptr,
-                              OAuth2AccessTokenConsumer::TokenResponse(
-                                  "accessToken", base::Time(), std::string()));
+  fetcher_->OnGetTokenSuccess(
+      nullptr, TokenResponseBuilder().WithAccessToken("accessToken").build());
 
   GoogleServiceAuthError error(GoogleServiceAuthError::USER_NOT_SIGNED_UP);
   fetcher_->OnUberAuthTokenFailure(error);
@@ -152,9 +150,9 @@
   EXPECT_EQ(0, consumer_.nb_correct_token_);
   EXPECT_EQ("", consumer_.last_token_);
 
-  fetcher_->OnGetTokenSuccess(nullptr,
-                              OAuth2AccessTokenConsumer::TokenResponse(
-                                  "accessToken", base::Time(), std::string()));
+  fetcher_->OnGetTokenSuccess(
+      nullptr, TokenResponseBuilder().WithAccessToken("accessToken").build());
+
   fetcher_->OnUberAuthTokenFailure(error);
   EXPECT_EQ(1, consumer_.nb_error_);
   EXPECT_EQ(0, consumer_.nb_correct_token_);
@@ -163,18 +161,18 @@
 
 TEST_F(UbertokenFetcherImplTest, PermanentFailureEventualSuccess) {
   GoogleServiceAuthError error(GoogleServiceAuthError::USER_NOT_SIGNED_UP);
-  fetcher_->OnGetTokenSuccess(nullptr,
-                              OAuth2AccessTokenConsumer::TokenResponse(
-                                  "accessToken", base::Time(), std::string()));
+
+  fetcher_->OnGetTokenSuccess(
+      nullptr, TokenResponseBuilder().WithAccessToken("accessToken").build());
 
   fetcher_->OnUberAuthTokenFailure(error);
   EXPECT_EQ(0, consumer_.nb_error_);
   EXPECT_EQ(0, consumer_.nb_correct_token_);
   EXPECT_EQ("", consumer_.last_token_);
 
-  fetcher_->OnGetTokenSuccess(nullptr,
-                              OAuth2AccessTokenConsumer::TokenResponse(
-                                  "accessToken", base::Time(), std::string()));
+  fetcher_->OnGetTokenSuccess(
+      nullptr, TokenResponseBuilder().WithAccessToken("accessToken").build());
+
   fetcher_->OnUberAuthTokenSuccess("uberToken");
   EXPECT_EQ(0, consumer_.nb_error_);
   EXPECT_EQ(1, consumer_.nb_correct_token_);
diff --git a/components/signin/public/identity_manager/access_token_fetcher_unittest.cc b/components/signin/public/identity_manager/access_token_fetcher_unittest.cc
index c541f24..5af56a6 100644
--- a/components/signin/public/identity_manager/access_token_fetcher_unittest.cc
+++ b/components/signin/public/identity_manager/access_token_fetcher_unittest.cc
@@ -32,6 +32,8 @@
 
 namespace {
 
+using TokenResponseBuilder = OAuth2AccessTokenConsumer::TokenResponse::Builder;
+
 const char kTestGaiaId[] = "dummyId";
 const char kTestGaiaId2[] = "dummyId2";
 const char kTestEmail[] = "me@gmail.com";
@@ -148,10 +150,11 @@
                             access_token_info()));
 
   token_service()->IssueAllTokensForAccount(
-      account_id,
-      OAuth2AccessTokenConsumer::TokenResponse(
-          access_token_info().token, access_token_info().expiration_time,
-          access_token_info().id_token));
+      account_id, TokenResponseBuilder()
+                      .WithAccessToken(access_token_info().token)
+                      .WithExpirationTime(access_token_info().expiration_time)
+                      .WithIdToken(access_token_info().id_token)
+                      .build());
 }
 
 TEST_F(AccessTokenFetcherTest,
@@ -178,10 +181,11 @@
                             access_token_info()));
 
   token_service()->IssueAllTokensForAccount(
-      account_id,
-      OAuth2AccessTokenConsumer::TokenResponse(
-          access_token_info().token, access_token_info().expiration_time,
-          access_token_info().id_token));
+      account_id, TokenResponseBuilder()
+                      .WithAccessToken(access_token_info().token)
+                      .WithExpirationTime(access_token_info().expiration_time)
+                      .WithIdToken(access_token_info().id_token)
+                      .build());
 }
 
 TEST_F(AccessTokenFetcherTest,
@@ -201,11 +205,14 @@
 
   // Before the refresh token is available, the callback shouldn't get called.
   EXPECT_CALL(callback, Run(_, _)).Times(0);
-  token_service()->IssueAllTokensForAccount(
-      account_id,
-      OAuth2AccessTokenConsumer::TokenResponse(
-          access_token_info().token, access_token_info().expiration_time,
-          access_token_info().id_token));
+  {
+    token_service()->IssueAllTokensForAccount(
+        account_id, TokenResponseBuilder()
+                        .WithAccessToken(access_token_info().token)
+                        .WithExpirationTime(access_token_info().expiration_time)
+                        .WithIdToken(access_token_info().id_token)
+                        .build());
+  }
 
   // Once the refresh token becomes available, we should get an access token
   // request.
@@ -218,11 +225,14 @@
   EXPECT_CALL(callback, Run(GoogleServiceAuthError::AuthErrorNone(),
                             access_token_info()));
 
-  token_service()->IssueAllTokensForAccount(
-      account_id,
-      OAuth2AccessTokenConsumer::TokenResponse(
-          access_token_info().token, access_token_info().expiration_time,
-          access_token_info().id_token));
+  {
+    token_service()->IssueAllTokensForAccount(
+        account_id, TokenResponseBuilder()
+                        .WithAccessToken(access_token_info().token)
+                        .WithExpirationTime(access_token_info().expiration_time)
+                        .WithIdToken(access_token_info().id_token)
+                        .build());
+  }
 }
 
 TEST_F(AccessTokenFetcherTest,
@@ -269,10 +279,11 @@
 
   // Now fulfilling the access token request should have no effect.
   token_service()->IssueAllTokensForAccount(
-      account_id,
-      OAuth2AccessTokenConsumer::TokenResponse(
-          access_token_info().token, access_token_info().expiration_time,
-          access_token_info().id_token));
+      account_id, TokenResponseBuilder()
+                      .WithAccessToken(access_token_info().token)
+                      .WithExpirationTime(access_token_info().expiration_time)
+                      .WithIdToken(access_token_info().id_token)
+                      .build());
 }
 
 TEST_F(AccessTokenFetcherTest, ReturnsErrorWhenAccountIsUnknown) {
@@ -424,10 +435,11 @@
   EXPECT_CALL(callback2, Run(GoogleServiceAuthError::AuthErrorNone(),
                              access_token_info()));
   token_service()->IssueAllTokensForAccount(
-      account_id,
-      OAuth2AccessTokenConsumer::TokenResponse(
-          access_token_info().token, access_token_info().expiration_time,
-          access_token_info().id_token));
+      account_id, TokenResponseBuilder()
+                      .WithAccessToken(access_token_info().token)
+                      .WithExpirationTime(access_token_info().expiration_time)
+                      .WithIdToken(access_token_info().id_token)
+                      .build());
 }
 
 TEST_F(AccessTokenFetcherTest, MultipleRequestsForDifferentAccountsFulfilled) {
@@ -459,21 +471,28 @@
   // called back with the access token.
   EXPECT_CALL(callback, Run(GoogleServiceAuthError::AuthErrorNone(),
                             access_token_info()));
-  token_service()->IssueAllTokensForAccount(
-      account_id,
-      OAuth2AccessTokenConsumer::TokenResponse(
-          access_token_info().token, access_token_info().expiration_time,
-          access_token_info().id_token));
+  {
+    token_service()->IssueAllTokensForAccount(
+        account_id, TokenResponseBuilder()
+                        .WithAccessToken(access_token_info().token)
+                        .WithExpirationTime(access_token_info().expiration_time)
+                        .WithIdToken(access_token_info().id_token)
+                        .build());
+  }
 
   // Once the second access token request is fulfilled, it should get
   // called back with the access token.
   EXPECT_CALL(callback2, Run(GoogleServiceAuthError::AuthErrorNone(),
                              access_token_info()));
-  token_service()->IssueAllTokensForAccount(
-      account_id2,
-      OAuth2AccessTokenConsumer::TokenResponse(
-          access_token_info().token, access_token_info().expiration_time,
-          access_token_info().id_token));
+  {
+    token_service()->IssueAllTokensForAccount(
+        account_id2,
+        TokenResponseBuilder()
+            .WithAccessToken(access_token_info().token)
+            .WithExpirationTime(access_token_info().expiration_time)
+            .WithIdToken(access_token_info().id_token)
+            .build());
+  }
 }
 
 TEST_F(AccessTokenFetcherTest,
@@ -525,10 +544,11 @@
               Run(GoogleServiceAuthError::AuthErrorNone(), access_token_info()))
       .WillOnce(testing::InvokeWithoutArgs(&run_loop4, &base::RunLoop::Quit));
   token_service()->IssueAllTokensForAccount(
-      account_id2,
-      OAuth2AccessTokenConsumer::TokenResponse(
-          access_token_info().token, access_token_info().expiration_time,
-          access_token_info().id_token));
+      account_id2, TokenResponseBuilder()
+                       .WithAccessToken(access_token_info().token)
+                       .WithExpirationTime(access_token_info().expiration_time)
+                       .WithIdToken(access_token_info().id_token)
+                       .build());
 
   run_loop4.Run();
 }
@@ -566,12 +586,12 @@
   // with the access token.
   EXPECT_CALL(callback, Run(GoogleServiceAuthError::AuthErrorNone(),
                             access_token_info()));
-
   token_service()->IssueAllTokensForAccount(
-      account_id,
-      OAuth2AccessTokenConsumer::TokenResponse(
-          access_token_info().token, access_token_info().expiration_time,
-          access_token_info().id_token));
+      account_id, TokenResponseBuilder()
+                      .WithAccessToken(access_token_info().token)
+                      .WithExpirationTime(access_token_info().expiration_time)
+                      .WithIdToken(access_token_info().id_token)
+                      .build());
 
   // Now add a second account and request an access token for it to test
   // that the default URLLoaderFactory is used if none is specified.
@@ -605,10 +625,11 @@
   EXPECT_CALL(callback2, Run(GoogleServiceAuthError::AuthErrorNone(),
                              access_token_info()));
   token_service()->IssueAllTokensForAccount(
-      account_id2,
-      OAuth2AccessTokenConsumer::TokenResponse(
-          access_token_info().token, access_token_info().expiration_time,
-          access_token_info().id_token));
+      account_id2, TokenResponseBuilder()
+                       .WithAccessToken(access_token_info().token)
+                       .WithExpirationTime(access_token_info().expiration_time)
+                       .WithIdToken(access_token_info().id_token)
+                       .build());
 }
 
 }  // namespace signin
diff --git a/components/signin/public/identity_manager/identity_test_environment.cc b/components/signin/public/identity_manager/identity_test_environment.cc
index d56cbc2..aca7b97 100644
--- a/components/signin/public/identity_manager/identity_test_environment.cc
+++ b/components/signin/public/identity_manager/identity_test_environment.cc
@@ -51,6 +51,8 @@
 #include "components/signin/internal/identity_manager/accounts_mutator_impl.h"
 #endif
 
+using TokenResponseBuilder = OAuth2AccessTokenConsumer::TokenResponse::Builder;
+
 namespace signin {
 
 class IdentityManagerDependenciesOwner {
@@ -476,7 +478,11 @@
         const std::string& id_token) {
   WaitForAccessTokenRequestIfNecessary(base::nullopt);
   fake_token_service()->IssueTokenForAllPendingRequests(
-      OAuth2AccessTokenConsumer::TokenResponse(token, expiration, id_token));
+      TokenResponseBuilder()
+          .WithAccessToken(token)
+          .WithExpirationTime(expiration)
+          .WithIdToken(id_token)
+          .build());
 }
 
 void IdentityTestEnvironment::
@@ -487,8 +493,11 @@
         const std::string& id_token) {
   WaitForAccessTokenRequestIfNecessary(account_id);
   fake_token_service()->IssueAllTokensForAccount(
-      account_id,
-      OAuth2AccessTokenConsumer::TokenResponse(token, expiration, id_token));
+      account_id, TokenResponseBuilder()
+                      .WithAccessToken(token)
+                      .WithExpirationTime(expiration)
+                      .WithIdToken(id_token)
+                      .build());
 }
 
 void IdentityTestEnvironment::
@@ -498,9 +507,12 @@
         const std::string& id_token,
         const ScopeSet& scopes) {
   WaitForAccessTokenRequestIfNecessary(base::nullopt);
-  fake_token_service()->IssueTokenForScope(
-      scopes,
-      OAuth2AccessTokenConsumer::TokenResponse(token, expiration, id_token));
+  fake_token_service()->IssueTokenForScope(scopes,
+                                           TokenResponseBuilder()
+                                               .WithAccessToken(token)
+                                               .WithExpirationTime(expiration)
+                                               .WithIdToken(id_token)
+                                               .build());
 }
 
 void IdentityTestEnvironment::
diff --git a/components/sync/BUILD.gn b/components/sync/BUILD.gn
index fb8dc3637..6e8b16c 100644
--- a/components/sync/BUILD.gn
+++ b/components/sync/BUILD.gn
@@ -477,6 +477,7 @@
     "protocol/proto_enum_conversions_unittest.cc",
     "protocol/proto_value_conversions_unittest.cc",
     "trusted_vault/download_keys_response_handler_unittest.cc",
+    "trusted_vault/proto_string_bytes_conversion_unittest.cc",
     "trusted_vault/securebox_unittest.cc",
     "trusted_vault/standalone_trusted_vault_backend_unittest.cc",
     "trusted_vault/trusted_vault_access_token_fetcher_frontend_unittest.cc",
diff --git a/components/sync/trusted_vault/BUILD.gn b/components/sync/trusted_vault/BUILD.gn
index fb4341b..718d164 100644
--- a/components/sync/trusted_vault/BUILD.gn
+++ b/components/sync/trusted_vault/BUILD.gn
@@ -6,6 +6,8 @@
   sources = [
     "download_keys_response_handler.cc",
     "download_keys_response_handler.h",
+    "proto_string_bytes_conversion.cc",
+    "proto_string_bytes_conversion.h",
     "securebox.cc",
     "securebox.h",
     "standalone_trusted_vault_backend.cc",
diff --git a/components/sync/trusted_vault/download_keys_response_handler.cc b/components/sync/trusted_vault/download_keys_response_handler.cc
index b733635..ffc72ac 100644
--- a/components/sync/trusted_vault/download_keys_response_handler.cc
+++ b/components/sync/trusted_vault/download_keys_response_handler.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "components/sync/protocol/vault.pb.h"
+#include "components/sync/trusted_vault/proto_string_bytes_conversion.h"
 #include "components/sync/trusted_vault/securebox.h"
 #include "crypto/hmac.h"
 
@@ -71,20 +72,17 @@
     const SecureBoxPrivateKey& member_private_key) {
   std::vector<ExtractedSharedKey> result;
   for (const sync_pb::SharedKey& shared_key : member.keys()) {
-    std::vector<uint8_t> wrapped_key(shared_key.wrapped_key().begin(),
-                                     shared_key.wrapped_key().end());
     base::Optional<std::vector<uint8_t>> decrypted_key =
-        member_private_key.Decrypt(base::span<const uint8_t>(),
-                                   kWrappedKeyHeader, wrapped_key);
+        member_private_key.Decrypt(
+            base::span<const uint8_t>(), kWrappedKeyHeader,
+            /*encrypted_payload=*/ProtoStringToBytes(shared_key.wrapped_key()));
     if (!decrypted_key.has_value()) {
       // Decryption failed.
       return std::vector<ExtractedSharedKey>();
     }
-    result.push_back(
-        ExtractedSharedKey{/*version=*/shared_key.epoch(), *decrypted_key,
-                           /*key_proof=*/
-                           std::vector<uint8_t>(shared_key.key_proof().begin(),
-                                                shared_key.key_proof().end())});
+    result.push_back(ExtractedSharedKey{
+        /*version=*/shared_key.epoch(), *decrypted_key,
+        /*key_proof=*/ProtoStringToBytes(shared_key.key_proof())});
   }
 
   std::sort(result.begin(), result.end(),
diff --git a/components/sync/trusted_vault/download_keys_response_handler_unittest.cc b/components/sync/trusted_vault/download_keys_response_handler_unittest.cc
index 608fc17..6a1f229 100644
--- a/components/sync/trusted_vault/download_keys_response_handler_unittest.cc
+++ b/components/sync/trusted_vault/download_keys_response_handler_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/strings/string_number_conversions.h"
 #include "components/sync/protocol/vault.pb.h"
+#include "components/sync/trusted_vault/proto_string_bytes_conversion.h"
 #include "components/sync/trusted_vault/securebox.h"
 #include "crypto/hmac.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -28,11 +29,6 @@
                                      'e', 'd', '_', 'k', 'e', 'y'};
 const char kSecurityDomainName[] = "chromesync";
 
-void AssignBytesToProtoString(base::span<const uint8_t> bytes,
-                              std::string* bytes_proto_field) {
-  *bytes_proto_field = std::string(bytes.begin(), bytes.end());
-}
-
 std::unique_ptr<SecureBoxKeyPair> MakeTestKeyPair() {
   std::vector<uint8_t> private_key_bytes;
   bool success = base::HexStringToBytes(kEncodedPrivateKey, &private_key_bytes);
@@ -50,7 +46,6 @@
   DCHECK_EQ(trusted_vault_keys.size(), trusted_vault_keys_versions.size());
   DCHECK_EQ(trusted_vault_keys.size(), signing_keys.size());
 
-  std::vector<uint8_t> public_key_bytes = public_key.ExportToBytes();
   AssignBytesToProtoString(public_key.ExportToBytes(),
                            member->mutable_public_key());
 
diff --git a/components/sync/trusted_vault/proto_string_bytes_conversion.cc b/components/sync/trusted_vault/proto_string_bytes_conversion.cc
new file mode 100644
index 0000000..d1d880f0
--- /dev/null
+++ b/components/sync/trusted_vault/proto_string_bytes_conversion.cc
@@ -0,0 +1,18 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/sync/trusted_vault/proto_string_bytes_conversion.h"
+
+namespace syncer {
+
+void AssignBytesToProtoString(base::span<const uint8_t> bytes,
+                              std::string* bytes_proto_field) {
+  *bytes_proto_field = std::string(bytes.begin(), bytes.end());
+}
+
+std::vector<uint8_t> ProtoStringToBytes(const std::string& bytes_string) {
+  return std::vector<uint8_t>(bytes_string.begin(), bytes_string.end());
+}
+
+}  // namespace syncer
diff --git a/components/sync/trusted_vault/proto_string_bytes_conversion.h b/components/sync/trusted_vault/proto_string_bytes_conversion.h
new file mode 100644
index 0000000..a2accd4
--- /dev/null
+++ b/components/sync/trusted_vault/proto_string_bytes_conversion.h
@@ -0,0 +1,28 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SYNC_TRUSTED_VAULT_PROTO_STRING_BYTES_CONVERSION_H_
+#define COMPONENTS_SYNC_TRUSTED_VAULT_PROTO_STRING_BYTES_CONVERSION_H_
+
+#include <string>
+#include <vector>
+
+#include "base/containers/span.h"
+
+namespace syncer {
+
+// Helper function for filling protobuf bytes field: protobuf represent them as
+// std::string, while in code std::vector<uint8_t> or base::span<uint8_t> is
+// more common.
+void AssignBytesToProtoString(base::span<const uint8_t> bytes,
+                              std::string* bytes_proto_field);
+
+// Helper function for converting protobuf bytes fields (that are represented as
+// std::string) to std::vector<uint8_t>, that is more convenient representation
+// of bytes in the code.
+std::vector<uint8_t> ProtoStringToBytes(const std::string& bytes_string);
+
+}  // namespace syncer
+
+#endif  // COMPONENTS_SYNC_TRUSTED_VAULT_PROTO_STRING_BYTES_CONVERSION_H_
diff --git a/components/sync/trusted_vault/proto_string_bytes_conversion_unittest.cc b/components/sync/trusted_vault/proto_string_bytes_conversion_unittest.cc
new file mode 100644
index 0000000..cf05882
--- /dev/null
+++ b/components/sync/trusted_vault/proto_string_bytes_conversion_unittest.cc
@@ -0,0 +1,30 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/sync/trusted_vault/proto_string_bytes_conversion.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+using testing::ElementsAre;
+using testing::Eq;
+
+TEST(ProtoStringBytesConversionTest, ShouldAssignBytesToProtoString) {
+  std::string proto_string;
+  AssignBytesToProtoString(std::vector<uint8_t>{65, 66, 67}, &proto_string);
+  EXPECT_THAT(proto_string, Eq("ABC"));
+}
+
+TEST(ProtoStringBytesConversionTest, ShouldConvertProtoStringToBytes) {
+  const std::string proto_string = "ABC";
+  EXPECT_THAT(ProtoStringToBytes(proto_string), ElementsAre(65, 66, 67));
+}
+
+}  // namespace
+
+}  // namespace syncer
diff --git a/components/sync/trusted_vault/standalone_trusted_vault_backend.cc b/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
index fa8ea11..d21743f 100644
--- a/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
+++ b/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
@@ -18,6 +18,7 @@
 #include "components/os_crypt/os_crypt.h"
 #include "components/sync/base/time.h"
 #include "components/sync/driver/sync_driver_switches.h"
+#include "components/sync/trusted_vault/proto_string_bytes_conversion.h"
 #include "components/sync/trusted_vault/securebox.h"
 
 namespace syncer {
@@ -104,9 +105,9 @@
   DCHECK(!ongoing_connection_request_);
 
   std::unique_ptr<SecureBoxKeyPair> key_pair =
-      SecureBoxKeyPair::CreateByPrivateKeyImport(base::as_bytes(
-          base::make_span(per_user_vault->local_device_registration_info()
-                              .private_key_material())));
+      SecureBoxKeyPair::CreateByPrivateKeyImport(
+          ProtoStringToBytes(per_user_vault->local_device_registration_info()
+                                 .private_key_material()));
   if (!key_pair) {
     // Corrupted state: device is registered, but |key_pair| can't be imported.
     // TODO(crbug.com/1094326): restore from this state (throw away the key and
@@ -122,14 +123,12 @@
     return;
   }
 
-  std::string last_key = per_user_vault->vault_key()
-                             .at(per_user_vault->vault_key_size() - 1)
-                             .key_material();
-  std::vector<uint8_t> last_key_bytes(last_key.begin(), last_key.end());
   // |this| outlives |connection_| and |ongoing_connection_request_|, so it's
   // safe to use base::Unretained() here.
   ongoing_connection_request_ = connection_->DownloadKeys(
-      *primary_account_, last_key_bytes,
+      *primary_account_,
+      /*last_trusted_vault_key=*/
+      ProtoStringToBytes(per_user_vault->vault_key().rbegin()->key_material()),
       per_user_vault->last_vault_key_version(), std::move(key_pair),
       base::BindOnce(&StandaloneTrustedVaultBackend::OnKeysDownloaded,
                      base::Unretained(this), account_info.gaia));
@@ -152,7 +151,8 @@
   per_user_vault->set_keys_are_stale(false);
   per_user_vault->clear_vault_key();
   for (const std::vector<uint8_t>& key : keys) {
-    per_user_vault->add_vault_key()->set_key_material(key.data(), key.size());
+    AssignBytesToProtoString(
+        key, per_user_vault->add_vault_key()->mutable_key_material());
   }
 
   WriteToDisk(data_, file_path_);
@@ -265,9 +265,10 @@
 
   std::unique_ptr<SecureBoxKeyPair> key_pair;
   if (per_user_vault->has_local_device_registration_info()) {
-    key_pair = SecureBoxKeyPair::CreateByPrivateKeyImport(base::as_bytes(
-        base::make_span(per_user_vault->local_device_registration_info()
-                            .private_key_material())));
+    key_pair = SecureBoxKeyPair::CreateByPrivateKeyImport(
+        /*private_key_bytes=*/ProtoStringToBytes(
+            per_user_vault->local_device_registration_info()
+                .private_key_material()));
     if (!key_pair) {
       // Device key is corrupted.
       // TODO(crbug.com/1102340): consider generation of new key in this case.
@@ -280,26 +281,22 @@
     // or registration callback is cancelled). To avoid duplicated registrations
     // device key is stored before sending the registration request, so the same
     // key will be used for future registration attempts.
-    std::vector<uint8_t> serialized_private_key =
-        key_pair->private_key().ExportToBytes();
-    per_user_vault->mutable_local_device_registration_info()
-        ->set_private_key_material(serialized_private_key.data(),
-                                   serialized_private_key.size());
+    AssignBytesToProtoString(
+        key_pair->private_key().ExportToBytes(),
+        per_user_vault->mutable_local_device_registration_info()
+            ->mutable_private_key_material());
     WriteToDisk(data_, file_path_);
   }
 
-  std::string last_key = per_user_vault->vault_key()
-                             .at(per_user_vault->vault_key_size() - 1)
-                             .key_material();
-  std::vector<uint8_t> last_key_bytes(last_key.begin(), last_key.end());
-
   // Cancel existing callbacks passed to |connection_| to ensure there is only
   // one ongoing request.
   AbandonConnectionRequest();
   // |this| outlives |connection_| and |ongoing_connection_request_|, so it's
   // safe to use base::Unretained() here.
   ongoing_connection_request_ = connection_->RegisterAuthenticationFactor(
-      *primary_account_, last_key_bytes,
+      *primary_account_,
+      /*last_trusted_vault_key=*/
+      ProtoStringToBytes(per_user_vault->vault_key().rbegin()->key_material()),
       per_user_vault->last_vault_key_version(), key_pair->public_key(),
       base::BindOnce(&StandaloneTrustedVaultBackend::OnDeviceRegistered,
                      base::Unretained(this), gaia_id));
@@ -399,8 +396,7 @@
   if (per_user_vault) {
     for (const sync_pb::LocalTrustedVaultKey& key :
          per_user_vault->vault_key()) {
-      const std::string& key_material = key.key_material();
-      vault_keys.emplace_back(key_material.begin(), key_material.end());
+      vault_keys.emplace_back(ProtoStringToBytes(key.key_material()));
     }
   }
 
diff --git a/components/sync/trusted_vault/trusted_vault_connection_impl.cc b/components/sync/trusted_vault/trusted_vault_connection_impl.cc
index 15311fdc..2d4f4e3 100644
--- a/components/sync/trusted_vault/trusted_vault_connection_impl.cc
+++ b/components/sync/trusted_vault/trusted_vault_connection_impl.cc
@@ -10,6 +10,7 @@
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/sync/protocol/vault.pb.h"
 #include "components/sync/trusted_vault/download_keys_response_handler.h"
+#include "components/sync/trusted_vault/proto_string_bytes_conversion.h"
 #include "components/sync/trusted_vault/securebox.h"
 #include "components/sync/trusted_vault/trusted_vault_access_token_fetcher.h"
 #include "components/sync/trusted_vault/trusted_vault_request.h"
@@ -27,17 +28,6 @@
 const char kListSecurityDomainsURLPathAndQuery[] = "/domain:list?view=1";
 const char kSecurityDomainName[] = "chromesync";
 
-// Helper function for filling protobuf bytes field: protobuf represent them as
-// std::string, while in code std::vector<uint8_t> or base::span<uint8_t> is
-// more common.
-// TODO(crbug.com/1113598): this function and its counterpart (proto string to
-// bytes vector) is useful in many places under trusted_vault directory,
-// consider moving it to some utility file.
-void AssignBytesToProtoString(base::span<const uint8_t> bytes,
-                              std::string* bytes_proto_field) {
-  *bytes_proto_field = std::string(bytes.begin(), bytes.end());
-}
-
 void ProcessRegisterDeviceResponse(
     TrustedVaultConnection::RegisterAuthenticationFactorCallback callback,
     TrustedVaultRequest::HttpStatus http_status,
diff --git a/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc b/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc
index a8023b0..edaea6c 100644
--- a/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc
+++ b/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc
@@ -14,6 +14,7 @@
 #include "components/signin/public/identity_manager/access_token_info.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/sync/protocol/vault.pb.h"
+#include "components/sync/trusted_vault/proto_string_bytes_conversion.h"
 #include "components/sync/trusted_vault/securebox.h"
 #include "components/sync/trusted_vault/trusted_vault_access_token_fetcher.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
@@ -169,21 +170,17 @@
 
   ASSERT_THAT(security_domain.members(), SizeIs(1));
   const sync_pb::SecurityDomain::Member& member = security_domain.members(0);
-  const std::vector<uint8_t> member_public_key_bytes(
-      member.public_key().begin(), member.public_key().end());
-  EXPECT_THAT(member_public_key_bytes,
+  EXPECT_THAT(ProtoStringToBytes(member.public_key()),
               Eq(key_pair->public_key().ExportToBytes()));
 
   ASSERT_THAT(member.keys(), SizeIs(1));
   const sync_pb::SharedKey& shared_key = member.keys(0);
   EXPECT_THAT(shared_key.epoch(), Eq(kLastKeyVersion));
 
-  const std::vector<uint8_t> wrapped_key_bytes(shared_key.wrapped_key().begin(),
-                                               shared_key.wrapped_key().end());
   base::Optional<std::vector<uint8_t>> decrypted_trusted_vault_key =
       key_pair->private_key().Decrypt(
           /*shared_key=*/base::span<const uint8_t>(), kWrappedKeyHeader,
-          /*encrypted_payload=*/wrapped_key_bytes);
+          /*encrypted_payload=*/ProtoStringToBytes(shared_key.wrapped_key()));
   ASSERT_THAT(decrypted_trusted_vault_key, Ne(base::nullopt));
   EXPECT_THAT(*decrypted_trusted_vault_key, Eq(kTrustedVaultKey));
 
diff --git a/content/browser/android/synchronous_compositor_host.cc b/content/browser/android/synchronous_compositor_host.cc
index 169259e..4b6a995 100644
--- a/content/browser/android/synchronous_compositor_host.cc
+++ b/content/browser/android/synchronous_compositor_host.cc
@@ -103,11 +103,17 @@
   void ReturnFrame(
       uint32_t layer_tree_frame_sink_id,
       uint32_t metadata_version,
+      const base::Optional<viz::LocalSurfaceId>& local_surface_id,
       base::Optional<viz::CompositorFrame> frame,
       base::Optional<viz::HitTestRegionList> hit_test_region_list) override {
-    if (!bridge_->ReceiveFrameOnIOThread(layer_tree_frame_sink_id,
-                                         metadata_version, std::move(frame),
-                                         std::move(hit_test_region_list))) {
+    if (frame && (!local_surface_id || !local_surface_id->is_valid())) {
+      bad_message::ReceivedBadMessage(
+          process_id_, bad_message::SYNC_COMPOSITOR_NO_LOCAL_SURFACE_ID);
+      return;
+    }
+    if (!bridge_->ReceiveFrameOnIOThread(
+            layer_tree_frame_sink_id, metadata_version, local_surface_id,
+            std::move(frame), std::move(hit_test_region_list))) {
       bad_message::ReceivedBadMessage(
           process_id_, bad_message::SYNC_COMPOSITOR_NO_FUTURE_FRAME);
     }
@@ -192,8 +198,7 @@
     const gfx::Rect& viewport_rect_for_tile_priority,
     const gfx::Transform& transform_for_tile_priority) {
   invalidate_needs_draw_ = false;
-  scoped_refptr<FrameFuture> frame_future =
-      new FrameFuture(rwhva_->GetLocalSurfaceId());
+  scoped_refptr<FrameFuture> frame_future = new FrameFuture();
   if (!allow_async_draw_) {
     allow_async_draw_ = allow_async_draw_ || IsReadyForSynchronousCall();
     auto frame_ptr = std::make_unique<Frame>();
@@ -227,6 +232,7 @@
           transform_for_tile_priority);
   uint32_t layer_tree_frame_sink_id;
   uint32_t metadata_version = 0u;
+  base::Optional<viz::LocalSurfaceId> local_surface_id;
   base::Optional<viz::CompositorFrame> compositor_frame;
   base::Optional<viz::HitTestRegionList> hit_test_region_list;
   blink::mojom::SyncCompositorCommonRendererParamsPtr common_renderer_params;
@@ -238,20 +244,30 @@
     if (!IsReadyForSynchronousCall() ||
         !GetSynchronousCompositor()->DemandDrawHw(
             std::move(params), &common_renderer_params,
-            &layer_tree_frame_sink_id, &metadata_version, &compositor_frame,
-            &hit_test_region_list)) {
+            &layer_tree_frame_sink_id, &metadata_version, &local_surface_id,
+            &compositor_frame, &hit_test_region_list)) {
       return SynchronousCompositor::Frame();
     }
   }
 
   UpdateState(std::move(common_renderer_params));
 
-  if (!compositor_frame)
+  if (compositor_frame) {
+    if (!local_surface_id || !local_surface_id->is_valid()) {
+      bad_message::ReceivedBadMessage(
+          rwhva_->GetRenderWidgetHost()->GetProcess()->GetID(),
+          bad_message::SYNC_COMPOSITOR_NO_LOCAL_SURFACE_ID);
+      return SynchronousCompositor::Frame();
+    }
+  } else {
     return SynchronousCompositor::Frame();
+  }
 
   SynchronousCompositor::Frame frame;
   frame.frame.reset(new viz::CompositorFrame);
   frame.layer_tree_frame_sink_id = layer_tree_frame_sink_id;
+  if (local_surface_id)
+    frame.local_surface_id = local_surface_id.value();
   *frame.frame = std::move(*compositor_frame);
   frame.hit_test_region_list = std::move(hit_test_region_list);
   UpdateFrameMetaData(metadata_version, frame.frame->metadata.Clone());
diff --git a/content/browser/android/synchronous_compositor_sync_call_bridge.cc b/content/browser/android/synchronous_compositor_sync_call_bridge.cc
index cd94469..34fe0313 100644
--- a/content/browser/android/synchronous_compositor_sync_call_bridge.cc
+++ b/content/browser/android/synchronous_compositor_sync_call_bridge.cc
@@ -40,6 +40,7 @@
 bool SynchronousCompositorSyncCallBridge::ReceiveFrameOnIOThread(
     int layer_tree_frame_sink_id,
     uint32_t metadata_version,
+    base::Optional<viz::LocalSurfaceId> local_surface_id,
     base::Optional<viz::CompositorFrame> compositor_frame,
     base::Optional<viz::HitTestRegionList> hit_test_region_list) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
@@ -54,6 +55,7 @@
   frame_futures_.pop_front();
 
   if (compositor_frame) {
+    DCHECK(local_surface_id);
     GetUIThreadTaskRunner({})->PostTask(
         FROM_HERE, base::BindOnce(&SynchronousCompositorSyncCallBridge::
                                       ProcessFrameMetadataOnUIThread,
@@ -61,6 +63,7 @@
                                   compositor_frame->metadata.Clone()));
     frame_ptr->frame.reset(new viz::CompositorFrame);
     *frame_ptr->frame = std::move(*compositor_frame);
+    frame_ptr->local_surface_id = local_surface_id.value();
     frame_ptr->hit_test_region_list = std::move(hit_test_region_list);
   }
   future->SetFrame(std::move(frame_ptr));
diff --git a/content/browser/android/synchronous_compositor_sync_call_bridge.h b/content/browser/android/synchronous_compositor_sync_call_bridge.h
index 262200f8..1699c750 100644
--- a/content/browser/android/synchronous_compositor_sync_call_bridge.h
+++ b/content/browser/android/synchronous_compositor_sync_call_bridge.h
@@ -82,6 +82,7 @@
   bool ReceiveFrameOnIOThread(
       int frame_sink_id,
       uint32_t metadata_version,
+      base::Optional<viz::LocalSurfaceId> local_surface_id,
       base::Optional<viz::CompositorFrame>,
       base::Optional<viz::HitTestRegionList> hit_test_region_list);
 
diff --git a/content/browser/background_fetch/storage/delete_registration_task.cc b/content/browser/background_fetch/storage/delete_registration_task.cc
index d738ff2..d4a860a 100644
--- a/content/browser/background_fetch/storage/delete_registration_task.cc
+++ b/content/browser/background_fetch/storage/delete_registration_task.cc
@@ -101,6 +101,7 @@
       SetStorageError(BackgroundFetchStorageError::kServiceWorkerStorageError);
       AbandonFetches(service_worker_registration_id_);
       std::move(done_closure).Run();
+      return;
     }
   }
 #endif  // DCHECK_IS_ON()
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index 95738c1..5f85aea 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -264,6 +264,7 @@
   ASGH_ASSOCIATED_INTERFACE_REQUEST = 236,
   ASGH_RECEIVED_CONTROL_MESSAGE = 237,
   CSDH_BAD_OWNER = 238,
+  SYNC_COMPOSITOR_NO_LOCAL_SURFACE_ID = 239,
 
   // Please add new elements here. The naming convention is abbreviated class
   // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index fe8e036..7e81d9e 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -2335,8 +2335,13 @@
 void ChildProcessSecurityPolicyImpl::AddOptInIsolatedOriginForBrowsingInstance(
     const IsolationContext& isolation_context,
     const url::Origin& origin) {
-  DCHECK(IsolatedOriginUtil::IsValidOriginForOptInIsolation(origin))
-      << "Attempting to opt-in invalid origin: " << origin;
+  if (!IsolatedOriginUtil::IsValidOriginForOptInIsolation(origin)) {
+    static auto* invalid_origin_optin_key = base::debug::AllocateCrashKeyString(
+        "invalid_opt_in_origin", base::debug::CrashKeySize::Size256);
+    base::debug::SetCrashKeyString(invalid_origin_optin_key,
+                                   origin.Serialize());
+    base::debug::DumpWithoutCrashing();
+  }
 
   BrowsingInstanceId browsing_instance_id(
       isolation_context.browsing_instance_id());
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 5da79c8..68b2d351 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -8727,25 +8727,7 @@
   // renderer one. The browser will just "push" the correct value.
   if (navigation_request->state() >=
       NavigationRequest::NavigationState::WILL_PROCESS_RESPONSE) {
-    if (params.sandbox_flags != navigation_request->SandboxFlagsToCommit()) {
-      DCHECK(false);
-
-      base::debug::ScopedCrashKeyString scoped_url(
-          base::debug::AllocateCrashKeyString(
-              "url", base::debug::CrashKeySize::Size256),
-          params.url.possibly_invalid_spec());
-      base::debug::ScopedCrashKeyString scoped_sandbox(
-          base::debug::AllocateCrashKeyString(
-              "sandbox", base::debug::CrashKeySize::Size256),
-          base::StringPrintf(
-              "%u, %u", uint32_t(params.sandbox_flags),
-              uint32_t(navigation_request->SandboxFlagsToCommit())));
-      base::debug::SetCrashKeyString(
-          base::debug::AllocateCrashKeyString(
-              "is_main_frame", base::debug::CrashKeySize::Size32),
-          frame_tree_node_->IsMainFrame() ? "true" : "false");
-      base::debug::DumpWithoutCrashing();
-    }
+    DCHECK_EQ(params.sandbox_flags, navigation_request->SandboxFlagsToCommit());
   }
 
   coep_reporter_ = navigation_request->TakeCoepReporter();
diff --git a/content/browser/web_contents/web_contents_impl_unittest.cc b/content/browser/web_contents/web_contents_impl_unittest.cc
index 1e4fa98..6cbf635 100644
--- a/content/browser/web_contents/web_contents_impl_unittest.cc
+++ b/content/browser/web_contents/web_contents_impl_unittest.cc
@@ -604,16 +604,27 @@
   navigation_to_url2->ReadyToCommit();
 
   TestRenderFrameHost* new_rfh = main_test_rfh();
-  EXPECT_FALSE(contents()->CrossProcessNavigationPending());
-  EXPECT_EQ(nullptr, contents()->GetPendingMainFrame());
-  EXPECT_NE(orig_rfh, new_rfh);
-  EXPECT_EQ(orig_rvh_delete_count, 1);
+  if (ShouldSkipEarlyCommitPendingForCrashedFrame()) {
+    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
+    EXPECT_NE(nullptr, contents()->GetPendingMainFrame());
+    EXPECT_EQ(orig_rfh, new_rfh);
+    EXPECT_EQ(orig_rvh_delete_count, 0);
+  } else {
+    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
+    EXPECT_EQ(nullptr, contents()->GetPendingMainFrame());
+    EXPECT_NE(orig_rfh, new_rfh);
+    EXPECT_EQ(orig_rvh_delete_count, 1);
+  }
 
   navigation_to_url2->Commit();
   SiteInstance* instance2 = contents()->GetSiteInstance();
 
   EXPECT_FALSE(contents()->CrossProcessNavigationPending());
-  EXPECT_EQ(new_rfh, main_rfh());
+  if (ShouldSkipEarlyCommitPendingForCrashedFrame()) {
+    EXPECT_NE(new_rfh, main_rfh());
+  } else {
+    EXPECT_EQ(new_rfh, main_rfh());
+  }
   EXPECT_NE(instance1, instance2);
   EXPECT_EQ(nullptr, contents()->GetPendingMainFrame());
 
diff --git a/content/public/browser/android/synchronous_compositor.cc b/content/public/browser/android/synchronous_compositor.cc
index 8d0b645..fd255be 100644
--- a/content/public/browser/android/synchronous_compositor.cc
+++ b/content/public/browser/android/synchronous_compositor.cc
@@ -17,13 +17,12 @@
 
 SynchronousCompositor::Frame::Frame(Frame&& rhs)
     : layer_tree_frame_sink_id(rhs.layer_tree_frame_sink_id),
-      frame(std::move(rhs.frame)) {}
+      frame(std::move(rhs.frame)),
+      local_surface_id(rhs.local_surface_id) {}
 
-SynchronousCompositor::FrameFuture::FrameFuture(
-    viz::LocalSurfaceId local_surface_id)
+SynchronousCompositor::FrameFuture::FrameFuture()
     : waitable_event_(base::WaitableEvent::ResetPolicy::MANUAL,
-                      base::WaitableEvent::InitialState::NOT_SIGNALED),
-      local_surface_id_(local_surface_id) {}
+                      base::WaitableEvent::InitialState::NOT_SIGNALED) {}
 
 SynchronousCompositor::FrameFuture::~FrameFuture() {}
 
@@ -48,6 +47,7 @@
 SynchronousCompositor::Frame& SynchronousCompositor::Frame::operator=(
     Frame&& rhs) {
   layer_tree_frame_sink_id = rhs.layer_tree_frame_sink_id;
+  local_surface_id = rhs.local_surface_id;
   frame = std::move(rhs.frame);
   return *this;
 }
diff --git a/content/public/browser/android/synchronous_compositor.h b/content/public/browser/android/synchronous_compositor.h
index 21a0280..4597208 100644
--- a/content/public/browser/android/synchronous_compositor.h
+++ b/content/public/browser/android/synchronous_compositor.h
@@ -57,6 +57,8 @@
 
     uint32_t layer_tree_frame_sink_id;
     std::unique_ptr<viz::CompositorFrame> frame;
+    // Invalid if |frame| is nullptr.
+    viz::LocalSurfaceId local_surface_id;
     base::Optional<viz::HitTestRegionList> hit_test_region_list;
 
    private:
@@ -65,10 +67,9 @@
 
   class FrameFuture : public base::RefCountedThreadSafe<FrameFuture> {
    public:
-    explicit FrameFuture(viz::LocalSurfaceId local_surface_id);
+    FrameFuture();
     void SetFrame(std::unique_ptr<Frame> frame);
     std::unique_ptr<Frame> GetFrame();
-    const viz::LocalSurfaceId& local_surface_id() { return local_surface_id_; }
 
    private:
     friend class base::RefCountedThreadSafe<FrameFuture>;
@@ -76,7 +77,6 @@
 
     base::WaitableEvent waitable_event_;
     std::unique_ptr<Frame> frame_;
-    viz::LocalSurfaceId local_surface_id_;
 #if DCHECK_IS_ON()
     bool waited_ = false;
 #endif
diff --git a/content/public/test/test_synchronous_compositor_android.cc b/content/public/test/test_synchronous_compositor_android.cc
index 89b2001..d71da9c 100644
--- a/content/public/test/test_synchronous_compositor_android.cc
+++ b/content/public/test/test_synchronous_compositor_android.cc
@@ -31,8 +31,7 @@
     const gfx::Size& viewport_size,
     const gfx::Rect& viewport_rect_for_tile_priority,
     const gfx::Transform& transform_for_tile_priority) {
-  auto future = base::MakeRefCounted<FrameFuture>(
-      viz::LocalSurfaceId(1, base::UnguessableToken::Create()));
+  auto future = base::MakeRefCounted<FrameFuture>();
   future->SetFrame(std::move(hardware_frame_));
   return future;
 }
@@ -61,6 +60,8 @@
     std::unique_ptr<viz::CompositorFrame> frame) {
   hardware_frame_ = std::make_unique<Frame>();
   hardware_frame_->layer_tree_frame_sink_id = layer_tree_frame_sink_id;
+  hardware_frame_->local_surface_id =
+      viz::LocalSurfaceId(1, base::UnguessableToken::Create());
   hardware_frame_->frame = std::move(frame);
 }
 
diff --git a/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm b/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm
index ad46a81a..afab6090 100644
--- a/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm
+++ b/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm
@@ -145,7 +145,7 @@
 void BluetoothLowEnergyDeviceWatcherMac::AddBluetoothPropertyListFileWatcher() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   property_list_watcher_->Watch(
-      BluetoothPlistFilePath(), false /* recursive */,
+      BluetoothPlistFilePath(), base::FilePathWatcher::Type::kNonRecursive,
       base::BindRepeating(&BluetoothLowEnergyDeviceWatcherMac::
                               OnPropertyListFileChangedOnFileThread,
                           this));
diff --git a/gin/v8_platform.cc b/gin/v8_platform.cc
index afba13c4..2aee1db08 100644
--- a/gin/v8_platform.cc
+++ b/gin/v8_platform.cc
@@ -258,7 +258,12 @@
                       Permission permissions) override {
     // If V8 sets permissions to none, we can discard the memory.
     if (permissions == v8::PageAllocator::Permission::kNoAccess) {
-      base::DecommitSystemPages(address, length, base::PageUpdatePermissions);
+      // Use PageKeepPermissionsIfPossible as an optimization, to avoid perf
+      // regression (see crrev.com/c/2563038 for details). This may cause the
+      // memory region to still be accessible on certain platforms, but at least
+      // the physical pages will be discarded.
+      base::DecommitSystemPages(address, length,
+                                base::PageKeepPermissionsIfPossible);
       return true;
     } else {
       return base::TrySetSystemPagesAccess(address, length,
diff --git a/google_apis/BUILD.gn b/google_apis/BUILD.gn
index d4d7509..bb74e013 100644
--- a/google_apis/BUILD.gn
+++ b/google_apis/BUILD.gn
@@ -88,6 +88,8 @@
     sources = [
       "gaia/core_account_id.cc",
       "gaia/core_account_id.h",
+      "gaia/gaia_access_token_fetcher.cc",
+      "gaia/gaia_access_token_fetcher.h",
       "gaia/gaia_auth_consumer.cc",
       "gaia/gaia_auth_consumer.h",
       "gaia/gaia_auth_fetcher.cc",
diff --git a/google_apis/gaia/fake_oauth2_access_token_manager.cc b/google_apis/gaia/fake_oauth2_access_token_manager.cc
index 1042bef5..3f3547f60 100644
--- a/google_apis/gaia/fake_oauth2_access_token_manager.cc
+++ b/google_apis/gaia/fake_oauth2_access_token_manager.cc
@@ -12,19 +12,21 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
-FakeOAuth2AccessTokenManager::PendingRequest::PendingRequest() {}
+using TokenResponseBuilder = OAuth2AccessTokenConsumer::TokenResponse::Builder;
+
+FakeOAuth2AccessTokenManager::PendingRequest::PendingRequest() = default;
 
 FakeOAuth2AccessTokenManager::PendingRequest::PendingRequest(
     const PendingRequest& other) = default;
 
-FakeOAuth2AccessTokenManager::PendingRequest::~PendingRequest() {}
+FakeOAuth2AccessTokenManager::PendingRequest::~PendingRequest() = default;
 
 FakeOAuth2AccessTokenManager::FakeOAuth2AccessTokenManager(
     OAuth2AccessTokenManager::Delegate* delegate)
     : OAuth2AccessTokenManager(delegate),
       auto_post_fetch_response_on_message_loop_(false) {}
 
-FakeOAuth2AccessTokenManager::~FakeOAuth2AccessTokenManager() {}
+FakeOAuth2AccessTokenManager::~FakeOAuth2AccessTokenManager() = default;
 
 void FakeOAuth2AccessTokenManager::IssueAllTokensForAccount(
     const CoreAccountId& account_id,
@@ -33,8 +35,10 @@
   DCHECK(!auto_post_fetch_response_on_message_loop_);
   CompleteRequests(account_id, true, FakeOAuth2AccessTokenManager::ScopeSet(),
                    GoogleServiceAuthError::AuthErrorNone(),
-                   OAuth2AccessTokenConsumer::TokenResponse(
-                       access_token, expiration, std::string() /* id_token */));
+                   TokenResponseBuilder()
+                       .WithAccessToken(access_token)
+                       .WithExpirationTime(expiration)
+                       .build());
 }
 
 void FakeOAuth2AccessTokenManager::IssueAllTokensForAccount(
@@ -60,8 +64,10 @@
   DCHECK(!auto_post_fetch_response_on_message_loop_);
   CompleteRequests(CoreAccountId(), false, scope,
                    GoogleServiceAuthError::AuthErrorNone(),
-                   OAuth2AccessTokenConsumer::TokenResponse(
-                       access_token, expiration, std::string() /* id_token */));
+                   TokenResponseBuilder()
+                       .WithAccessToken(access_token)
+                       .WithExpirationTime(expiration)
+                       .build());
 }
 
 void FakeOAuth2AccessTokenManager::IssueTokenForScope(
@@ -95,8 +101,10 @@
   CompleteRequests(CoreAccountId(), true,
                    FakeOAuth2AccessTokenManager::ScopeSet(),
                    GoogleServiceAuthError::AuthErrorNone(),
-                   OAuth2AccessTokenConsumer::TokenResponse(
-                       access_token, expiration, std::string() /* id_token */));
+                   TokenResponseBuilder()
+                       .WithAccessToken(access_token)
+                       .WithExpirationTime(expiration)
+                       .build());
 }
 
 void FakeOAuth2AccessTokenManager::IssueTokenForAllPendingRequests(
@@ -133,10 +141,7 @@
             base::Time());
       }
 
-      it->request->InformConsumer(
-          error, OAuth2AccessTokenConsumer::TokenResponse(
-                     token_response.access_token,
-                     token_response.expiration_time, token_response.id_token));
+      it->request->InformConsumer(error, token_response);
     }
   }
 }
@@ -190,8 +195,10 @@
                        weak_ptr_factory_.GetWeakPtr(), account_id,
                        /*all_scoped=*/true, scopes,
                        GoogleServiceAuthError::AuthErrorNone(),
-                       OAuth2AccessTokenConsumer::TokenResponse(
-                           "access_token", base::Time::Max(), std::string())));
+                       TokenResponseBuilder()
+                           .WithAccessToken("access_token")
+                           .WithExpirationTime(base::Time::Max())
+                           .build()));
   }
 }
 
diff --git a/google_apis/gaia/gaia_access_token_fetcher.cc b/google_apis/gaia/gaia_access_token_fetcher.cc
new file mode 100644
index 0000000..fed71a7e
--- /dev/null
+++ b/google_apis/gaia/gaia_access_token_fetcher.cc
@@ -0,0 +1,93 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "google_apis/gaia/gaia_access_token_fetcher.h"
+
+#include <string>
+
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+// static
+std::unique_ptr<GaiaAccessTokenFetcher>
+GaiaAccessTokenFetcher::CreateExchangeRefreshTokenForAccessTokenInstance(
+    OAuth2AccessTokenConsumer* consumer,
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    const std::string& refresh_token) {
+  // Using `new` to access a non-public constructor.
+  return base::WrapUnique(new GaiaAccessTokenFetcher(
+      consumer, url_loader_factory, refresh_token, std::string()));
+}
+
+// static
+std::unique_ptr<GaiaAccessTokenFetcher>
+GaiaAccessTokenFetcher::CreateExchangeAuthCodeForRefeshTokenInstance(
+    OAuth2AccessTokenConsumer* consumer,
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    const std::string& auth_code) {
+  // Using `new` to access a non-public constructor.
+  return base::WrapUnique(new GaiaAccessTokenFetcher(
+      consumer, url_loader_factory, std::string(), auth_code));
+}
+
+GaiaAccessTokenFetcher::GaiaAccessTokenFetcher(
+    OAuth2AccessTokenConsumer* consumer,
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    const std::string& refresh_token,
+    const std::string& auth_code)
+    : OAuth2AccessTokenFetcherImpl(consumer,
+                                   url_loader_factory,
+                                   refresh_token,
+                                   auth_code) {}
+
+GaiaAccessTokenFetcher::~GaiaAccessTokenFetcher() = default;
+
+void GaiaAccessTokenFetcher::RecordResponseCodeUma(int error_value) const {
+  base::UmaHistogramSparse("Gaia.ResponseCodesForOAuth2AccessToken",
+                           error_value);
+}
+
+void GaiaAccessTokenFetcher::RecordBadRequestTypeUma(
+    OAuth2ErrorCodesForHistogram access_error) const {
+  UMA_HISTOGRAM_ENUMERATION("Gaia.BadRequestTypeForOAuth2AccessToken",
+                            access_error, OAUTH2_ACCESS_ERROR_COUNT);
+}
+
+GURL GaiaAccessTokenFetcher::GetAccessTokenURL() const {
+  return GaiaUrls::GetInstance()->oauth2_token_url();
+}
+
+net::NetworkTrafficAnnotationTag
+GaiaAccessTokenFetcher::GetTrafficAnnotationTag() const {
+  return net::DefineNetworkTrafficAnnotation("oauth2_access_token_fetcher", R"(
+    semantics {
+      sender: "OAuth 2.0 Access Token Fetcher"
+      description:
+        "This request is used by the Token Service to fetch an OAuth 2.0 "
+        "access token for a known Google account."
+      trigger:
+        "This request can be triggered at any moment when any service "
+        "requests an OAuth 2.0 access token from the Token Service."
+      data:
+        "Chrome OAuth 2.0 client id and secret, the set of OAuth 2.0 "
+        "scopes and the OAuth 2.0 refresh token."
+      destination: GOOGLE_OWNED_SERVICE
+    }
+    policy {
+      cookies_allowed: NO
+      setting:
+        "This feature cannot be disabled in settings, but if user signs "
+        "out of Chrome, this request would not be made."
+      chrome_policy {
+        SigninAllowed {
+          policy_options {mode: MANDATORY}
+          SigninAllowed: false
+        }
+      }
+    })");
+}
diff --git a/google_apis/gaia/gaia_access_token_fetcher.h b/google_apis/gaia/gaia_access_token_fetcher.h
new file mode 100644
index 0000000..4ec0b87f
--- /dev/null
+++ b/google_apis/gaia/gaia_access_token_fetcher.h
@@ -0,0 +1,54 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GOOGLE_APIS_GAIA_GAIA_ACCESS_TOKEN_FETCHER_H_
+#define GOOGLE_APIS_GAIA_GAIA_ACCESS_TOKEN_FETCHER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
+
+class OAuth2AccessTokenConsumer;
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+// An implementation of OAuth2AccessTokenFetcherImpl for retrieving OAuth2
+// tokens from Google's authorization server.  See "Refreshing an access token"
+// for more Google specific info:
+// https://developers.google.com/identity/protocols/oauth2/web-server?csw=1#obtainingaccesstokens
+class GaiaAccessTokenFetcher : public OAuth2AccessTokenFetcherImpl {
+ public:
+  static std::unique_ptr<GaiaAccessTokenFetcher>
+  CreateExchangeRefreshTokenForAccessTokenInstance(
+      OAuth2AccessTokenConsumer* consumer,
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      const std::string& refresh_token);
+  static std::unique_ptr<GaiaAccessTokenFetcher>
+  CreateExchangeAuthCodeForRefeshTokenInstance(
+      OAuth2AccessTokenConsumer* consumer,
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      const std::string& auth_code);
+
+  ~GaiaAccessTokenFetcher() override;
+
+ private:
+  GaiaAccessTokenFetcher(
+      OAuth2AccessTokenConsumer* consumer,
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      const std::string& refresh_token,
+      const std::string& auth_code);
+
+  // OAuth2AccessTokenFetcherImpl:
+  void RecordResponseCodeUma(int error_value) const override;
+  void RecordBadRequestTypeUma(
+      OAuth2ErrorCodesForHistogram access_error) const override;
+  GURL GetAccessTokenURL() const override;
+  net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() const override;
+};
+
+#endif  // GOOGLE_APIS_GAIA_GAIA_ACCESS_TOKEN_FETCHER_H_
diff --git a/google_apis/gaia/oauth2_access_token_consumer.cc b/google_apis/gaia/oauth2_access_token_consumer.cc
index 47343596..db7c8466 100644
--- a/google_apis/gaia/oauth2_access_token_consumer.cc
+++ b/google_apis/gaia/oauth2_access_token_consumer.cc
@@ -4,18 +4,76 @@
 
 #include "google_apis/gaia/oauth2_access_token_consumer.h"
 
+OAuth2AccessTokenConsumer::TokenResponse::TokenResponse() = default;
+
+OAuth2AccessTokenConsumer::TokenResponse::TokenResponse(const TokenResponse&) =
+    default;
+
+OAuth2AccessTokenConsumer::TokenResponse::TokenResponse(TokenResponse&&) =
+    default;
+
 OAuth2AccessTokenConsumer::TokenResponse::TokenResponse(
     const std::string& access_token,
+    const std::string& refresh_token,
     const base::Time& expiration_time,
     const std::string& id_token)
     : access_token(access_token),
+      refresh_token(refresh_token),
       expiration_time(expiration_time),
       id_token(id_token) {}
 
-OAuth2AccessTokenConsumer::~OAuth2AccessTokenConsumer() {}
+OAuth2AccessTokenConsumer::TokenResponse::~TokenResponse() = default;
+
+OAuth2AccessTokenConsumer::TokenResponse&
+OAuth2AccessTokenConsumer::TokenResponse::operator=(
+    const TokenResponse& response) = default;
+
+OAuth2AccessTokenConsumer::TokenResponse&
+OAuth2AccessTokenConsumer::TokenResponse::operator=(TokenResponse&& response) =
+    default;
+
+OAuth2AccessTokenConsumer::TokenResponse::Builder::Builder() = default;
+
+OAuth2AccessTokenConsumer::TokenResponse::Builder::~Builder() = default;
+
+OAuth2AccessTokenConsumer::TokenResponse::Builder&
+OAuth2AccessTokenConsumer::TokenResponse::Builder::WithAccessToken(
+    const std::string& access_token) {
+  access_token_ = access_token;
+  return *this;
+}
+
+OAuth2AccessTokenConsumer::TokenResponse::Builder&
+OAuth2AccessTokenConsumer::TokenResponse::Builder::WithRefreshToken(
+    const std::string& refresh_token) {
+  refresh_token_ = refresh_token;
+  return *this;
+}
+
+OAuth2AccessTokenConsumer::TokenResponse::Builder&
+OAuth2AccessTokenConsumer::TokenResponse::Builder::WithExpirationTime(
+    const base::Time& expiration_time) {
+  expiration_time_ = expiration_time;
+  return *this;
+}
+
+OAuth2AccessTokenConsumer::TokenResponse::Builder&
+OAuth2AccessTokenConsumer::TokenResponse::Builder::WithIdToken(
+    const std::string& id_token) {
+  id_token_ = id_token;
+  return *this;
+}
+
+OAuth2AccessTokenConsumer::TokenResponse
+OAuth2AccessTokenConsumer::TokenResponse::Builder::build() {
+  return TokenResponse(access_token_, refresh_token_, expiration_time_,
+                       id_token_);
+}
+
+OAuth2AccessTokenConsumer::~OAuth2AccessTokenConsumer() = default;
 
 void OAuth2AccessTokenConsumer::OnGetTokenSuccess(
     const TokenResponse& token_response) {}
 
 void OAuth2AccessTokenConsumer::OnGetTokenFailure(
-    const GoogleServiceAuthError& error) {}
\ No newline at end of file
+    const GoogleServiceAuthError& error) {}
diff --git a/google_apis/gaia/oauth2_access_token_consumer.h b/google_apis/gaia/oauth2_access_token_consumer.h
index bc23ba9..da788f1 100644
--- a/google_apis/gaia/oauth2_access_token_consumer.h
+++ b/google_apis/gaia/oauth2_access_token_consumer.h
@@ -18,14 +18,19 @@
  public:
   // Structure representing information contained in OAuth2 access token.
   struct TokenResponse {
-    TokenResponse() = default;
-    TokenResponse(const std::string& access_token,
-                  const base::Time& expiration_time,
-                  const std::string& id_token);
+    TokenResponse();
+    TokenResponse(const TokenResponse& response);
+    TokenResponse(TokenResponse&& response);
+    ~TokenResponse();
+    TokenResponse& operator=(const TokenResponse& response);
+    TokenResponse& operator=(TokenResponse&& response);
 
     // OAuth2 access token.
     std::string access_token;
 
+    // OAuth2 refresh token.  May be empty.
+    std::string refresh_token;
+
     // The date until which the |access_token| can be used.
     // This value has a built-in safety margin, so it can be used as-is.
     base::Time expiration_time;
@@ -33,6 +38,35 @@
     // Contains extra information regarding the user's currently registered
     // services.
     std::string id_token;
+
+    // Helper class to make building TokenResponse objects clearer.
+    class Builder {
+     public:
+      Builder();
+      ~Builder();
+
+      Builder& WithAccessToken(const std::string& access_token);
+      Builder& WithRefreshToken(const std::string& refresh_token);
+      Builder& WithExpirationTime(const base::Time& expiration_time);
+      Builder& WithIdToken(const std::string& id_token);
+
+      TokenResponse build();
+
+     private:
+      std::string access_token_;
+      std::string refresh_token_;
+      base::Time expiration_time_;
+      std::string id_token_;
+    };
+
+   private:
+    friend class Builder;
+    friend class OAuth2AccessTokenConsumer;
+
+    TokenResponse(const std::string& access_token,
+                  const std::string& refresh_token,
+                  const base::Time& expiration_time,
+                  const std::string& id_token);
   };
 
   OAuth2AccessTokenConsumer() = default;
diff --git a/google_apis/gaia/oauth2_access_token_fetcher_impl.cc b/google_apis/gaia/oauth2_access_token_fetcher_impl.cc
index dd780608..d407f73 100644
--- a/google_apis/gaia/oauth2_access_token_fetcher_impl.cc
+++ b/google_apis/gaia/oauth2_access_token_fetcher_impl.cc
@@ -10,19 +10,14 @@
 
 #include "base/bind.h"
 #include "base/json/json_reader.h"
-#include "base/metrics/histogram.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
 #include "base/values.h"
 #include "google_apis/gaia/gaia_auth_util.h"
-#include "google_apis/gaia/gaia_urls.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "net/base/escape.h"
 #include "net/http/http_status_code.h"
-#include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
@@ -31,90 +26,58 @@
 constexpr char kGetAccessTokenBodyFormat[] =
     "client_id=%s&"
     "client_secret=%s&"
-    "grant_type=refresh_token&"
-    "refresh_token=%s";
+    "grant_type=%s&"
+    "%s=%s";
 
 constexpr char kGetAccessTokenBodyWithScopeFormat[] =
     "client_id=%s&"
     "client_secret=%s&"
-    "grant_type=refresh_token&"
-    "refresh_token=%s&"
+    "grant_type=%s&"
+    "%s=%s&"
     "scope=%s";
 
+constexpr char kGrantTypeAuthCode[] = "authorization_code";
+constexpr char kGrantTypeRefreshToken[] = "refresh_token";
+
+constexpr char kKeyAuthCode[] = "code";
+constexpr char kKeyRefreshToken[] = "refresh_token";
+
 constexpr char kAccessTokenKey[] = "access_token";
+constexpr char krefreshTokenKey[] = "refresh_token";
 constexpr char kExpiresInKey[] = "expires_in";
 constexpr char kIdTokenKey[] = "id_token";
 constexpr char kErrorKey[] = "error";
 
-// Enumerated constants for logging server responses on 400 errors, matching
-// RFC 6749.
-enum OAuth2ErrorCodesForHistogram {
-  OAUTH2_ACCESS_ERROR_INVALID_REQUEST = 0,
-  OAUTH2_ACCESS_ERROR_INVALID_CLIENT,
-  OAUTH2_ACCESS_ERROR_INVALID_GRANT,
-  OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT,
-  OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE,
-  OAUTH2_ACCESS_ERROR_INVALID_SCOPE,
-  OAUTH2_ACCESS_ERROR_UNKNOWN,
-  OAUTH2_ACCESS_ERROR_COUNT
-};
-
-OAuth2ErrorCodesForHistogram OAuth2ErrorToHistogramValue(
-    const std::string& error) {
+OAuth2AccessTokenFetcherImpl::OAuth2ErrorCodesForHistogram
+OAuth2ErrorToHistogramValue(const std::string& error) {
   if (error == "invalid_request")
-    return OAUTH2_ACCESS_ERROR_INVALID_REQUEST;
+    return OAuth2AccessTokenFetcherImpl::OAUTH2_ACCESS_ERROR_INVALID_REQUEST;
   else if (error == "invalid_client")
-    return OAUTH2_ACCESS_ERROR_INVALID_CLIENT;
+    return OAuth2AccessTokenFetcherImpl::OAUTH2_ACCESS_ERROR_INVALID_CLIENT;
   else if (error == "invalid_grant")
-    return OAUTH2_ACCESS_ERROR_INVALID_GRANT;
+    return OAuth2AccessTokenFetcherImpl::OAUTH2_ACCESS_ERROR_INVALID_GRANT;
   else if (error == "unauthorized_client")
-    return OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT;
+    return OAuth2AccessTokenFetcherImpl::
+        OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT;
   else if (error == "unsupported_grant_type")
-    return OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE;
+    return OAuth2AccessTokenFetcherImpl::
+        OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE;
   else if (error == "invalid_scope")
-    return OAUTH2_ACCESS_ERROR_INVALID_SCOPE;
+    return OAuth2AccessTokenFetcherImpl::OAUTH2_ACCESS_ERROR_INVALID_SCOPE;
 
-  return OAUTH2_ACCESS_ERROR_UNKNOWN;
+  return OAuth2AccessTokenFetcherImpl::OAUTH2_ACCESS_ERROR_UNKNOWN;
 }
 
 static GoogleServiceAuthError CreateAuthError(int net_error) {
   CHECK_NE(net_error, net::OK);
-  DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
-                << net_error;
+  DLOG(WARNING) << "Could not reach Authorization servers: errno " << net_error;
   return GoogleServiceAuthError::FromConnectionError(net_error);
 }
 
 static std::unique_ptr<network::SimpleURLLoader> CreateURLLoader(
     const GURL& url,
-    const std::string& body) {
-  net::NetworkTrafficAnnotationTag traffic_annotation =
-      net::DefineNetworkTrafficAnnotation("oauth2_access_token_fetcher", R"(
-        semantics {
-          sender: "OAuth 2.0 Access Token Fetcher"
-          description:
-            "This request is used by the Token Service to fetch an OAuth 2.0 "
-            "access token for a known Google account."
-          trigger:
-            "This request can be triggered at any moment when any service "
-            "requests an OAuth 2.0 access token from the Token Service."
-          data:
-            "Chrome OAuth 2.0 client id and secret, the set of OAuth 2.0 "
-            "scopes and the OAuth 2.0 refresh token."
-          destination: GOOGLE_OWNED_SERVICE
-        }
-        policy {
-          cookies_allowed: NO
-          setting:
-            "This feature cannot be disabled in settings, but if user signs "
-            "out of Chrome, this request would not be made."
-          chrome_policy {
-            SigninAllowed {
-              policy_options {mode: MANDATORY}
-              SigninAllowed: false
-            }
-          }
-        })");
-
+    const std::string& body,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation) {
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->url = url;
   resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
@@ -160,13 +123,19 @@
 OAuth2AccessTokenFetcherImpl::OAuth2AccessTokenFetcherImpl(
     OAuth2AccessTokenConsumer* consumer,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    const std::string& refresh_token)
+    const std::string& refresh_token,
+    const std::string& auth_code)
     : OAuth2AccessTokenFetcher(consumer),
       url_loader_factory_(url_loader_factory),
       refresh_token_(refresh_token),
-      state_(INITIAL) {}
+      auth_code_(auth_code),
+      state_(INITIAL) {
+  // It's an error to specify neither a refresh token nor an auth code, or
+  // to specify both at the same time.
+  CHECK_NE(refresh_token_.empty(), auth_code_.empty());
+}
 
-OAuth2AccessTokenFetcherImpl::~OAuth2AccessTokenFetcherImpl() {}
+OAuth2AccessTokenFetcherImpl::~OAuth2AccessTokenFetcherImpl() = default;
 
 void OAuth2AccessTokenFetcherImpl::CancelRequest() {
   url_loader_.reset();
@@ -185,10 +154,11 @@
 void OAuth2AccessTokenFetcherImpl::StartGetAccessToken() {
   CHECK_EQ(INITIAL, state_);
   state_ = GET_ACCESS_TOKEN_STARTED;
-  url_loader_ =
-      CreateURLLoader(MakeGetAccessTokenUrl(),
-                      MakeGetAccessTokenBody(client_id_, client_secret_,
-                                             refresh_token_, scopes_));
+  url_loader_ = CreateURLLoader(
+      GetAccessTokenURL(),
+      MakeGetAccessTokenBody(client_id_, client_secret_, refresh_token_,
+                             auth_code_, scopes_),
+      GetTrafficAnnotationTag());
   // It's safe to use Unretained below as the |url_loader_| is owned by |this|.
   url_loader_->DownloadToString(
       url_loader_factory_.get(),
@@ -211,8 +181,8 @@
     histogram_value = url_loader_->NetError();
     net_failure = true;
   }
-  base::UmaHistogramSparse("Gaia.ResponseCodesForOAuth2AccessToken",
-                           histogram_value);
+  RecordResponseCodeUma(histogram_value);
+
   if (net_failure) {
     OnGetTokenFailure(CreateAuthError(histogram_value));
     return;
@@ -238,19 +208,17 @@
     case net::HTTP_BAD_REQUEST: {
       // HTTP_BAD_REQUEST (400) usually contains error as per
       // http://tools.ietf.org/html/rfc6749#section-5.2.
-      std::string gaia_error;
+      std::string oauth2_error;
       if (!ParseGetAccessTokenFailureResponse(std::move(response_body),
-                                              &gaia_error)) {
+                                              &oauth2_error)) {
         OnGetTokenFailure(
             GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR));
         return;
       }
 
       OAuth2ErrorCodesForHistogram access_error(
-          OAuth2ErrorToHistogramValue(gaia_error));
-      UMA_HISTOGRAM_ENUMERATION("Gaia.BadRequestTypeForOAuth2AccessToken",
-                                access_error,
-                                OAUTH2_ACCESS_ERROR_COUNT);
+          OAuth2ErrorToHistogramValue(oauth2_error));
+      RecordBadRequestTypeUma(access_error);
 
       OnGetTokenFailure(
           access_error == OAUTH2_ACCESS_ERROR_INVALID_GRANT
@@ -280,11 +248,9 @@
 
   // The request was successfully fetched and it returned OK.
   // Parse out the access token and the expiration time.
-  std::string access_token;
-  int expires_in;
-  std::string id_token;
-  if (!ParseGetAccessTokenSuccessResponse(
-          std::move(response_body), &access_token, &expires_in, &id_token)) {
+  OAuth2AccessTokenConsumer::TokenResponse token_response;
+  if (!ParseGetAccessTokenSuccessResponse(std::move(response_body),
+                                          &token_response)) {
     DLOG(WARNING) << "Response doesn't match expected format";
     OnGetTokenFailure(
         GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
@@ -292,10 +258,7 @@
   }
   // The token will expire in |expires_in| seconds. Take a 10% error margin to
   // prevent reusing a token too close to its expiration date.
-  OnGetTokenSuccess(OAuth2AccessTokenConsumer::TokenResponse(
-      access_token,
-      base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10),
-      id_token));
+  OnGetTokenSuccess(token_response);
 }
 
 void OAuth2AccessTokenFetcherImpl::OnGetTokenSuccess(
@@ -316,33 +279,42 @@
 }
 
 // static
-GURL OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenUrl() {
-  return GaiaUrls::GetInstance()->oauth2_token_url();
-}
-
-// static
 std::string OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody(
     const std::string& client_id,
     const std::string& client_secret,
     const std::string& refresh_token,
+    const std::string& auth_code,
     const std::vector<std::string>& scopes) {
+  // It's an error to specify neither a refresh token nor an auth code, or
+  // to specify both at the same time.
+  CHECK_NE(refresh_token.empty(), auth_code.empty());
+
   std::string enc_client_id = net::EscapeUrlEncodedData(client_id, true);
   std::string enc_client_secret =
       net::EscapeUrlEncodedData(client_secret, true);
-  std::string enc_refresh_token =
-      net::EscapeUrlEncodedData(refresh_token, true);
+
+  const char* key = nullptr;
+  const char* grant_type = nullptr;
+  std::string enc_value;
+  if (refresh_token.empty()) {
+    key = kKeyAuthCode;
+    grant_type = kGrantTypeAuthCode;
+    enc_value = net::EscapeUrlEncodedData(auth_code, true);
+  } else {
+    key = kKeyRefreshToken;
+    grant_type = kGrantTypeRefreshToken;
+    enc_value = net::EscapeUrlEncodedData(refresh_token, true);
+  }
+
   if (scopes.empty()) {
-    return base::StringPrintf(kGetAccessTokenBodyFormat,
-                              enc_client_id.c_str(),
-                              enc_client_secret.c_str(),
-                              enc_refresh_token.c_str());
+    return base::StringPrintf(kGetAccessTokenBodyFormat, enc_client_id.c_str(),
+                              enc_client_secret.c_str(), grant_type, key,
+                              enc_value.c_str());
   } else {
     std::string scopes_string = base::JoinString(scopes, " ");
     return base::StringPrintf(
-        kGetAccessTokenBodyWithScopeFormat,
-        enc_client_id.c_str(),
-        enc_client_secret.c_str(),
-        enc_refresh_token.c_str(),
+        kGetAccessTokenBodyWithScopeFormat, enc_client_id.c_str(),
+        enc_client_secret.c_str(), grant_type, key, enc_value.c_str(),
         net::EscapeUrlEncodedData(scopes_string, true).c_str());
   }
 }
@@ -350,18 +322,25 @@
 // static
 bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
     std::unique_ptr<std::string> response_body,
-    std::string* access_token,
-    int* expires_in,
-    std::string* id_token) {
-  CHECK(access_token);
+    OAuth2AccessTokenConsumer::TokenResponse* token_response) {
+  CHECK(token_response);
   std::unique_ptr<base::DictionaryValue> value =
       ParseGetAccessTokenResponse(std::move(response_body));
   if (!value)
     return false;
-  // ID token field is optional.
-  value->GetString(kIdTokenKey, id_token);
-  return value->GetString(kAccessTokenKey, access_token) &&
-         value->GetInteger(kExpiresInKey, expires_in);
+
+  // Refresh and id token are optional and don't cause an error if missing.
+  value->GetString(krefreshTokenKey, &token_response->refresh_token);
+  value->GetString(kIdTokenKey, &token_response->id_token);
+
+  int expires_in;
+  bool ok = value->GetString(kAccessTokenKey, &token_response->access_token) &&
+            value->GetInteger(kExpiresInKey, &expires_in);
+  if (ok) {
+    token_response->expiration_time =
+        base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10);
+  }
+  return ok;
 }
 
 // static
diff --git a/google_apis/gaia/oauth2_access_token_fetcher_impl.h b/google_apis/gaia/oauth2_access_token_fetcher_impl.h
index 6bf897d..07b5e17 100644
--- a/google_apis/gaia/oauth2_access_token_fetcher_impl.h
+++ b/google_apis/gaia/oauth2_access_token_fetcher_impl.h
@@ -14,6 +14,7 @@
 #include "base/memory/ref_counted.h"
 #include "google_apis/gaia/oauth2_access_token_consumer.h"
 #include "google_apis/gaia/oauth2_access_token_fetcher.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
 #include "url/gurl.h"
 
 class OAuth2AccessTokenFetcherImplTest;
@@ -24,13 +25,11 @@
 }
 
 // Abstracts the details to get OAuth2 access token from OAuth2 refresh token.
-// See "Using the Refresh Token" section in:
-// http://code.google.com/apis/accounts/docs/OAuth2WebServer.html
+// See general document about Oauth2 in https://tools.ietf.org/html/rfc6749.
 //
 // This class should be used on a single thread, but it can be whichever thread
-// that you like.
-// Also, do not reuse the same instance. Once Start() is called, the instance
-// should not be reused.
+// that you like. Also, do not reuse the same instance. Once Start() is called,
+// the instance should not be reused.
 //
 // Usage:
 // * Create an instance with a consumer.
@@ -39,13 +38,28 @@
 //   thread Start was called with the results.
 //
 // This class can handle one request at a time. To parallelize requests,
-// create multiple instances.
+// create multiple instances.  Prefer `GaiaAccessTokenFetcher` over this class
+// while talking to Google's OAuth servers.
 class OAuth2AccessTokenFetcherImpl : public OAuth2AccessTokenFetcher {
  public:
+  // Enumerated constants for logging server responses on 400 errors, matching
+  // RFC 6749.
+  enum OAuth2ErrorCodesForHistogram {
+    OAUTH2_ACCESS_ERROR_INVALID_REQUEST = 0,
+    OAUTH2_ACCESS_ERROR_INVALID_CLIENT,
+    OAUTH2_ACCESS_ERROR_INVALID_GRANT,
+    OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT,
+    OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE,
+    OAUTH2_ACCESS_ERROR_INVALID_SCOPE,
+    OAUTH2_ACCESS_ERROR_UNKNOWN,
+    OAUTH2_ACCESS_ERROR_COUNT,
+  };
+
   OAuth2AccessTokenFetcherImpl(
       OAuth2AccessTokenConsumer* consumer,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      const std::string& refresh_token);
+      const std::string& refresh_token,
+      const std::string& auth_code = std::string());
   ~OAuth2AccessTokenFetcherImpl() override;
 
   // Implementation of OAuth2AccessTokenFetcher
@@ -63,6 +77,17 @@
     ERROR_STATE,
   };
 
+  // Derived classes override these methods to record UMA histograms if needed.
+  virtual void RecordResponseCodeUma(int error_value) const {}
+  virtual void RecordBadRequestTypeUma(
+      OAuth2ErrorCodesForHistogram access_error) const {}
+
+  // Derived class must specify the GetAccessToken base URL to use.
+  virtual GURL GetAccessTokenURL() const = 0;
+
+  // Derived classes must specify a network annotation for the fetcher.
+  virtual net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() const = 0;
+
   void OnURLLoadComplete(std::unique_ptr<std::string> response_body);
 
   // Helper methods for the flow.
@@ -74,19 +99,16 @@
       const OAuth2AccessTokenConsumer::TokenResponse& token_response);
   void OnGetTokenFailure(const GoogleServiceAuthError& error);
 
-  // Other helpers.
-  static GURL MakeGetAccessTokenUrl();
   static std::string MakeGetAccessTokenBody(
       const std::string& client_id,
       const std::string& client_secret,
       const std::string& refresh_token,
+      const std::string& auth_code,
       const std::vector<std::string>& scopes);
 
   static bool ParseGetAccessTokenSuccessResponse(
       std::unique_ptr<std::string> response_body,
-      std::string* access_token,
-      int* expires_in,
-      std::string* id_token);
+      OAuth2AccessTokenConsumer::TokenResponse* token_response);
 
   static bool ParseGetAccessTokenFailureResponse(
       std::unique_ptr<std::string> response_body,
@@ -95,6 +117,7 @@
   // State that is set during construction.
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
   const std::string refresh_token_;
+  const std::string auth_code_;
   State state_;
 
   // While a fetch is in progress.
diff --git a/google_apis/gaia/oauth2_access_token_fetcher_impl_unittest.cc b/google_apis/gaia/oauth2_access_token_fetcher_impl_unittest.cc
index 4d1ce69..2380e5d 100644
--- a/google_apis/gaia/oauth2_access_token_fetcher_impl_unittest.cc
+++ b/google_apis/gaia/oauth2_access_token_fetcher_impl_unittest.cc
@@ -12,6 +12,8 @@
 #include "base/bind.h"
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "google_apis/gaia/gaia_access_token_fetcher.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "google_apis/gaia/oauth2_access_token_consumer.h"
@@ -74,9 +76,11 @@
 class OAuth2AccessTokenFetcherImplTest : public testing::Test {
  public:
   OAuth2AccessTokenFetcherImplTest()
-      : fetcher_(&consumer_,
-                 url_loader_factory_.GetSafeWeakWrapper(),
-                 "refresh_token") {
+      : fetcher_(GaiaAccessTokenFetcher::
+                     CreateExchangeRefreshTokenForAccessTokenInstance(
+                         &consumer_,
+                         url_loader_factory_.GetSafeWeakWrapper(),
+                         "refresh_token")) {
     url_loader_factory_.SetInterceptor(base::BindRepeating(
         &URLLoaderFactoryInterceptor::Intercept,
         base::Unretained(&url_loader_factory_interceptor_)));
@@ -118,21 +122,21 @@
   MockOAuth2AccessTokenConsumer consumer_;
   URLLoaderFactoryInterceptor url_loader_factory_interceptor_;
   network::TestURLLoaderFactory url_loader_factory_;
-  OAuth2AccessTokenFetcherImpl fetcher_;
+  std::unique_ptr<GaiaAccessTokenFetcher> fetcher_;
 };
 
 // These four tests time out, see http://crbug.com/113446.
 TEST_F(OAuth2AccessTokenFetcherImplTest, GetAccessTokenRequestFailure) {
   SetupGetAccessToken(net::ERR_FAILED, net::HTTP_OK, std::string());
   EXPECT_CALL(consumer_, OnGetTokenFailure(_)).Times(1);
-  fetcher_.Start("client_id", "client_secret", ScopeList());
+  fetcher_->Start("client_id", "client_secret", ScopeList());
   base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(OAuth2AccessTokenFetcherImplTest, GetAccessTokenResponseCodeFailure) {
   SetupGetAccessToken(net::OK, net::HTTP_FORBIDDEN, std::string());
   EXPECT_CALL(consumer_, OnGetTokenFailure(_)).Times(1);
-  fetcher_.Start("client_id", "client_secret", ScopeList());
+  fetcher_->Start("client_id", "client_secret", ScopeList());
   base::RunLoop().RunUntilIdle();
 }
 
@@ -144,14 +148,14 @@
   ASSERT_TRUE(expected_error.IsTransientError());
   SetupProxyError();
   EXPECT_CALL(consumer_, OnGetTokenFailure(expected_error)).Times(1);
-  fetcher_.Start("client_id", "client_secret", ScopeList());
+  fetcher_->Start("client_id", "client_secret", ScopeList());
   base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(OAuth2AccessTokenFetcherImplTest, Success) {
   SetupGetAccessToken(net::OK, net::HTTP_OK, kValidTokenResponse);
   EXPECT_CALL(consumer_, OnGetTokenSuccess(_)).Times(1);
-  fetcher_.Start("client_id", "client_secret", ScopeList());
+  fetcher_->Start("client_id", "client_secret", ScopeList());
   base::RunLoop().RunUntilIdle();
 }
 
@@ -162,7 +166,7 @@
       "grant_type=refresh_token&"
       "refresh_token=rt1";
   EXPECT_EQ(body, OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody(
-                      "cid1", "cs1", "rt1", ScopeList()));
+                      "cid1", "cs1", "rt1", std::string(), ScopeList()));
 }
 
 TEST_F(OAuth2AccessTokenFetcherImplTest, MakeGetAccessTokenBodyOneScope) {
@@ -174,7 +178,7 @@
       "scope=https://www.googleapis.com/foo";
   ScopeList scopes = {"https://www.googleapis.com/foo"};
   EXPECT_EQ(body, OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody(
-                      "cid1", "cs1", "rt1", scopes));
+                      "cid1", "cs1", "rt1", std::string(), scopes));
 }
 
 TEST_F(OAuth2AccessTokenFetcherImplTest, MakeGetAccessTokenBodyMultipleScopes) {
@@ -190,49 +194,43 @@
                       "https://www.googleapis.com/bar",
                       "https://www.googleapis.com/baz"};
   EXPECT_EQ(body, OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody(
-                      "cid1", "cs1", "rt1", scopes));
+                      "cid1", "cs1", "rt1", std::string(), scopes));
 }
 
 TEST_F(OAuth2AccessTokenFetcherImplTest, ParseGetAccessTokenResponseNoBody) {
-  std::string at;
-  int expires_in;
-  std::string id_token;
+  OAuth2AccessTokenConsumer::TokenResponse token_response;
   auto empty_body = std::make_unique<std::string>("");
   EXPECT_FALSE(OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
-      std::move(empty_body), &at, &expires_in, &id_token));
-  EXPECT_TRUE(at.empty());
+      std::move(empty_body), &token_response));
+  EXPECT_TRUE(token_response.access_token.empty());
 }
 
 TEST_F(OAuth2AccessTokenFetcherImplTest, ParseGetAccessTokenResponseBadJson) {
-  std::string at;
-  int expires_in;
-  std::string id_token;
+  OAuth2AccessTokenConsumer::TokenResponse token_response;
   EXPECT_FALSE(OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
-      std::make_unique<std::string>("foo"), &at, &expires_in, &id_token));
-  EXPECT_TRUE(at.empty());
+      std::make_unique<std::string>("foo"), &token_response));
+  EXPECT_TRUE(token_response.access_token.empty());
 }
 
 TEST_F(OAuth2AccessTokenFetcherImplTest,
        ParseGetAccessTokenResponseNoAccessToken) {
-  std::string at;
-  int expires_in;
-  std::string id_token;
+  OAuth2AccessTokenConsumer::TokenResponse token_response;
   EXPECT_FALSE(OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
-      std::make_unique<std::string>(kTokenResponseNoAccessToken), &at,
-      &expires_in, &id_token));
-  EXPECT_TRUE(at.empty());
+      std::make_unique<std::string>(kTokenResponseNoAccessToken),
+      &token_response));
+  EXPECT_TRUE(token_response.access_token.empty());
 }
 
 TEST_F(OAuth2AccessTokenFetcherImplTest, ParseGetAccessTokenResponseSuccess) {
-  std::string at;
-  int expires_in;
-  std::string id_token;
+  OAuth2AccessTokenConsumer::TokenResponse token_response;
   EXPECT_TRUE(OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
-      std::make_unique<std::string>(kValidTokenResponse), &at, &expires_in,
-      &id_token));
-  EXPECT_EQ("at1", at);
-  EXPECT_EQ(3600, expires_in);
-  EXPECT_EQ("id_token", id_token);
+      std::make_unique<std::string>(kValidTokenResponse), &token_response));
+  EXPECT_EQ("at1", token_response.access_token);
+  base::TimeDelta expires_in =
+      token_response.expiration_time - base::Time::Now();
+  EXPECT_LT(0, expires_in.InSeconds());
+  EXPECT_GT(3600, expires_in.InSeconds());
+  EXPECT_EQ("id_token", token_response.id_token);
 }
 
 TEST_F(OAuth2AccessTokenFetcherImplTest,
diff --git a/google_apis/gaia/oauth2_access_token_manager_unittest.cc b/google_apis/gaia/oauth2_access_token_manager_unittest.cc
index 0faaceae..e343dae 100644
--- a/google_apis/gaia/oauth2_access_token_manager_unittest.cc
+++ b/google_apis/gaia/oauth2_access_token_manager_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/test/task_environment.h"
+#include "google_apis/gaia/gaia_access_token_fetcher.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "google_apis/gaia/google_service_auth_error.h"
@@ -38,9 +39,10 @@
       OAuth2AccessTokenConsumer* consumer) override {
     EXPECT_NE(account_ids_to_refresh_tokens_.find(account_id),
               account_ids_to_refresh_tokens_.end());
-    return std::make_unique<OAuth2AccessTokenFetcherImpl>(
-        consumer, url_loader_factory,
-        account_ids_to_refresh_tokens_[account_id]);
+    return GaiaAccessTokenFetcher::
+        CreateExchangeRefreshTokenForAccessTokenInstance(
+            consumer, url_loader_factory,
+            account_ids_to_refresh_tokens_[account_id]);
   }
 
   bool HasRefreshToken(const CoreAccountId& account_id) const override {
diff --git a/ios/chrome/browser/web/error_page_egtest.mm b/ios/chrome/browser/web/error_page_egtest.mm
index 765b054..3e857656 100644
--- a/ios/chrome/browser/web/error_page_egtest.mm
+++ b/ios/chrome/browser/web/error_page_egtest.mm
@@ -6,6 +6,7 @@
 #include <string>
 
 #include "base/bind.h"
+#import "base/test/ios/wait_util.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
@@ -65,6 +66,40 @@
   GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
 }
 
+// Tests that the error page is correctly displayed after navigating back to it
+// multiple times. See http://crbug.com/944037 .
+- (void)testBackForwardErrorPage {
+  // Using ERR_CONNECTION_CLOSED doesn't work.
+  std::string errorText = net::ErrorToShortString(net::ERR_INVALID_URL);
+  self.serverRespondsWithContent = YES;
+
+  [ChromeEarlGrey loadURL:GURL("chrome://invalid")];
+  [ChromeEarlGrey waitForWebStateContainingText:errorText];
+  // Add some delay otherwise the back/forward navigations are occurring too
+  // fast.
+  base::test::ios::SpinRunLoopWithMinDelay(base::TimeDelta::FromSecondsD(0.2));
+
+  // Navigate to a page which responds.
+  [ChromeEarlGrey loadURL:self.testServer->GetURL("/echo-query?bar")];
+  [ChromeEarlGrey waitForWebStateContainingText:"bar"];
+  base::test::ios::SpinRunLoopWithMinDelay(base::TimeDelta::FromSecondsD(0.2));
+
+  [ChromeEarlGrey goBack];
+  [ChromeEarlGrey waitForWebStateContainingText:errorText];
+  base::test::ios::SpinRunLoopWithMinDelay(base::TimeDelta::FromSecondsD(0.2));
+
+  [ChromeEarlGrey goForward];
+  [ChromeEarlGrey waitForWebStateContainingText:"bar"];
+  base::test::ios::SpinRunLoopWithMinDelay(base::TimeDelta::FromSecondsD(0.2));
+
+  [ChromeEarlGrey goBack];
+  [ChromeEarlGrey waitForWebStateContainingText:errorText];
+  base::test::ios::SpinRunLoopWithMinDelay(base::TimeDelta::FromSecondsD(0.2));
+
+  [ChromeEarlGrey goForward];
+  [ChromeEarlGrey waitForWebStateContainingText:"bar"];
+}
+
 // Loads the URL which fails to load, then sucessfully reloads the page.
 - (void)testReloadErrorPage {
   // No response leads to ERR_CONNECTION_CLOSED error.
diff --git a/ios/web/js_messaging/BUILD.gn b/ios/web/js_messaging/BUILD.gn
index 011d8244..7466714 100644
--- a/ios/web/js_messaging/BUILD.gn
+++ b/ios/web/js_messaging/BUILD.gn
@@ -69,6 +69,7 @@
   deps = [
     "//base",
     "//base/test:test_support",
+    "//ios/testing:embedded_test_server_support",
     "//ios/web/common",
     "//ios/web/js_messaging",
     "//ios/web/public",
diff --git a/ios/web/js_messaging/web_frame_web_state_observer_inttest.mm b/ios/web/js_messaging/web_frame_web_state_observer_inttest.mm
index a4d3a368..4c1efdccb 100644
--- a/ios/web/js_messaging/web_frame_web_state_observer_inttest.mm
+++ b/ios/web/js_messaging/web_frame_web_state_observer_inttest.mm
@@ -5,10 +5,15 @@
 #import "ios/web/public/test/web_test_with_web_state.h"
 
 #include "base/ios/ios_util.h"
+#include "ios/testing/embedded_test_server_handlers.h"
 #import "ios/web/public/js_messaging/web_frame.h"
 #import "ios/web/public/js_messaging/web_frames_manager.h"
+#import "ios/web/public/test/navigation_test_util.h"
+#import "ios/web/public/test/web_view_content_test_util.h"
 #import "ios/web/public/web_state.h"
 #include "ios/web/public/web_state_observer.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/request_handler_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -58,7 +63,18 @@
 
 namespace web {
 
-using WebFrameWebStateObserverInttest = WebTestWithWebState;
+class WebFrameWebStateObserverInttest : public WebTestWithWebState {
+ protected:
+  void SetUp() override {
+    WebTestWithWebState::SetUp();
+    test_server_.RegisterRequestHandler(base::BindRepeating(
+        &net::test_server::HandlePrefixedRequest, "/echo-query",
+        base::BindRepeating(&testing::HandlePageWithContents)));
+    ASSERT_TRUE(test_server_.Start());
+  }
+
+  net::EmbeddedTestServer test_server_;
+};
 
 // Web frame events should be registered on HTTP navigation.
 TEST_F(WebFrameWebStateObserverInttest, SingleWebFrameHTTP) {
@@ -66,12 +82,18 @@
   web_state()->AddObserver(&observer);
   EXPECT_CALL(observer, WebFrameDidBecomeAvailable(web_state(), testing::_))
       .WillOnce(VerifyMainWebFrame(web_state()));
-  LoadHtml(@"<p></p>", GURL("http://testurl1"));
+
+  test::LoadUrl(web_state(), test_server_.GetURL("/echo-query?test"));
+  ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "test"));
+
   EXPECT_CALL(observer, WebFrameDidBecomeAvailable(web_state(), testing::_))
       .WillOnce(VerifyMainWebFrame(web_state()));
   EXPECT_CALL(observer, WebFrameWillBecomeUnavailable(web_state(), testing::_))
       .WillOnce(VerifyMainWebFrame(web_state()));
-  LoadHtml(@"<p></p>", GURL("http://testurl2"));
+
+  test::LoadUrl(web_state(), test_server_.GetURL("/echo-query?secondPage"));
+  ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "secondPage"));
+
   web_state()->RemoveObserver(&observer);
 }
 
@@ -79,14 +101,28 @@
 TEST_F(WebFrameWebStateObserverInttest, SingleWebFrameHTTPS) {
   testing::StrictMock<WebStateObserverMock> observer;
   web_state()->AddObserver(&observer);
+
   EXPECT_CALL(observer, WebFrameDidBecomeAvailable(web_state(), testing::_))
       .WillOnce(VerifyMainWebFrame(web_state()));
-  LoadHtml(@"<p></p>", GURL("https://testurl1"));
+
+  // Load a first page to avoid having an item inserted during the |LoadHtml|.
+  test::LoadUrl(web_state(), test_server_.GetURL("/echo-query?test"));
+  ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "test"));
+
   EXPECT_CALL(observer, WebFrameDidBecomeAvailable(web_state(), testing::_))
       .WillOnce(VerifyMainWebFrame(web_state()));
   EXPECT_CALL(observer, WebFrameWillBecomeUnavailable(web_state(), testing::_))
       .WillOnce(VerifyMainWebFrame(web_state()));
+
+  LoadHtml(@"<p></p>", GURL("https://testurl1"));
+
+  EXPECT_CALL(observer, WebFrameDidBecomeAvailable(web_state(), testing::_))
+      .WillOnce(VerifyMainWebFrame(web_state()));
+  EXPECT_CALL(observer, WebFrameWillBecomeUnavailable(web_state(), testing::_))
+      .WillOnce(VerifyMainWebFrame(web_state()));
+
   LoadHtml(@"<p></p>", GURL("https://testurl2"));
+
   web_state()->RemoveObserver(&observer);
 }
 
@@ -95,6 +131,13 @@
   testing::StrictMock<WebStateObserverMock> observer;
   web_state()->AddObserver(&observer);
 
+  EXPECT_CALL(observer, WebFrameDidBecomeAvailable(web_state(), testing::_))
+      .WillOnce(VerifyMainWebFrame(web_state()));
+
+  // Load a first page to avoid having an item inserted during the |LoadHtml|.
+  test::LoadUrl(web_state(), test_server_.GetURL("/echo-query?test"));
+  ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "test"));
+
   // The order in which the main and child frames become available is not
   // guaranteed due to the async nature of messaging. The following expectations
   // use separate matchers to identify main and child frames so that they can
@@ -105,6 +148,9 @@
   EXPECT_CALL(observer,
               WebFrameDidBecomeAvailable(web_state(), Not(Truly(IsMainFrame))))
       .WillOnce(VerifyChildWebFrame(web_state()));
+  EXPECT_CALL(observer, WebFrameWillBecomeUnavailable(web_state(), testing::_))
+      .WillOnce(VerifyMainWebFrame(web_state()));
+
   LoadHtml(@"<p><iframe/></p>", GURL("https://testurl1"));
 
   EXPECT_CALL(observer,
@@ -119,6 +165,7 @@
   EXPECT_CALL(observer, WebFrameWillBecomeUnavailable(web_state(),
                                                       Not(Truly(IsMainFrame))))
       .WillOnce(VerifyChildWebFrame(web_state()));
+
   LoadHtml(@"<p><iframe/></p>", GURL("https://testurl2"));
 
   web_state()->RemoveObserver(&observer);
diff --git a/ios/web/navigation/crw_wk_navigation_handler.mm b/ios/web/navigation/crw_wk_navigation_handler.mm
index 7e790e31..7ff4abf 100644
--- a/ios/web/navigation/crw_wk_navigation_handler.mm
+++ b/ios/web/navigation/crw_wk_navigation_handler.mm
@@ -983,9 +983,7 @@
       !context->GetUrl().SchemeIs(url::kAboutScheme) &&
       !IsRestoreSessionUrl(context->GetUrl())) {
     [self.delegate webViewHandlerUpdateSSLStatusForCurrentNavigationItem:self];
-    if ((base::FeatureList::IsEnabled(web::features::kUseJSForErrorPage) ||
-         !context->IsLoadingErrorPage()) &&
-        !IsRestoreSessionUrl(webViewURL)) {
+    if (!context->IsLoadingErrorPage() && !IsRestoreSessionUrl(webViewURL)) {
       [self setLastCommittedNavigationItemTitle:webView.title];
     }
   }
@@ -1999,13 +1997,16 @@
 
   ErrorPageHelper* errorPage = [[ErrorPageHelper alloc] initWithError:error];
   WKBackForwardListItem* backForwardItem = webView.backForwardList.currentItem;
-  // There are 3 possible scenarios here:
+  // There are 4 possible scenarios here:
   //   1. Current nav item is an error page for failed URL;
   //   2. Current nav item has a failed URL. This may happen when
   //      back/forward/refresh on a loaded page;
   //   3. Current nav item is an irrelevant page.
   //   4. Current nav item is a session restoration.
-  // For 1, 2 and 4, load an empty string to remove existing JS code.
+  // For 1, 2 and 4, load an empty string to remove existing JS code. The URL is
+  // also updated to the URL of the page that failed to allow back/forward
+  // navigations even on navigations originating from pushstate. See
+  // crbug.com/1153261.
   // For 3, load error page file to create a new nav item.
   // The actual error HTML will be loaded in didFinishNavigation callback.
   WKNavigation* errorNavigation = nil;
@@ -2017,7 +2018,8 @@
     errorNavigation = [webView loadFileURL:errorPage.errorPageFileURL
                    allowingReadAccessToURL:errorPage.errorPageFileURL];
   } else {
-    errorNavigation = [webView loadHTMLString:@"" baseURL:backForwardItem.URL];
+    errorNavigation = [webView loadHTMLString:@""
+                                      baseURL:errorPage.failedNavigationURL];
   }
   [self.navigationStates setState:web::WKNavigationState::REQUESTED
                     forNavigation:errorNavigation];
diff --git a/ios/web/navigation/error_retry_state_machine_unittest.mm b/ios/web/navigation/error_retry_state_machine_unittest.mm
index ce203c6f..d1de6fda 100644
--- a/ios/web/navigation/error_retry_state_machine_unittest.mm
+++ b/ios/web/navigation/error_retry_state_machine_unittest.mm
@@ -4,6 +4,8 @@
 
 #include "ios/web/navigation/error_retry_state_machine.h"
 
+#include "base/test/scoped_feature_list.h"
+#import "ios/web/common/features.h"
 #include "ios/web/navigation/wk_navigation_util.h"
 #import "ios/web/public/web_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -19,7 +21,15 @@
 
 static const char kTestUrl[] = "http://test.com";
 
-typedef PlatformTest ErrorRetryStateMachineTest;
+class ErrorRetryStateMachineTest : public PlatformTest {
+ public:
+  ErrorRetryStateMachineTest() {
+    scoped_feature_list_.InitAndDisableFeature(features::kUseJSForErrorPage);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
 
 // Tests load failure during provisional navigation.
 TEST_F(ErrorRetryStateMachineTest, OfflineThenReload) {
diff --git a/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm b/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm
index 441e4ee..3db816bd 100644
--- a/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm
+++ b/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm
@@ -317,6 +317,9 @@
 // Tests that AddPendingItem does not create a new NavigationItem if the new
 // pending item is a reload of app-specific URL.
 TEST_F(WKBasedNavigationManagerTest, ReusePendingItemForReloadAppSpecificURL) {
+  if (base::FeatureList::IsEnabled(features::kUseJSForErrorPage))
+    return;
+
   // Simulate a previous app-specific navigation.
   NSString* url = @"about:blank?for=chrome%3A%2F%2Fnewtab";
   [mock_wk_list_ setCurrentURL:url];
diff --git a/ios/web/navigation/wk_navigation_util_unittest.mm b/ios/web/navigation/wk_navigation_util_unittest.mm
index 16c714d6..06275ad 100644
--- a/ios/web/navigation/wk_navigation_util_unittest.mm
+++ b/ios/web/navigation/wk_navigation_util_unittest.mm
@@ -438,8 +438,14 @@
   EXPECT_FALSE(URLNeedsUserAgentType(
       non_user_agent_urls.ReplaceComponents(scheme_replacements)));
 
-  // Not a placeholder or normal URL.
-  EXPECT_TRUE(URLNeedsUserAgentType(GURL("about:blank?for=")));
+  if (base::FeatureList::IsEnabled(features::kUseJSForErrorPage)) {
+    // about:blank pages.
+    EXPECT_FALSE(URLNeedsUserAgentType(GURL("about:blank")));
+  } else {
+    // Not a placeholder.
+    EXPECT_TRUE(URLNeedsUserAgentType(GURL("about:blank?for=")));
+  }
+  // Normal URL.
   EXPECT_TRUE(URLNeedsUserAgentType(GURL("http://www.0.com")));
 
   // file:// URL.
diff --git a/ios/web/web_state/error_page_inttest.mm b/ios/web/web_state/error_page_inttest.mm
index 823d1a7..7ee0c26 100644
--- a/ios/web/web_state/error_page_inttest.mm
+++ b/ios/web/web_state/error_page_inttest.mm
@@ -167,6 +167,11 @@
 #define MAYBE_BackForwardErrorPage FLAKY_BackForwardErrorPage
 #endif
 TEST_F(ErrorPageTest, MAYBE_BackForwardErrorPage) {
+  if (base::FeatureList::IsEnabled(features::kUseJSForErrorPage)) {
+    // TODO(crbug.com/1151938): Check if the test can be re-enabled with higher
+    // version of WebKit.
+    return;
+  }
   test::LoadUrl(web_state(), server_.GetURL("/close-socket"));
   ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/close-socket")));
 
@@ -220,7 +225,14 @@
   server_responds_with_content_ = false;
   test::LoadUrl(web_state(), server_.GetURL("/echo-query?foo"));
   ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/echo-query?foo")));
-  ASSERT_FALSE(security_state_info());
+  if (base::FeatureList::IsEnabled(features::kUseJSForErrorPage)) {
+    ASSERT_TRUE(security_state_info());
+    ASSERT_TRUE(security_state_info()->visible_ssl_status);
+    EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED,
+              security_state_info()->visible_ssl_status->security_style);
+  } else {
+    ASSERT_FALSE(security_state_info());
+  }
 
   // Reload the page, which should load without errors.
   server_responds_with_content_ = true;
@@ -244,8 +256,13 @@
   ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/echo-query?foo")));
   ASSERT_TRUE(security_state_info());
   ASSERT_TRUE(security_state_info()->visible_ssl_status);
-  EXPECT_EQ(SECURITY_STYLE_UNKNOWN,
-            security_state_info()->visible_ssl_status->security_style);
+  if (base::FeatureList::IsEnabled(features::kUseJSForErrorPage)) {
+    EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED,
+              security_state_info()->visible_ssl_status->security_style);
+  } else {
+    EXPECT_EQ(SECURITY_STYLE_UNKNOWN,
+              security_state_info()->visible_ssl_status->security_style);
+  }
 }
 
 // Sucessfully loads the page, goes back, stops the server, goes forward and
@@ -278,8 +295,13 @@
   ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/echo-query?foo")));
   ASSERT_TRUE(security_state_info());
   ASSERT_TRUE(security_state_info()->visible_ssl_status);
-  EXPECT_EQ(SECURITY_STYLE_UNKNOWN,
-            security_state_info()->visible_ssl_status->security_style);
+  if (base::FeatureList::IsEnabled(features::kUseJSForErrorPage)) {
+    EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED,
+              security_state_info()->visible_ssl_status->security_style);
+  } else {
+    EXPECT_EQ(SECURITY_STYLE_UNKNOWN,
+              security_state_info()->visible_ssl_status->security_style);
+  }
 #endif  // TARGET_IPHONE_SIMULATOR
 }
 
@@ -304,8 +326,13 @@
   ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/close-socket")));
   ASSERT_TRUE(security_state_info());
   ASSERT_TRUE(security_state_info()->visible_ssl_status);
-  EXPECT_EQ(SECURITY_STYLE_UNKNOWN,
-            security_state_info()->visible_ssl_status->security_style);
+  if (base::FeatureList::IsEnabled(features::kUseJSForErrorPage)) {
+    EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED,
+              security_state_info()->visible_ssl_status->security_style);
+  } else {
+    EXPECT_EQ(SECURITY_STYLE_UNKNOWN,
+              security_state_info()->visible_ssl_status->security_style);
+  }
 }
 
 // Sucessfully loads the page, then loads the URL which fails to load, then
diff --git a/ios/web/web_state/web_state_observer_inttest.mm b/ios/web/web_state/web_state_observer_inttest.mm
index 25ace0c..ae8287d3 100644
--- a/ios/web/web_state/web_state_observer_inttest.mm
+++ b/ios/web/web_state/web_state_observer_inttest.mm
@@ -998,9 +998,11 @@
   EXPECT_CALL(observer_,
               PageLoaded(web_state(), PageLoadCompletionStatus::FAILURE));
 
-  // Load error page HTML by [WKWebView loadHTMLString:baseURL:].
-  EXPECT_CALL(observer_, DidStartLoading(web_state()));
-  EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  if (!base::FeatureList::IsEnabled(features::kUseJSForErrorPage)) {
+    // Load error page HTML by [WKWebView loadHTMLString:baseURL:].
+    EXPECT_CALL(observer_, DidStartLoading(web_state()));
+    EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  }
 
   test::LoadUrl(web_state(), url);
 
@@ -1013,7 +1015,7 @@
                                          testing::CreateConnectionLostError(),
                                          /*is_post=*/false, /*is_otr=*/false,
                                          /*cert_status=*/0)));
-  DCHECK_EQ(item->GetTitle(), base::UTF8ToUTF16(kFailedTitle));
+  EXPECT_EQ(item->GetTitle(), base::UTF8ToUTF16(kFailedTitle));
 }
 
 // Tests navigation to a URL with /..; suffix. On iOS 12 and earlier this
@@ -1131,9 +1133,11 @@
   EXPECT_CALL(observer_,
               PageLoaded(web_state(), PageLoadCompletionStatus::FAILURE));
 
-  // Load error page HTML by [WKWebView loadHTMLString:baseURL:].
-  EXPECT_CALL(observer_, DidStartLoading(web_state()));
-  EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  if (!base::FeatureList::IsEnabled(features::kUseJSForErrorPage)) {
+    // Load error page HTML by [WKWebView loadHTMLString:baseURL:].
+    EXPECT_CALL(observer_, DidStartLoading(web_state()));
+    EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  }
 
   test::LoadUrl(web_state(), url);
   NSError* error = testing::CreateErrorWithUnderlyingErrorChain(
@@ -1177,9 +1181,11 @@
   EXPECT_CALL(observer_,
               PageLoaded(web_state(), PageLoadCompletionStatus::FAILURE));
 
-  // Load error page HTML by [WKWebView loadHTMLString:baseURL:].
-  EXPECT_CALL(observer_, DidStartLoading(web_state()));
-  EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  if (!base::FeatureList::IsEnabled(features::kUseJSForErrorPage)) {
+    // Load error page HTML by [WKWebView loadHTMLString:baseURL:].
+    EXPECT_CALL(observer_, DidStartLoading(web_state()));
+    EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  }
 
   test::LoadUrl(web_state(), url);
   NSError* error = testing::CreateErrorWithUnderlyingErrorChain(
@@ -1993,9 +1999,11 @@
   EXPECT_CALL(observer_,
               PageLoaded(web_state(), PageLoadCompletionStatus::FAILURE));
 
-  // Load error page HTML by [WKWebView loadHTMLString:baseURL:].
-  EXPECT_CALL(observer_, DidStartLoading(web_state()));
-  EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  if (!base::FeatureList::IsEnabled(features::kUseJSForErrorPage)) {
+    // Load error page HTML by [WKWebView loadHTMLString:baseURL:].
+    EXPECT_CALL(observer_, DidStartLoading(web_state()));
+    EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  }
 
   test::LoadUrl(web_state(), url);
 
@@ -2036,9 +2044,11 @@
   EXPECT_CALL(observer_,
               PageLoaded(web_state(), PageLoadCompletionStatus::FAILURE));
 
-  // Finally, the error page itself is loaded.
-  EXPECT_CALL(observer_, DidStartLoading(web_state()));
-  EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  if (!base::FeatureList::IsEnabled(features::kUseJSForErrorPage)) {
+    // Finally, the error page itself is loaded.
+    EXPECT_CALL(observer_, DidStartLoading(web_state()));
+    EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  }
 
   test::LoadUrl(web_state(), url);
   EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
@@ -2085,7 +2095,6 @@
           WebStatePolicyDecider::PolicyDecision::CancelAndDisplayError(error)));
 
   EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
-  EXPECT_CALL(observer_, TitleWasSet(web_state()));
   EXPECT_CALL(observer_, DidStopLoading(web_state()));
   EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
   EXPECT_CALL(observer_,
diff --git a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
index d993ac3..7e9bf64 100644
--- a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
@@ -17,6 +17,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/sequence_checker.h"
+#include "base/stl_util.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -221,25 +222,17 @@
   }
 
   // Prepare exif.
-  const uint8_t* exif_buffer;
+  const uint8_t* exif_buffer = nullptr;
   size_t exif_buffer_size = 0;
   if (exif_mapping) {
     exif_buffer = static_cast<const uint8_t*>(exif_mapping->memory());
     exif_buffer_size = exif_mapping->size();
-  } else {
-    exif_buffer = nullptr;
   }
-  // When the exif buffer contains a thumbnail, the VAAPI encoder would
-  // generate a corrupted JPEG. We can work around the problem by supplying an
-  // all-zero buffer with the same size and fill in the real exif buffer after
-  // encoding.
-  // TODO(shenghao): Remove this mechanism after b/79840013 is fixed.
-  std::vector<uint8_t> exif_buffer_dummy(exif_buffer_size, 0);
-  size_t exif_offset = 0;
 
-  if (!jpeg_encoder_->Encode(input_size, exif_buffer_dummy.data(),
-                             exif_buffer_size, quality, blit_surface->id(),
-                             cached_output_buffer_->id(), &exif_offset)) {
+  if (!jpeg_encoder_->Encode(input_size, /*exif_buffer=*/nullptr,
+                             /*exif_buffer_size=*/0u, quality,
+                             blit_surface->id(), cached_output_buffer_->id(),
+                             /*exif_offset=*/nullptr)) {
     VLOGF(1) << "Encode JPEG failed";
     notify_error_cb_.Run(task_id, PLATFORM_FAILURE);
     return;
@@ -268,12 +261,14 @@
     return;
   }
 
-  bool isMapped = output_gmb_buffer->Map();
-  if (!isMapped) {
+  const bool is_mapped = output_gmb_buffer->Map();
+  if (!is_mapped) {
     VLOGF(1) << "Map the output gmb buffer failed";
     notify_error_cb_.Run(task_id, PLATFORM_FAILURE);
     return;
   }
+  base::ScopedClosureRunner output_gmb_buffer_unmapper(base::BindOnce(
+      &gfx::GpuMemoryBuffer::Unmap, base::Unretained(output_gmb_buffer.get())));
 
   // Get the encoded output. DownloadFromVABuffer() is a blocking call. It
   // would wait until encoding is finished.
@@ -282,22 +277,82 @@
   // Since the format of |output_gmb_buffer| is gfx::BufferFormat::R_8, we can
   // use its area as the maximum bytes we need to download to avoid buffer
   // overflow.
-  if (!vaapi_wrapper_->DownloadFromVABuffer(
-          cached_output_buffer_->id(), blit_surface->id(),
-          static_cast<uint8_t*>(output_memory),
-          output_gmb_buffer->GetSize().GetArea(), &encoded_size)) {
-    VLOGF(1) << "Failed to retrieve output image from VA coded buffer";
+  // Since we didn't supply EXIF data to the JPEG encoder, it creates a default
+  // APP0 segment in the header. We will download the result to an offset and
+  // replace the APP0 segment by APP1 including EXIF data:
+  //      SOI + APP0 (2 + 2 + 14 bytes) + other data
+  //   -> SOI + APP1 (2 + 2 + |exif_buffer_size| bytes) + other data
+  // Note that |exif_buffer_size| >= 14 since EXIF + TIFF headers are 14 bytes,
+  // and <= (2^16-1)-2 since APP1 data size is stored in 2 bytes.
+  // TODO(b/171369066, b/171340559): Remove this workaround when Intel iHD
+  // driver has fixed the EXIF handling.
+  constexpr size_t kApp0DataSize = 14;
+  constexpr size_t kMaxExifSize = ((1u << 16) - 1) - 2;
+  if (exif_buffer_size > 0 &&
+      (exif_buffer_size < kApp0DataSize || exif_buffer_size > kMaxExifSize)) {
+    VLOGF(1) << "Unexpected EXIF data size (" << exif_buffer_size << ")";
     notify_error_cb_.Run(task_id, PLATFORM_FAILURE);
-
-    output_gmb_buffer->Unmap();
     return;
   }
+  const size_t output_offset =
+      exif_buffer_size > 0 ? exif_buffer_size - kApp0DataSize : 0;
+  const size_t output_size =
+      base::checked_cast<size_t>(output_gmb_buffer->GetSize().GetArea());
+  if (output_offset >= output_size) {
+    VLOGF(1) << "Output buffer size (" << output_size << ") is too small";
+    notify_error_cb_.Run(task_id, PLATFORM_FAILURE);
+    return;
+  }
+  uint8_t* frame_content = output_memory + output_offset;
+  const size_t max_frame_size = output_size - output_offset;
+  if (!vaapi_wrapper_->DownloadFromVABuffer(cached_output_buffer_->id(),
+                                            blit_surface->id(), frame_content,
+                                            max_frame_size, &encoded_size)) {
+    VLOGF(1) << "Failed to retrieve output image from VA coded buffer";
+    notify_error_cb_.Run(task_id, PLATFORM_FAILURE);
+    return;
+  }
+  DCHECK_LE(encoded_size, max_frame_size);
 
-  // Copy the real exif buffer into preserved space.
-  memcpy(static_cast<uint8_t*>(output_memory) + exif_offset, exif_buffer,
-         exif_buffer_size);
+  if (exif_buffer_size > 0) {
+    // Check the output header is 2+2+14 bytes APP0 as expected.
+    constexpr uint8_t kJpegSoiAndApp0Header[] = {
+        0xFF, JPEG_SOI, 0xFF, JPEG_APP0, 0x00, 0x10,
+    };
+    if (encoded_size < base::size(kJpegSoiAndApp0Header)) {
+      VLOGF(1) << "Unexpected JPEG data size received from encoder";
+      notify_error_cb_.Run(task_id, PLATFORM_FAILURE);
+      return;
+    }
+    for (size_t i = 0; i < base::size(kJpegSoiAndApp0Header); ++i) {
+      if (frame_content[i] != kJpegSoiAndApp0Header[i]) {
+        VLOGF(1) << "Unexpected JPEG header received from encoder";
+        notify_error_cb_.Run(task_id, PLATFORM_FAILURE);
+        return;
+      }
+    }
+    // Copy the EXIF data into preserved space.
+    const uint8_t jpeg_soi_and_app1_header[] = {
+        0xFF,
+        JPEG_SOI,
+        0xFF,
+        JPEG_APP1,
+        static_cast<uint8_t>((exif_buffer_size + 2) / 256),
+        static_cast<uint8_t>((exif_buffer_size + 2) % 256),
+    };
+    DCHECK_GE(output_size, base::size(jpeg_soi_and_app1_header));
+    if (exif_buffer_size > output_size - base::size(jpeg_soi_and_app1_header)) {
+      VLOGF(1) << "Insufficient buffer size reserved for JPEG APP1 data";
+      notify_error_cb_.Run(task_id, PLATFORM_FAILURE);
+      return;
+    }
+    memcpy(output_memory, jpeg_soi_and_app1_header,
+           base::size(jpeg_soi_and_app1_header));
+    memcpy(output_memory + base::size(jpeg_soi_and_app1_header), exif_buffer,
+           exif_buffer_size);
+    encoded_size += output_offset;
+  }
 
-  output_gmb_buffer->Unmap();
   video_frame_ready_cb_.Run(task_id, encoded_size);
 }
 
diff --git a/net/dns/dns_config_service_posix.cc b/net/dns/dns_config_service_posix.cc
index 4d4df80fe..a357d2e 100644
--- a/net/dns/dns_config_service_posix.cc
+++ b/net/dns/dns_config_service_posix.cc
@@ -115,7 +115,8 @@
 
   bool Watch(const CallbackType& callback) {
     callback_ = callback;
-    return watcher_.Watch(base::FilePath(kFilePathConfig), false,
+    return watcher_.Watch(base::FilePath(kFilePathConfig),
+                          base::FilePathWatcher::Type::kNonRecursive,
                           base::BindRepeating(&DnsConfigWatcher::OnCallback,
                                               base::Unretained(this)));
   }
diff --git a/net/dns/dns_config_service_win.cc b/net/dns/dns_config_service_win.cc
index 37fd844..1d6dcaf 100644
--- a/net/dns/dns_config_service_win.cc
+++ b/net/dns/dns_config_service_win.cc
@@ -620,7 +620,8 @@
     dnscache_watcher_.Watch(kDnscachePath, callback);
     policy_watcher_.Watch(kPolicyPath, callback);
 
-    if (!hosts_watcher_.Watch(GetHostsPath(), false,
+    if (!hosts_watcher_.Watch(GetHostsPath(),
+                              base::FilePathWatcher::Type::kNonRecursive,
                               base::BindRepeating(&Watcher::OnHostsChanged,
                                                   base::Unretained(this)))) {
       UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
diff --git a/remoting/host/config_file_watcher.cc b/remoting/host/config_file_watcher.cc
index 74d500f..3b60ec79 100644
--- a/remoting/host/config_file_watcher.cc
+++ b/remoting/host/config_file_watcher.cc
@@ -142,7 +142,7 @@
   // Start watching the configuration file.
   config_watcher_.reset(new base::FilePathWatcher());
   if (!config_watcher_->Watch(
-          config_path_, false,
+          config_path_, base::FilePathWatcher::Type::kNonRecursive,
           base::BindRepeating(&ConfigFileWatcherImpl::OnConfigUpdated, this))) {
     PLOG(ERROR) << "Couldn't watch file '" << config_path_.value() << "'";
     main_task_runner_->PostTask(
diff --git a/remoting/host/linux/audio_pipe_reader.cc b/remoting/host/linux/audio_pipe_reader.cc
index fdd4a39..4f44c2f 100644
--- a/remoting/host/linux/audio_pipe_reader.cc
+++ b/remoting/host/linux/audio_pipe_reader.cc
@@ -69,7 +69,7 @@
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   if (!file_watcher_.Watch(
-          pipe_path_.DirName(), true,
+          pipe_path_.DirName(), base::FilePathWatcher::Type::kRecursive,
           base::BindRepeating(&AudioPipeReader::OnDirectoryChanged,
                               base::Unretained(this)))) {
     LOG(ERROR) << "Failed to watch pulseaudio directory "
diff --git a/remoting/host/linux/certificate_watcher.cc b/remoting/host/linux/certificate_watcher.cc
index b5541937..ee031ee 100644
--- a/remoting/host/linux/certificate_watcher.cc
+++ b/remoting/host/linux/certificate_watcher.cc
@@ -113,7 +113,7 @@
 
   // base::Unretained() is safe since this class owns the FileWatcher.
   file_watcher_->Watch(
-      cert_watch_path_, true,
+      cert_watch_path_, base::FilePathWatcher::Type::kRecursive,
       base::BindRepeating(&CertDbContentWatcher::OnCertDirectoryChanged,
                           base::Unretained(this)));
 
diff --git a/services/device/time_zone_monitor/time_zone_monitor_linux.cc b/services/device/time_zone_monitor/time_zone_monitor_linux.cc
index 9f69bd6..d5f2abc2 100644
--- a/services/device/time_zone_monitor/time_zone_monitor_linux.cc
+++ b/services/device/time_zone_monitor/time_zone_monitor_linux.cc
@@ -129,8 +129,9 @@
     };
     for (size_t index = 0; index < base::size(kFilesToWatch); ++index) {
       file_path_watchers_.push_back(std::make_unique<base::FilePathWatcher>());
-      file_path_watchers_.back()->Watch(base::FilePath(kFilesToWatch[index]),
-                                        false, callback);
+      file_path_watchers_.back()->Watch(
+          base::FilePath(kFilesToWatch[index]),
+          base::FilePathWatcher::Type::kNonRecursive, callback);
     }
   }
 
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index b7136a2e..6ae5567e 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -8496,7 +8496,8 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/chromeos.unit_tests.filter"
         ],
         "merge": {
           "args": [],
@@ -10440,7 +10441,8 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/chromeos.unit_tests.filter"
         ],
         "merge": {
           "args": [],
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index b1d2f10..30b308f 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -2722,6 +2722,9 @@
         },
       },
       'Linux Chromium OS ASan LSan Tests (1)': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/chromeos.unit_tests.filter',
+        ],
         # These are slow on the ASAN trybot for some reason.
         # crbug.com/794372
         'swarming': {
@@ -2729,6 +2732,9 @@
         },
       },
       'Linux ChromiumOS MSan Tests': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/chromeos.unit_tests.filter',
+        ],
         # These are very slow on the Chrome OS MSAN trybot for some reason.
         # crbug.com/865455
         'swarming': {
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index cbf5b3db..e95e3758e 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -221,7 +221,7 @@
 // Controls whether the implementation of the performance.measureMemory
 // web API uses PerformanceManager or not.
 const base::Feature kWebMeasureMemoryViaPerformanceManager{
-    "WebMeasureMemoryViaPerformanceManager", base::FEATURE_DISABLED_BY_DEFAULT};
+    "WebMeasureMemoryViaPerformanceManager", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables negotiation of experimental multiplex codec in SDP.
 const base::Feature kWebRtcMultiplexCodec{"WebRTC-MultiplexCodec",
diff --git a/third_party/blink/public/mojom/input/synchronous_compositor.mojom b/third_party/blink/public/mojom/input/synchronous_compositor.mojom
index 04f130f..a1a266fb5 100644
--- a/third_party/blink/public/mojom/input/synchronous_compositor.mojom
+++ b/third_party/blink/public/mojom/input/synchronous_compositor.mojom
@@ -10,6 +10,7 @@
 import "services/viz/public/mojom/compositing/compositor_frame.mojom";
 import "services/viz/public/mojom/compositing/compositor_frame_metadata.mojom";
 import "services/viz/public/mojom/compositing/frame_timing_details.mojom";
+import "services/viz/public/mojom/compositing/local_surface_id.mojom";
 import "services/viz/public/mojom/compositing/returned_resource.mojom";
 import "services/viz/public/mojom/hit_test/hit_test_region_list.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
@@ -56,6 +57,7 @@
       (SyncCompositorCommonRendererParams result,
        uint32 layer_tree_frame_sink_id,
        uint32 metadata_version,
+       viz.mojom.LocalSurfaceId? local_surface_id,
        viz.mojom.CompositorFrame? frame,
        viz.mojom.HitTestRegionList? hit_test_region_list);
 
@@ -143,6 +145,7 @@
   // Response from DrawHwAsync.
   ReturnFrame(uint32 layer_tree_frame_sink_id,
               uint32 metadata_version,
+              viz.mojom.LocalSurfaceId? local_surface_id,
               viz.mojom.CompositorFrame? frame,
               viz.mojom.HitTestRegionList? hit_test_region_list);
 
diff --git a/third_party/blink/public/mojom/payments/payment_request.mojom b/third_party/blink/public/mojom/payments/payment_request.mojom
index 577cc64..0935f62 100644
--- a/third_party/blink/public/mojom/payments/payment_request.mojom
+++ b/third_party/blink/public/mojom/payments/payment_request.mojom
@@ -38,7 +38,8 @@
   USER_CANCEL,
   NOT_SUPPORTED,
   NOT_SUPPORTED_FOR_INVALID_ORIGIN_OR_SSL,
-  ALREADY_SHOWING
+  ALREADY_SHOWING,
+  INVALID_DATA_FROM_RENDERER,
 };
 
 enum CanMakePaymentQueryResult {
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 1e74ccf..9cbbff8 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -263,6 +263,7 @@
   deps = [
     "//build:chromeos_buildflags",
     "//components/paint_preview/common",
+    "//components/performance_manager/public/mojom:mojom_blink",
     "//components/shared_highlighting/core/common",
     "//gpu/config",
     "//mojo/public/cpp/bindings:bindings",
diff --git a/third_party/blink/renderer/core/editing/inline_box_position.cc b/third_party/blink/renderer/core/editing/inline_box_position.cc
index d20b9b55..14cab4d 100644
--- a/third_party/blink/renderer/core/editing/inline_box_position.cc
+++ b/third_party/blink/renderer/core/editing/inline_box_position.cc
@@ -366,11 +366,6 @@
   return ComputeInlineBoxPositionTemplate<EditingInFlatTreeStrategy>(position);
 }
 
-InlineBoxPosition ComputeInlineBoxPosition(const VisiblePosition& position) {
-  DCHECK(position.IsValid()) << position;
-  return ComputeInlineBoxPosition(position.ToPositionWithAffinity());
-}
-
 PositionWithAffinity ComputeInlineAdjustedPosition(
     const PositionWithAffinity& position) {
   return ComputeInlineAdjustedPositionAlgorithm(position, 0);
@@ -381,13 +376,6 @@
   return ComputeInlineAdjustedPositionAlgorithm(position, 0);
 }
 
-PositionWithAffinity ComputeInlineAdjustedPosition(
-    const VisiblePosition& position) {
-  DCHECK(position.IsValid()) << position;
-  return ComputeInlineAdjustedPositionAlgorithm(
-      position.ToPositionWithAffinity(), 0);
-}
-
 InlineBoxPosition ComputeInlineBoxPositionForInlineAdjustedPosition(
     const PositionWithAffinity& position) {
   return ComputeInlineBoxPositionForInlineAdjustedPositionAlgorithm(position);
diff --git a/third_party/blink/renderer/core/editing/inline_box_position.h b/third_party/blink/renderer/core/editing/inline_box_position.h
index 3d58ea1..5dab73e 100644
--- a/third_party/blink/renderer/core/editing/inline_box_position.h
+++ b/third_party/blink/renderer/core/editing/inline_box_position.h
@@ -69,12 +69,10 @@
 ComputeInlineBoxPosition(const PositionWithAffinity&);
 CORE_EXPORT InlineBoxPosition
 ComputeInlineBoxPosition(const PositionInFlatTreeWithAffinity&);
-CORE_EXPORT InlineBoxPosition ComputeInlineBoxPosition(const VisiblePosition&);
 
 PositionWithAffinity ComputeInlineAdjustedPosition(const PositionWithAffinity&);
 PositionInFlatTreeWithAffinity ComputeInlineAdjustedPosition(
     const PositionInFlatTreeWithAffinity&);
-PositionWithAffinity ComputeInlineAdjustedPosition(const VisiblePosition&);
 
 InlineBoxPosition ComputeInlineBoxPositionForInlineAdjustedPosition(
     const PositionWithAffinity&);
diff --git a/third_party/blink/renderer/core/editing/selection_modifier.cc b/third_party/blink/renderer/core/editing/selection_modifier.cc
index 61766be..91c8b81 100644
--- a/third_party/blink/renderer/core/editing/selection_modifier.cc
+++ b/third_party/blink/renderer/core/editing/selection_modifier.cc
@@ -74,11 +74,11 @@
 VisiblePosition SelectionModifier::PreviousParagraphPosition(
     const VisiblePosition& passed_position,
     LayoutUnit x_point) {
-  DCHECK(passed_position.IsValid()) << passed_position;
   VisiblePosition position = passed_position;
   do {
-    const VisiblePosition& new_position =
-        PreviousLinePosition(position, x_point);
+    DCHECK(position.IsValid()) << position;
+    const VisiblePosition& new_position = CreateVisiblePosition(
+        PreviousLinePosition(position.ToPositionWithAffinity(), x_point));
     if (new_position.IsNull() ||
         new_position.DeepEquivalent() == position.DeepEquivalent())
       break;
@@ -91,10 +91,11 @@
 VisiblePosition SelectionModifier::NextParagraphPosition(
     const VisiblePosition& passed_position,
     LayoutUnit x_point) {
-  DCHECK(passed_position.IsValid()) << passed_position;
   VisiblePosition position = passed_position;
   do {
-    const VisiblePosition& new_position = NextLinePosition(position, x_point);
+    DCHECK(position.IsValid()) << position;
+    const VisiblePosition& new_position = CreateVisiblePosition(
+        NextLinePosition(position.ToPositionWithAffinity(), x_point));
     if (new_position.IsNull() ||
         new_position.DeepEquivalent() == position.DeepEquivalent())
       break;
@@ -151,7 +152,8 @@
 
 namespace {
 
-base::Optional<TextDirection> DirectionAt(const VisiblePosition& position) {
+base::Optional<TextDirection> DirectionAt(
+    const PositionWithAffinity& position) {
   if (position.IsNull())
     return base::nullopt;
   const PositionWithAffinity adjusted = ComputeInlineAdjustedPosition(position);
@@ -173,7 +175,8 @@
 }
 
 // TODO(xiaochengh): Deduplicate code with |DirectionAt()|.
-base::Optional<TextDirection> LineDirectionAt(const VisiblePosition& position) {
+base::Optional<TextDirection> LineDirectionAt(
+    const PositionWithAffinity& position) {
   if (position.IsNull())
     return base::nullopt;
   const PositionWithAffinity adjusted = ComputeInlineAdjustedPosition(position);
@@ -198,9 +201,9 @@
 
 TextDirection DirectionOf(const VisibleSelection& visible_selection) {
   base::Optional<TextDirection> maybe_start_direction =
-      DirectionAt(visible_selection.VisibleStart());
+      DirectionAt(visible_selection.VisibleStart().ToPositionWithAffinity());
   base::Optional<TextDirection> maybe_end_direction =
-      DirectionAt(visible_selection.VisibleEnd());
+      DirectionAt(visible_selection.VisibleEnd().ToPositionWithAffinity());
   if (maybe_start_direction.has_value() && maybe_end_direction.has_value() &&
       maybe_start_direction.value() == maybe_end_direction.value())
     return maybe_start_direction.value();
@@ -215,7 +218,7 @@
 }
 
 TextDirection SelectionModifier::LineDirectionOfExtent() const {
-  return LineDirectionAt(selection_.VisibleExtent())
+  return LineDirectionAt(selection_.VisibleExtent().ToPositionWithAffinity())
       .value_or(DirectionOfEnclosingBlockOf(selection_.Extent()));
 }
 
@@ -382,11 +385,17 @@
       return CreateVisiblePosition(NextWordPositionForPlatform(
           ComputeVisibleExtent(selection_).DeepEquivalent()));
     case TextGranularity::kSentence:
-      return NextSentencePosition(ComputeVisibleExtent(selection_));
-    case TextGranularity::kLine:
-      return NextLinePosition(
-          ComputeVisibleExtent(selection_),
-          LineDirectionPointForBlockDirectionNavigation(selection_.Extent()));
+      return CreateVisiblePosition(
+          NextSentencePosition(
+              ComputeVisibleExtent(selection_).DeepEquivalent()),
+          TextAffinity::kUpstreamIfPossible);
+    case TextGranularity::kLine: {
+      const VisiblePosition& pos = ComputeVisibleExtent(selection_);
+      DCHECK(pos.IsValid()) << pos;
+      return CreateVisiblePosition(NextLinePosition(
+          pos.ToPositionWithAffinity(),
+          LineDirectionPointForBlockDirectionNavigation(selection_.Extent())));
+    }
     case TextGranularity::kParagraph:
       return NextParagraphPosition(
           ComputeVisibleExtent(selection_),
@@ -399,8 +408,11 @@
       return EndOfParagraph(EndForPlatform());
     case TextGranularity::kDocumentBoundary: {
       const VisiblePosition& pos = EndForPlatform();
-      if (IsEditablePosition(pos.DeepEquivalent()))
-        return EndOfEditableContent(pos);
+      if (IsEditablePosition(pos.DeepEquivalent())) {
+        DCHECK(pos.IsValid()) << pos;
+        return CreateVisiblePosition(
+            EndOfEditableContent(pos.DeepEquivalent()));
+      }
       return EndOfDocument(pos);
     }
   }
@@ -462,7 +474,10 @@
       return CreateVisiblePosition(NextWordPositionForPlatform(
           ComputeVisibleExtent(selection_).DeepEquivalent()));
     case TextGranularity::kSentence:
-      return NextSentencePosition(ComputeVisibleExtent(selection_));
+      return CreateVisiblePosition(
+          NextSentencePosition(
+              ComputeVisibleExtent(selection_).DeepEquivalent()),
+          TextAffinity::kUpstreamIfPossible);
     case TextGranularity::kLine: {
       // down-arrowing from a range selection that ends at the start of a line
       // needs to leave the selection at that line start (no need to call
@@ -470,9 +485,10 @@
       const VisiblePosition& pos = EndForPlatform();
       if (selection_.IsRange() && IsStartOfLine(pos))
         return pos;
-      return NextLinePosition(
-          pos,
-          LineDirectionPointForBlockDirectionNavigation(selection_.Start()));
+      DCHECK(pos.IsValid()) << pos;
+      return CreateVisiblePosition(NextLinePosition(
+          pos.ToPositionWithAffinity(),
+          LineDirectionPointForBlockDirectionNavigation(selection_.Start())));
     }
     case TextGranularity::kParagraph:
       return NextParagraphPosition(
@@ -486,8 +502,11 @@
       return EndOfParagraph(EndForPlatform());
     case TextGranularity::kDocumentBoundary: {
       const VisiblePosition& pos = EndForPlatform();
-      if (IsEditablePosition(pos.DeepEquivalent()))
-        return EndOfEditableContent(pos);
+      if (IsEditablePosition(pos.DeepEquivalent())) {
+        DCHECK(pos.IsValid()) << pos;
+        return CreateVisiblePosition(
+            EndOfEditableContent(pos.DeepEquivalent()));
+      }
       return EndOfDocument(pos);
     }
   }
@@ -556,11 +575,15 @@
       return CreateVisiblePosition(PreviousWordPosition(
           ComputeVisibleExtent(selection_).DeepEquivalent()));
     case TextGranularity::kSentence:
-      return PreviousSentencePosition(ComputeVisibleExtent(selection_));
-    case TextGranularity::kLine:
-      return PreviousLinePosition(
-          ComputeVisibleExtent(selection_),
-          LineDirectionPointForBlockDirectionNavigation(selection_.Extent()));
+      return CreateVisiblePosition(PreviousSentencePosition(
+          ComputeVisibleExtent(selection_).DeepEquivalent()));
+    case TextGranularity::kLine: {
+      const VisiblePosition& pos = ComputeVisibleExtent(selection_);
+      DCHECK(pos.IsValid()) << pos;
+      return CreateVisiblePosition(PreviousLinePosition(
+          pos.ToPositionWithAffinity(),
+          LineDirectionPointForBlockDirectionNavigation(selection_.Extent())));
+    }
     case TextGranularity::kParagraph:
       return PreviousParagraphPosition(
           ComputeVisibleExtent(selection_),
@@ -573,8 +596,11 @@
       return StartOfParagraph(StartForPlatform());
     case TextGranularity::kDocumentBoundary: {
       const VisiblePosition pos = StartForPlatform();
-      if (IsEditablePosition(pos.DeepEquivalent()))
-        return StartOfEditableContent(pos);
+      if (IsEditablePosition(pos.DeepEquivalent())) {
+        DCHECK(pos.IsValid()) << pos;
+        return CreateVisiblePosition(
+            StartOfEditableContent(pos.DeepEquivalent()));
+      }
       return StartOfDocument(pos);
     }
   }
@@ -639,13 +665,17 @@
           ComputeVisibleExtent(selection_).DeepEquivalent()));
       break;
     case TextGranularity::kSentence:
-      pos = PreviousSentencePosition(ComputeVisibleExtent(selection_));
+      pos = CreateVisiblePosition(PreviousSentencePosition(
+          ComputeVisibleExtent(selection_).DeepEquivalent()));
       break;
-    case TextGranularity::kLine:
-      pos = PreviousLinePosition(
-          StartForPlatform(),
-          LineDirectionPointForBlockDirectionNavigation(selection_.Start()));
+    case TextGranularity::kLine: {
+      const VisiblePosition& start = StartForPlatform();
+      DCHECK(start.IsValid()) << start;
+      pos = CreateVisiblePosition(PreviousLinePosition(
+          start.ToPositionWithAffinity(),
+          LineDirectionPointForBlockDirectionNavigation(selection_.Start())));
       break;
+    }
     case TextGranularity::kParagraph:
       pos = PreviousParagraphPosition(
           StartForPlatform(),
@@ -662,10 +692,13 @@
       break;
     case TextGranularity::kDocumentBoundary:
       pos = StartForPlatform();
-      if (IsEditablePosition(pos.DeepEquivalent()))
-        pos = StartOfEditableContent(pos);
-      else
+      if (IsEditablePosition(pos.DeepEquivalent())) {
+        DCHECK(pos.IsValid()) << pos;
+        pos =
+            CreateVisiblePosition(StartOfEditableContent(pos.DeepEquivalent()));
+      } else {
         pos = StartOfDocument(pos);
+      }
       break;
   }
   return pos;
@@ -813,9 +846,8 @@
 }
 
 // TODO(yosin): Maybe baseline would be better?
-static bool AbsoluteCaretY(const VisiblePosition& c, int& y) {
-  DCHECK(c.IsValid()) << c;
-  IntRect rect = AbsoluteCaretBoundsOf(c.ToPositionWithAffinity());
+static bool AbsoluteCaretY(const PositionWithAffinity& c, int& y) {
+  IntRect rect = AbsoluteCaretBoundsOf(c);
   if (rect.IsEmpty())
     return false;
   y = rect.Y() + rect.Height() / 2;
@@ -860,7 +892,8 @@
   }
 
   int start_y;
-  if (!AbsoluteCaretY(pos, start_y))
+  DCHECK(pos.IsValid()) << pos;
+  if (!AbsoluteCaretY(pos.ToPositionWithAffinity(), start_y))
     return false;
   if (direction == SelectionModifyVerticalDirection::kUp)
     start_y = -start_y;
@@ -873,15 +906,19 @@
        iteration_count < kMaxIterationForPageGranularityMovement; p = next) {
     ++iteration_count;
 
-    if (direction == SelectionModifyVerticalDirection::kUp)
-      next = PreviousLinePosition(p, x_pos);
-    else
-      next = NextLinePosition(p, x_pos);
+    if (direction == SelectionModifyVerticalDirection::kUp) {
+      next = CreateVisiblePosition(
+          PreviousLinePosition(p.ToPositionWithAffinity(), x_pos));
+    } else {
+      next = CreateVisiblePosition(
+          NextLinePosition(p.ToPositionWithAffinity(), x_pos));
+    }
 
     if (next.IsNull() || next.DeepEquivalent() == p.DeepEquivalent())
       break;
     int next_y;
-    if (!AbsoluteCaretY(next, next_y))
+    DCHECK(next.IsValid()) << next;
+    if (!AbsoluteCaretY(next.ToPositionWithAffinity(), next_y))
       break;
     if (direction == SelectionModifyVerticalDirection::kUp)
       next_y = -next_y;
diff --git a/third_party/blink/renderer/core/editing/selection_modifier.h b/third_party/blink/renderer/core/editing/selection_modifier.h
index 0beea62..db0e55f 100644
--- a/third_party/blink/renderer/core/editing/selection_modifier.h
+++ b/third_party/blink/renderer/core/editing/selection_modifier.h
@@ -102,10 +102,11 @@
   VisiblePosition ModifyMovingBackward(TextGranularity);
   Position NextWordPositionForPlatform(const Position&);
 
-  static VisiblePosition PreviousLinePosition(const VisiblePosition&,
-                                              LayoutUnit line_direction_point);
-  static VisiblePosition NextLinePosition(const VisiblePosition&,
-                                          LayoutUnit line_direction_point);
+  static PositionWithAffinity PreviousLinePosition(
+      const PositionWithAffinity&,
+      LayoutUnit line_direction_point);
+  static PositionWithAffinity NextLinePosition(const PositionWithAffinity&,
+                                               LayoutUnit line_direction_point);
   static VisiblePosition PreviousParagraphPosition(
       const VisiblePosition&,
       LayoutUnit line_direction_point);
diff --git a/third_party/blink/renderer/core/editing/selection_modifier_line.cc b/third_party/blink/renderer/core/editing/selection_modifier_line.cc
index 7ee6536..1623cdf9 100644
--- a/third_party/blink/renderer/core/editing/selection_modifier_line.cc
+++ b/third_party/blink/renderer/core/editing/selection_modifier_line.cc
@@ -51,7 +51,7 @@
  public:
   AbstractLineBox() = default;
 
-  static AbstractLineBox CreateFor(const VisiblePosition&);
+  static AbstractLineBox CreateFor(const PositionWithAffinity&);
 
   bool IsNull() const { return type_ == Type::kNull; }
 
@@ -249,9 +249,10 @@
 };
 
 // static
-AbstractLineBox AbstractLineBox::CreateFor(const VisiblePosition& position) {
+AbstractLineBox AbstractLineBox::CreateFor(
+    const PositionWithAffinity& position) {
   if (position.IsNull() ||
-      !position.DeepEquivalent().AnchorNode()->GetLayoutObject()) {
+      !position.GetPosition().AnchorNode()->GetLayoutObject()) {
     return AbstractLineBox();
   }
 
@@ -343,18 +344,19 @@
   return nullptr;
 }
 
-bool InSameLine(const Node& node, const VisiblePosition& visible_position) {
+bool InSameLine(const Node& node, const PositionWithAffinity& position) {
   if (!node.GetLayoutObject())
     return true;
-  return InSameLine(CreateVisiblePosition(FirstPositionInOrBeforeNode(node)),
-                    visible_position);
+  return InSameLine(CreateVisiblePosition(FirstPositionInOrBeforeNode(node))
+                        .ToPositionWithAffinity(),
+                    position);
 }
 
 Node* FindNodeInPreviousLine(const Node& start_node,
-                             const VisiblePosition& visible_position) {
+                             const PositionWithAffinity& position) {
   for (Node* runner = PreviousLeafWithSameEditability(start_node); runner;
        runner = PreviousLeafWithSameEditability(*runner)) {
-    if (!InSameLine(*runner, visible_position))
+    if (!InSameLine(*runner, position))
       return runner;
   }
   return nullptr;
@@ -363,11 +365,9 @@
 // FIXME: consolidate with code in previousLinePosition.
 Position PreviousRootInlineBoxCandidatePosition(
     Node* node,
-    const VisiblePosition& visible_position) {
-  DCHECK(visible_position.IsValid()) << visible_position;
-  ContainerNode* highest_root =
-      HighestEditableRoot(visible_position.DeepEquivalent());
-  Node* const previous_node = FindNodeInPreviousLine(*node, visible_position);
+    const PositionWithAffinity& position) {
+  ContainerNode* highest_root = HighestEditableRoot(position.GetPosition());
+  Node* const previous_node = FindNodeInPreviousLine(*node, position);
   for (Node* runner = previous_node; runner && !runner->IsShadowRoot();
        runner = PreviousLeafWithSameEditability(*runner)) {
     if (HighestEditableRootOfNode(*runner) != highest_root)
@@ -385,16 +385,14 @@
 
 Position NextRootInlineBoxCandidatePosition(
     Node* node,
-    const VisiblePosition& visible_position) {
-  DCHECK(visible_position.IsValid()) << visible_position;
-  ContainerNode* highest_root =
-      HighestEditableRoot(visible_position.DeepEquivalent());
+    const PositionWithAffinity& position) {
+  ContainerNode* highest_root = HighestEditableRoot(position.GetPosition());
   // TODO(xiaochengh): We probably also need to pass in the starting editability
   // to |PreviousLeafWithSameEditability|.
-  const bool is_editable = HasEditableStyle(
-      *visible_position.DeepEquivalent().ComputeContainerNode());
+  const bool is_editable =
+      HasEditableStyle(*position.GetPosition().ComputeContainerNode());
   Node* next_node = NextLeafWithGivenEditability(node, is_editable);
-  while (next_node && InSameLine(*next_node, visible_position)) {
+  while (next_node && InSameLine(*next_node, position)) {
     next_node = NextLeafWithGivenEditability(next_node, is_editable);
   }
 
@@ -414,24 +412,22 @@
 }  // namespace
 
 // static
-VisiblePosition SelectionModifier::PreviousLinePosition(
-    const VisiblePosition& visible_position,
+PositionWithAffinity SelectionModifier::PreviousLinePosition(
+    const PositionWithAffinity& position,
     LayoutUnit line_direction_point) {
-  DCHECK(visible_position.IsValid()) << visible_position;
-
   // TODO(xiaochengh): Make all variables |const|.
 
-  Position p = visible_position.DeepEquivalent();
+  Position p = position.GetPosition();
   Node* node = p.AnchorNode();
 
   if (!node)
-    return VisiblePosition();
+    return PositionWithAffinity();
 
   LayoutObject* layout_object = node->GetLayoutObject();
   if (!layout_object)
-    return VisiblePosition();
+    return PositionWithAffinity();
 
-  AbstractLineBox line = AbstractLineBox::CreateFor(visible_position);
+  AbstractLineBox line = AbstractLineBox::CreateFor(position);
   if (!line.IsNull()) {
     line = line.PreviousLine();
     if (line.IsNull() || !line.CanBeCaretContainer())
@@ -439,15 +435,14 @@
   }
 
   if (line.IsNull()) {
-    Position position =
-        PreviousRootInlineBoxCandidatePosition(node, visible_position);
-    if (position.IsNotNull()) {
-      const VisiblePosition candidate = CreateVisiblePosition(position);
-      line = AbstractLineBox::CreateFor(candidate);
+    Position candidate = PreviousRootInlineBoxCandidatePosition(node, position);
+    if (candidate.IsNotNull()) {
+      line = AbstractLineBox::CreateFor(
+          CreateVisiblePosition(candidate).ToPositionWithAffinity());
       if (line.IsNull()) {
         // TODO(editing-dev): Investigate if this is correct for null
-        // |candidate|.
-        return candidate;
+        // |CreateVisiblePosition(candidate)|.
+        return PositionWithAffinity(candidate);
       }
     }
   }
@@ -457,18 +452,17 @@
     PhysicalOffset point_in_line =
         line.AbsoluteLineDirectionPointToLocalPointInBlock(
             line_direction_point);
-    if (auto position =
+    if (auto candidate =
             line.PositionForPoint(point_in_line, IsEditablePosition(p))) {
       // If the current position is inside an editable position, then the next
       // shouldn't end up inside non-editable as that would cross the editing
       // boundaries which would be an invalid selection.
       if (IsEditablePosition(p) &&
-          !IsEditablePosition(position.GetPosition())) {
-        return CreateVisiblePosition(
-            AdjustBackwardPositionToAvoidCrossingEditingBoundaries(
-                position, visible_position.DeepEquivalent()));
+          !IsEditablePosition(candidate.GetPosition())) {
+        return AdjustBackwardPositionToAvoidCrossingEditingBoundaries(candidate,
+                                                                      p);
       }
-      return CreateVisiblePosition(position);
+      return candidate;
     }
   }
 
@@ -479,29 +473,27 @@
                               ? RootEditableElement(*node)
                               : node->GetDocument().documentElement();
   if (!root_element)
-    return VisiblePosition();
-  return VisiblePosition::FirstPositionInNode(*root_element);
+    return PositionWithAffinity();
+  return PositionWithAffinity(Position::FirstPositionInNode(*root_element));
 }
 
 // static
-VisiblePosition SelectionModifier::NextLinePosition(
-    const VisiblePosition& visible_position,
+PositionWithAffinity SelectionModifier::NextLinePosition(
+    const PositionWithAffinity& position,
     LayoutUnit line_direction_point) {
-  DCHECK(visible_position.IsValid()) << visible_position;
-
   // TODO(xiaochengh): Make all variables |const|.
 
-  Position p = visible_position.DeepEquivalent();
+  Position p = position.GetPosition();
   Node* node = p.AnchorNode();
 
   if (!node)
-    return VisiblePosition();
+    return PositionWithAffinity();
 
   LayoutObject* layout_object = node->GetLayoutObject();
   if (!layout_object)
-    return VisiblePosition();
+    return PositionWithAffinity();
 
-  AbstractLineBox line = AbstractLineBox::CreateFor(visible_position);
+  AbstractLineBox line = AbstractLineBox::CreateFor(position);
   if (!line.IsNull()) {
     line = line.NextLine();
     if (line.IsNull() || !line.CanBeCaretContainer())
@@ -513,15 +505,15 @@
     Node* child = NodeTraversal::ChildAt(*node, p.ComputeEditingOffset());
     Node* search_start_node =
         child ? child : &NodeTraversal::LastWithinOrSelf(*node);
-    Position position =
-        NextRootInlineBoxCandidatePosition(search_start_node, visible_position);
-    if (position.IsNotNull()) {
-      const VisiblePosition candidate = CreateVisiblePosition(position);
-      line = AbstractLineBox::CreateFor(candidate);
+    Position candidate =
+        NextRootInlineBoxCandidatePosition(search_start_node, position);
+    if (candidate.IsNotNull()) {
+      line = AbstractLineBox::CreateFor(
+          CreateVisiblePosition(candidate).ToPositionWithAffinity());
       if (line.IsNull()) {
         // TODO(editing-dev): Investigate if this is correct for null
-        // |candidate|.
-        return candidate;
+        // |CreateVisiblePosition(candidate)|.
+        return PositionWithAffinity(candidate);
       }
     }
   }
@@ -531,18 +523,17 @@
     PhysicalOffset point_in_line =
         line.AbsoluteLineDirectionPointToLocalPointInBlock(
             line_direction_point);
-    if (auto position =
+    if (auto candidate =
             line.PositionForPoint(point_in_line, IsEditablePosition(p))) {
       // If the current position is inside an editable position, then the next
       // shouldn't end up inside non-editable as that would cross the editing
       // boundaries which would be an invalid selection.
       if (IsEditablePosition(p) &&
-          !IsEditablePosition(position.GetPosition())) {
-        return CreateVisiblePosition(
-            AdjustForwardPositionToAvoidCrossingEditingBoundaries(
-                position, visible_position.DeepEquivalent()));
+          !IsEditablePosition(candidate.GetPosition())) {
+        return AdjustForwardPositionToAvoidCrossingEditingBoundaries(candidate,
+                                                                     p);
       }
-      return CreateVisiblePosition(position);
+      return candidate;
     }
   }
 
@@ -553,8 +544,8 @@
                               ? RootEditableElement(*node)
                               : node->GetDocument().documentElement();
   if (!root_element)
-    return VisiblePosition();
-  return VisiblePosition::LastPositionInNode(*root_element);
+    return PositionWithAffinity();
+  return PositionWithAffinity(Position::LastPositionInNode(*root_element));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/editing/visible_units.cc b/third_party/blink/renderer/core/editing/visible_units.cc
index 9807ced0..1ae38de 100644
--- a/third_party/blink/renderer/core/editing/visible_units.cc
+++ b/third_party/blink/renderer/core/editing/visible_units.cc
@@ -452,25 +452,20 @@
 
 // ---------
 
-VisiblePosition StartOfEditableContent(
-    const VisiblePosition& visible_position) {
-  DCHECK(visible_position.IsValid()) << visible_position;
-  ContainerNode* highest_root =
-      HighestEditableRoot(visible_position.DeepEquivalent());
+Position StartOfEditableContent(const Position& position) {
+  ContainerNode* highest_root = HighestEditableRoot(position);
   if (!highest_root)
-    return VisiblePosition();
+    return Position();
 
-  return VisiblePosition::FirstPositionInNode(*highest_root);
+  return Position::FirstPositionInNode(*highest_root);
 }
 
-VisiblePosition EndOfEditableContent(const VisiblePosition& visible_position) {
-  DCHECK(visible_position.IsValid()) << visible_position;
-  ContainerNode* highest_root =
-      HighestEditableRoot(visible_position.DeepEquivalent());
+Position EndOfEditableContent(const Position& position) {
+  ContainerNode* highest_root = HighestEditableRoot(position);
   if (!highest_root)
-    return VisiblePosition();
+    return Position();
 
-  return VisiblePosition::LastPositionInNode(*highest_root);
+  return Position::LastPositionInNode(*highest_root);
 }
 
 bool IsEndOfEditableOrNonEditableContent(const VisiblePosition& position) {
diff --git a/third_party/blink/renderer/core/editing/visible_units.h b/third_party/blink/renderer/core/editing/visible_units.h
index 16fa1fb5..473ff89 100644
--- a/third_party/blink/renderer/core/editing/visible_units.h
+++ b/third_party/blink/renderer/core/editing/visible_units.h
@@ -151,8 +151,8 @@
 CORE_EXPORT VisiblePosition EndOfSentence(const VisiblePosition&);
 CORE_EXPORT VisiblePositionInFlatTree
 EndOfSentence(const VisiblePositionInFlatTree&);
-VisiblePosition PreviousSentencePosition(const VisiblePosition&);
-VisiblePosition NextSentencePosition(const VisiblePosition&);
+Position PreviousSentencePosition(const Position&);
+Position NextSentencePosition(const Position&);
 EphemeralRange ExpandEndToSentenceBoundary(const EphemeralRange&);
 EphemeralRange ExpandRangeToSentenceBoundary(const EphemeralRange&);
 
@@ -240,8 +240,8 @@
 bool IsEndOfDocument(const VisiblePosition&);
 
 // editable content
-VisiblePosition StartOfEditableContent(const VisiblePosition&);
-VisiblePosition EndOfEditableContent(const VisiblePosition&);
+Position StartOfEditableContent(const Position&);
+Position EndOfEditableContent(const Position&);
 CORE_EXPORT bool IsEndOfEditableOrNonEditableContent(const VisiblePosition&);
 CORE_EXPORT bool IsEndOfEditableOrNonEditableContent(
     const VisiblePositionInFlatTree&);
diff --git a/third_party/blink/renderer/core/editing/visible_units_sentence.cc b/third_party/blink/renderer/core/editing/visible_units_sentence.cc
index 8a328f5..3293563 100644
--- a/third_party/blink/renderer/core/editing/visible_units_sentence.cc
+++ b/third_party/blink/renderer/core/editing/visible_units_sentence.cc
@@ -240,30 +240,15 @@
 
 // ----
 
-PositionInFlatTreeWithAffinity NextSentencePosition(
-    const PositionInFlatTree& start) {
+PositionInFlatTree NextSentencePosition(const PositionInFlatTree& start) {
   const PositionInFlatTree result = NextSentencePositionInternal(start);
   return AdjustForwardPositionToAvoidCrossingEditingBoundaries(
-      PositionInFlatTreeWithAffinity(result), start);
+             PositionInFlatTreeWithAffinity(result), start)
+      .GetPosition();
 }
 
-PositionWithAffinity NextSentencePosition(const Position& start) {
-  const PositionInFlatTreeWithAffinity result =
-      NextSentencePosition(ToPositionInFlatTree(start));
-  return ToPositionInDOMTreeWithAffinity(result);
-}
-
-VisiblePosition NextSentencePosition(const VisiblePosition& c) {
-  return CreateVisiblePosition(
-      NextSentencePosition(c.DeepEquivalent()).GetPosition(),
-      TextAffinity::kUpstreamIfPossible);
-}
-
-VisiblePositionInFlatTree NextSentencePosition(
-    const VisiblePositionInFlatTree& c) {
-  return CreateVisiblePosition(
-      NextSentencePosition(c.DeepEquivalent()).GetPosition(),
-      TextAffinity::kUpstreamIfPossible);
+Position NextSentencePosition(const Position& start) {
+  return ToPositionInDOMTree(NextSentencePosition(ToPositionInFlatTree(start)));
 }
 
 // ----
@@ -283,10 +268,6 @@
       PreviousSentencePosition(ToPositionInFlatTree(position)));
 }
 
-VisiblePosition PreviousSentencePosition(const VisiblePosition& c) {
-  return CreateVisiblePosition(PreviousSentencePosition(c.DeepEquivalent()));
-}
-
 // ----
 
 PositionInFlatTree StartOfSentencePosition(const PositionInFlatTree& position) {
diff --git a/third_party/blink/renderer/core/frame/deprecation.cc b/third_party/blink/renderer/core/frame/deprecation.cc
index 72e3444f..95f0aa2 100644
--- a/third_party/blink/renderer/core/frame/deprecation.cc
+++ b/third_party/blink/renderer/core/frame/deprecation.cc
@@ -686,6 +686,11 @@
     if (window->GetFrame())
       deprecation = &window->GetFrame()->GetPage()->GetDeprecation();
   } else if (auto* scope = DynamicTo<WorkerOrWorkletGlobalScope>(context)) {
+    // TODO(crbug.com/1146824): Remove this once PlzDedicatedWorker and
+    // PlzServiceWorker ship.
+    if (!scope->IsInitialized()) {
+      return;
+    }
     deprecation = &scope->GetDeprecation();
   }
 
diff --git a/third_party/blink/renderer/core/frame/navigator.h b/third_party/blink/renderer/core/frame/navigator.h
index e88c35c1..5eb1673 100644
--- a/third_party/blink/renderer/core/frame/navigator.h
+++ b/third_party/blink/renderer/core/frame/navigator.h
@@ -39,7 +39,9 @@
   // NavigatorCookies
   bool cookieEnabled() const;
 
-  bool webdriver() const { return true; }
+  bool webdriver() const {
+    return RuntimeEnabledFeatures::AutomationControlledEnabled();
+  }
 
   String productSub() const;
   String vendor() const;
diff --git a/third_party/blink/renderer/core/frame/navigator_automation_information.idl b/third_party/blink/renderer/core/frame/navigator_automation_information.idl
index 3d7ca44..047fa67 100644
--- a/third_party/blink/renderer/core/frame/navigator_automation_information.idl
+++ b/third_party/blink/renderer/core/frame/navigator_automation_information.idl
@@ -4,8 +4,6 @@
 
 // https://w3c.github.io/webdriver/#interface
 
-[
-    RuntimeEnabled=AutomationControlled
-] interface mixin NavigatorAutomationInformation {
+interface mixin NavigatorAutomationInformation {
     readonly attribute boolean webdriver;
 };
diff --git a/third_party/blink/renderer/core/html/forms/html_form_element.cc b/third_party/blink/renderer/core/html/forms/html_form_element.cc
index cbdc861..05cff13 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_form_element.cc
@@ -580,7 +580,8 @@
   }
 
   is_in_reset_function_ = false;
-  frame->GetPage()->GetChromeClient().FormElementReset(*this);
+  if (frame->GetPage())
+    frame->GetPage()->GetChromeClient().FormElementReset(*this);
 }
 
 void HTMLFormElement::ParseAttribute(
diff --git a/third_party/blink/renderer/core/html/forms/html_input_element.cc b/third_party/blink/renderer/core/html/forms/html_input_element.cc
index 5b02043..966621d 100644
--- a/third_party/blink/renderer/core/html/forms/html_input_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_input_element.cc
@@ -1195,9 +1195,8 @@
 
   if (value_changed) {
     NotifyFormStateChanged();
-    if (value.IsEmpty() && HasBeenPasswordField()) {
-      GetDocument().GetFrame()->GetPage()->GetChromeClient().PasswordFieldReset(
-          *this);
+    if (value.IsEmpty() && HasBeenPasswordField() && GetDocument().GetPage()) {
+      GetDocument().GetPage()->GetChromeClient().PasswordFieldReset(*this);
     }
   }
 }
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object.cc b/third_party/blink/renderer/core/layout/layout_box_model_object.cc
index 366f86b22..eec78a97 100644
--- a/third_party/blink/renderer/core/layout/layout_box_model_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_box_model_object.cc
@@ -776,8 +776,13 @@
     }
   }
   if (this_box && this_box->IsGridItem() &&
-      this_box->HasOverrideContainingBlockContentLogicalHeight())
+      this_box->HasOverrideContainingBlockContentLogicalHeight()) {
+    if (RuntimeEnabledFeatures::TableCellNewPercentsEnabled()) {
+      return this_box->OverrideContainingBlockContentLogicalHeight() ==
+             kIndefiniteSize;
+    }
     return false;
+  }
   if (this_box && this_box->IsCustomItem() &&
       (this_box->HasOverrideContainingBlockContentLogicalHeight() ||
        this_box->HasOverridePercentageResolutionBlockSize()))
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 9e6f3957..e62ce56 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -89,6 +89,7 @@
 #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_outline_utils.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
 #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_clipper.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
@@ -2104,6 +2105,31 @@
   return StyleRef();
 }
 
+const ComputedStyle* LayoutObject::SlowStyleForContinuationOutline() const {
+  NOT_DESTROYED();
+  // Fail fast using bitfields is done in |StyleForContinuationOutline|.
+  DCHECK(IsAnonymous() && !IsInline());
+  const auto* block_flow = DynamicTo<LayoutBlockFlow>(this);
+  if (!block_flow)
+    return nullptr;
+
+  // Check ancestors of the continuation in case nested inline boxes; e.g.
+  // <span style="outline: auto">
+  //   <span>
+  //     <div>block</div>
+  //   </span>
+  // </span>
+  for (const LayoutObject* continuation = block_flow->Continuation();
+       UNLIKELY(continuation && continuation->IsLayoutInline());
+       continuation = continuation->Parent()) {
+    const ComputedStyle& style = continuation->StyleRef();
+    if (style.OutlineStyleIsAuto() &&
+        NGOutlineUtils::HasPaintedOutline(style, continuation->GetNode()))
+      return &style;
+  }
+  return nullptr;
+}
+
 // Called when an object that was floating or positioned becomes a normal flow
 // object again. We have to make sure the layout tree updates as needed to
 // accommodate the new normal flow object.
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 3aea8e86..0e30a3e 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -1325,6 +1325,15 @@
     NOT_DESTROYED();
     return nullptr;
   }
+  // Returns the |ComputedStyle| to use for painting outlines. When |this| is
+  // a block in a continuation-chain, it may need to paint outlines if its
+  // ancestor inline boxes in the DOM tree has outlines.
+  const ComputedStyle* StyleForContinuationOutline() const {
+    NOT_DESTROYED();
+    if (UNLIKELY(IsAnonymous() && !IsInline()))
+      return SlowStyleForContinuationOutline();
+    return nullptr;
+  }
 
   bool IsFloating() const {
     NOT_DESTROYED();
@@ -3590,6 +3599,8 @@
   LayoutFlowThread* LocateFlowThreadContainingBlock() const;
   void RemoveFromLayoutFlowThreadRecursive(LayoutFlowThread*);
 
+  const ComputedStyle* SlowStyleForContinuationOutline() const;
+
   StyleDifference AdjustStyleDifference(StyleDifference) const;
 
 #if DCHECK_IS_ON()
diff --git a/third_party/blink/renderer/core/layout/layout_replaced.cc b/third_party/blink/renderer/core/layout/layout_replaced.cc
index 00c8003b..0dee4d4 100644
--- a/third_party/blink/renderer/core/layout/layout_replaced.cc
+++ b/third_party/blink/renderer/core/layout/layout_replaced.cc
@@ -135,6 +135,10 @@
   if (StyleRef().LogicalHeight().IsAuto())
     return StretchBlockSizeIfAuto();
 
+  if (RuntimeEnabledFeatures::TableCellNewPercentsEnabled() &&
+      StyleRef().LogicalHeight().IsFixed())
+    return true;
+
   if (StyleRef().LogicalHeight().IsSpecified()) {
     if (HasAutoHeightOrContainingBlockWithAutoHeight())
       return false;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
index c9ab641..d34fb9c 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
@@ -1090,8 +1090,7 @@
       // Issue full invalidation, in case the number of column rules have
       // changed.
       needs_full_invalidation = true;
-    } else if (block->IsAnonymous() && !block->IsInline() &&
-               block->IsLayoutBlockFlow() && block->Continuation()) {
+    } else if (block->StyleForContinuationOutline()) {
       // When this is a block-in-inline created by |SplineInlines|, we may need
       // to paint outlines for this. See |NGBoxFragmentPainter|.
       needs_full_invalidation = true;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
index 396a64c..58cd4b5 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
@@ -226,6 +226,15 @@
                     const HitTestLocation& hit_test_location,
                     const PhysicalOffset& accumulated_offset) const;
 
+  // Returns the |ComputedStyle| to use for painting outlines. When |this| is
+  // a block in a continuation-chain, it may need to paint outlines if its
+  // ancestor inline boxes in the DOM tree has outlines.
+  const ComputedStyle* StyleForContinuationOutline() const {
+    if (const auto* layout_object = GetLayoutObject())
+      return layout_object->StyleForContinuationOutline();
+    return nullptr;
+  }
+
   // Fragment offset is this fragment's offset from parent.
   // Needed to compensate for LayoutInline Legacy code offsets.
   void AddSelfOutlineRects(const PhysicalOffset& additional_offset,
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
index 60c1894..75ed2f3 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
@@ -374,35 +374,6 @@
   return 0;
 }
 
-// Returns the |ComputedStyle| to use for painting outlines. When |fragment| is
-// a block in a continuation-chain, it may need to paint outlines if its
-// ancestor inline boxes in the DOM tree has outlines.
-const ComputedStyle* StyleForContinuationOutline(
-    const NGPhysicalBoxFragment& fragment) {
-  // Fail fast if |fragment| is not a anonymous block.
-  const LayoutObject* layout_object = fragment.GetLayoutObject();
-  if (!layout_object || !layout_object->IsAnonymous() ||
-      layout_object->IsInline())
-    return nullptr;
-
-  // Check ancestors of the continuation in case nested inline boxes; e.g.
-  // <span style="outline: auto">
-  //   <span>
-  //     <div>block</div>
-  //   </span>
-  // </span>
-  for (const LayoutObject* continuation =
-           To<LayoutBoxModelObject>(layout_object)->Continuation();
-       continuation && continuation->IsLayoutInline();
-       continuation = continuation->Parent()) {
-    const ComputedStyle& style = continuation->StyleRef();
-    if (style.OutlineStyleIsAuto() &&
-        NGOutlineUtils::HasPaintedOutline(style, continuation->GetNode()))
-      return &style;
-  }
-  return nullptr;
-}
-
 }  // anonymous namespace
 
 PhysicalRect NGBoxFragmentPainter::SelfInkOverflow() const {
@@ -677,7 +648,7 @@
     }
   } else if (ShouldPaintDescendantOutlines(paint_phase)) {
     if (const ComputedStyle* outline_style =
-            StyleForContinuationOutline(fragment)) {
+            fragment.StyleForContinuationOutline()) {
       NGFragmentPainter(fragment, GetDisplayItemClient())
           .PaintOutline(paint_info, paint_offset, *outline_style);
     }
diff --git a/third_party/blink/renderer/core/paint/paint_invalidator.cc b/third_party/blink/renderer/core/paint/paint_invalidator.cc
index a5444d2..8e22200 100644
--- a/third_party/blink/renderer/core/paint/paint_invalidator.cc
+++ b/third_party/blink/renderer/core/paint/paint_invalidator.cc
@@ -69,8 +69,7 @@
         object.StyleRef().HasOutline()) ||
        // If this is a block-in-inline, it may need to paint outline.
        // See |StyleForContinuationOutline|.
-       (layout_block_flow && layout_block_flow->IsAnonymous() &&
-        !layout_block_flow->IsInline() && layout_block_flow->Continuation())))
+       (layout_block_flow && layout_block_flow->StyleForContinuationOutline())))
     context.painting_layer->SetNeedsPaintPhaseDescendantOutlines();
 }
 
diff --git a/third_party/blink/renderer/core/timing/build.gni b/third_party/blink/renderer/core/timing/build.gni
index f0b692b..016200a 100644
--- a/third_party/blink/renderer/core/timing/build.gni
+++ b/third_party/blink/renderer/core/timing/build.gni
@@ -17,8 +17,6 @@
   "layout_shift_attribution.h",
   "measure_memory/measure_memory_controller.cc",
   "measure_memory/measure_memory_controller.h",
-  "measure_memory/measure_memory_delegate.cc",
-  "measure_memory/measure_memory_delegate.h",
   "memory_info.cc",
   "memory_info.h",
   "performance.cc",
diff --git a/third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.cc b/third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.cc
index 9c362ef8..4e45ebf 100644
--- a/third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.cc
+++ b/third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.cc
@@ -4,11 +4,12 @@
 
 #include "third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.h"
 
-#include "third_party/blink/renderer/core/timing/measure_memory/measure_memory_delegate.h"
-
+#include "components/performance_manager/public/mojom/coordination_unit.mojom-blink.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_memory_attribution.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_memory_breakdown_entry.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_memory_measurement.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
@@ -20,10 +21,17 @@
 #include "third_party/blink/renderer/platform/heap/heap_allocator.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
+#include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "v8/include/v8.h"
 
+using performance_manager::mojom::blink::WebMemoryAttribution;
+using performance_manager::mojom::blink::WebMemoryAttributionPtr;
+using performance_manager::mojom::blink::WebMemoryBreakdownEntryPtr;
+using performance_manager::mojom::blink::WebMemoryMeasurement;
+using performance_manager::mojom::blink::WebMemoryMeasurementPtr;
+
 namespace blink {
 
 MeasureMemoryController::MeasureMemoryController(
@@ -61,26 +69,28 @@
     exception_state.RethrowV8Exception(try_catch.Exception());
     return ScriptPromise();
   }
-  v8::MeasureMemoryExecution execution =
+  auto measurement_mode =
       RuntimeEnabledFeatures::ForceEagerMeasureMemoryEnabled(
           ExecutionContext::From(script_state))
-          ? v8::MeasureMemoryExecution::kEager
-          : v8::MeasureMemoryExecution::kDefault;
+          ? WebMemoryMeasurement::Mode::kEager
+          : WebMemoryMeasurement::Mode::kDefault;
 
   auto* impl = MakeGarbageCollected<MeasureMemoryController>(
       base::PassKey<MeasureMemoryController>(), isolate, context,
       promise_resolver);
+  Document* document = LocalDOMWindow::From(script_state)->document();
+  document->GetResourceCoordinator()->OnWebMemoryMeasurementRequested(
+      measurement_mode, WTF::Bind(&MeasureMemoryController::MeasurementComplete,
+                                  WrapPersistent(impl)));
 
-  isolate->MeasureMemory(
-      std::make_unique<MeasureMemoryDelegate>(
-          isolate, context,
-          WTF::Bind(&MeasureMemoryController::MainMeasurementComplete,
-                    WrapPersistent(impl))),
-      execution);
   return ScriptPromise(script_state, promise_resolver->GetPromise());
 }
 
 bool MeasureMemoryController::IsMeasureMemoryAvailable(LocalDOMWindow* window) {
+  if (!base::FeatureList::IsEnabled(
+          features::kWebMeasureMemoryViaPerformanceManager)) {
+    return false;
+  }
   if (!window || !window->CrossOriginIsolatedCapability()) {
     return false;
   }
@@ -91,10 +101,66 @@
   if (!local_frame || local_frame->IsCrossOriginToMainFrame()) {
     return false;
   }
+
+  // We need DocumentResourceCoordinator to query PerformanceManager.
+  if (!window->document() || !window->document()->GetResourceCoordinator()) {
+    return false;
+  }
+
   return true;
 }
 
-void MeasureMemoryController::MainMeasurementComplete(Result breakdown) {
+namespace {
+
+// These functions convert WebMemory* mojo structs to IDL and JS values.
+WTF::String ConvertScope(WebMemoryAttribution::Scope scope) {
+  switch (scope) {
+    case WebMemoryAttribution::Scope::kWindow:
+      return "Window";
+  }
+}
+
+MemoryAttribution* ConvertAttribution(
+    const WebMemoryAttributionPtr& attribution) {
+  auto* result = MemoryAttribution::Create();
+  result->setUrl(attribution->url);
+  result->setScope(ConvertScope(attribution->scope));
+  result->setContainer(nullptr);
+  return result;
+}
+
+MemoryBreakdownEntry* ConvertBreakdown(
+    const WebMemoryBreakdownEntryPtr& breakdown_entry) {
+  auto* result = MemoryBreakdownEntry::Create();
+  result->setBytes(breakdown_entry->bytes);
+  HeapVector<Member<MemoryAttribution>> attribution;
+  for (const auto& entry : breakdown_entry->attribution) {
+    attribution.push_back(ConvertAttribution(entry));
+  }
+  result->setAttribution(attribution);
+  result->setUserAgentSpecificTypes(Vector<String>());
+  return result;
+}
+
+MemoryMeasurement* ConvertResult(const WebMemoryMeasurementPtr& measurement) {
+  HeapVector<Member<MemoryBreakdownEntry>> breakdown;
+  for (const auto& entry : measurement->breakdown) {
+    breakdown.push_back(ConvertBreakdown(entry));
+  }
+  size_t bytes = 0;
+  for (auto entry : breakdown) {
+    bytes += entry->bytes();
+  }
+  auto* result = MemoryMeasurement::Create();
+  result->setBreakdown(breakdown);
+  result->setBytes(bytes);
+  return result;
+}
+
+}  // anonymous namespace
+
+void MeasureMemoryController::MeasurementComplete(
+    WebMemoryMeasurementPtr measurement) {
   if (context_.IsEmpty()) {
     // The context was garbage collected in the meantime.
     return;
@@ -102,13 +168,7 @@
   v8::HandleScope handle_scope(isolate_);
   v8::Local<v8::Context> context = context_.NewLocal(isolate_);
   v8::Context::Scope context_scope(context);
-  auto* result = MemoryMeasurement::Create();
-  size_t total_size = 0;
-  for (auto entry : breakdown) {
-    total_size += entry->bytes();
-  }
-  result->setBytes(total_size);
-  result->setBreakdown(breakdown);
+  auto* result = ConvertResult(measurement);
   v8::Local<v8::Promise::Resolver> promise_resolver =
       promise_resolver_.NewLocal(isolate_);
   promise_resolver->Resolve(context, ToV8(result, promise_resolver, isolate_))
diff --git a/third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.h b/third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.h
index 9e1a7f68..6baae0c 100644
--- a/third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.h
+++ b/third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_MEASURE_MEMORY_MEASURE_MEMORY_CONTROLLER_H_
 
 #include "base/types/pass_key.h"
+#include "components/performance_manager/public/mojom/coordination_unit.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/bindings/scoped_persistent.h"
@@ -17,7 +18,6 @@
 namespace blink {
 
 class LocalDOMWindow;
-class MemoryBreakdownEntry;
 class ScriptState;
 class ExceptionState;
 
@@ -30,21 +30,6 @@
 class MeasureMemoryController final
     : public GarbageCollected<MeasureMemoryController> {
  public:
-  using Result = HeapVector<Member<MemoryBreakdownEntry>>;
-  using ResultCallback = base::OnceCallback<void(Result)>;
-
-  // PerformanceManager in blink/renderer/controller uses this interface
-  // to provide an implementation of memory measurement for dedicated workers.
-  //
-  // It will be removed in the future when performance.measureMemory switches
-  // to a mojo-based implementation that queries PerformanceManager in the
-  // browser process.
-  class V8MemoryReporter {
-   public:
-    virtual void GetMemoryUsage(MeasureMemoryController::ResultCallback,
-                                v8::MeasureMemoryExecution) = 0;
-  };
-
   // Private constructor guarded by PassKey. Use the StartMeasurement() wrapper
   // to construct the object and start the measurement.
   MeasureMemoryController(base::PassKey<MeasureMemoryController>,
@@ -58,13 +43,11 @@
 
   void Trace(Visitor* visitor) const;
 
-  // The entry point for injecting dependency on PerformanceManager.
-  CORE_EXPORT static void SetDedicatedWorkerMemoryReporter(V8MemoryReporter*);
-
  private:
   static bool IsMeasureMemoryAvailable(LocalDOMWindow* window);
   // Invoked when the memory of the main V8 isolate is measured.
-  void MainMeasurementComplete(Result);
+  void MeasurementComplete(
+      performance_manager::mojom::blink::WebMemoryMeasurementPtr);
 
   v8::Isolate* isolate_;
   ScopedPersistent<v8::Context> context_;
diff --git a/third_party/blink/renderer/core/timing/measure_memory/measure_memory_delegate.cc b/third_party/blink/renderer/core/timing/measure_memory/measure_memory_delegate.cc
deleted file mode 100644
index bde7eb2..0000000
--- a/third_party/blink/renderer/core/timing/measure_memory/measure_memory_delegate.cc
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/core/timing/measure_memory/measure_memory_delegate.h"
-
-#include "third_party/blink/public/platform/platform.h"
-#include "third_party/blink/public/platform/web_security_origin.h"
-#include "third_party/blink/public/web/web_frame.h"
-#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_memory_attribution.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_memory_breakdown_entry.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_memory_measurement.h"
-#include "third_party/blink/renderer/core/dom/document.h"
-#include "third_party/blink/renderer/core/execution_context/execution_context.h"
-#include "third_party/blink/renderer/core/frame/frame.h"
-#include "third_party/blink/renderer/core/frame/frame_owner.h"
-#include "third_party/blink/renderer/core/frame/local_dom_window.h"
-#include "third_party/blink/renderer/core/frame/local_frame.h"
-#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
-#include "third_party/blink/renderer/platform/bindings/script_state.h"
-#include "third_party/blink/renderer/platform/weborigin/kurl.h"
-#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
-
-namespace blink {
-
-MeasureMemoryDelegate::MeasureMemoryDelegate(v8::Isolate* isolate,
-                                             v8::Local<v8::Context> context,
-                                             ResultCallback callback)
-    : isolate_(isolate),
-      context_(isolate, context),
-      callback_(std::move(callback)) {
-  context_.SetPhantom();
-}
-
-// Returns true if the given context should be included in the current memory
-// measurement. Currently it is very conservative and allows only the same
-// origin contexts that belong to the same JavaScript origin.
-// With COOP/COEP we will be able to relax this restriction for the contexts
-// that opt-in into memory measurement.
-bool MeasureMemoryDelegate::ShouldMeasure(v8::Local<v8::Context> context) {
-  if (context_.IsEmpty()) {
-    // The original context was garbage collected in the meantime.
-    return false;
-  }
-  v8::Local<v8::Context> original_context = context_.NewLocal(isolate_);
-  ExecutionContext* original_execution_context =
-      ExecutionContext::From(original_context);
-  ExecutionContext* execution_context = ExecutionContext::From(context);
-  if (!original_execution_context || !execution_context) {
-    // One of the contexts is detached or is created by DevTools.
-    return false;
-  }
-  if (original_execution_context->GetAgent() != execution_context->GetAgent()) {
-    // Context do not belong to the same JavaScript agent.
-    return false;
-  }
-  if (ScriptState::From(context)->World().IsIsolatedWorld()) {
-    // Context belongs to an extension. Skip it.
-    return false;
-  }
-  const SecurityOrigin* original_security_origin =
-      original_execution_context->GetSecurityContext().GetSecurityOrigin();
-  const SecurityOrigin* security_origin =
-      execution_context->GetSecurityContext().GetSecurityOrigin();
-  if (!original_security_origin->IsSameOriginWith(security_origin)) {
-    // TODO(ulan): Allow only same-origin contexts until the implementation
-    // is switched to PerformanceManager.
-    return false;
-  }
-  return true;
-}
-
-namespace {
-// Helper functions for constructing a memory measurement result.
-
-LocalFrame* GetLocalFrame(v8::Local<v8::Context> context) {
-  LocalDOMWindow* window = ToLocalDOMWindow(context);
-  if (!window) {
-    // The context was detached. Ignore it.
-    return nullptr;
-  }
-  return window->GetFrame();
-}
-
-String GetUrl(const LocalFrame* requesting_frame, const LocalFrame* frame) {
-  // Only same-origin frames are reported until we switch to PerformanceManager.
-  DCHECK(requesting_frame->GetSecurityContext()->GetSecurityOrigin()->CanAccess(
-      frame->GetSecurityContext()->GetSecurityOrigin()));
-  return frame->GetDocument()->Url().GetString();
-}
-
-MemoryAttribution* CreateMemoryAttribution(const String& url) {
-  auto* result = MemoryAttribution::Create();
-  result->setUrl(url);
-  result->setScope("Window");
-  result->setContainer(nullptr);
-  return result;
-}
-
-MemoryBreakdownEntry* CreateMemoryBreakdownEntry(size_t bytes,
-                                                 const Vector<String>& types,
-                                                 const String& url) {
-  auto* result = MemoryBreakdownEntry::Create();
-  result->setBytes(bytes);
-  result->setUserAgentSpecificTypes(types);
-  HeapVector<Member<MemoryAttribution>> attribution;
-  if (url.length()) {
-    attribution.push_back(CreateMemoryAttribution(url));
-  }
-  result->setAttribution(attribution);
-  return result;
-}
-
-}  // anonymous namespace
-
-// Constructs a memory measurement result based on the given list of (context,
-// size) pairs and resolves the promise.
-void MeasureMemoryDelegate::MeasurementComplete(
-    const std::vector<std::pair<v8::Local<v8::Context>, size_t>>& context_sizes,
-    size_t unattributed_size) {
-  if (context_.IsEmpty()) {
-    // The context was garbage collected in the meantime.
-    return;
-  }
-  v8::Local<v8::Context> context = context_.NewLocal(isolate_);
-  LocalFrame* requesting_frame = GetLocalFrame(context);
-  if (!requesting_frame) {
-    // The context was detached in the meantime.
-    return;
-  }
-  v8::Context::Scope context_scope(context);
-  size_t total_size = 0;
-  for (const auto& context_size : context_sizes) {
-    total_size += context_size.second;
-  }
-  HeapVector<Member<MemoryBreakdownEntry>> breakdown;
-  size_t detached_size = 0;
-  const String kJS("JS");
-  const Vector<String> js_types = {kJS};
-  for (const auto& it : context_sizes) {
-    size_t bytes = it.second;
-    LocalFrame* frame = GetLocalFrame(it.first);
-    if (!frame) {
-      detached_size += bytes;
-      continue;
-    }
-    String url = GetUrl(requesting_frame, frame);
-    breakdown.push_back(CreateMemoryBreakdownEntry(bytes, js_types, url));
-  }
-  if (detached_size) {
-    const String kDetached("Detached");
-    breakdown.push_back(CreateMemoryBreakdownEntry(
-        detached_size, Vector<String>{kJS, kDetached}, ""));
-  }
-  if (unattributed_size) {
-    breakdown.push_back(
-        CreateMemoryBreakdownEntry(unattributed_size, Vector<String>{kJS}, ""));
-  }
-  std::move(callback_).Run(breakdown);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/timing/measure_memory/measure_memory_delegate.h b/third_party/blink/renderer/core/timing/measure_memory/measure_memory_delegate.h
deleted file mode 100644
index 5bb1f48..0000000
--- a/third_party/blink/renderer/core/timing/measure_memory/measure_memory_delegate.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_MEASURE_MEMORY_MEASURE_MEMORY_DELEGATE_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_MEASURE_MEMORY_MEASURE_MEMORY_DELEGATE_H_
-
-#include "third_party/blink/renderer/platform/bindings/scoped_persistent.h"
-#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
-#include "third_party/blink/renderer/platform/heap/member.h"
-#include "v8/include/v8.h"
-
-namespace blink {
-
-class MemoryBreakdownEntry;
-
-// Specifies V8 contexts to be measured and invokes the given callback once V8
-// completes the memory measurement.
-class MeasureMemoryDelegate : public v8::MeasureMemoryDelegate {
- public:
-  using ResultCallback =
-      base::OnceCallback<void(HeapVector<Member<MemoryBreakdownEntry>>)>;
-
-  MeasureMemoryDelegate(v8::Isolate* isolate,
-                        v8::Local<v8::Context> context,
-                        ResultCallback callback);
-
-  // v8::MeasureMemoryDelegate overrides.
-  bool ShouldMeasure(v8::Local<v8::Context> context) override;
-  void MeasurementComplete(
-      const std::vector<std::pair<v8::Local<v8::Context>, size_t>>&
-          context_sizes,
-      size_t unattributed_size) override;
- private:
-  v8::Isolate* isolate_;
-  ScopedPersistent<v8::Context> context_;
-  ResultCallback callback_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_MEASURE_MEMORY_MEASURE_MEMORY_DELEGATE_H_
diff --git a/third_party/blink/renderer/core/workers/worker_global_scope.h b/third_party/blink/renderer/core/workers/worker_global_scope.h
index e1237af..8d23edf 100644
--- a/third_party/blink/renderer/core/workers/worker_global_scope.h
+++ b/third_party/blink/renderer/core/workers/worker_global_scope.h
@@ -85,6 +85,7 @@
   void Dispose() override;
   WorkerThread* GetThread() const final { return thread_; }
   const base::UnguessableToken& GetDevToolsToken() const override;
+  bool IsInitialized() const final { return !url_.IsNull(); }
 
   void ExceptionUnhandled(int exception_id);
 
diff --git a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h
index 29e33dc5..9f79c128 100644
--- a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h
+++ b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h
@@ -156,6 +156,10 @@
 
   virtual int GetOutstandingThrottledLimit() const;
 
+  // TODO(crbug.com/1146824): Remove this once PlzDedicatedWorker and
+  // PlzServiceWorker ship.
+  virtual bool IsInitialized() const = 0;
+
   Deprecation& GetDeprecation() { return deprecation_; }
 
  protected:
diff --git a/third_party/blink/renderer/core/workers/worklet_global_scope.h b/third_party/blink/renderer/core/workers/worklet_global_scope.h
index b57faf0..dd8e5528 100644
--- a/third_party/blink/renderer/core/workers/worklet_global_scope.h
+++ b/third_party/blink/renderer/core/workers/worklet_global_scope.h
@@ -71,6 +71,7 @@
   void Dispose() override;
   WorkerThread* GetThread() const final;
   const base::UnguessableToken& GetDevToolsToken() const override;
+  bool IsInitialized() const final { return true; }
 
   virtual LocalFrame* GetFrame() const;
 
diff --git a/third_party/blink/renderer/modules/imagecapture/image_capture.cc b/third_party/blink/renderer/modules/imagecapture/image_capture.cc
index 3d312881f..c019993c 100644
--- a/third_party/blink/renderer/modules/imagecapture/image_capture.cc
+++ b/third_party/blink/renderer/modules/imagecapture/image_capture.cc
@@ -43,6 +43,9 @@
 
 const char kNoServiceError[] = "ImageCapture service unavailable.";
 
+const char kInvalidStateTrackError[] =
+    "The associated Track is in an invalid state";
+
 bool TrackIsInactive(const MediaStreamTrack& track) {
   // Spec instructs to return an exception if the Track's readyState() is not
   // "live". Also reject if the track is disabled or muted.
@@ -183,8 +186,7 @@
 
   if (TrackIsInactive(*stream_track_)) {
     resolver->Reject(MakeGarbageCollected<DOMException>(
-        DOMExceptionCode::kInvalidStateError,
-        "The associated Track is in an invalid state."));
+        DOMExceptionCode::kInvalidStateError, kInvalidStateTrackError));
     return promise;
   }
 
@@ -216,8 +218,7 @@
 
   if (TrackIsInactive(*stream_track_)) {
     resolver->Reject(MakeGarbageCollected<DOMException>(
-        DOMExceptionCode::kInvalidStateError,
-        "The associated Track is in an invalid state."));
+        DOMExceptionCode::kInvalidStateError, kInvalidStateTrackError));
     return promise;
   }
 
@@ -253,8 +254,7 @@
 
   if (TrackIsInactive(*stream_track_)) {
     resolver->Reject(MakeGarbageCollected<DOMException>(
-        DOMExceptionCode::kInvalidStateError,
-        "The associated Track is in an invalid state."));
+        DOMExceptionCode::kInvalidStateError, kInvalidStateTrackError));
     return promise;
   }
 
@@ -342,8 +342,7 @@
 
   if (TrackIsInactive(*stream_track_)) {
     resolver->Reject(MakeGarbageCollected<DOMException>(
-        DOMExceptionCode::kInvalidStateError,
-        "The associated Track is in an invalid state."));
+        DOMExceptionCode::kInvalidStateError, kInvalidStateTrackError));
     return promise;
   }
 
@@ -978,8 +977,7 @@
 
   if (TrackIsInactive(*stream_track_)) {
     resolver->Reject(MakeGarbageCollected<DOMException>(
-        DOMExceptionCode::kOperationError,
-        "The associated Track is in an invalid state."));
+        DOMExceptionCode::kOperationError, kInvalidStateTrackError));
     service_requests_.erase(resolver);
     return;
   }
diff --git a/third_party/blink/renderer/modules/installedapp/BUILD.gn b/third_party/blink/renderer/modules/installedapp/BUILD.gn
index 749de147..9eb2ce9 100644
--- a/third_party/blink/renderer/modules/installedapp/BUILD.gn
+++ b/third_party/blink/renderer/modules/installedapp/BUILD.gn
@@ -11,5 +11,8 @@
     "navigator_installed_app.cc",
     "navigator_installed_app.h",
   ]
-  deps = [ "//third_party/blink/renderer/modules/manifest" ]
+  deps = [
+    "//services/metrics/public/cpp:ukm_builders",
+    "//third_party/blink/renderer/modules/manifest",
+  ]
 }
diff --git a/third_party/blink/renderer/modules/installedapp/DEPS b/third_party/blink/renderer/modules/installedapp/DEPS
new file mode 100644
index 0000000..dc115d0
--- /dev/null
+++ b/third_party/blink/renderer/modules/installedapp/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+    "+services/metrics/public/cpp",
+]
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/installedapp/installed_app_controller.cc b/third_party/blink/renderer/modules/installedapp/installed_app_controller.cc
index b9ec220..7a57164 100644
--- a/third_party/blink/renderer/modules/installedapp/installed_app_controller.cc
+++ b/third_party/blink/renderer/modules/installedapp/installed_app_controller.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "services/metrics/public/cpp/ukm_builders.h"
 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
 #include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/blink/public/mojom/installedapp/related_application.mojom-blink.h"
@@ -105,6 +106,12 @@
       app->setVersion(res->version);
     applications.push_back(app);
   }
+
+  LocalDOMWindow* window = GetSupplementable();
+  ukm::builders::InstalledRelatedApps(window->UkmSourceID())
+      .SetCalled(true)
+      .Record(window->UkmRecorder());
+
   callbacks->OnSuccess(applications);
 }
 
diff --git a/third_party/blink/renderer/modules/payments/payment_request.cc b/third_party/blink/renderer/modules/payments/payment_request.cc
index 3bc7d493..5fe5153 100644
--- a/third_party/blink/renderer/modules/payments/payment_request.cc
+++ b/third_party/blink/renderer/modules/payments/payment_request.cc
@@ -1503,6 +1503,10 @@
 
   switch (error) {
     case PaymentErrorReason::USER_CANCEL:
+    // Intentional fall through.
+    case PaymentErrorReason::INVALID_DATA_FROM_RENDERER:
+    // Intentional fall through.
+    case PaymentErrorReason::ALREADY_SHOWING:
       exception_code = DOMExceptionCode::kAbortError;
       break;
 
@@ -1515,10 +1519,6 @@
       not_supported_for_invalid_origin_or_ssl_error_ = error_message;
       break;
 
-    case PaymentErrorReason::ALREADY_SHOWING:
-      exception_code = DOMExceptionCode::kAbortError;
-      break;
-
     case PaymentErrorReason::UNKNOWN:
       break;
   }
diff --git a/third_party/blink/renderer/platform/instrumentation/BUILD.gn b/third_party/blink/renderer/platform/instrumentation/BUILD.gn
index 870af66..122520b 100644
--- a/third_party/blink/renderer/platform/instrumentation/BUILD.gn
+++ b/third_party/blink/renderer/platform/instrumentation/BUILD.gn
@@ -37,11 +37,11 @@
     "use_counter.h",
   ]
 
-  deps = [
+  deps = [ "//services/service_manager/public/cpp" ]
+  public_deps = [
     "//components/performance_manager/public/mojom:mojom_blink",
-    "//services/service_manager/public/cpp",
+    "//third_party/blink/renderer/platform/heap:heap",
   ]
-  public_deps = [ "//third_party/blink/renderer/platform/heap:heap" ]
   allow_circular_includes_from = public_deps
 }
 
diff --git a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.cc b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.cc
index b0a57d7..92acf76 100644
--- a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.cc
+++ b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.cc
@@ -71,4 +71,10 @@
   service_->OnFirstContentfulPaint(time_since_navigation_start);
 }
 
+void DocumentResourceCoordinator::OnWebMemoryMeasurementRequested(
+    WebMemoryMeasurementMode mode,
+    OnWebMemoryMeasurementRequestedCallback callback) {
+  service_->OnWebMemoryMeasurementRequested(mode, std::move(callback));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h
index 38573e0..f7d4cfc 100644
--- a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h
+++ b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h
@@ -21,6 +21,11 @@
   USING_FAST_MALLOC(DocumentResourceCoordinator);
 
  public:
+  using WebMemoryMeasurementMode =
+      ::performance_manager::mojom::blink::WebMemoryMeasurement::Mode;
+  using OnWebMemoryMeasurementRequestedCallback = ::performance_manager::mojom::
+      blink::DocumentCoordinationUnit::OnWebMemoryMeasurementRequestedCallback;
+
   // Returns nullptr if instrumentation is not enabled.
   static std::unique_ptr<DocumentResourceCoordinator> MaybeCreate(
       const BrowserInterfaceBrokerProxy&);
@@ -35,6 +40,9 @@
   void OnNonPersistentNotificationCreated();
   void SetHadFormInteraction();
   void OnFirstContentfulPaint(base::TimeDelta time_since_navigation_start);
+  void OnWebMemoryMeasurementRequested(
+      WebMemoryMeasurementMode mode,
+      OnWebMemoryMeasurementRequestedCallback callback);
 
  private:
   explicit DocumentResourceCoordinator(const BrowserInterfaceBrokerProxy&);
diff --git a/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc b/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc
index 52c9b96..a5e27fd 100644
--- a/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc
@@ -392,9 +392,14 @@
   }
   // NOTE: submit_frame will be empty if viz_frame_submission_enabled_ enabled,
   // but it won't be used upstream
-  sync_client_->SubmitCompositorFrame(layer_tree_frame_sink_id_,
-                                      std::move(submit_frame),
-                                      client_->BuildHitTestData());
+  // Because OnDraw can synchronously override the viewport without going
+  // through commit and activation, we generate our own LocalSurfaceId by
+  // checking the submitted frame instead of using the one set here.
+  sync_client_->SubmitCompositorFrame(
+      layer_tree_frame_sink_id_,
+      viz_frame_submission_enabled_ ? local_surface_id_
+                                    : child_local_surface_id_,
+      std::move(submit_frame), client_->BuildHitTestData());
   did_submit_frame_ = true;
 }
 
diff --git a/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.h b/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.h
index 8a543664..774140db 100644
--- a/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.h
+++ b/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.h
@@ -56,6 +56,7 @@
   virtual void Invalidate(bool needs_draw) = 0;
   virtual void SubmitCompositorFrame(
       uint32_t layer_tree_frame_sink_id,
+      const viz::LocalSurfaceId& local_surface_id,
       base::Optional<viz::CompositorFrame> frame,
       base::Optional<viz::HitTestRegionList> hit_test_region_list) = 0;
   virtual void SetNeedsBeginFrames(bool needs_begin_frames) = 0;
diff --git a/third_party/blink/renderer/platform/widget/input/synchronous_compositor_proxy.cc b/third_party/blink/renderer/platform/widget/input/synchronous_compositor_proxy.cc
index f7dc657..77a8cf1 100644
--- a/third_party/blink/renderer/platform/widget/input/synchronous_compositor_proxy.cc
+++ b/third_party/blink/renderer/platform/widget/input/synchronous_compositor_proxy.cc
@@ -133,7 +133,8 @@
   if (hardware_draw_reply_) {
     // Did not swap.
     std::move(hardware_draw_reply_)
-        .Run(PopulateNewCommonParams(), 0u, 0u, base::nullopt, base::nullopt);
+        .Run(PopulateNewCommonParams(), 0u, 0u, base::nullopt, base::nullopt,
+             base::nullopt);
   }
 }
 
@@ -215,6 +216,7 @@
 
 void SynchronousCompositorProxy::SubmitCompositorFrame(
     uint32_t layer_tree_frame_sink_id,
+    const viz::LocalSurfaceId& local_surface_id,
     base::Optional<viz::CompositorFrame> frame,
     base::Optional<viz::HitTestRegionList> hit_test_region_list) {
   // Verify that exactly one of these is true.
@@ -225,9 +227,10 @@
   if (hardware_draw_reply_) {
     // For viz the CF was submitted directly via CompositorFrameSink
     DCHECK(frame || viz_frame_submission_enabled_);
+    DCHECK(local_surface_id.is_valid());
     std::move(hardware_draw_reply_)
         .Run(std::move(common_renderer_params), layer_tree_frame_sink_id,
-             NextMetadataVersion(), std::move(frame),
+             NextMetadataVersion(), local_surface_id, std::move(frame),
              std::move(hit_test_region_list));
   } else if (software_draw_reply_) {
     DCHECK(frame);
@@ -332,10 +335,12 @@
     mojom::blink::SyncCompositorCommonRendererParamsPtr,
     uint32_t layer_tree_frame_sink_id,
     uint32_t metadata_version,
+    const base::Optional<viz::LocalSurfaceId>& local_surface_id,
     base::Optional<viz::CompositorFrame> frame,
     base::Optional<viz::HitTestRegionList> hit_test_region_list) {
   control_host_->ReturnFrame(layer_tree_frame_sink_id, metadata_version,
-                             std::move(frame), std::move(hit_test_region_list));
+                             local_surface_id, std::move(frame),
+                             std::move(hit_test_region_list));
 }
 
 void SynchronousCompositorProxy::SendBeginFrameResponse(
diff --git a/third_party/blink/renderer/platform/widget/input/synchronous_compositor_proxy.h b/third_party/blink/renderer/platform/widget/input/synchronous_compositor_proxy.h
index 846bbd1..19cfb5c 100644
--- a/third_party/blink/renderer/platform/widget/input/synchronous_compositor_proxy.h
+++ b/third_party/blink/renderer/platform/widget/input/synchronous_compositor_proxy.h
@@ -59,6 +59,7 @@
   void Invalidate(bool needs_draw) final;
   void SubmitCompositorFrame(
       uint32_t layer_tree_frame_sink_id,
+      const viz::LocalSurfaceId& local_surface_id,
       base::Optional<viz::CompositorFrame> frame,
       base::Optional<viz::HitTestRegionList> hit_test_region_list) final;
   void SetNeedsBeginFrames(bool needs_begin_frames) final;
@@ -99,6 +100,7 @@
       mojom::blink::SyncCompositorCommonRendererParamsPtr,
       uint32_t layer_tree_frame_sink_id,
       uint32_t metadata_version,
+      const base::Optional<viz::LocalSurfaceId>& local_surface_id,
       base::Optional<viz::CompositorFrame>,
       base::Optional<viz::HitTestRegionList> hit_test_region_list);
 
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index beacc9c..4c3ba07 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -722,6 +722,9 @@
 crbug.com/591099 external/wpt/css/css-flexbox/position-absolute-014.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-flexbox/position-absolute-015.html [ Failure ]
 
+### external/wpt/css/css-grid/
+crbug.com/591099 external/wpt/css/css-grid/grid-items/percentage-size-indefinite-replaced.html [ Failure ]
+
 ### external/wpt/css/css-pseudo/
 crbug.com/591099 external/wpt/css/css-pseudo/active-selection-011.html [ Failure ]
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index f806d4c..1c91a7b 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3881,6 +3881,7 @@
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/grid-order-property-auto-placement-003.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/grid-order-property-auto-placement-004.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/grid-order-property-auto-placement-005.html [ Failure ]
+crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/percentage-size-indefinite-replaced.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/percentage-size-replaced-subitems-001.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/percentage-size-subitems-001.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/remove-svg-grid-item-001.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 191280b7..26c2868 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -208083,7 +208083,7 @@
       []
      ],
      "border-shorthand-serialization-expected.txt": [
-      "11e3cbab5c713a3d4cdbede0f65f442df2bded53",
+      "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
       []
      ],
      "cssstyledeclaration-custom-properties-expected.txt": [
@@ -295409,6 +295409,13 @@
        {}
       ]
      ],
+     "positon-absolute-scrollable-overflow-001.html": [
+      "67e24a247570cdc56033d509e0fb7cbddda17591",
+      [
+       null,
+       {}
+      ]
+     ],
      "sticky": {
       "position-sticky-bottom.html": [
        "9cfae6d3ba9bdeafa2ff6fcd820dc2c3fdb13660",
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/percentage-size-indefinite-replaced.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/percentage-size-indefinite-replaced.html
new file mode 100644
index 0000000..3610f2e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/percentage-size-indefinite-replaced.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1153028">
+<meta name="assert" content="When determining a auto row, an indefinite percentage block-size should be considered auto, and use the inline-size + aspect-ratio.">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="display: grid; width: 50px;">
+  <canvas width=200 height=200 style="background: green; height: 100%; width: 200%;"></canvas>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/positon-absolute-scrollable-overflow-001.html b/third_party/blink/web_tests/external/wpt/css/css-position/positon-absolute-scrollable-overflow-001.html
new file mode 100644
index 0000000..67e24a2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/positon-absolute-scrollable-overflow-001.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSS Positioned Layout Test: Absolute positioned elements are included in the scrollable overflow area</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-position/#valdef-position-absolute">
+<link rel="help" href="https://drafts.csswg.org/css-overflow/#scrollable">
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scrollheight">
+<meta name=assert content="Absolute positioned elements are included in the scrollable overflow area of its containing block.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<style>
+.containing-block {
+  position: relative;
+  width: 100px;
+  height: 100px;
+  margin: 100px;
+}
+
+.abspos {
+  position: absolute;
+  background: rgba(0, 255, 0, 0.5);
+  width: 10px;
+  height: 10px;
+  left: 0;
+  top: 0;
+}
+</style>
+<body onload="checkLayout('.containing-block');">
+  <div id="log"></div>
+  <div class="containing-block" data-expected-scroll-width="100" data-expected-scroll-height="500">
+    <div class="abspos" style="left: -50px; height: 500px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="100" data-expected-scroll-height="550">
+    <div class="abspos" style="left: -50px; height: 500px; top: 50px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="100" data-expected-scroll-height="700">
+    <div class="abspos" style="left: -50px; height: 500px; top: 200px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="100" data-expected-scroll-height="400">
+    <div class="abspos" style="left: -50px; height: 500px; top: -100px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="100" data-expected-scroll-height="100">
+    <div class="abspos" style="left: -50px; height: 500px; top: -1000px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="100" data-expected-scroll-height="100">
+    <div class="abspos" style="left: 50px; height: 500px; top: -1000px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="210" data-expected-scroll-height="700">
+    <div class="abspos" style="left: 200px; height: 500px; top: 200px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="500" data-expected-scroll-height="100">
+    <div class="abspos" style="width: 500px; top: -50px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="550" data-expected-scroll-height="100">
+    <div class="abspos" style="width: 500px; left: 50px; top: -50px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="700" data-expected-scroll-height="100">
+    <div class="abspos" style="width: 500px; left: 200px; top: -50px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="400" data-expected-scroll-height="100">
+    <div class="abspos" style="width: 500px; left: -100px; top: -50px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="100" data-expected-scroll-height="100">
+    <div class="abspos" style="width: 500px; left: -1000px; top: -50px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="100" data-expected-scroll-height="100">
+    <div class="abspos" style="width: 500px; left: -1000px; top: 50px;"></div>
+  </div>
+  <div class="containing-block" data-expected-scroll-width="700" data-expected-scroll-height="210">
+    <div class="abspos" style="width: 500px; left: 200px; top: 200px;"></div>
+  </div>
+</body>
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
index 684a9c0..8e7fa59 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
@@ -32,6 +32,7 @@
 PASS window.cached_navigator.userAgent is ''
 PASS window.cached_navigator.vendor is window.navigator.vendor
 PASS window.cached_navigator.vendorSub is ''
+PASS window.cached_navigator.webdriver is false
 PASS window.cached_navigator_bluetooth.onadvertisementreceived is null
 PASS window.cached_navigator_connection.onchange is null
 PASS window.cached_navigator_connection.ontypechange is null
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
index 3fd986e..e8f2830 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
@@ -32,6 +32,7 @@
 PASS window.cached_navigator.userAgent is ''
 PASS window.cached_navigator.vendor is window.navigator.vendor
 PASS window.cached_navigator.vendorSub is ''
+PASS window.cached_navigator.webdriver is false
 PASS window.cached_navigator_bluetooth.onadvertisementreceived is null
 PASS window.cached_navigator_connection.onchange is null
 PASS window.cached_navigator_connection.ontypechange is null
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
index ea3dd26..4352df4 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
@@ -32,6 +32,7 @@
 PASS window.cached_navigator.userAgent is ''
 PASS window.cached_navigator.vendor is window.navigator.vendor
 PASS window.cached_navigator.vendorSub is ''
+PASS window.cached_navigator.webdriver is false
 PASS window.cached_navigator_bluetooth.onadvertisementreceived is null
 PASS window.cached_navigator_connection.onchange is null
 PASS window.cached_navigator_connection.ontypechange is null
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
index 2c184971..70d2bb64 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
@@ -77,6 +77,7 @@
 PASS oldChildWindow.navigator.virtualKeyboard.boundingRect.y is newChildWindow.navigator.virtualKeyboard.boundingRect.y
 PASS oldChildWindow.navigator.virtualKeyboard.ongeometrychange is newChildWindow.navigator.virtualKeyboard.ongeometrychange
 PASS oldChildWindow.navigator.virtualKeyboard.overlaysContent is newChildWindow.navigator.virtualKeyboard.overlaysContent
+PASS oldChildWindow.navigator.webdriver is newChildWindow.navigator.webdriver
 PASS oldChildWindow.navigator.xr.ondevicechange is newChildWindow.navigator.xr.ondevicechange
 PASS oldChildWindow.onabort is newChildWindow.onabort
 PASS oldChildWindow.onafterprint is newChildWindow.onafterprint
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
index 9484589..43e29e3 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
@@ -43,6 +43,7 @@
 PASS childWindow.navigator.virtualKeyboard.boundingRect.y is 0
 PASS childWindow.navigator.virtualKeyboard.ongeometrychange is null
 PASS childWindow.navigator.virtualKeyboard.overlaysContent is false
+PASS childWindow.navigator.webdriver is false
 PASS childWindow.onabort is null
 PASS childWindow.onafterprint is null
 PASS childWindow.onanimationend is null
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
index 4210d73..25cc520 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
@@ -43,6 +43,7 @@
 PASS childWindow.navigator.virtualKeyboard.boundingRect.y is 0
 PASS childWindow.navigator.virtualKeyboard.ongeometrychange is null
 PASS childWindow.navigator.virtualKeyboard.overlaysContent is false
+PASS childWindow.navigator.webdriver is false
 PASS childWindow.onabort is null
 PASS childWindow.onafterprint is null
 PASS childWindow.onanimationend is null
diff --git a/third_party/blink/web_tests/fast/mediastream/constructors-expected.txt b/third_party/blink/web_tests/fast/mediastream/constructors-expected.txt
index cc9ba993e..ffb3472 100644
--- a/third_party/blink/web_tests/fast/mediastream/constructors-expected.txt
+++ b/third_party/blink/web_tests/fast/mediastream/constructors-expected.txt
@@ -8,7 +8,7 @@
 PASS RTCPeerConnection() threw exception TypeError: Failed to construct 'RTCPeerConnection': Please use the 'new' operator, this DOM object constructor cannot be called as a function..
 PASS RTCSessionDescription() threw exception TypeError: Failed to construct 'RTCSessionDescription': Please use the 'new' operator, this DOM object constructor cannot be called as a function..
 PASS RTCIceCandidate() threw exception TypeError: Failed to construct 'RTCIceCandidate': Please use the 'new' operator, this DOM object constructor cannot be called as a function..
-PASS new RTCPeerConnection({iceServers:[{url:'stun://foobar.com:12345'}]}, null); did not throw exception.
+PASS new RTCPeerConnection({iceServers:[{url:'stun:foobar.com:12345'}]}, null); did not throw exception.
 PASS new RTCSessionDescription({type:'offer',sdp:'foobar'}); did not throw exception.
 PASS successfullyParsed is true
 
diff --git a/third_party/blink/web_tests/fast/mediastream/constructors.html b/third_party/blink/web_tests/fast/mediastream/constructors.html
index 9bedcdc..32c74c95 100644
--- a/third_party/blink/web_tests/fast/mediastream/constructors.html
+++ b/third_party/blink/web_tests/fast/mediastream/constructors.html
@@ -17,7 +17,7 @@
 shouldThrow("RTCSessionDescription()");
 shouldThrow("RTCIceCandidate()");
 
-shouldNotThrow("new RTCPeerConnection({iceServers:[{url:'stun://foobar.com:12345'}]}, null);");
+shouldNotThrow("new RTCPeerConnection({iceServers:[{url:'stun:foobar.com:12345'}]}, null);");
 shouldNotThrow("new RTCSessionDescription({type:'offer',sdp:'foobar'});");
 
 window.jsTestIsAsync = false;
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/viewport-testing/console-key-navigation.js b/third_party/blink/web_tests/http/tests/devtools/console/viewport-testing/console-key-navigation.js
index dea56b3..72468a2 100644
--- a/third_party/blink/web_tests/http/tests/devtools/console/viewport-testing/console-key-navigation.js
+++ b/third_party/blink/web_tests/http/tests/devtools/console/viewport-testing/console-key-navigation.js
@@ -213,8 +213,8 @@
       name += '#' + element.id;
     if (element.getAttribute('aria-label'))
       name += ':' + element.getAttribute('aria-label');
-    else if (element.title)
-      name += ':' + element.title;
+    else if (UI.Tooltip.getContent(element))
+      name += ':' + UI.Tooltip.getContent(element);
     else if (element.textContent && element.textContent.length < 50) {
       name += ':' + element.textContent.replace('\u200B', '');
     } else if (element.className)
diff --git a/third_party/blink/web_tests/http/tests/workers/deprecation-report-crash.html b/third_party/blink/web_tests/http/tests/workers/deprecation-report-crash.html
new file mode 100644
index 0000000..613e2f0
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/workers/deprecation-report-crash.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+<script src = "/resources/testharness.js"></script>
+<script src = "/resources/testharnessreport.js"></script>
+<script>
+// This is a regression test for https://crbug.com/1146824.
+async_test((t) => {
+  const worker = new Worker('\n/path<', {type: 'module'});
+
+  worker.onerror = () => t.done();
+}, 'Creating a worker should not crash.');
+</script>
+</html>
diff --git a/third_party/blink/web_tests/navigator_webdriver/navigator_webdriver.html b/third_party/blink/web_tests/navigator_webdriver/navigator_webdriver.html
deleted file mode 100644
index f35279b..0000000
--- a/third_party/blink/web_tests/navigator_webdriver/navigator_webdriver.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!DOCTYPE html>
-<body>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script>
-
-// navigator.webdriver property is a runtime enabled feature
-// that should not exist if not excplicitly enabled
-test(function() {
-  assert_false("webdriver" in window.navigator);
-  assert_true(navigator.webdriver === undefined);
-}, "Test if webdriver property is not in the prototype chain");
-</script>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/navigator_webdriver/navigator_webdriver_enabled.html b/third_party/blink/web_tests/navigator_webdriver/navigator_webdriver_enabled.html
index 0ab4451..52060fd 100644
--- a/third_party/blink/web_tests/navigator_webdriver/navigator_webdriver_enabled.html
+++ b/third_party/blink/web_tests/navigator_webdriver/navigator_webdriver_enabled.html
@@ -10,6 +10,10 @@
 		"This test only works when run as a layout test!");
 }, "Prerequisites to running the rest of the tests");
 
+test(function () {
+  assert_false(window.navigator.webdriver);
+}, "Test if navigator.webdriver is false without automation");
+
 internals.runtimeFlags.automationControlledEnabled = true;
 
 test(function() {
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
index 5b3ba57..a96554f0 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
@@ -32,6 +32,7 @@
 PASS window.cached_navigator.userAgent is ''
 PASS window.cached_navigator.vendor is window.navigator.vendor
 PASS window.cached_navigator.vendorSub is ''
+PASS window.cached_navigator.webdriver is false
 PASS window.cached_navigator_connection.onchange is null
 PASS window.cached_navigator_connection.saveData is false
 PASS window.cached_navigator_mediaDevices.ondevicechange is null
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
index 9df7a26a..afb062d 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
@@ -32,6 +32,7 @@
 PASS window.cached_navigator.userAgent is ''
 PASS window.cached_navigator.vendor is window.navigator.vendor
 PASS window.cached_navigator.vendorSub is ''
+PASS window.cached_navigator.webdriver is false
 PASS window.cached_navigator_connection.onchange is null
 PASS window.cached_navigator_connection.saveData is false
 PASS window.cached_navigator_mediaDevices.ondevicechange is null
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
index 7ab8cf1..8f86d40 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
@@ -32,6 +32,7 @@
 PASS window.cached_navigator.userAgent is ''
 PASS window.cached_navigator.vendor is window.navigator.vendor
 PASS window.cached_navigator.vendorSub is ''
+PASS window.cached_navigator.webdriver is false
 PASS window.cached_navigator_connection.onchange is null
 PASS window.cached_navigator_connection.saveData is false
 PASS window.cached_navigator_mediaDevices.ondevicechange is null
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
index fcf2bb4f3..f683cb6 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
@@ -57,6 +57,7 @@
 PASS oldChildWindow.navigator.userAgent is newChildWindow.navigator.userAgent
 PASS oldChildWindow.navigator.vendor is newChildWindow.navigator.vendor
 PASS oldChildWindow.navigator.vendorSub is newChildWindow.navigator.vendorSub
+PASS oldChildWindow.navigator.webdriver is newChildWindow.navigator.webdriver
 PASS oldChildWindow.navigator.xr.ondevicechange is newChildWindow.navigator.xr.ondevicechange
 PASS oldChildWindow.onabort is newChildWindow.onabort
 PASS oldChildWindow.onafterprint is newChildWindow.onafterprint
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
index 1f6025be..1eb4e2e 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
@@ -33,6 +33,7 @@
 PASS childWindow.navigator.userAgent is ''
 PASS childWindow.navigator.vendor is window.navigator.vendor
 PASS childWindow.navigator.vendorSub is ''
+PASS childWindow.navigator.webdriver is false
 PASS childWindow.onabort is null
 PASS childWindow.onafterprint is null
 PASS childWindow.onanimationend is null
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
index e6f27d0..e3dc9b3 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
@@ -33,6 +33,7 @@
 PASS childWindow.navigator.userAgent is ''
 PASS childWindow.navigator.vendor is window.navigator.vendor
 PASS childWindow.navigator.vendorSub is ''
+PASS childWindow.navigator.webdriver is false
 PASS childWindow.onabort is null
 PASS childWindow.onafterprint is null
 PASS childWindow.onanimationend is null
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 47b642e..ea48c46 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -4582,6 +4582,7 @@
     getter vendor
     getter vendorSub
     getter wakeLock
+    getter webdriver
     getter webkitPersistentStorage
     getter webkitTemporaryStorage
     getter xr
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 112ba8e..24630333 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -5481,6 +5481,7 @@
     getter vendorSub
     getter virtualKeyboard
     getter wakeLock
+    getter webdriver
     getter webkitPersistentStorage
     getter webkitTemporaryStorage
     getter xr
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index 98d02b9..cc943454 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-10-4-17-g3facafa44
-Revision: 3facafa44e2ac49ac15359bf6c83110614a6cbf7
+Version: VER-2-10-4-18-g56c610b14
+Revision: 56c610b145212b7acfb24a17e86fc0ba15aa3052
 CPEPrefix: cpe:/a:freetype:freetype:2.10.1
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4ca5101..72bf461 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -6653,6 +6653,7 @@
   <int value="236" label="ASGH_ASSOCIATED_INTERFACE_REQUEST"/>
   <int value="237" label="ASGH_RECEIVED_CONTROL_MESSAGE"/>
   <int value="238" label="CSDH_BAD_OWNER"/>
+  <int value="239" label="SYNC_COMPOSITOR_NO_LOCAL_SURFACE_ID"/>
 </enum>
 
 <enum name="BadMessageReasonExtensions">
@@ -16770,6 +16771,7 @@
   <int value="37" label="showWebVitalsInPerformancePanel"/>
   <int value="38" label="recorder"/>
   <int value="39" label="APCA"/>
+  <int value="40" label="cspViolationsView"/>
 </enum>
 
 <enum name="DevToolsGridOverlayOpenedFrom">
diff --git a/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml b/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
index f662d44..b7e25d5 100644
--- a/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
@@ -554,7 +554,7 @@
 </histogram>
 
 <histogram name="Accessibility.LanguageDetection.PercentageLanguageDetected"
-    units="%" expires_after="2020-12-01">
+    units="%" expires_after="2021-05-23">
   <owner>chrishall@chromium.org</owner>
   <owner>aboxhall@chromium.org</owner>
   <owner>dmazzoni@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/event/histograms.xml b/tools/metrics/histograms/histograms_xml/event/histograms.xml
index ed8eb39..de72b54 100644
--- a/tools/metrics/histograms/histograms_xml/event/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/event/histograms.xml
@@ -1640,6 +1640,16 @@
   </summary>
 </histogram>
 
+<histogram name="Event.WaylandDragDrop.IncomingDataTransferTime" units="ms"
+    expires_after="M90">
+  <owner>rjkroege@chromium.org</owner>
+  <owner>adunaev@igalia.com</owner>
+  <summary>
+    Delay between the drag coming into the window and the window is actually
+    notified.
+  </summary>
+</histogram>
+
 <histogram base="true" name="EventLatency" units="microseconds"
     expires_after="2021-05-09">
   <owner>mohsen@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index 2798f7ec..8b950d1 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -12560,7 +12560,7 @@
 </histogram>
 
 <histogram name="Rollback.RollbackSaveResult"
-    enum="Rollback_RollbackSaveResult" expires_after="2021-01-01">
+    enum="Rollback_RollbackSaveResult" expires_after="2021-03-01">
   <owner>mpolzer@google.com</owner>
   <owner>managed-platforms@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/update_engine/histograms.xml b/tools/metrics/histograms/histograms_xml/update_engine/histograms.xml
index 88735461..18f878f 100644
--- a/tools/metrics/histograms/histograms_xml/update_engine/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/update_engine/histograms.xml
@@ -377,7 +377,7 @@
 </histogram>
 
 <histogram name="UpdateEngine.EnterpriseRollback.Failure"
-    enum="UpdateEngineChromeOsVersionPrefix" expires_after="2021-01-01">
+    enum="UpdateEngineChromeOsVersionPrefix" expires_after="2021-03-01">
   <owner>mpolzer@google.com</owner>
   <owner>managed-platforms@google.com</owner>
   <summary>
@@ -395,7 +395,7 @@
 </histogram>
 
 <histogram name="UpdateEngine.EnterpriseRollback.Success"
-    enum="UpdateEngineChromeOsVersionPrefix" expires_after="2021-01-01">
+    enum="UpdateEngineChromeOsVersionPrefix" expires_after="2021-03-01">
   <owner>mpolzer@google.com</owner>
   <owner>managed-platforms@google.com</owner>
   <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index bcbea7b..b2d4514a 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -5907,6 +5907,19 @@
   </metric>
 </event>
 
+<event name="InstalledRelatedApps">
+  <owner>rayankans@chromium.org</owner>
+  <owner>peter@chromium.org</owner>
+  <summary>
+    Recorded before resolving a call to navigator.getInstalledRelatedApps().
+  </summary>
+  <metric name="Called">
+    <summary>
+      Always true.
+    </summary>
+  </metric>
+</event>
+
 <event name="Intervention.DocumentWrite.ScriptBlock" singular="True">
   <owner>bmcquade@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 96d077fa..3a2e2f8e 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,11 +6,11 @@
         },
         "mac": {
             "hash": "23c054de3c5738695431f962b9f5e79a888eb135",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/254ea334b65f8a7d5d4ab087f861df0572fa6af7/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/ca8783182d27a2fd1ae1212c6c75be922f42a840/trace_processor_shell"
         },
         "linux": {
             "hash": "68451064b202f0b800ab6a86da1cdeb450fe5745",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/254ea334b65f8a7d5d4ab087f861df0572fa6af7/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/ca8783182d27a2fd1ae1212c6c75be922f42a840/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/page_sets/update_webrtc_cases b/tools/perf/page_sets/update_webrtc_cases
index 32eb7f6..35ac6913 100755
--- a/tools/perf/page_sets/update_webrtc_cases
+++ b/tools/perf/page_sets/update_webrtc_cases
@@ -25,31 +25,26 @@
             'src/multiple-peerconnections',
             'src/pause-play',
         ],
-        'revision': '9f0ff9343a38dbf16199a964a18d7411d940c601',
+        'revision': '63a46f73c917ffcdf765bd11622b10bf473eb11c',
     },
     'samples': {
         'dirs': [
             'src/content/datachannel/datatransfer',
             'src/content/getusermedia/resolution',
-            'src/content/peerconnection/audio',
         ],
-        'files': [
-            'src/js/common.js',
-        ],
-        'revision': '6f9a14c10ee9b990d56c309d86d4b9129a4aa626',
+        'revision': 'a4ca7fbc92825b4993d9a7fa1844206e0c718454',
     },
     'adapter': {
         'files': [
-            'release/adapter.js',
+            'adapter-latest.js',
         ],
-        'revision': '5b7ce4bdce79b9cb754fe27b097106e9491e0354',
+        'revision': 'bc3f52c57baaae789df6965c43f418cc995a9edb',
     },
 }
 
 ADDED_SCRIPT_TAGS = (
     '<script src="%s.js"></script>\n'
-    '<script src="adapter.js"></script>\n'
-    '<script src="common.js"></script>\n'
+    '<script src="adapter-latest.js"></script>\n'
     '</body></html>'
 )
 
@@ -122,7 +117,7 @@
           '  * Deletes the <meta> tags.\n'
           '  * Discards the CSS files and corresponding link tags.\n'
           '  * Discards the JS files and corresponding script tags except for '
-          'main.js, adapter.js and common.js.\n'
+          'main.js and adapter-latest.js.\n'
           '  * Renames the index.html and main.js files for each test to '
           'testname.html and testname.js.'))
 
diff --git a/tools/perf/page_sets/webrtc_cases.py b/tools/perf/page_sets/webrtc_cases.py
index 171d9421..bbf19e8 100644
--- a/tools/perf/page_sets/webrtc_cases.py
+++ b/tools/perf/page_sets/webrtc_cases.py
@@ -51,21 +51,6 @@
         description='Amount of data transferred by data channel in 10 seconds')
 
 
-class AudioCall(WebrtcPage):
-  """Why: Sets up a WebRTC audio call."""
-
-  def __init__(self, page_set, codec, tags):
-    super(AudioCall, self).__init__(
-        url='file://webrtc_cases/audio.html?codec=%s' % codec,
-        name='audio_call_%s_10s' % codec.lower(),
-        page_set=page_set, tags=tags)
-    self.codec = codec
-
-  def ExecuteTest(self, action_runner):
-    action_runner.ExecuteJavaScript('codecSelector.value="%s";' % self.codec)
-    action_runner.ClickElement('button[id="callButton"]')
-    action_runner.Wait(10)
-
 class CanvasCapturePeerConnection(WebrtcPage):
   """Why: Sets up a canvas capture stream connection to a peer connection."""
 
diff --git a/tools/perf/page_sets/webrtc_cases/adapter-latest.js b/tools/perf/page_sets/webrtc_cases/adapter-latest.js
new file mode 100644
index 0000000..a8a0aa7
--- /dev/null
+++ b/tools/perf/page_sets/webrtc_cases/adapter-latest.js
@@ -0,0 +1,5628 @@
+/*
+ * Copyright 2017 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+
+'use strict';
+
+var _adapter_factory = require('./adapter_factory.js');
+
+var adapter = (0, _adapter_factory.adapterFactory)({ window: typeof window === 'undefined' ? undefined : window });
+module.exports = adapter; // this is the difference from adapter_core.
+
+},{"./adapter_factory.js":2}],2:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.adapterFactory = adapterFactory;
+
+var _utils = require('./utils');
+
+var utils = _interopRequireWildcard(_utils);
+
+var _chrome_shim = require('./chrome/chrome_shim');
+
+var chromeShim = _interopRequireWildcard(_chrome_shim);
+
+var _edge_shim = require('./edge/edge_shim');
+
+var edgeShim = _interopRequireWildcard(_edge_shim);
+
+var _firefox_shim = require('./firefox/firefox_shim');
+
+var firefoxShim = _interopRequireWildcard(_firefox_shim);
+
+var _safari_shim = require('./safari/safari_shim');
+
+var safariShim = _interopRequireWildcard(_safari_shim);
+
+var _common_shim = require('./common_shim');
+
+var commonShim = _interopRequireWildcard(_common_shim);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+// Shimming starts here.
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+function adapterFactory() {
+  var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
+      window = _ref.window;
+
+  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
+    shimChrome: true,
+    shimFirefox: true,
+    shimEdge: true,
+    shimSafari: true
+  };
+
+  // Utils.
+  var logging = utils.log;
+  var browserDetails = utils.detectBrowser(window);
+
+  var adapter = {
+    browserDetails: browserDetails,
+    commonShim: commonShim,
+    extractVersion: utils.extractVersion,
+    disableLog: utils.disableLog,
+    disableWarnings: utils.disableWarnings
+  };
+
+  // Shim browser if found.
+  switch (browserDetails.browser) {
+    case 'chrome':
+      if (!chromeShim || !chromeShim.shimPeerConnection || !options.shimChrome) {
+        logging('Chrome shim is not included in this adapter release.');
+        return adapter;
+      }
+      if (browserDetails.version === null) {
+        logging('Chrome shim can not determine version, not shimming.');
+        return adapter;
+      }
+      logging('adapter.js shimming chrome.');
+      // Export to the adapter global object visible in the browser.
+      adapter.browserShim = chromeShim;
+
+      chromeShim.shimGetUserMedia(window);
+      chromeShim.shimMediaStream(window);
+      chromeShim.shimPeerConnection(window);
+      chromeShim.shimOnTrack(window);
+      chromeShim.shimAddTrackRemoveTrack(window);
+      chromeShim.shimGetSendersWithDtmf(window);
+      chromeShim.shimGetStats(window);
+      chromeShim.shimSenderReceiverGetStats(window);
+      chromeShim.fixNegotiationNeeded(window);
+
+      commonShim.shimRTCIceCandidate(window);
+      commonShim.shimConnectionState(window);
+      commonShim.shimMaxMessageSize(window);
+      commonShim.shimSendThrowTypeError(window);
+      commonShim.removeAllowExtmapMixed(window);
+      break;
+    case 'firefox':
+      if (!firefoxShim || !firefoxShim.shimPeerConnection || !options.shimFirefox) {
+        logging('Firefox shim is not included in this adapter release.');
+        return adapter;
+      }
+      logging('adapter.js shimming firefox.');
+      // Export to the adapter global object visible in the browser.
+      adapter.browserShim = firefoxShim;
+
+      firefoxShim.shimGetUserMedia(window);
+      firefoxShim.shimPeerConnection(window);
+      firefoxShim.shimOnTrack(window);
+      firefoxShim.shimRemoveStream(window);
+      firefoxShim.shimSenderGetStats(window);
+      firefoxShim.shimReceiverGetStats(window);
+      firefoxShim.shimRTCDataChannel(window);
+      firefoxShim.shimAddTransceiver(window);
+      firefoxShim.shimGetParameters(window);
+      firefoxShim.shimCreateOffer(window);
+      firefoxShim.shimCreateAnswer(window);
+
+      commonShim.shimRTCIceCandidate(window);
+      commonShim.shimConnectionState(window);
+      commonShim.shimMaxMessageSize(window);
+      commonShim.shimSendThrowTypeError(window);
+      break;
+    case 'edge':
+      if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) {
+        logging('MS edge shim is not included in this adapter release.');
+        return adapter;
+      }
+      logging('adapter.js shimming edge.');
+      // Export to the adapter global object visible in the browser.
+      adapter.browserShim = edgeShim;
+
+      edgeShim.shimGetUserMedia(window);
+      edgeShim.shimGetDisplayMedia(window);
+      edgeShim.shimPeerConnection(window);
+      edgeShim.shimReplaceTrack(window);
+
+      // the edge shim implements the full RTCIceCandidate object.
+
+      commonShim.shimMaxMessageSize(window);
+      commonShim.shimSendThrowTypeError(window);
+      break;
+    case 'safari':
+      if (!safariShim || !options.shimSafari) {
+        logging('Safari shim is not included in this adapter release.');
+        return adapter;
+      }
+      logging('adapter.js shimming safari.');
+      // Export to the adapter global object visible in the browser.
+      adapter.browserShim = safariShim;
+
+      safariShim.shimRTCIceServerUrls(window);
+      safariShim.shimCreateOfferLegacy(window);
+      safariShim.shimCallbacksAPI(window);
+      safariShim.shimLocalStreamsAPI(window);
+      safariShim.shimRemoteStreamsAPI(window);
+      safariShim.shimTrackEventTransceiver(window);
+      safariShim.shimGetUserMedia(window);
+      safariShim.shimAudioContext(window);
+
+      commonShim.shimRTCIceCandidate(window);
+      commonShim.shimMaxMessageSize(window);
+      commonShim.shimSendThrowTypeError(window);
+      commonShim.removeAllowExtmapMixed(window);
+      break;
+    default:
+      logging('Unsupported browser!');
+      break;
+  }
+
+  return adapter;
+}
+
+// Browser shims.
+
+},{"./chrome/chrome_shim":3,"./common_shim":6,"./edge/edge_shim":7,"./firefox/firefox_shim":11,"./safari/safari_shim":14,"./utils":15}],3:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined;
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+var _getusermedia = require('./getusermedia');
+
+Object.defineProperty(exports, 'shimGetUserMedia', {
+  enumerable: true,
+  get: function get() {
+    return _getusermedia.shimGetUserMedia;
+  }
+});
+
+var _getdisplaymedia = require('./getdisplaymedia');
+
+Object.defineProperty(exports, 'shimGetDisplayMedia', {
+  enumerable: true,
+  get: function get() {
+    return _getdisplaymedia.shimGetDisplayMedia;
+  }
+});
+exports.shimMediaStream = shimMediaStream;
+exports.shimOnTrack = shimOnTrack;
+exports.shimGetSendersWithDtmf = shimGetSendersWithDtmf;
+exports.shimGetStats = shimGetStats;
+exports.shimSenderReceiverGetStats = shimSenderReceiverGetStats;
+exports.shimAddTrackRemoveTrackWithNative = shimAddTrackRemoveTrackWithNative;
+exports.shimAddTrackRemoveTrack = shimAddTrackRemoveTrack;
+exports.shimPeerConnection = shimPeerConnection;
+exports.fixNegotiationNeeded = fixNegotiationNeeded;
+
+var _utils = require('../utils.js');
+
+var utils = _interopRequireWildcard(_utils);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+function shimMediaStream(window) {
+  window.MediaStream = window.MediaStream || window.webkitMediaStream;
+}
+
+function shimOnTrack(window) {
+  if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) {
+    Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
+      get: function get() {
+        return this._ontrack;
+      },
+      set: function set(f) {
+        if (this._ontrack) {
+          this.removeEventListener('track', this._ontrack);
+        }
+        this.addEventListener('track', this._ontrack = f);
+      },
+
+      enumerable: true,
+      configurable: true
+    });
+    var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription;
+    window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() {
+      var _this = this;
+
+      if (!this._ontrackpoly) {
+        this._ontrackpoly = function (e) {
+          // onaddstream does not fire when a track is added to an existing
+          // stream. But stream.onaddtrack is implemented so we use that.
+          e.stream.addEventListener('addtrack', function (te) {
+            var receiver = void 0;
+            if (window.RTCPeerConnection.prototype.getReceivers) {
+              receiver = _this.getReceivers().find(function (r) {
+                return r.track && r.track.id === te.track.id;
+              });
+            } else {
+              receiver = { track: te.track };
+            }
+
+            var event = new Event('track');
+            event.track = te.track;
+            event.receiver = receiver;
+            event.transceiver = { receiver: receiver };
+            event.streams = [e.stream];
+            _this.dispatchEvent(event);
+          });
+          e.stream.getTracks().forEach(function (track) {
+            var receiver = void 0;
+            if (window.RTCPeerConnection.prototype.getReceivers) {
+              receiver = _this.getReceivers().find(function (r) {
+                return r.track && r.track.id === track.id;
+              });
+            } else {
+              receiver = { track: track };
+            }
+            var event = new Event('track');
+            event.track = track;
+            event.receiver = receiver;
+            event.transceiver = { receiver: receiver };
+            event.streams = [e.stream];
+            _this.dispatchEvent(event);
+          });
+        };
+        this.addEventListener('addstream', this._ontrackpoly);
+      }
+      return origSetRemoteDescription.apply(this, arguments);
+    };
+  } else {
+    // even if RTCRtpTransceiver is in window, it is only used and
+    // emitted in unified-plan. Unfortunately this means we need
+    // to unconditionally wrap the event.
+    utils.wrapPeerConnectionEvent(window, 'track', function (e) {
+      if (!e.transceiver) {
+        Object.defineProperty(e, 'transceiver', { value: { receiver: e.receiver } });
+      }
+      return e;
+    });
+  }
+}
+
+function shimGetSendersWithDtmf(window) {
+  // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
+  if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && !('getSenders' in window.RTCPeerConnection.prototype) && 'createDTMFSender' in window.RTCPeerConnection.prototype) {
+    var shimSenderWithDtmf = function shimSenderWithDtmf(pc, track) {
+      return {
+        track: track,
+        get dtmf() {
+          if (this._dtmf === undefined) {
+            if (track.kind === 'audio') {
+              this._dtmf = pc.createDTMFSender(track);
+            } else {
+              this._dtmf = null;
+            }
+          }
+          return this._dtmf;
+        },
+        _pc: pc
+      };
+    };
+
+    // augment addTrack when getSenders is not available.
+    if (!window.RTCPeerConnection.prototype.getSenders) {
+      window.RTCPeerConnection.prototype.getSenders = function getSenders() {
+        this._senders = this._senders || [];
+        return this._senders.slice(); // return a copy of the internal state.
+      };
+      var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
+      window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) {
+        var sender = origAddTrack.apply(this, arguments);
+        if (!sender) {
+          sender = shimSenderWithDtmf(this, track);
+          this._senders.push(sender);
+        }
+        return sender;
+      };
+
+      var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
+      window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) {
+        origRemoveTrack.apply(this, arguments);
+        var idx = this._senders.indexOf(sender);
+        if (idx !== -1) {
+          this._senders.splice(idx, 1);
+        }
+      };
+    }
+    var origAddStream = window.RTCPeerConnection.prototype.addStream;
+    window.RTCPeerConnection.prototype.addStream = function addStream(stream) {
+      var _this2 = this;
+
+      this._senders = this._senders || [];
+      origAddStream.apply(this, [stream]);
+      stream.getTracks().forEach(function (track) {
+        _this2._senders.push(shimSenderWithDtmf(_this2, track));
+      });
+    };
+
+    var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
+    window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) {
+      var _this3 = this;
+
+      this._senders = this._senders || [];
+      origRemoveStream.apply(this, [stream]);
+
+      stream.getTracks().forEach(function (track) {
+        var sender = _this3._senders.find(function (s) {
+          return s.track === track;
+        });
+        if (sender) {
+          // remove sender
+          _this3._senders.splice(_this3._senders.indexOf(sender), 1);
+        }
+      });
+    };
+  } else if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && 'getSenders' in window.RTCPeerConnection.prototype && 'createDTMFSender' in window.RTCPeerConnection.prototype && window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) {
+    var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
+    window.RTCPeerConnection.prototype.getSenders = function getSenders() {
+      var _this4 = this;
+
+      var senders = origGetSenders.apply(this, []);
+      senders.forEach(function (sender) {
+        return sender._pc = _this4;
+      });
+      return senders;
+    };
+
+    Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
+      get: function get() {
+        if (this._dtmf === undefined) {
+          if (this.track.kind === 'audio') {
+            this._dtmf = this._pc.createDTMFSender(this.track);
+          } else {
+            this._dtmf = null;
+          }
+        }
+        return this._dtmf;
+      }
+    });
+  }
+}
+
+function shimGetStats(window) {
+  if (!window.RTCPeerConnection) {
+    return;
+  }
+
+  var origGetStats = window.RTCPeerConnection.prototype.getStats;
+  window.RTCPeerConnection.prototype.getStats = function getStats() {
+    var _this5 = this;
+
+    var _arguments = Array.prototype.slice.call(arguments),
+        selector = _arguments[0],
+        onSucc = _arguments[1],
+        onErr = _arguments[2];
+
+    // If selector is a function then we are in the old style stats so just
+    // pass back the original getStats format to avoid breaking old users.
+
+
+    if (arguments.length > 0 && typeof selector === 'function') {
+      return origGetStats.apply(this, arguments);
+    }
+
+    // When spec-style getStats is supported, return those when called with
+    // either no arguments or the selector argument is null.
+    if (origGetStats.length === 0 && (arguments.length === 0 || typeof selector !== 'function')) {
+      return origGetStats.apply(this, []);
+    }
+
+    var fixChromeStats_ = function fixChromeStats_(response) {
+      var standardReport = {};
+      var reports = response.result();
+      reports.forEach(function (report) {
+        var standardStats = {
+          id: report.id,
+          timestamp: report.timestamp,
+          type: {
+            localcandidate: 'local-candidate',
+            remotecandidate: 'remote-candidate'
+          }[report.type] || report.type
+        };
+        report.names().forEach(function (name) {
+          standardStats[name] = report.stat(name);
+        });
+        standardReport[standardStats.id] = standardStats;
+      });
+
+      return standardReport;
+    };
+
+    // shim getStats with maplike support
+    var makeMapStats = function makeMapStats(stats) {
+      return new Map(Object.keys(stats).map(function (key) {
+        return [key, stats[key]];
+      }));
+    };
+
+    if (arguments.length >= 2) {
+      var successCallbackWrapper_ = function successCallbackWrapper_(response) {
+        onSucc(makeMapStats(fixChromeStats_(response)));
+      };
+
+      return origGetStats.apply(this, [successCallbackWrapper_, selector]);
+    }
+
+    // promise-support
+    return new Promise(function (resolve, reject) {
+      origGetStats.apply(_this5, [function (response) {
+        resolve(makeMapStats(fixChromeStats_(response)));
+      }, reject]);
+    }).then(onSucc, onErr);
+  };
+}
+
+function shimSenderReceiverGetStats(window) {
+  if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender && window.RTCRtpReceiver)) {
+    return;
+  }
+
+  // shim sender stats.
+  if (!('getStats' in window.RTCRtpSender.prototype)) {
+    var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
+    if (origGetSenders) {
+      window.RTCPeerConnection.prototype.getSenders = function getSenders() {
+        var _this6 = this;
+
+        var senders = origGetSenders.apply(this, []);
+        senders.forEach(function (sender) {
+          return sender._pc = _this6;
+        });
+        return senders;
+      };
+    }
+
+    var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
+    if (origAddTrack) {
+      window.RTCPeerConnection.prototype.addTrack = function addTrack() {
+        var sender = origAddTrack.apply(this, arguments);
+        sender._pc = this;
+        return sender;
+      };
+    }
+    window.RTCRtpSender.prototype.getStats = function getStats() {
+      var sender = this;
+      return this._pc.getStats().then(function (result) {
+        return (
+          /* Note: this will include stats of all senders that
+           *   send a track with the same id as sender.track as
+           *   it is not possible to identify the RTCRtpSender.
+           */
+          utils.filterStats(result, sender.track, true)
+        );
+      });
+    };
+  }
+
+  // shim receiver stats.
+  if (!('getStats' in window.RTCRtpReceiver.prototype)) {
+    var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers;
+    if (origGetReceivers) {
+      window.RTCPeerConnection.prototype.getReceivers = function getReceivers() {
+        var _this7 = this;
+
+        var receivers = origGetReceivers.apply(this, []);
+        receivers.forEach(function (receiver) {
+          return receiver._pc = _this7;
+        });
+        return receivers;
+      };
+    }
+    utils.wrapPeerConnectionEvent(window, 'track', function (e) {
+      e.receiver._pc = e.srcElement;
+      return e;
+    });
+    window.RTCRtpReceiver.prototype.getStats = function getStats() {
+      var receiver = this;
+      return this._pc.getStats().then(function (result) {
+        return utils.filterStats(result, receiver.track, false);
+      });
+    };
+  }
+
+  if (!('getStats' in window.RTCRtpSender.prototype && 'getStats' in window.RTCRtpReceiver.prototype)) {
+    return;
+  }
+
+  // shim RTCPeerConnection.getStats(track).
+  var origGetStats = window.RTCPeerConnection.prototype.getStats;
+  window.RTCPeerConnection.prototype.getStats = function getStats() {
+    if (arguments.length > 0 && arguments[0] instanceof window.MediaStreamTrack) {
+      var track = arguments[0];
+      var sender = void 0;
+      var receiver = void 0;
+      var err = void 0;
+      this.getSenders().forEach(function (s) {
+        if (s.track === track) {
+          if (sender) {
+            err = true;
+          } else {
+            sender = s;
+          }
+        }
+      });
+      this.getReceivers().forEach(function (r) {
+        if (r.track === track) {
+          if (receiver) {
+            err = true;
+          } else {
+            receiver = r;
+          }
+        }
+        return r.track === track;
+      });
+      if (err || sender && receiver) {
+        return Promise.reject(new DOMException('There are more than one sender or receiver for the track.', 'InvalidAccessError'));
+      } else if (sender) {
+        return sender.getStats();
+      } else if (receiver) {
+        return receiver.getStats();
+      }
+      return Promise.reject(new DOMException('There is no sender or receiver for the track.', 'InvalidAccessError'));
+    }
+    return origGetStats.apply(this, arguments);
+  };
+}
+
+function shimAddTrackRemoveTrackWithNative(window) {
+  // shim addTrack/removeTrack with native variants in order to make
+  // the interactions with legacy getLocalStreams behave as in other browsers.
+  // Keeps a mapping stream.id => [stream, rtpsenders...]
+  window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() {
+    var _this8 = this;
+
+    this._shimmedLocalStreams = this._shimmedLocalStreams || {};
+    return Object.keys(this._shimmedLocalStreams).map(function (streamId) {
+      return _this8._shimmedLocalStreams[streamId][0];
+    });
+  };
+
+  var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
+  window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) {
+    if (!stream) {
+      return origAddTrack.apply(this, arguments);
+    }
+    this._shimmedLocalStreams = this._shimmedLocalStreams || {};
+
+    var sender = origAddTrack.apply(this, arguments);
+    if (!this._shimmedLocalStreams[stream.id]) {
+      this._shimmedLocalStreams[stream.id] = [stream, sender];
+    } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) {
+      this._shimmedLocalStreams[stream.id].push(sender);
+    }
+    return sender;
+  };
+
+  var origAddStream = window.RTCPeerConnection.prototype.addStream;
+  window.RTCPeerConnection.prototype.addStream = function addStream(stream) {
+    var _this9 = this;
+
+    this._shimmedLocalStreams = this._shimmedLocalStreams || {};
+
+    stream.getTracks().forEach(function (track) {
+      var alreadyExists = _this9.getSenders().find(function (s) {
+        return s.track === track;
+      });
+      if (alreadyExists) {
+        throw new DOMException('Track already exists.', 'InvalidAccessError');
+      }
+    });
+    var existingSenders = this.getSenders();
+    origAddStream.apply(this, arguments);
+    var newSenders = this.getSenders().filter(function (newSender) {
+      return existingSenders.indexOf(newSender) === -1;
+    });
+    this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders);
+  };
+
+  var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
+  window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) {
+    this._shimmedLocalStreams = this._shimmedLocalStreams || {};
+    delete this._shimmedLocalStreams[stream.id];
+    return origRemoveStream.apply(this, arguments);
+  };
+
+  var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
+  window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) {
+    var _this10 = this;
+
+    this._shimmedLocalStreams = this._shimmedLocalStreams || {};
+    if (sender) {
+      Object.keys(this._shimmedLocalStreams).forEach(function (streamId) {
+        var idx = _this10._shimmedLocalStreams[streamId].indexOf(sender);
+        if (idx !== -1) {
+          _this10._shimmedLocalStreams[streamId].splice(idx, 1);
+        }
+        if (_this10._shimmedLocalStreams[streamId].length === 1) {
+          delete _this10._shimmedLocalStreams[streamId];
+        }
+      });
+    }
+    return origRemoveTrack.apply(this, arguments);
+  };
+}
+
+function shimAddTrackRemoveTrack(window) {
+  if (!window.RTCPeerConnection) {
+    return;
+  }
+  var browserDetails = utils.detectBrowser(window);
+  // shim addTrack and removeTrack.
+  if (window.RTCPeerConnection.prototype.addTrack && browserDetails.version >= 65) {
+    return shimAddTrackRemoveTrackWithNative(window);
+  }
+
+  // also shim pc.getLocalStreams when addTrack is shimmed
+  // to return the original streams.
+  var origGetLocalStreams = window.RTCPeerConnection.prototype.getLocalStreams;
+  window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() {
+    var _this11 = this;
+
+    var nativeStreams = origGetLocalStreams.apply(this);
+    this._reverseStreams = this._reverseStreams || {};
+    return nativeStreams.map(function (stream) {
+      return _this11._reverseStreams[stream.id];
+    });
+  };
+
+  var origAddStream = window.RTCPeerConnection.prototype.addStream;
+  window.RTCPeerConnection.prototype.addStream = function addStream(stream) {
+    var _this12 = this;
+
+    this._streams = this._streams || {};
+    this._reverseStreams = this._reverseStreams || {};
+
+    stream.getTracks().forEach(function (track) {
+      var alreadyExists = _this12.getSenders().find(function (s) {
+        return s.track === track;
+      });
+      if (alreadyExists) {
+        throw new DOMException('Track already exists.', 'InvalidAccessError');
+      }
+    });
+    // Add identity mapping for consistency with addTrack.
+    // Unless this is being used with a stream from addTrack.
+    if (!this._reverseStreams[stream.id]) {
+      var newStream = new window.MediaStream(stream.getTracks());
+      this._streams[stream.id] = newStream;
+      this._reverseStreams[newStream.id] = stream;
+      stream = newStream;
+    }
+    origAddStream.apply(this, [stream]);
+  };
+
+  var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
+  window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) {
+    this._streams = this._streams || {};
+    this._reverseStreams = this._reverseStreams || {};
+
+    origRemoveStream.apply(this, [this._streams[stream.id] || stream]);
+    delete this._reverseStreams[this._streams[stream.id] ? this._streams[stream.id].id : stream.id];
+    delete this._streams[stream.id];
+  };
+
+  window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) {
+    var _this13 = this;
+
+    if (this.signalingState === 'closed') {
+      throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError');
+    }
+    var streams = [].slice.call(arguments, 1);
+    if (streams.length !== 1 || !streams[0].getTracks().find(function (t) {
+      return t === track;
+    })) {
+      // this is not fully correct but all we can manage without
+      // [[associated MediaStreams]] internal slot.
+      throw new DOMException('The adapter.js addTrack polyfill only supports a single ' + ' stream which is associated with the specified track.', 'NotSupportedError');
+    }
+
+    var alreadyExists = this.getSenders().find(function (s) {
+      return s.track === track;
+    });
+    if (alreadyExists) {
+      throw new DOMException('Track already exists.', 'InvalidAccessError');
+    }
+
+    this._streams = this._streams || {};
+    this._reverseStreams = this._reverseStreams || {};
+    var oldStream = this._streams[stream.id];
+    if (oldStream) {
+      // this is using odd Chrome behaviour, use with caution:
+      // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
+      // Note: we rely on the high-level addTrack/dtmf shim to
+      // create the sender with a dtmf sender.
+      oldStream.addTrack(track);
+
+      // Trigger ONN async.
+      Promise.resolve().then(function () {
+        _this13.dispatchEvent(new Event('negotiationneeded'));
+      });
+    } else {
+      var newStream = new window.MediaStream([track]);
+      this._streams[stream.id] = newStream;
+      this._reverseStreams[newStream.id] = stream;
+      this.addStream(newStream);
+    }
+    return this.getSenders().find(function (s) {
+      return s.track === track;
+    });
+  };
+
+  // replace the internal stream id with the external one and
+  // vice versa.
+  function replaceInternalStreamId(pc, description) {
+    var sdp = description.sdp;
+    Object.keys(pc._reverseStreams || []).forEach(function (internalId) {
+      var externalStream = pc._reverseStreams[internalId];
+      var internalStream = pc._streams[externalStream.id];
+      sdp = sdp.replace(new RegExp(internalStream.id, 'g'), externalStream.id);
+    });
+    return new RTCSessionDescription({
+      type: description.type,
+      sdp: sdp
+    });
+  }
+  function replaceExternalStreamId(pc, description) {
+    var sdp = description.sdp;
+    Object.keys(pc._reverseStreams || []).forEach(function (internalId) {
+      var externalStream = pc._reverseStreams[internalId];
+      var internalStream = pc._streams[externalStream.id];
+      sdp = sdp.replace(new RegExp(externalStream.id, 'g'), internalStream.id);
+    });
+    return new RTCSessionDescription({
+      type: description.type,
+      sdp: sdp
+    });
+  }
+  ['createOffer', 'createAnswer'].forEach(function (method) {
+    var nativeMethod = window.RTCPeerConnection.prototype[method];
+    var methodObj = _defineProperty({}, method, function () {
+      var _this14 = this;
+
+      var args = arguments;
+      var isLegacyCall = arguments.length && typeof arguments[0] === 'function';
+      if (isLegacyCall) {
+        return nativeMethod.apply(this, [function (description) {
+          var desc = replaceInternalStreamId(_this14, description);
+          args[0].apply(null, [desc]);
+        }, function (err) {
+          if (args[1]) {
+            args[1].apply(null, err);
+          }
+        }, arguments[2]]);
+      }
+      return nativeMethod.apply(this, arguments).then(function (description) {
+        return replaceInternalStreamId(_this14, description);
+      });
+    });
+    window.RTCPeerConnection.prototype[method] = methodObj[method];
+  });
+
+  var origSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription;
+  window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() {
+    if (!arguments.length || !arguments[0].type) {
+      return origSetLocalDescription.apply(this, arguments);
+    }
+    arguments[0] = replaceExternalStreamId(this, arguments[0]);
+    return origSetLocalDescription.apply(this, arguments);
+  };
+
+  // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier
+
+  var origLocalDescription = Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype, 'localDescription');
+  Object.defineProperty(window.RTCPeerConnection.prototype, 'localDescription', {
+    get: function get() {
+      var description = origLocalDescription.get.apply(this);
+      if (description.type === '') {
+        return description;
+      }
+      return replaceInternalStreamId(this, description);
+    }
+  });
+
+  window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) {
+    var _this15 = this;
+
+    if (this.signalingState === 'closed') {
+      throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError');
+    }
+    // We can not yet check for sender instanceof RTCRtpSender
+    // since we shim RTPSender. So we check if sender._pc is set.
+    if (!sender._pc) {
+      throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + 'does not implement interface RTCRtpSender.', 'TypeError');
+    }
+    var isLocal = sender._pc === this;
+    if (!isLocal) {
+      throw new DOMException('Sender was not created by this connection.', 'InvalidAccessError');
+    }
+
+    // Search for the native stream the senders track belongs to.
+    this._streams = this._streams || {};
+    var stream = void 0;
+    Object.keys(this._streams).forEach(function (streamid) {
+      var hasTrack = _this15._streams[streamid].getTracks().find(function (track) {
+        return sender.track === track;
+      });
+      if (hasTrack) {
+        stream = _this15._streams[streamid];
+      }
+    });
+
+    if (stream) {
+      if (stream.getTracks().length === 1) {
+        // if this is the last track of the stream, remove the stream. This
+        // takes care of any shimmed _senders.
+        this.removeStream(this._reverseStreams[stream.id]);
+      } else {
+        // relying on the same odd chrome behaviour as above.
+        stream.removeTrack(sender.track);
+      }
+      this.dispatchEvent(new Event('negotiationneeded'));
+    }
+  };
+}
+
+function shimPeerConnection(window) {
+  var browserDetails = utils.detectBrowser(window);
+
+  if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) {
+    // very basic support for old versions.
+    window.RTCPeerConnection = window.webkitRTCPeerConnection;
+  }
+  if (!window.RTCPeerConnection) {
+    return;
+  }
+
+  var addIceCandidateNullSupported = window.RTCPeerConnection.prototype.addIceCandidate.length === 0;
+
+  // shim implicit creation of RTCSessionDescription/RTCIceCandidate
+  if (browserDetails.version < 53) {
+    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) {
+      var nativeMethod = window.RTCPeerConnection.prototype[method];
+      var methodObj = _defineProperty({}, method, function () {
+        arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]);
+        return nativeMethod.apply(this, arguments);
+      });
+      window.RTCPeerConnection.prototype[method] = methodObj[method];
+    });
+  }
+
+  // support for addIceCandidate(null or undefined)
+  var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate;
+  window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() {
+    if (!addIceCandidateNullSupported && !arguments[0]) {
+      if (arguments[1]) {
+        arguments[1].apply(null);
+      }
+      return Promise.resolve();
+    }
+    // Firefox 68+ emits and processes {candidate: "", ...}, ignore
+    // in older versions. Native support planned for Chrome M77.
+    if (browserDetails.version < 78 && arguments[0] && arguments[0].candidate === '') {
+      return Promise.resolve();
+    }
+    return nativeAddIceCandidate.apply(this, arguments);
+  };
+}
+
+// Attempt to fix ONN in plan-b mode.
+function fixNegotiationNeeded(window) {
+  var browserDetails = utils.detectBrowser(window);
+  utils.wrapPeerConnectionEvent(window, 'negotiationneeded', function (e) {
+    var pc = e.target;
+    if (browserDetails.version < 72 || pc.getConfiguration && pc.getConfiguration().sdpSemantics === 'plan-b') {
+      if (pc.signalingState !== 'stable') {
+        return;
+      }
+    }
+    return e;
+  });
+}
+
+},{"../utils.js":15,"./getdisplaymedia":4,"./getusermedia":5}],4:[function(require,module,exports){
+/*
+ *  Copyright (c) 2018 The adapter.js project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.shimGetDisplayMedia = shimGetDisplayMedia;
+function shimGetDisplayMedia(window, getSourceId) {
+  if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) {
+    return;
+  }
+  if (!window.navigator.mediaDevices) {
+    return;
+  }
+  // getSourceId is a function that returns a promise resolving with
+  // the sourceId of the screen/window/tab to be shared.
+  if (typeof getSourceId !== 'function') {
+    console.error('shimGetDisplayMedia: getSourceId argument is not ' + 'a function');
+    return;
+  }
+  window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) {
+    return getSourceId(constraints).then(function (sourceId) {
+      var widthSpecified = constraints.video && constraints.video.width;
+      var heightSpecified = constraints.video && constraints.video.height;
+      var frameRateSpecified = constraints.video && constraints.video.frameRate;
+      constraints.video = {
+        mandatory: {
+          chromeMediaSource: 'desktop',
+          chromeMediaSourceId: sourceId,
+          maxFrameRate: frameRateSpecified || 3
+        }
+      };
+      if (widthSpecified) {
+        constraints.video.mandatory.maxWidth = widthSpecified;
+      }
+      if (heightSpecified) {
+        constraints.video.mandatory.maxHeight = heightSpecified;
+      }
+      return window.navigator.mediaDevices.getUserMedia(constraints);
+    });
+  };
+}
+
+},{}],5:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+exports.shimGetUserMedia = shimGetUserMedia;
+
+var _utils = require('../utils.js');
+
+var utils = _interopRequireWildcard(_utils);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+var logging = utils.log;
+
+function shimGetUserMedia(window) {
+  var navigator = window && window.navigator;
+
+  if (!navigator.mediaDevices) {
+    return;
+  }
+
+  var browserDetails = utils.detectBrowser(window);
+
+  var constraintsToChrome_ = function constraintsToChrome_(c) {
+    if ((typeof c === 'undefined' ? 'undefined' : _typeof(c)) !== 'object' || c.mandatory || c.optional) {
+      return c;
+    }
+    var cc = {};
+    Object.keys(c).forEach(function (key) {
+      if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
+        return;
+      }
+      var r = _typeof(c[key]) === 'object' ? c[key] : { ideal: c[key] };
+      if (r.exact !== undefined && typeof r.exact === 'number') {
+        r.min = r.max = r.exact;
+      }
+      var oldname_ = function oldname_(prefix, name) {
+        if (prefix) {
+          return prefix + name.charAt(0).toUpperCase() + name.slice(1);
+        }
+        return name === 'deviceId' ? 'sourceId' : name;
+      };
+      if (r.ideal !== undefined) {
+        cc.optional = cc.optional || [];
+        var oc = {};
+        if (typeof r.ideal === 'number') {
+          oc[oldname_('min', key)] = r.ideal;
+          cc.optional.push(oc);
+          oc = {};
+          oc[oldname_('max', key)] = r.ideal;
+          cc.optional.push(oc);
+        } else {
+          oc[oldname_('', key)] = r.ideal;
+          cc.optional.push(oc);
+        }
+      }
+      if (r.exact !== undefined && typeof r.exact !== 'number') {
+        cc.mandatory = cc.mandatory || {};
+        cc.mandatory[oldname_('', key)] = r.exact;
+      } else {
+        ['min', 'max'].forEach(function (mix) {
+          if (r[mix] !== undefined) {
+            cc.mandatory = cc.mandatory || {};
+            cc.mandatory[oldname_(mix, key)] = r[mix];
+          }
+        });
+      }
+    });
+    if (c.advanced) {
+      cc.optional = (cc.optional || []).concat(c.advanced);
+    }
+    return cc;
+  };
+
+  var shimConstraints_ = function shimConstraints_(constraints, func) {
+    if (browserDetails.version >= 61) {
+      return func(constraints);
+    }
+    constraints = JSON.parse(JSON.stringify(constraints));
+    if (constraints && _typeof(constraints.audio) === 'object') {
+      var remap = function remap(obj, a, b) {
+        if (a in obj && !(b in obj)) {
+          obj[b] = obj[a];
+          delete obj[a];
+        }
+      };
+      constraints = JSON.parse(JSON.stringify(constraints));
+      remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');
+      remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');
+      constraints.audio = constraintsToChrome_(constraints.audio);
+    }
+    if (constraints && _typeof(constraints.video) === 'object') {
+      // Shim facingMode for mobile & surface pro.
+      var face = constraints.video.facingMode;
+      face = face && ((typeof face === 'undefined' ? 'undefined' : _typeof(face)) === 'object' ? face : { ideal: face });
+      var getSupportedFacingModeLies = browserDetails.version < 66;
+
+      if (face && (face.exact === 'user' || face.exact === 'environment' || face.ideal === 'user' || face.ideal === 'environment') && !(navigator.mediaDevices.getSupportedConstraints && navigator.mediaDevices.getSupportedConstraints().facingMode && !getSupportedFacingModeLies)) {
+        delete constraints.video.facingMode;
+        var matches = void 0;
+        if (face.exact === 'environment' || face.ideal === 'environment') {
+          matches = ['back', 'rear'];
+        } else if (face.exact === 'user' || face.ideal === 'user') {
+          matches = ['front'];
+        }
+        if (matches) {
+          // Look for matches in label, or use last cam for back (typical).
+          return navigator.mediaDevices.enumerateDevices().then(function (devices) {
+            devices = devices.filter(function (d) {
+              return d.kind === 'videoinput';
+            });
+            var dev = devices.find(function (d) {
+              return matches.some(function (match) {
+                return d.label.toLowerCase().includes(match);
+              });
+            });
+            if (!dev && devices.length && matches.includes('back')) {
+              dev = devices[devices.length - 1]; // more likely the back cam
+            }
+            if (dev) {
+              constraints.video.deviceId = face.exact ? { exact: dev.deviceId } : { ideal: dev.deviceId };
+            }
+            constraints.video = constraintsToChrome_(constraints.video);
+            logging('chrome: ' + JSON.stringify(constraints));
+            return func(constraints);
+          });
+        }
+      }
+      constraints.video = constraintsToChrome_(constraints.video);
+    }
+    logging('chrome: ' + JSON.stringify(constraints));
+    return func(constraints);
+  };
+
+  var shimError_ = function shimError_(e) {
+    if (browserDetails.version >= 64) {
+      return e;
+    }
+    return {
+      name: {
+        PermissionDeniedError: 'NotAllowedError',
+        PermissionDismissedError: 'NotAllowedError',
+        InvalidStateError: 'NotAllowedError',
+        DevicesNotFoundError: 'NotFoundError',
+        ConstraintNotSatisfiedError: 'OverconstrainedError',
+        TrackStartError: 'NotReadableError',
+        MediaDeviceFailedDueToShutdown: 'NotAllowedError',
+        MediaDeviceKillSwitchOn: 'NotAllowedError',
+        TabCaptureError: 'AbortError',
+        ScreenCaptureError: 'AbortError',
+        DeviceCaptureError: 'AbortError'
+      }[e.name] || e.name,
+      message: e.message,
+      constraint: e.constraint || e.constraintName,
+      toString: function toString() {
+        return this.name + (this.message && ': ') + this.message;
+      }
+    };
+  };
+
+  var getUserMedia_ = function getUserMedia_(constraints, onSuccess, onError) {
+    shimConstraints_(constraints, function (c) {
+      navigator.webkitGetUserMedia(c, onSuccess, function (e) {
+        if (onError) {
+          onError(shimError_(e));
+        }
+      });
+    });
+  };
+  navigator.getUserMedia = getUserMedia_.bind(navigator);
+
+  // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
+  // function which returns a Promise, it does not accept spec-style
+  // constraints.
+  if (navigator.mediaDevices.getUserMedia) {
+    var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
+    navigator.mediaDevices.getUserMedia = function (cs) {
+      return shimConstraints_(cs, function (c) {
+        return origGetUserMedia(c).then(function (stream) {
+          if (c.audio && !stream.getAudioTracks().length || c.video && !stream.getVideoTracks().length) {
+            stream.getTracks().forEach(function (track) {
+              track.stop();
+            });
+            throw new DOMException('', 'NotFoundError');
+          }
+          return stream;
+        }, function (e) {
+          return Promise.reject(shimError_(e));
+        });
+      });
+    };
+  }
+}
+
+},{"../utils.js":15}],6:[function(require,module,exports){
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+exports.shimRTCIceCandidate = shimRTCIceCandidate;
+exports.shimMaxMessageSize = shimMaxMessageSize;
+exports.shimSendThrowTypeError = shimSendThrowTypeError;
+exports.shimConnectionState = shimConnectionState;
+exports.removeAllowExtmapMixed = removeAllowExtmapMixed;
+
+var _sdp = require('sdp');
+
+var _sdp2 = _interopRequireDefault(_sdp);
+
+var _utils = require('./utils');
+
+var utils = _interopRequireWildcard(_utils);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function shimRTCIceCandidate(window) {
+  // foundation is arbitrarily chosen as an indicator for full support for
+  // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
+  if (!window.RTCIceCandidate || window.RTCIceCandidate && 'foundation' in window.RTCIceCandidate.prototype) {
+    return;
+  }
+
+  var NativeRTCIceCandidate = window.RTCIceCandidate;
+  window.RTCIceCandidate = function RTCIceCandidate(args) {
+    // Remove the a= which shouldn't be part of the candidate string.
+    if ((typeof args === 'undefined' ? 'undefined' : _typeof(args)) === 'object' && args.candidate && args.candidate.indexOf('a=') === 0) {
+      args = JSON.parse(JSON.stringify(args));
+      args.candidate = args.candidate.substr(2);
+    }
+
+    if (args.candidate && args.candidate.length) {
+      // Augment the native candidate with the parsed fields.
+      var nativeCandidate = new NativeRTCIceCandidate(args);
+      var parsedCandidate = _sdp2.default.parseCandidate(args.candidate);
+      var augmentedCandidate = Object.assign(nativeCandidate, parsedCandidate);
+
+      // Add a serializer that does not serialize the extra attributes.
+      augmentedCandidate.toJSON = function toJSON() {
+        return {
+          candidate: augmentedCandidate.candidate,
+          sdpMid: augmentedCandidate.sdpMid,
+          sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
+          usernameFragment: augmentedCandidate.usernameFragment
+        };
+      };
+      return augmentedCandidate;
+    }
+    return new NativeRTCIceCandidate(args);
+  };
+  window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype;
+
+  // Hook up the augmented candidate in onicecandidate and
+  // addEventListener('icecandidate', ...)
+  utils.wrapPeerConnectionEvent(window, 'icecandidate', function (e) {
+    if (e.candidate) {
+      Object.defineProperty(e, 'candidate', {
+        value: new window.RTCIceCandidate(e.candidate),
+        writable: 'false'
+      });
+    }
+    return e;
+  });
+}
+
+function shimMaxMessageSize(window) {
+  if (!window.RTCPeerConnection) {
+    return;
+  }
+  var browserDetails = utils.detectBrowser(window);
+
+  if (!('sctp' in window.RTCPeerConnection.prototype)) {
+    Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {
+      get: function get() {
+        return typeof this._sctp === 'undefined' ? null : this._sctp;
+      }
+    });
+  }
+
+  var sctpInDescription = function sctpInDescription(description) {
+    if (!description || !description.sdp) {
+      return false;
+    }
+    var sections = _sdp2.default.splitSections(description.sdp);
+    sections.shift();
+    return sections.some(function (mediaSection) {
+      var mLine = _sdp2.default.parseMLine(mediaSection);
+      return mLine && mLine.kind === 'application' && mLine.protocol.indexOf('SCTP') !== -1;
+    });
+  };
+
+  var getRemoteFirefoxVersion = function getRemoteFirefoxVersion(description) {
+    // TODO: Is there a better solution for detecting Firefox?
+    var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);
+    if (match === null || match.length < 2) {
+      return -1;
+    }
+    var version = parseInt(match[1], 10);
+    // Test for NaN (yes, this is ugly)
+    return version !== version ? -1 : version;
+  };
+
+  var getCanSendMaxMessageSize = function getCanSendMaxMessageSize(remoteIsFirefox) {
+    // Every implementation we know can send at least 64 KiB.
+    // Note: Although Chrome is technically able to send up to 256 KiB, the
+    //       data does not reach the other peer reliably.
+    //       See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
+    var canSendMaxMessageSize = 65536;
+    if (browserDetails.browser === 'firefox') {
+      if (browserDetails.version < 57) {
+        if (remoteIsFirefox === -1) {
+          // FF < 57 will send in 16 KiB chunks using the deprecated PPID
+          // fragmentation.
+          canSendMaxMessageSize = 16384;
+        } else {
+          // However, other FF (and RAWRTC) can reassemble PPID-fragmented
+          // messages. Thus, supporting ~2 GiB when sending.
+          canSendMaxMessageSize = 2147483637;
+        }
+      } else if (browserDetails.version < 60) {
+        // Currently, all FF >= 57 will reset the remote maximum message size
+        // to the default value when a data channel is created at a later
+        // stage. :(
+        // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
+        canSendMaxMessageSize = browserDetails.version === 57 ? 65535 : 65536;
+      } else {
+        // FF >= 60 supports sending ~2 GiB
+        canSendMaxMessageSize = 2147483637;
+      }
+    }
+    return canSendMaxMessageSize;
+  };
+
+  var getMaxMessageSize = function getMaxMessageSize(description, remoteIsFirefox) {
+    // Note: 65536 bytes is the default value from the SDP spec. Also,
+    //       every implementation we know supports receiving 65536 bytes.
+    var maxMessageSize = 65536;
+
+    // FF 57 has a slightly incorrect default remote max message size, so
+    // we need to adjust it here to avoid a failure when sending.
+    // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
+    if (browserDetails.browser === 'firefox' && browserDetails.version === 57) {
+      maxMessageSize = 65535;
+    }
+
+    var match = _sdp2.default.matchPrefix(description.sdp, 'a=max-message-size:');
+    if (match.length > 0) {
+      maxMessageSize = parseInt(match[0].substr(19), 10);
+    } else if (browserDetails.browser === 'firefox' && remoteIsFirefox !== -1) {
+      // If the maximum message size is not present in the remote SDP and
+      // both local and remote are Firefox, the remote peer can receive
+      // ~2 GiB.
+      maxMessageSize = 2147483637;
+    }
+    return maxMessageSize;
+  };
+
+  var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription;
+  window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() {
+    this._sctp = null;
+    // Chrome decided to not expose .sctp in plan-b mode.
+    // As usual, adapter.js has to do an 'ugly worakaround'
+    // to cover up the mess.
+    if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) {
+      var _getConfiguration = this.getConfiguration(),
+          sdpSemantics = _getConfiguration.sdpSemantics;
+
+      if (sdpSemantics === 'plan-b') {
+        Object.defineProperty(this, 'sctp', {
+          get: function get() {
+            return typeof this._sctp === 'undefined' ? null : this._sctp;
+          },
+
+          enumerable: true,
+          configurable: true
+        });
+      }
+    }
+
+    if (sctpInDescription(arguments[0])) {
+      // Check if the remote is FF.
+      var isFirefox = getRemoteFirefoxVersion(arguments[0]);
+
+      // Get the maximum message size the local peer is capable of sending
+      var canSendMMS = getCanSendMaxMessageSize(isFirefox);
+
+      // Get the maximum message size of the remote peer.
+      var remoteMMS = getMaxMessageSize(arguments[0], isFirefox);
+
+      // Determine final maximum message size
+      var maxMessageSize = void 0;
+      if (canSendMMS === 0 && remoteMMS === 0) {
+        maxMessageSize = Number.POSITIVE_INFINITY;
+      } else if (canSendMMS === 0 || remoteMMS === 0) {
+        maxMessageSize = Math.max(canSendMMS, remoteMMS);
+      } else {
+        maxMessageSize = Math.min(canSendMMS, remoteMMS);
+      }
+
+      // Create a dummy RTCSctpTransport object and the 'maxMessageSize'
+      // attribute.
+      var sctp = {};
+      Object.defineProperty(sctp, 'maxMessageSize', {
+        get: function get() {
+          return maxMessageSize;
+        }
+      });
+      this._sctp = sctp;
+    }
+
+    return origSetRemoteDescription.apply(this, arguments);
+  };
+}
+
+function shimSendThrowTypeError(window) {
+  if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) {
+    return;
+  }
+
+  // Note: Although Firefox >= 57 has a native implementation, the maximum
+  //       message size can be reset for all data channels at a later stage.
+  //       See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
+
+  function wrapDcSend(dc, pc) {
+    var origDataChannelSend = dc.send;
+    dc.send = function send() {
+      var data = arguments[0];
+      var length = data.length || data.size || data.byteLength;
+      if (dc.readyState === 'open' && pc.sctp && length > pc.sctp.maxMessageSize) {
+        throw new TypeError('Message too large (can send a maximum of ' + pc.sctp.maxMessageSize + ' bytes)');
+      }
+      return origDataChannelSend.apply(dc, arguments);
+    };
+  }
+  var origCreateDataChannel = window.RTCPeerConnection.prototype.createDataChannel;
+  window.RTCPeerConnection.prototype.createDataChannel = function createDataChannel() {
+    var dataChannel = origCreateDataChannel.apply(this, arguments);
+    wrapDcSend(dataChannel, this);
+    return dataChannel;
+  };
+  utils.wrapPeerConnectionEvent(window, 'datachannel', function (e) {
+    wrapDcSend(e.channel, e.target);
+    return e;
+  });
+}
+
+/* shims RTCConnectionState by pretending it is the same as iceConnectionState.
+ * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12
+ * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect
+ * since DTLS failures would be hidden. See
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827
+ * for the Firefox tracking bug.
+ */
+function shimConnectionState(window) {
+  if (!window.RTCPeerConnection || 'connectionState' in window.RTCPeerConnection.prototype) {
+    return;
+  }
+  var proto = window.RTCPeerConnection.prototype;
+  Object.defineProperty(proto, 'connectionState', {
+    get: function get() {
+      return {
+        completed: 'connected',
+        checking: 'connecting'
+      }[this.iceConnectionState] || this.iceConnectionState;
+    },
+
+    enumerable: true,
+    configurable: true
+  });
+  Object.defineProperty(proto, 'onconnectionstatechange', {
+    get: function get() {
+      return this._onconnectionstatechange || null;
+    },
+    set: function set(cb) {
+      if (this._onconnectionstatechange) {
+        this.removeEventListener('connectionstatechange', this._onconnectionstatechange);
+        delete this._onconnectionstatechange;
+      }
+      if (cb) {
+        this.addEventListener('connectionstatechange', this._onconnectionstatechange = cb);
+      }
+    },
+
+    enumerable: true,
+    configurable: true
+  });
+
+  ['setLocalDescription', 'setRemoteDescription'].forEach(function (method) {
+    var origMethod = proto[method];
+    proto[method] = function () {
+      if (!this._connectionstatechangepoly) {
+        this._connectionstatechangepoly = function (e) {
+          var pc = e.target;
+          if (pc._lastConnectionState !== pc.connectionState) {
+            pc._lastConnectionState = pc.connectionState;
+            var newEvent = new Event('connectionstatechange', e);
+            pc.dispatchEvent(newEvent);
+          }
+          return e;
+        };
+        this.addEventListener('iceconnectionstatechange', this._connectionstatechangepoly);
+      }
+      return origMethod.apply(this, arguments);
+    };
+  });
+}
+
+function removeAllowExtmapMixed(window) {
+  /* remove a=extmap-allow-mixed for webrtc.org < M71 */
+  if (!window.RTCPeerConnection) {
+    return;
+  }
+  var browserDetails = utils.detectBrowser(window);
+  if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) {
+    return;
+  }
+  if (browserDetails.browser === 'safari' && browserDetails.version >= 605) {
+    return;
+  }
+  var nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription;
+  window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription(desc) {
+    if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) {
+      desc.sdp = desc.sdp.split('\n').filter(function (line) {
+        return line.trim() !== 'a=extmap-allow-mixed';
+      }).join('\n');
+    }
+    return nativeSRD.apply(this, arguments);
+  };
+}
+
+},{"./utils":15,"sdp":17}],7:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined;
+
+var _getusermedia = require('./getusermedia');
+
+Object.defineProperty(exports, 'shimGetUserMedia', {
+  enumerable: true,
+  get: function get() {
+    return _getusermedia.shimGetUserMedia;
+  }
+});
+
+var _getdisplaymedia = require('./getdisplaymedia');
+
+Object.defineProperty(exports, 'shimGetDisplayMedia', {
+  enumerable: true,
+  get: function get() {
+    return _getdisplaymedia.shimGetDisplayMedia;
+  }
+});
+exports.shimPeerConnection = shimPeerConnection;
+exports.shimReplaceTrack = shimReplaceTrack;
+
+var _utils = require('../utils');
+
+var utils = _interopRequireWildcard(_utils);
+
+var _filtericeservers = require('./filtericeservers');
+
+var _rtcpeerconnectionShim = require('rtcpeerconnection-shim');
+
+var _rtcpeerconnectionShim2 = _interopRequireDefault(_rtcpeerconnectionShim);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+function shimPeerConnection(window) {
+  var browserDetails = utils.detectBrowser(window);
+
+  if (window.RTCIceGatherer) {
+    if (!window.RTCIceCandidate) {
+      window.RTCIceCandidate = function RTCIceCandidate(args) {
+        return args;
+      };
+    }
+    if (!window.RTCSessionDescription) {
+      window.RTCSessionDescription = function RTCSessionDescription(args) {
+        return args;
+      };
+    }
+    // this adds an additional event listener to MediaStrackTrack that signals
+    // when a tracks enabled property was changed. Workaround for a bug in
+    // addStream, see below. No longer required in 15025+
+    if (browserDetails.version < 15025) {
+      var origMSTEnabled = Object.getOwnPropertyDescriptor(window.MediaStreamTrack.prototype, 'enabled');
+      Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', {
+        set: function set(value) {
+          origMSTEnabled.set.call(this, value);
+          var ev = new Event('enabled');
+          ev.enabled = value;
+          this.dispatchEvent(ev);
+        }
+      });
+    }
+  }
+
+  // ORTC defines the DTMF sender a bit different.
+  // https://github.com/w3c/ortc/issues/714
+  if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) {
+    Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
+      get: function get() {
+        if (this._dtmf === undefined) {
+          if (this.track.kind === 'audio') {
+            this._dtmf = new window.RTCDtmfSender(this);
+          } else if (this.track.kind === 'video') {
+            this._dtmf = null;
+          }
+        }
+        return this._dtmf;
+      }
+    });
+  }
+  // Edge currently only implements the RTCDtmfSender, not the
+  // RTCDTMFSender alias. See http://draft.ortc.org/#rtcdtmfsender2*
+  if (window.RTCDtmfSender && !window.RTCDTMFSender) {
+    window.RTCDTMFSender = window.RTCDtmfSender;
+  }
+
+  var RTCPeerConnectionShim = (0, _rtcpeerconnectionShim2.default)(window, browserDetails.version);
+  window.RTCPeerConnection = function RTCPeerConnection(config) {
+    if (config && config.iceServers) {
+      config.iceServers = (0, _filtericeservers.filterIceServers)(config.iceServers, browserDetails.version);
+      utils.log('ICE servers after filtering:', config.iceServers);
+    }
+    return new RTCPeerConnectionShim(config);
+  };
+  window.RTCPeerConnection.prototype = RTCPeerConnectionShim.prototype;
+}
+
+function shimReplaceTrack(window) {
+  // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
+  if (window.RTCRtpSender && !('replaceTrack' in window.RTCRtpSender.prototype)) {
+    window.RTCRtpSender.prototype.replaceTrack = window.RTCRtpSender.prototype.setTrack;
+  }
+}
+
+},{"../utils":15,"./filtericeservers":8,"./getdisplaymedia":9,"./getusermedia":10,"rtcpeerconnection-shim":16}],8:[function(require,module,exports){
+/*
+ *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.filterIceServers = filterIceServers;
+
+var _utils = require('../utils');
+
+var utils = _interopRequireWildcard(_utils);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+// Edge does not like
+// 1) stun: filtered after 14393 unless ?transport=udp is present
+// 2) turn: that does not have all of turn:host:port?transport=udp
+// 3) turn: with ipv6 addresses
+// 4) turn: occurring muliple times
+function filterIceServers(iceServers, edgeVersion) {
+  var hasTurn = false;
+  iceServers = JSON.parse(JSON.stringify(iceServers));
+  return iceServers.filter(function (server) {
+    if (server && (server.urls || server.url)) {
+      var urls = server.urls || server.url;
+      if (server.url && !server.urls) {
+        utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
+      }
+      var isString = typeof urls === 'string';
+      if (isString) {
+        urls = [urls];
+      }
+      urls = urls.filter(function (url) {
+        // filter STUN unconditionally.
+        if (url.indexOf('stun:') === 0) {
+          return false;
+        }
+
+        var validTurn = url.startsWith('turn') && !url.startsWith('turn:[') && url.includes('transport=udp');
+        if (validTurn && !hasTurn) {
+          hasTurn = true;
+          return true;
+        }
+        return validTurn && !hasTurn;
+      });
+
+      delete server.url;
+      server.urls = isString ? urls[0] : urls;
+      return !!urls.length;
+    }
+  });
+}
+
+},{"../utils":15}],9:[function(require,module,exports){
+/*
+ *  Copyright (c) 2018 The adapter.js project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.shimGetDisplayMedia = shimGetDisplayMedia;
+function shimGetDisplayMedia(window) {
+  if (!('getDisplayMedia' in window.navigator)) {
+    return;
+  }
+  if (!window.navigator.mediaDevices) {
+    return;
+  }
+  if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) {
+    return;
+  }
+  window.navigator.mediaDevices.getDisplayMedia = window.navigator.getDisplayMedia.bind(window.navigator);
+}
+
+},{}],10:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.shimGetUserMedia = shimGetUserMedia;
+function shimGetUserMedia(window) {
+  var navigator = window && window.navigator;
+
+  var shimError_ = function shimError_(e) {
+    return {
+      name: { PermissionDeniedError: 'NotAllowedError' }[e.name] || e.name,
+      message: e.message,
+      constraint: e.constraint,
+      toString: function toString() {
+        return this.name;
+      }
+    };
+  };
+
+  // getUserMedia error shim.
+  var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
+  navigator.mediaDevices.getUserMedia = function (c) {
+    return origGetUserMedia(c).catch(function (e) {
+      return Promise.reject(shimError_(e));
+    });
+  };
+}
+
+},{}],11:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined;
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+var _getusermedia = require('./getusermedia');
+
+Object.defineProperty(exports, 'shimGetUserMedia', {
+  enumerable: true,
+  get: function get() {
+    return _getusermedia.shimGetUserMedia;
+  }
+});
+
+var _getdisplaymedia = require('./getdisplaymedia');
+
+Object.defineProperty(exports, 'shimGetDisplayMedia', {
+  enumerable: true,
+  get: function get() {
+    return _getdisplaymedia.shimGetDisplayMedia;
+  }
+});
+exports.shimOnTrack = shimOnTrack;
+exports.shimPeerConnection = shimPeerConnection;
+exports.shimSenderGetStats = shimSenderGetStats;
+exports.shimReceiverGetStats = shimReceiverGetStats;
+exports.shimRemoveStream = shimRemoveStream;
+exports.shimRTCDataChannel = shimRTCDataChannel;
+exports.shimAddTransceiver = shimAddTransceiver;
+exports.shimGetParameters = shimGetParameters;
+exports.shimCreateOffer = shimCreateOffer;
+exports.shimCreateAnswer = shimCreateAnswer;
+
+var _utils = require('../utils');
+
+var utils = _interopRequireWildcard(_utils);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+function shimOnTrack(window) {
+  if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) {
+    Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
+      get: function get() {
+        return { receiver: this.receiver };
+      }
+    });
+  }
+}
+
+function shimPeerConnection(window) {
+  var browserDetails = utils.detectBrowser(window);
+
+  if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) {
+    return; // probably media.peerconnection.enabled=false in about:config
+  }
+  if (!window.RTCPeerConnection && window.mozRTCPeerConnection) {
+    // very basic support for old versions.
+    window.RTCPeerConnection = window.mozRTCPeerConnection;
+  }
+
+  if (browserDetails.version < 53) {
+    // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
+    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) {
+      var nativeMethod = window.RTCPeerConnection.prototype[method];
+      var methodObj = _defineProperty({}, method, function () {
+        arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]);
+        return nativeMethod.apply(this, arguments);
+      });
+      window.RTCPeerConnection.prototype[method] = methodObj[method];
+    });
+  }
+
+  // support for addIceCandidate(null or undefined)
+  // as well as ignoring {sdpMid, candidate: ""}
+  if (browserDetails.version < 68) {
+    var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate;
+    window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() {
+      if (!arguments[0]) {
+        if (arguments[1]) {
+          arguments[1].apply(null);
+        }
+        return Promise.resolve();
+      }
+      // Firefox 68+ emits and processes {candidate: "", ...}, ignore
+      // in older versions.
+      if (arguments[0] && arguments[0].candidate === '') {
+        return Promise.resolve();
+      }
+      return nativeAddIceCandidate.apply(this, arguments);
+    };
+  }
+
+  var modernStatsTypes = {
+    inboundrtp: 'inbound-rtp',
+    outboundrtp: 'outbound-rtp',
+    candidatepair: 'candidate-pair',
+    localcandidate: 'local-candidate',
+    remotecandidate: 'remote-candidate'
+  };
+
+  var nativeGetStats = window.RTCPeerConnection.prototype.getStats;
+  window.RTCPeerConnection.prototype.getStats = function getStats() {
+    var _arguments = Array.prototype.slice.call(arguments),
+        selector = _arguments[0],
+        onSucc = _arguments[1],
+        onErr = _arguments[2];
+
+    return nativeGetStats.apply(this, [selector || null]).then(function (stats) {
+      if (browserDetails.version < 53 && !onSucc) {
+        // Shim only promise getStats with spec-hyphens in type names
+        // Leave callback version alone; misc old uses of forEach before Map
+        try {
+          stats.forEach(function (stat) {
+            stat.type = modernStatsTypes[stat.type] || stat.type;
+          });
+        } catch (e) {
+          if (e.name !== 'TypeError') {
+            throw e;
+          }
+          // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
+          stats.forEach(function (stat, i) {
+            stats.set(i, Object.assign({}, stat, {
+              type: modernStatsTypes[stat.type] || stat.type
+            }));
+          });
+        }
+      }
+      return stats;
+    }).then(onSucc, onErr);
+  };
+}
+
+function shimSenderGetStats(window) {
+  if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) {
+    return;
+  }
+  if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) {
+    return;
+  }
+  var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
+  if (origGetSenders) {
+    window.RTCPeerConnection.prototype.getSenders = function getSenders() {
+      var _this = this;
+
+      var senders = origGetSenders.apply(this, []);
+      senders.forEach(function (sender) {
+        return sender._pc = _this;
+      });
+      return senders;
+    };
+  }
+
+  var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
+  if (origAddTrack) {
+    window.RTCPeerConnection.prototype.addTrack = function addTrack() {
+      var sender = origAddTrack.apply(this, arguments);
+      sender._pc = this;
+      return sender;
+    };
+  }
+  window.RTCRtpSender.prototype.getStats = function getStats() {
+    return this.track ? this._pc.getStats(this.track) : Promise.resolve(new Map());
+  };
+}
+
+function shimReceiverGetStats(window) {
+  if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) {
+    return;
+  }
+  if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) {
+    return;
+  }
+  var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers;
+  if (origGetReceivers) {
+    window.RTCPeerConnection.prototype.getReceivers = function getReceivers() {
+      var _this2 = this;
+
+      var receivers = origGetReceivers.apply(this, []);
+      receivers.forEach(function (receiver) {
+        return receiver._pc = _this2;
+      });
+      return receivers;
+    };
+  }
+  utils.wrapPeerConnectionEvent(window, 'track', function (e) {
+    e.receiver._pc = e.srcElement;
+    return e;
+  });
+  window.RTCRtpReceiver.prototype.getStats = function getStats() {
+    return this._pc.getStats(this.track);
+  };
+}
+
+function shimRemoveStream(window) {
+  if (!window.RTCPeerConnection || 'removeStream' in window.RTCPeerConnection.prototype) {
+    return;
+  }
+  window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) {
+    var _this3 = this;
+
+    utils.deprecated('removeStream', 'removeTrack');
+    this.getSenders().forEach(function (sender) {
+      if (sender.track && stream.getTracks().includes(sender.track)) {
+        _this3.removeTrack(sender);
+      }
+    });
+  };
+}
+
+function shimRTCDataChannel(window) {
+  // rename DataChannel to RTCDataChannel (native fix in FF60):
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851
+  if (window.DataChannel && !window.RTCDataChannel) {
+    window.RTCDataChannel = window.DataChannel;
+  }
+}
+
+function shimAddTransceiver(window) {
+  // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
+  // Firefox ignores the init sendEncodings options passed to addTransceiver
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
+  if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) {
+    return;
+  }
+  var origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver;
+  if (origAddTransceiver) {
+    window.RTCPeerConnection.prototype.addTransceiver = function addTransceiver() {
+      this.setParametersPromises = [];
+      var initParameters = arguments[1];
+      var shouldPerformCheck = initParameters && 'sendEncodings' in initParameters;
+      if (shouldPerformCheck) {
+        // If sendEncodings params are provided, validate grammar
+        initParameters.sendEncodings.forEach(function (encodingParam) {
+          if ('rid' in encodingParam) {
+            var ridRegex = /^[a-z0-9]{0,16}$/i;
+            if (!ridRegex.test(encodingParam.rid)) {
+              throw new TypeError('Invalid RID value provided.');
+            }
+          }
+          if ('scaleResolutionDownBy' in encodingParam) {
+            if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) {
+              throw new RangeError('scale_resolution_down_by must be >= 1.0');
+            }
+          }
+          if ('maxFramerate' in encodingParam) {
+            if (!(parseFloat(encodingParam.maxFramerate) >= 0)) {
+              throw new RangeError('max_framerate must be >= 0.0');
+            }
+          }
+        });
+      }
+      var transceiver = origAddTransceiver.apply(this, arguments);
+      if (shouldPerformCheck) {
+        // Check if the init options were applied. If not we do this in an
+        // asynchronous way and save the promise reference in a global object.
+        // This is an ugly hack, but at the same time is way more robust than
+        // checking the sender parameters before and after the createOffer
+        // Also note that after the createoffer we are not 100% sure that
+        // the params were asynchronously applied so we might miss the
+        // opportunity to recreate offer.
+        var sender = transceiver.sender;
+
+        var params = sender.getParameters();
+        if (!('encodings' in params) ||
+        // Avoid being fooled by patched getParameters() below.
+        params.encodings.length === 1 && Object.keys(params.encodings[0]).length === 0) {
+          params.encodings = initParameters.sendEncodings;
+          sender.sendEncodings = initParameters.sendEncodings;
+          this.setParametersPromises.push(sender.setParameters(params).then(function () {
+            delete sender.sendEncodings;
+          }).catch(function () {
+            delete sender.sendEncodings;
+          }));
+        }
+      }
+      return transceiver;
+    };
+  }
+}
+
+function shimGetParameters(window) {
+  if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCRtpSender)) {
+    return;
+  }
+  var origGetParameters = window.RTCRtpSender.prototype.getParameters;
+  if (origGetParameters) {
+    window.RTCRtpSender.prototype.getParameters = function getParameters() {
+      var params = origGetParameters.apply(this, arguments);
+      if (!('encodings' in params)) {
+        params.encodings = [].concat(this.sendEncodings || [{}]);
+      }
+      return params;
+    };
+  }
+}
+
+function shimCreateOffer(window) {
+  // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
+  // Firefox ignores the init sendEncodings options passed to addTransceiver
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
+  if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) {
+    return;
+  }
+  var origCreateOffer = window.RTCPeerConnection.prototype.createOffer;
+  window.RTCPeerConnection.prototype.createOffer = function createOffer() {
+    var _this4 = this,
+        _arguments2 = arguments;
+
+    if (this.setParametersPromises && this.setParametersPromises.length) {
+      return Promise.all(this.setParametersPromises).then(function () {
+        return origCreateOffer.apply(_this4, _arguments2);
+      }).finally(function () {
+        _this4.setParametersPromises = [];
+      });
+    }
+    return origCreateOffer.apply(this, arguments);
+  };
+}
+
+function shimCreateAnswer(window) {
+  // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
+  // Firefox ignores the init sendEncodings options passed to addTransceiver
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
+  if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) {
+    return;
+  }
+  var origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer;
+  window.RTCPeerConnection.prototype.createAnswer = function createAnswer() {
+    var _this5 = this,
+        _arguments3 = arguments;
+
+    if (this.setParametersPromises && this.setParametersPromises.length) {
+      return Promise.all(this.setParametersPromises).then(function () {
+        return origCreateAnswer.apply(_this5, _arguments3);
+      }).finally(function () {
+        _this5.setParametersPromises = [];
+      });
+    }
+    return origCreateAnswer.apply(this, arguments);
+  };
+}
+
+},{"../utils":15,"./getdisplaymedia":12,"./getusermedia":13}],12:[function(require,module,exports){
+/*
+ *  Copyright (c) 2018 The adapter.js project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.shimGetDisplayMedia = shimGetDisplayMedia;
+function shimGetDisplayMedia(window, preferredMediaSource) {
+  if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) {
+    return;
+  }
+  if (!window.navigator.mediaDevices) {
+    return;
+  }
+  window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) {
+    if (!(constraints && constraints.video)) {
+      var err = new DOMException('getDisplayMedia without video ' + 'constraints is undefined');
+      err.name = 'NotFoundError';
+      // from https://heycam.github.io/webidl/#idl-DOMException-error-names
+      err.code = 8;
+      return Promise.reject(err);
+    }
+    if (constraints.video === true) {
+      constraints.video = { mediaSource: preferredMediaSource };
+    } else {
+      constraints.video.mediaSource = preferredMediaSource;
+    }
+    return window.navigator.mediaDevices.getUserMedia(constraints);
+  };
+}
+
+},{}],13:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+exports.shimGetUserMedia = shimGetUserMedia;
+
+var _utils = require('../utils');
+
+var utils = _interopRequireWildcard(_utils);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+function shimGetUserMedia(window) {
+  var browserDetails = utils.detectBrowser(window);
+  var navigator = window && window.navigator;
+  var MediaStreamTrack = window && window.MediaStreamTrack;
+
+  navigator.getUserMedia = function (constraints, onSuccess, onError) {
+    // Replace Firefox 44+'s deprecation warning with unprefixed version.
+    utils.deprecated('navigator.getUserMedia', 'navigator.mediaDevices.getUserMedia');
+    navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
+  };
+
+  if (!(browserDetails.version > 55 && 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) {
+    var remap = function remap(obj, a, b) {
+      if (a in obj && !(b in obj)) {
+        obj[b] = obj[a];
+        delete obj[a];
+      }
+    };
+
+    var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
+    navigator.mediaDevices.getUserMedia = function (c) {
+      if ((typeof c === 'undefined' ? 'undefined' : _typeof(c)) === 'object' && _typeof(c.audio) === 'object') {
+        c = JSON.parse(JSON.stringify(c));
+        remap(c.audio, 'autoGainControl', 'mozAutoGainControl');
+        remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');
+      }
+      return nativeGetUserMedia(c);
+    };
+
+    if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {
+      var nativeGetSettings = MediaStreamTrack.prototype.getSettings;
+      MediaStreamTrack.prototype.getSettings = function () {
+        var obj = nativeGetSettings.apply(this, arguments);
+        remap(obj, 'mozAutoGainControl', 'autoGainControl');
+        remap(obj, 'mozNoiseSuppression', 'noiseSuppression');
+        return obj;
+      };
+    }
+
+    if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) {
+      var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints;
+      MediaStreamTrack.prototype.applyConstraints = function (c) {
+        if (this.kind === 'audio' && (typeof c === 'undefined' ? 'undefined' : _typeof(c)) === 'object') {
+          c = JSON.parse(JSON.stringify(c));
+          remap(c, 'autoGainControl', 'mozAutoGainControl');
+          remap(c, 'noiseSuppression', 'mozNoiseSuppression');
+        }
+        return nativeApplyConstraints.apply(this, [c]);
+      };
+    }
+  }
+}
+
+},{"../utils":15}],14:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+exports.shimLocalStreamsAPI = shimLocalStreamsAPI;
+exports.shimRemoteStreamsAPI = shimRemoteStreamsAPI;
+exports.shimCallbacksAPI = shimCallbacksAPI;
+exports.shimGetUserMedia = shimGetUserMedia;
+exports.shimConstraints = shimConstraints;
+exports.shimRTCIceServerUrls = shimRTCIceServerUrls;
+exports.shimTrackEventTransceiver = shimTrackEventTransceiver;
+exports.shimCreateOfferLegacy = shimCreateOfferLegacy;
+exports.shimAudioContext = shimAudioContext;
+
+var _utils = require('../utils');
+
+var utils = _interopRequireWildcard(_utils);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+function shimLocalStreamsAPI(window) {
+  if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) {
+    return;
+  }
+  if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {
+    window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() {
+      if (!this._localStreams) {
+        this._localStreams = [];
+      }
+      return this._localStreams;
+    };
+  }
+  if (!('addStream' in window.RTCPeerConnection.prototype)) {
+    var _addTrack = window.RTCPeerConnection.prototype.addTrack;
+    window.RTCPeerConnection.prototype.addStream = function addStream(stream) {
+      var _this = this;
+
+      if (!this._localStreams) {
+        this._localStreams = [];
+      }
+      if (!this._localStreams.includes(stream)) {
+        this._localStreams.push(stream);
+      }
+      // Try to emulate Chrome's behaviour of adding in audio-video order.
+      // Safari orders by track id.
+      stream.getAudioTracks().forEach(function (track) {
+        return _addTrack.call(_this, track, stream);
+      });
+      stream.getVideoTracks().forEach(function (track) {
+        return _addTrack.call(_this, track, stream);
+      });
+    };
+
+    window.RTCPeerConnection.prototype.addTrack = function addTrack(track) {
+      var _this2 = this;
+
+      for (var _len = arguments.length, streams = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+        streams[_key - 1] = arguments[_key];
+      }
+
+      if (streams) {
+        streams.forEach(function (stream) {
+          if (!_this2._localStreams) {
+            _this2._localStreams = [stream];
+          } else if (!_this2._localStreams.includes(stream)) {
+            _this2._localStreams.push(stream);
+          }
+        });
+      }
+      return _addTrack.apply(this, arguments);
+    };
+  }
+  if (!('removeStream' in window.RTCPeerConnection.prototype)) {
+    window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) {
+      var _this3 = this;
+
+      if (!this._localStreams) {
+        this._localStreams = [];
+      }
+      var index = this._localStreams.indexOf(stream);
+      if (index === -1) {
+        return;
+      }
+      this._localStreams.splice(index, 1);
+      var tracks = stream.getTracks();
+      this.getSenders().forEach(function (sender) {
+        if (tracks.includes(sender.track)) {
+          _this3.removeTrack(sender);
+        }
+      });
+    };
+  }
+}
+
+function shimRemoteStreamsAPI(window) {
+  if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) {
+    return;
+  }
+  if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {
+    window.RTCPeerConnection.prototype.getRemoteStreams = function getRemoteStreams() {
+      return this._remoteStreams ? this._remoteStreams : [];
+    };
+  }
+  if (!('onaddstream' in window.RTCPeerConnection.prototype)) {
+    Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {
+      get: function get() {
+        return this._onaddstream;
+      },
+      set: function set(f) {
+        var _this4 = this;
+
+        if (this._onaddstream) {
+          this.removeEventListener('addstream', this._onaddstream);
+          this.removeEventListener('track', this._onaddstreampoly);
+        }
+        this.addEventListener('addstream', this._onaddstream = f);
+        this.addEventListener('track', this._onaddstreampoly = function (e) {
+          e.streams.forEach(function (stream) {
+            if (!_this4._remoteStreams) {
+              _this4._remoteStreams = [];
+            }
+            if (_this4._remoteStreams.includes(stream)) {
+              return;
+            }
+            _this4._remoteStreams.push(stream);
+            var event = new Event('addstream');
+            event.stream = stream;
+            _this4.dispatchEvent(event);
+          });
+        });
+      }
+    });
+    var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription;
+    window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() {
+      var pc = this;
+      if (!this._onaddstreampoly) {
+        this.addEventListener('track', this._onaddstreampoly = function (e) {
+          e.streams.forEach(function (stream) {
+            if (!pc._remoteStreams) {
+              pc._remoteStreams = [];
+            }
+            if (pc._remoteStreams.indexOf(stream) >= 0) {
+              return;
+            }
+            pc._remoteStreams.push(stream);
+            var event = new Event('addstream');
+            event.stream = stream;
+            pc.dispatchEvent(event);
+          });
+        });
+      }
+      return origSetRemoteDescription.apply(pc, arguments);
+    };
+  }
+}
+
+function shimCallbacksAPI(window) {
+  if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) {
+    return;
+  }
+  var prototype = window.RTCPeerConnection.prototype;
+  var origCreateOffer = prototype.createOffer;
+  var origCreateAnswer = prototype.createAnswer;
+  var setLocalDescription = prototype.setLocalDescription;
+  var setRemoteDescription = prototype.setRemoteDescription;
+  var addIceCandidate = prototype.addIceCandidate;
+
+  prototype.createOffer = function createOffer(successCallback, failureCallback) {
+    var options = arguments.length >= 2 ? arguments[2] : arguments[0];
+    var promise = origCreateOffer.apply(this, [options]);
+    if (!failureCallback) {
+      return promise;
+    }
+    promise.then(successCallback, failureCallback);
+    return Promise.resolve();
+  };
+
+  prototype.createAnswer = function createAnswer(successCallback, failureCallback) {
+    var options = arguments.length >= 2 ? arguments[2] : arguments[0];
+    var promise = origCreateAnswer.apply(this, [options]);
+    if (!failureCallback) {
+      return promise;
+    }
+    promise.then(successCallback, failureCallback);
+    return Promise.resolve();
+  };
+
+  var withCallback = function withCallback(description, successCallback, failureCallback) {
+    var promise = setLocalDescription.apply(this, [description]);
+    if (!failureCallback) {
+      return promise;
+    }
+    promise.then(successCallback, failureCallback);
+    return Promise.resolve();
+  };
+  prototype.setLocalDescription = withCallback;
+
+  withCallback = function withCallback(description, successCallback, failureCallback) {
+    var promise = setRemoteDescription.apply(this, [description]);
+    if (!failureCallback) {
+      return promise;
+    }
+    promise.then(successCallback, failureCallback);
+    return Promise.resolve();
+  };
+  prototype.setRemoteDescription = withCallback;
+
+  withCallback = function withCallback(candidate, successCallback, failureCallback) {
+    var promise = addIceCandidate.apply(this, [candidate]);
+    if (!failureCallback) {
+      return promise;
+    }
+    promise.then(successCallback, failureCallback);
+    return Promise.resolve();
+  };
+  prototype.addIceCandidate = withCallback;
+}
+
+function shimGetUserMedia(window) {
+  var navigator = window && window.navigator;
+
+  if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+    // shim not needed in Safari 12.1
+    var mediaDevices = navigator.mediaDevices;
+    var _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices);
+    navigator.mediaDevices.getUserMedia = function (constraints) {
+      return _getUserMedia(shimConstraints(constraints));
+    };
+  }
+
+  if (!navigator.getUserMedia && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+    navigator.getUserMedia = function getUserMedia(constraints, cb, errcb) {
+      navigator.mediaDevices.getUserMedia(constraints).then(cb, errcb);
+    }.bind(navigator);
+  }
+}
+
+function shimConstraints(constraints) {
+  if (constraints && constraints.video !== undefined) {
+    return Object.assign({}, constraints, { video: utils.compactObject(constraints.video) });
+  }
+
+  return constraints;
+}
+
+function shimRTCIceServerUrls(window) {
+  if (!window.RTCPeerConnection) {
+    return;
+  }
+  // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
+  var OrigPeerConnection = window.RTCPeerConnection;
+  window.RTCPeerConnection = function RTCPeerConnection(pcConfig, pcConstraints) {
+    if (pcConfig && pcConfig.iceServers) {
+      var newIceServers = [];
+      for (var i = 0; i < pcConfig.iceServers.length; i++) {
+        var server = pcConfig.iceServers[i];
+        if (!server.hasOwnProperty('urls') && server.hasOwnProperty('url')) {
+          utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
+          server = JSON.parse(JSON.stringify(server));
+          server.urls = server.url;
+          delete server.url;
+          newIceServers.push(server);
+        } else {
+          newIceServers.push(pcConfig.iceServers[i]);
+        }
+      }
+      pcConfig.iceServers = newIceServers;
+    }
+    return new OrigPeerConnection(pcConfig, pcConstraints);
+  };
+  window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
+  // wrap static methods. Currently just generateCertificate.
+  if ('generateCertificate' in OrigPeerConnection) {
+    Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
+      get: function get() {
+        return OrigPeerConnection.generateCertificate;
+      }
+    });
+  }
+}
+
+function shimTrackEventTransceiver(window) {
+  // Add event.transceiver member over deprecated event.receiver
+  if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) {
+    Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
+      get: function get() {
+        return { receiver: this.receiver };
+      }
+    });
+  }
+}
+
+function shimCreateOfferLegacy(window) {
+  var origCreateOffer = window.RTCPeerConnection.prototype.createOffer;
+  window.RTCPeerConnection.prototype.createOffer = function createOffer(offerOptions) {
+    if (offerOptions) {
+      if (typeof offerOptions.offerToReceiveAudio !== 'undefined') {
+        // support bit values
+        offerOptions.offerToReceiveAudio = !!offerOptions.offerToReceiveAudio;
+      }
+      var audioTransceiver = this.getTransceivers().find(function (transceiver) {
+        return transceiver.receiver.track.kind === 'audio';
+      });
+      if (offerOptions.offerToReceiveAudio === false && audioTransceiver) {
+        if (audioTransceiver.direction === 'sendrecv') {
+          if (audioTransceiver.setDirection) {
+            audioTransceiver.setDirection('sendonly');
+          } else {
+            audioTransceiver.direction = 'sendonly';
+          }
+        } else if (audioTransceiver.direction === 'recvonly') {
+          if (audioTransceiver.setDirection) {
+            audioTransceiver.setDirection('inactive');
+          } else {
+            audioTransceiver.direction = 'inactive';
+          }
+        }
+      } else if (offerOptions.offerToReceiveAudio === true && !audioTransceiver) {
+        this.addTransceiver('audio');
+      }
+
+      if (typeof offerOptions.offerToReceiveVideo !== 'undefined') {
+        // support bit values
+        offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo;
+      }
+      var videoTransceiver = this.getTransceivers().find(function (transceiver) {
+        return transceiver.receiver.track.kind === 'video';
+      });
+      if (offerOptions.offerToReceiveVideo === false && videoTransceiver) {
+        if (videoTransceiver.direction === 'sendrecv') {
+          if (videoTransceiver.setDirection) {
+            videoTransceiver.setDirection('sendonly');
+          } else {
+            videoTransceiver.direction = 'sendonly';
+          }
+        } else if (videoTransceiver.direction === 'recvonly') {
+          if (videoTransceiver.setDirection) {
+            videoTransceiver.setDirection('inactive');
+          } else {
+            videoTransceiver.direction = 'inactive';
+          }
+        }
+      } else if (offerOptions.offerToReceiveVideo === true && !videoTransceiver) {
+        this.addTransceiver('video');
+      }
+    }
+    return origCreateOffer.apply(this, arguments);
+  };
+}
+
+function shimAudioContext(window) {
+  if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || window.AudioContext) {
+    return;
+  }
+  window.AudioContext = window.webkitAudioContext;
+}
+
+},{"../utils":15}],15:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+/* eslint-env node */
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+exports.extractVersion = extractVersion;
+exports.wrapPeerConnectionEvent = wrapPeerConnectionEvent;
+exports.disableLog = disableLog;
+exports.disableWarnings = disableWarnings;
+exports.log = log;
+exports.deprecated = deprecated;
+exports.detectBrowser = detectBrowser;
+exports.compactObject = compactObject;
+exports.walkStats = walkStats;
+exports.filterStats = filterStats;
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+var logDisabled_ = true;
+var deprecationWarnings_ = true;
+
+/**
+ * Extract browser version out of the provided user agent string.
+ *
+ * @param {!string} uastring userAgent string.
+ * @param {!string} expr Regular expression used as match criteria.
+ * @param {!number} pos position in the version string to be returned.
+ * @return {!number} browser version.
+ */
+function extractVersion(uastring, expr, pos) {
+  var match = uastring.match(expr);
+  return match && match.length >= pos && parseInt(match[pos], 10);
+}
+
+// Wraps the peerconnection event eventNameToWrap in a function
+// which returns the modified event object (or false to prevent
+// the event).
+function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
+  if (!window.RTCPeerConnection) {
+    return;
+  }
+  var proto = window.RTCPeerConnection.prototype;
+  var nativeAddEventListener = proto.addEventListener;
+  proto.addEventListener = function (nativeEventName, cb) {
+    if (nativeEventName !== eventNameToWrap) {
+      return nativeAddEventListener.apply(this, arguments);
+    }
+    var wrappedCallback = function wrappedCallback(e) {
+      var modifiedEvent = wrapper(e);
+      if (modifiedEvent) {
+        if (cb.handleEvent) {
+          cb.handleEvent(modifiedEvent);
+        } else {
+          cb(modifiedEvent);
+        }
+      }
+    };
+    this._eventMap = this._eventMap || {};
+    if (!this._eventMap[eventNameToWrap]) {
+      this._eventMap[eventNameToWrap] = new Map();
+    }
+    this._eventMap[eventNameToWrap].set(cb, wrappedCallback);
+    return nativeAddEventListener.apply(this, [nativeEventName, wrappedCallback]);
+  };
+
+  var nativeRemoveEventListener = proto.removeEventListener;
+  proto.removeEventListener = function (nativeEventName, cb) {
+    if (nativeEventName !== eventNameToWrap || !this._eventMap || !this._eventMap[eventNameToWrap]) {
+      return nativeRemoveEventListener.apply(this, arguments);
+    }
+    if (!this._eventMap[eventNameToWrap].has(cb)) {
+      return nativeRemoveEventListener.apply(this, arguments);
+    }
+    var unwrappedCb = this._eventMap[eventNameToWrap].get(cb);
+    this._eventMap[eventNameToWrap].delete(cb);
+    if (this._eventMap[eventNameToWrap].size === 0) {
+      delete this._eventMap[eventNameToWrap];
+    }
+    if (Object.keys(this._eventMap).length === 0) {
+      delete this._eventMap;
+    }
+    return nativeRemoveEventListener.apply(this, [nativeEventName, unwrappedCb]);
+  };
+
+  Object.defineProperty(proto, 'on' + eventNameToWrap, {
+    get: function get() {
+      return this['_on' + eventNameToWrap];
+    },
+    set: function set(cb) {
+      if (this['_on' + eventNameToWrap]) {
+        this.removeEventListener(eventNameToWrap, this['_on' + eventNameToWrap]);
+        delete this['_on' + eventNameToWrap];
+      }
+      if (cb) {
+        this.addEventListener(eventNameToWrap, this['_on' + eventNameToWrap] = cb);
+      }
+    },
+
+    enumerable: true,
+    configurable: true
+  });
+}
+
+function disableLog(bool) {
+  if (typeof bool !== 'boolean') {
+    return new Error('Argument type: ' + (typeof bool === 'undefined' ? 'undefined' : _typeof(bool)) + '. Please use a boolean.');
+  }
+  logDisabled_ = bool;
+  return bool ? 'adapter.js logging disabled' : 'adapter.js logging enabled';
+}
+
+/**
+ * Disable or enable deprecation warnings
+ * @param {!boolean} bool set to true to disable warnings.
+ */
+function disableWarnings(bool) {
+  if (typeof bool !== 'boolean') {
+    return new Error('Argument type: ' + (typeof bool === 'undefined' ? 'undefined' : _typeof(bool)) + '. Please use a boolean.');
+  }
+  deprecationWarnings_ = !bool;
+  return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
+}
+
+function log() {
+  if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object') {
+    if (logDisabled_) {
+      return;
+    }
+    if (typeof console !== 'undefined' && typeof console.log === 'function') {
+      console.log.apply(console, arguments);
+    }
+  }
+}
+
+/**
+ * Shows a deprecation warning suggesting the modern and spec-compatible API.
+ */
+function deprecated(oldMethod, newMethod) {
+  if (!deprecationWarnings_) {
+    return;
+  }
+  console.warn(oldMethod + ' is deprecated, please use ' + newMethod + ' instead.');
+}
+
+/**
+ * Browser detector.
+ *
+ * @return {object} result containing browser and version
+ *     properties.
+ */
+function detectBrowser(window) {
+  // Returned result object.
+  var result = { browser: null, version: null };
+
+  // Fail early if it's not a browser
+  if (typeof window === 'undefined' || !window.navigator) {
+    result.browser = 'Not a browser.';
+    return result;
+  }
+
+  var navigator = window.navigator;
+
+
+  if (navigator.mozGetUserMedia) {
+    // Firefox.
+    result.browser = 'firefox';
+    result.version = extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1);
+  } else if (navigator.webkitGetUserMedia || window.isSecureContext === false && window.webkitRTCPeerConnection && !window.RTCIceGatherer) {
+    // Chrome, Chromium, Webview, Opera.
+    // Version matches Chrome/WebRTC version.
+    // Chrome 74 removed webkitGetUserMedia on http as well so we need the
+    // more complicated fallback to webkitRTCPeerConnection.
+    result.browser = 'chrome';
+    result.version = extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2);
+  } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) {
+    // Edge.
+    result.browser = 'edge';
+    result.version = extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2);
+  } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) {
+    // Safari.
+    result.browser = 'safari';
+    result.version = extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1);
+    result.supportsUnifiedPlan = window.RTCRtpTransceiver && 'currentDirection' in window.RTCRtpTransceiver.prototype;
+  } else {
+    // Default fallthrough: not supported.
+    result.browser = 'Not a supported browser.';
+    return result;
+  }
+
+  return result;
+}
+
+/**
+ * Checks if something is an object.
+ *
+ * @param {*} val The something you want to check.
+ * @return true if val is an object, false otherwise.
+ */
+function isObject(val) {
+  return Object.prototype.toString.call(val) === '[object Object]';
+}
+
+/**
+ * Remove all empty objects and undefined values
+ * from a nested object -- an enhanced and vanilla version
+ * of Lodash's `compact`.
+ */
+function compactObject(data) {
+  if (!isObject(data)) {
+    return data;
+  }
+
+  return Object.keys(data).reduce(function (accumulator, key) {
+    var isObj = isObject(data[key]);
+    var value = isObj ? compactObject(data[key]) : data[key];
+    var isEmptyObject = isObj && !Object.keys(value).length;
+    if (value === undefined || isEmptyObject) {
+      return accumulator;
+    }
+    return Object.assign(accumulator, _defineProperty({}, key, value));
+  }, {});
+}
+
+/* iterates the stats graph recursively. */
+function walkStats(stats, base, resultSet) {
+  if (!base || resultSet.has(base.id)) {
+    return;
+  }
+  resultSet.set(base.id, base);
+  Object.keys(base).forEach(function (name) {
+    if (name.endsWith('Id')) {
+      walkStats(stats, stats.get(base[name]), resultSet);
+    } else if (name.endsWith('Ids')) {
+      base[name].forEach(function (id) {
+        walkStats(stats, stats.get(id), resultSet);
+      });
+    }
+  });
+}
+
+/* filter getStats for a sender/receiver track. */
+function filterStats(result, track, outbound) {
+  var streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp';
+  var filteredResult = new Map();
+  if (track === null) {
+    return filteredResult;
+  }
+  var trackStats = [];
+  result.forEach(function (value) {
+    if (value.type === 'track' && value.trackIdentifier === track.id) {
+      trackStats.push(value);
+    }
+  });
+  trackStats.forEach(function (trackStat) {
+    result.forEach(function (stats) {
+      if (stats.type === streamStatsType && stats.trackId === trackStat.id) {
+        walkStats(result, stats, filteredResult);
+      }
+    });
+  });
+  return filteredResult;
+}
+
+},{}],16:[function(require,module,exports){
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+var SDPUtils = require('sdp');
+
+function fixStatsType(stat) {
+  return {
+    inboundrtp: 'inbound-rtp',
+    outboundrtp: 'outbound-rtp',
+    candidatepair: 'candidate-pair',
+    localcandidate: 'local-candidate',
+    remotecandidate: 'remote-candidate'
+  }[stat.type] || stat.type;
+}
+
+function writeMediaSection(transceiver, caps, type, stream, dtlsRole) {
+  var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
+
+  // Map ICE parameters (ufrag, pwd) to SDP.
+  sdp += SDPUtils.writeIceParameters(
+      transceiver.iceGatherer.getLocalParameters());
+
+  // Map DTLS parameters to SDP.
+  sdp += SDPUtils.writeDtlsParameters(
+      transceiver.dtlsTransport.getLocalParameters(),
+      type === 'offer' ? 'actpass' : dtlsRole || 'active');
+
+  sdp += 'a=mid:' + transceiver.mid + '\r\n';
+
+  if (transceiver.rtpSender && transceiver.rtpReceiver) {
+    sdp += 'a=sendrecv\r\n';
+  } else if (transceiver.rtpSender) {
+    sdp += 'a=sendonly\r\n';
+  } else if (transceiver.rtpReceiver) {
+    sdp += 'a=recvonly\r\n';
+  } else {
+    sdp += 'a=inactive\r\n';
+  }
+
+  if (transceiver.rtpSender) {
+    var trackId = transceiver.rtpSender._initialTrackId ||
+        transceiver.rtpSender.track.id;
+    transceiver.rtpSender._initialTrackId = trackId;
+    // spec.
+    var msid = 'msid:' + (stream ? stream.id : '-') + ' ' +
+        trackId + '\r\n';
+    sdp += 'a=' + msid;
+    // for Chrome. Legacy should no longer be required.
+    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
+        ' ' + msid;
+
+    // RTX
+    if (transceiver.sendEncodingParameters[0].rtx) {
+      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
+          ' ' + msid;
+      sdp += 'a=ssrc-group:FID ' +
+          transceiver.sendEncodingParameters[0].ssrc + ' ' +
+          transceiver.sendEncodingParameters[0].rtx.ssrc +
+          '\r\n';
+    }
+  }
+  // FIXME: this should be written by writeRtpDescription.
+  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
+      ' cname:' + SDPUtils.localCName + '\r\n';
+  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
+    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
+        ' cname:' + SDPUtils.localCName + '\r\n';
+  }
+  return sdp;
+}
+
+// Edge does not like
+// 1) stun: filtered after 14393 unless ?transport=udp is present
+// 2) turn: that does not have all of turn:host:port?transport=udp
+// 3) turn: with ipv6 addresses
+// 4) turn: occurring muliple times
+function filterIceServers(iceServers, edgeVersion) {
+  var hasTurn = false;
+  iceServers = JSON.parse(JSON.stringify(iceServers));
+  return iceServers.filter(function(server) {
+    if (server && (server.urls || server.url)) {
+      var urls = server.urls || server.url;
+      if (server.url && !server.urls) {
+        console.warn('RTCIceServer.url is deprecated! Use urls instead.');
+      }
+      var isString = typeof urls === 'string';
+      if (isString) {
+        urls = [urls];
+      }
+      urls = urls.filter(function(url) {
+        var validTurn = url.indexOf('turn:') === 0 &&
+            url.indexOf('transport=udp') !== -1 &&
+            url.indexOf('turn:[') === -1 &&
+            !hasTurn;
+
+        if (validTurn) {
+          hasTurn = true;
+          return true;
+        }
+        return url.indexOf('stun:') === 0 && edgeVersion >= 14393 &&
+            url.indexOf('?transport=udp') === -1;
+      });
+
+      delete server.url;
+      server.urls = isString ? urls[0] : urls;
+      return !!urls.length;
+    }
+  });
+}
+
+// Determines the intersection of local and remote capabilities.
+function getCommonCapabilities(localCapabilities, remoteCapabilities) {
+  var commonCapabilities = {
+    codecs: [],
+    headerExtensions: [],
+    fecMechanisms: []
+  };
+
+  var findCodecByPayloadType = function(pt, codecs) {
+    pt = parseInt(pt, 10);
+    for (var i = 0; i < codecs.length; i++) {
+      if (codecs[i].payloadType === pt ||
+          codecs[i].preferredPayloadType === pt) {
+        return codecs[i];
+      }
+    }
+  };
+
+  var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
+    var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
+    var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
+    return lCodec && rCodec &&
+        lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
+  };
+
+  localCapabilities.codecs.forEach(function(lCodec) {
+    for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
+      var rCodec = remoteCapabilities.codecs[i];
+      if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
+          lCodec.clockRate === rCodec.clockRate) {
+        if (lCodec.name.toLowerCase() === 'rtx' &&
+            lCodec.parameters && rCodec.parameters.apt) {
+          // for RTX we need to find the local rtx that has a apt
+          // which points to the same local codec as the remote one.
+          if (!rtxCapabilityMatches(lCodec, rCodec,
+              localCapabilities.codecs, remoteCapabilities.codecs)) {
+            continue;
+          }
+        }
+        rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
+        // number of channels is the highest common number of channels
+        rCodec.numChannels = Math.min(lCodec.numChannels,
+            rCodec.numChannels);
+        // push rCodec so we reply with offerer payload type
+        commonCapabilities.codecs.push(rCodec);
+
+        // determine common feedback mechanisms
+        rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) {
+          for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
+            if (lCodec.rtcpFeedback[j].type === fb.type &&
+                lCodec.rtcpFeedback[j].parameter === fb.parameter) {
+              return true;
+            }
+          }
+          return false;
+        });
+        // FIXME: also need to determine .parameters
+        //  see https://github.com/openpeer/ortc/issues/569
+        break;
+      }
+    }
+  });
+
+  localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {
+    for (var i = 0; i < remoteCapabilities.headerExtensions.length;
+         i++) {
+      var rHeaderExtension = remoteCapabilities.headerExtensions[i];
+      if (lHeaderExtension.uri === rHeaderExtension.uri) {
+        commonCapabilities.headerExtensions.push(rHeaderExtension);
+        break;
+      }
+    }
+  });
+
+  // FIXME: fecMechanisms
+  return commonCapabilities;
+}
+
+// is action=setLocalDescription with type allowed in signalingState
+function isActionAllowedInSignalingState(action, type, signalingState) {
+  return {
+    offer: {
+      setLocalDescription: ['stable', 'have-local-offer'],
+      setRemoteDescription: ['stable', 'have-remote-offer']
+    },
+    answer: {
+      setLocalDescription: ['have-remote-offer', 'have-local-pranswer'],
+      setRemoteDescription: ['have-local-offer', 'have-remote-pranswer']
+    }
+  }[type][action].indexOf(signalingState) !== -1;
+}
+
+function maybeAddCandidate(iceTransport, candidate) {
+  // Edge's internal representation adds some fields therefore
+  // not all fieldѕ are taken into account.
+  var alreadyAdded = iceTransport.getRemoteCandidates()
+      .find(function(remoteCandidate) {
+        return candidate.foundation === remoteCandidate.foundation &&
+            candidate.ip === remoteCandidate.ip &&
+            candidate.port === remoteCandidate.port &&
+            candidate.priority === remoteCandidate.priority &&
+            candidate.protocol === remoteCandidate.protocol &&
+            candidate.type === remoteCandidate.type;
+      });
+  if (!alreadyAdded) {
+    iceTransport.addRemoteCandidate(candidate);
+  }
+  return !alreadyAdded;
+}
+
+
+function makeError(name, description) {
+  var e = new Error(description);
+  e.name = name;
+  // legacy error codes from https://heycam.github.io/webidl/#idl-DOMException-error-names
+  e.code = {
+    NotSupportedError: 9,
+    InvalidStateError: 11,
+    InvalidAccessError: 15,
+    TypeError: undefined,
+    OperationError: undefined
+  }[name];
+  return e;
+}
+
+module.exports = function(window, edgeVersion) {
+  // https://w3c.github.io/mediacapture-main/#mediastream
+  // Helper function to add the track to the stream and
+  // dispatch the event ourselves.
+  function addTrackToStreamAndFireEvent(track, stream) {
+    stream.addTrack(track);
+    stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack',
+        {track: track}));
+  }
+
+  function removeTrackFromStreamAndFireEvent(track, stream) {
+    stream.removeTrack(track);
+    stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack',
+        {track: track}));
+  }
+
+  function fireAddTrack(pc, track, receiver, streams) {
+    var trackEvent = new Event('track');
+    trackEvent.track = track;
+    trackEvent.receiver = receiver;
+    trackEvent.transceiver = {receiver: receiver};
+    trackEvent.streams = streams;
+    window.setTimeout(function() {
+      pc._dispatchEvent('track', trackEvent);
+    });
+  }
+
+  var RTCPeerConnection = function(config) {
+    var pc = this;
+
+    var _eventTarget = document.createDocumentFragment();
+    ['addEventListener', 'removeEventListener', 'dispatchEvent']
+        .forEach(function(method) {
+          pc[method] = _eventTarget[method].bind(_eventTarget);
+        });
+
+    this.canTrickleIceCandidates = null;
+
+    this.needNegotiation = false;
+
+    this.localStreams = [];
+    this.remoteStreams = [];
+
+    this._localDescription = null;
+    this._remoteDescription = null;
+
+    this.signalingState = 'stable';
+    this.iceConnectionState = 'new';
+    this.connectionState = 'new';
+    this.iceGatheringState = 'new';
+
+    config = JSON.parse(JSON.stringify(config || {}));
+
+    this.usingBundle = config.bundlePolicy === 'max-bundle';
+    if (config.rtcpMuxPolicy === 'negotiate') {
+      throw(makeError('NotSupportedError',
+          'rtcpMuxPolicy \'negotiate\' is not supported'));
+    } else if (!config.rtcpMuxPolicy) {
+      config.rtcpMuxPolicy = 'require';
+    }
+
+    switch (config.iceTransportPolicy) {
+      case 'all':
+      case 'relay':
+        break;
+      default:
+        config.iceTransportPolicy = 'all';
+        break;
+    }
+
+    switch (config.bundlePolicy) {
+      case 'balanced':
+      case 'max-compat':
+      case 'max-bundle':
+        break;
+      default:
+        config.bundlePolicy = 'balanced';
+        break;
+    }
+
+    config.iceServers = filterIceServers(config.iceServers || [], edgeVersion);
+
+    this._iceGatherers = [];
+    if (config.iceCandidatePoolSize) {
+      for (var i = config.iceCandidatePoolSize; i > 0; i--) {
+        this._iceGatherers.push(new window.RTCIceGatherer({
+          iceServers: config.iceServers,
+          gatherPolicy: config.iceTransportPolicy
+        }));
+      }
+    } else {
+      config.iceCandidatePoolSize = 0;
+    }
+
+    this._config = config;
+
+    // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
+    // everything that is needed to describe a SDP m-line.
+    this.transceivers = [];
+
+    this._sdpSessionId = SDPUtils.generateSessionId();
+    this._sdpSessionVersion = 0;
+
+    this._dtlsRole = undefined; // role for a=setup to use in answers.
+
+    this._isClosed = false;
+  };
+
+  Object.defineProperty(RTCPeerConnection.prototype, 'localDescription', {
+    configurable: true,
+    get: function() {
+      return this._localDescription;
+    }
+  });
+  Object.defineProperty(RTCPeerConnection.prototype, 'remoteDescription', {
+    configurable: true,
+    get: function() {
+      return this._remoteDescription;
+    }
+  });
+
+  // set up event handlers on prototype
+  RTCPeerConnection.prototype.onicecandidate = null;
+  RTCPeerConnection.prototype.onaddstream = null;
+  RTCPeerConnection.prototype.ontrack = null;
+  RTCPeerConnection.prototype.onremovestream = null;
+  RTCPeerConnection.prototype.onsignalingstatechange = null;
+  RTCPeerConnection.prototype.oniceconnectionstatechange = null;
+  RTCPeerConnection.prototype.onconnectionstatechange = null;
+  RTCPeerConnection.prototype.onicegatheringstatechange = null;
+  RTCPeerConnection.prototype.onnegotiationneeded = null;
+  RTCPeerConnection.prototype.ondatachannel = null;
+
+  RTCPeerConnection.prototype._dispatchEvent = function(name, event) {
+    if (this._isClosed) {
+      return;
+    }
+    this.dispatchEvent(event);
+    if (typeof this['on' + name] === 'function') {
+      this['on' + name](event);
+    }
+  };
+
+  RTCPeerConnection.prototype._emitGatheringStateChange = function() {
+    var event = new Event('icegatheringstatechange');
+    this._dispatchEvent('icegatheringstatechange', event);
+  };
+
+  RTCPeerConnection.prototype.getConfiguration = function() {
+    return this._config;
+  };
+
+  RTCPeerConnection.prototype.getLocalStreams = function() {
+    return this.localStreams;
+  };
+
+  RTCPeerConnection.prototype.getRemoteStreams = function() {
+    return this.remoteStreams;
+  };
+
+  // internal helper to create a transceiver object.
+  // (which is not yet the same as the WebRTC 1.0 transceiver)
+  RTCPeerConnection.prototype._createTransceiver = function(kind, doNotAdd) {
+    var hasBundleTransport = this.transceivers.length > 0;
+    var transceiver = {
+      track: null,
+      iceGatherer: null,
+      iceTransport: null,
+      dtlsTransport: null,
+      localCapabilities: null,
+      remoteCapabilities: null,
+      rtpSender: null,
+      rtpReceiver: null,
+      kind: kind,
+      mid: null,
+      sendEncodingParameters: null,
+      recvEncodingParameters: null,
+      stream: null,
+      associatedRemoteMediaStreams: [],
+      wantReceive: true
+    };
+    if (this.usingBundle && hasBundleTransport) {
+      transceiver.iceTransport = this.transceivers[0].iceTransport;
+      transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;
+    } else {
+      var transports = this._createIceAndDtlsTransports();
+      transceiver.iceTransport = transports.iceTransport;
+      transceiver.dtlsTransport = transports.dtlsTransport;
+    }
+    if (!doNotAdd) {
+      this.transceivers.push(transceiver);
+    }
+    return transceiver;
+  };
+
+  RTCPeerConnection.prototype.addTrack = function(track, stream) {
+    if (this._isClosed) {
+      throw makeError('InvalidStateError',
+          'Attempted to call addTrack on a closed peerconnection.');
+    }
+
+    var alreadyExists = this.transceivers.find(function(s) {
+      return s.track === track;
+    });
+
+    if (alreadyExists) {
+      throw makeError('InvalidAccessError', 'Track already exists.');
+    }
+
+    var transceiver;
+    for (var i = 0; i < this.transceivers.length; i++) {
+      if (!this.transceivers[i].track &&
+          this.transceivers[i].kind === track.kind) {
+        transceiver = this.transceivers[i];
+      }
+    }
+    if (!transceiver) {
+      transceiver = this._createTransceiver(track.kind);
+    }
+
+    this._maybeFireNegotiationNeeded();
+
+    if (this.localStreams.indexOf(stream) === -1) {
+      this.localStreams.push(stream);
+    }
+
+    transceiver.track = track;
+    transceiver.stream = stream;
+    transceiver.rtpSender = new window.RTCRtpSender(track,
+        transceiver.dtlsTransport);
+    return transceiver.rtpSender;
+  };
+
+  RTCPeerConnection.prototype.addStream = function(stream) {
+    var pc = this;
+    if (edgeVersion >= 15025) {
+      stream.getTracks().forEach(function(track) {
+        pc.addTrack(track, stream);
+      });
+    } else {
+      // Clone is necessary for local demos mostly, attaching directly
+      // to two different senders does not work (build 10547).
+      // Fixed in 15025 (or earlier)
+      var clonedStream = stream.clone();
+      stream.getTracks().forEach(function(track, idx) {
+        var clonedTrack = clonedStream.getTracks()[idx];
+        track.addEventListener('enabled', function(event) {
+          clonedTrack.enabled = event.enabled;
+        });
+      });
+      clonedStream.getTracks().forEach(function(track) {
+        pc.addTrack(track, clonedStream);
+      });
+    }
+  };
+
+  RTCPeerConnection.prototype.removeTrack = function(sender) {
+    if (this._isClosed) {
+      throw makeError('InvalidStateError',
+          'Attempted to call removeTrack on a closed peerconnection.');
+    }
+
+    if (!(sender instanceof window.RTCRtpSender)) {
+      throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' +
+          'does not implement interface RTCRtpSender.');
+    }
+
+    var transceiver = this.transceivers.find(function(t) {
+      return t.rtpSender === sender;
+    });
+
+    if (!transceiver) {
+      throw makeError('InvalidAccessError',
+          'Sender was not created by this connection.');
+    }
+    var stream = transceiver.stream;
+
+    transceiver.rtpSender.stop();
+    transceiver.rtpSender = null;
+    transceiver.track = null;
+    transceiver.stream = null;
+
+    // remove the stream from the set of local streams
+    var localStreams = this.transceivers.map(function(t) {
+      return t.stream;
+    });
+    if (localStreams.indexOf(stream) === -1 &&
+        this.localStreams.indexOf(stream) > -1) {
+      this.localStreams.splice(this.localStreams.indexOf(stream), 1);
+    }
+
+    this._maybeFireNegotiationNeeded();
+  };
+
+  RTCPeerConnection.prototype.removeStream = function(stream) {
+    var pc = this;
+    stream.getTracks().forEach(function(track) {
+      var sender = pc.getSenders().find(function(s) {
+        return s.track === track;
+      });
+      if (sender) {
+        pc.removeTrack(sender);
+      }
+    });
+  };
+
+  RTCPeerConnection.prototype.getSenders = function() {
+    return this.transceivers.filter(function(transceiver) {
+      return !!transceiver.rtpSender;
+    })
+    .map(function(transceiver) {
+      return transceiver.rtpSender;
+    });
+  };
+
+  RTCPeerConnection.prototype.getReceivers = function() {
+    return this.transceivers.filter(function(transceiver) {
+      return !!transceiver.rtpReceiver;
+    })
+    .map(function(transceiver) {
+      return transceiver.rtpReceiver;
+    });
+  };
+
+
+  RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex,
+      usingBundle) {
+    var pc = this;
+    if (usingBundle && sdpMLineIndex > 0) {
+      return this.transceivers[0].iceGatherer;
+    } else if (this._iceGatherers.length) {
+      return this._iceGatherers.shift();
+    }
+    var iceGatherer = new window.RTCIceGatherer({
+      iceServers: this._config.iceServers,
+      gatherPolicy: this._config.iceTransportPolicy
+    });
+    Object.defineProperty(iceGatherer, 'state',
+        {value: 'new', writable: true}
+    );
+
+    this.transceivers[sdpMLineIndex].bufferedCandidateEvents = [];
+    this.transceivers[sdpMLineIndex].bufferCandidates = function(event) {
+      var end = !event.candidate || Object.keys(event.candidate).length === 0;
+      // polyfill since RTCIceGatherer.state is not implemented in
+      // Edge 10547 yet.
+      iceGatherer.state = end ? 'completed' : 'gathering';
+      if (pc.transceivers[sdpMLineIndex].bufferedCandidateEvents !== null) {
+        pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event);
+      }
+    };
+    iceGatherer.addEventListener('localcandidate',
+      this.transceivers[sdpMLineIndex].bufferCandidates);
+    return iceGatherer;
+  };
+
+  // start gathering from an RTCIceGatherer.
+  RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) {
+    var pc = this;
+    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
+    if (iceGatherer.onlocalcandidate) {
+      return;
+    }
+    var bufferedCandidateEvents =
+      this.transceivers[sdpMLineIndex].bufferedCandidateEvents;
+    this.transceivers[sdpMLineIndex].bufferedCandidateEvents = null;
+    iceGatherer.removeEventListener('localcandidate',
+      this.transceivers[sdpMLineIndex].bufferCandidates);
+    iceGatherer.onlocalcandidate = function(evt) {
+      if (pc.usingBundle && sdpMLineIndex > 0) {
+        // if we know that we use bundle we can drop candidates with
+        // Ñ•dpMLineIndex > 0. If we don't do this then our state gets
+        // confused since we dispose the extra ice gatherer.
+        return;
+      }
+      var event = new Event('icecandidate');
+      event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
+
+      var cand = evt.candidate;
+      // Edge emits an empty object for RTCIceCandidateComplete‥
+      var end = !cand || Object.keys(cand).length === 0;
+      if (end) {
+        // polyfill since RTCIceGatherer.state is not implemented in
+        // Edge 10547 yet.
+        if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') {
+          iceGatherer.state = 'completed';
+        }
+      } else {
+        if (iceGatherer.state === 'new') {
+          iceGatherer.state = 'gathering';
+        }
+        // RTCIceCandidate doesn't have a component, needs to be added
+        cand.component = 1;
+        // also the usernameFragment. TODO: update SDP to take both variants.
+        cand.ufrag = iceGatherer.getLocalParameters().usernameFragment;
+
+        var serializedCandidate = SDPUtils.writeCandidate(cand);
+        event.candidate = Object.assign(event.candidate,
+            SDPUtils.parseCandidate(serializedCandidate));
+
+        event.candidate.candidate = serializedCandidate;
+        event.candidate.toJSON = function() {
+          return {
+            candidate: event.candidate.candidate,
+            sdpMid: event.candidate.sdpMid,
+            sdpMLineIndex: event.candidate.sdpMLineIndex,
+            usernameFragment: event.candidate.usernameFragment
+          };
+        };
+      }
+
+      // update local description.
+      var sections = SDPUtils.getMediaSections(pc._localDescription.sdp);
+      if (!end) {
+        sections[event.candidate.sdpMLineIndex] +=
+            'a=' + event.candidate.candidate + '\r\n';
+      } else {
+        sections[event.candidate.sdpMLineIndex] +=
+            'a=end-of-candidates\r\n';
+      }
+      pc._localDescription.sdp =
+          SDPUtils.getDescription(pc._localDescription.sdp) +
+          sections.join('');
+      var complete = pc.transceivers.every(function(transceiver) {
+        return transceiver.iceGatherer &&
+            transceiver.iceGatherer.state === 'completed';
+      });
+
+      if (pc.iceGatheringState !== 'gathering') {
+        pc.iceGatheringState = 'gathering';
+        pc._emitGatheringStateChange();
+      }
+
+      // Emit candidate. Also emit null candidate when all gatherers are
+      // complete.
+      if (!end) {
+        pc._dispatchEvent('icecandidate', event);
+      }
+      if (complete) {
+        pc._dispatchEvent('icecandidate', new Event('icecandidate'));
+        pc.iceGatheringState = 'complete';
+        pc._emitGatheringStateChange();
+      }
+    };
+
+    // emit already gathered candidates.
+    window.setTimeout(function() {
+      bufferedCandidateEvents.forEach(function(e) {
+        iceGatherer.onlocalcandidate(e);
+      });
+    }, 0);
+  };
+
+  // Create ICE transport and DTLS transport.
+  RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {
+    var pc = this;
+    var iceTransport = new window.RTCIceTransport(null);
+    iceTransport.onicestatechange = function() {
+      pc._updateIceConnectionState();
+      pc._updateConnectionState();
+    };
+
+    var dtlsTransport = new window.RTCDtlsTransport(iceTransport);
+    dtlsTransport.ondtlsstatechange = function() {
+      pc._updateConnectionState();
+    };
+    dtlsTransport.onerror = function() {
+      // onerror does not set state to failed by itself.
+      Object.defineProperty(dtlsTransport, 'state',
+          {value: 'failed', writable: true});
+      pc._updateConnectionState();
+    };
+
+    return {
+      iceTransport: iceTransport,
+      dtlsTransport: dtlsTransport
+    };
+  };
+
+  // Destroy ICE gatherer, ICE transport and DTLS transport.
+  // Without triggering the callbacks.
+  RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
+      sdpMLineIndex) {
+    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
+    if (iceGatherer) {
+      delete iceGatherer.onlocalcandidate;
+      delete this.transceivers[sdpMLineIndex].iceGatherer;
+    }
+    var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
+    if (iceTransport) {
+      delete iceTransport.onicestatechange;
+      delete this.transceivers[sdpMLineIndex].iceTransport;
+    }
+    var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;
+    if (dtlsTransport) {
+      delete dtlsTransport.ondtlsstatechange;
+      delete dtlsTransport.onerror;
+      delete this.transceivers[sdpMLineIndex].dtlsTransport;
+    }
+  };
+
+  // Start the RTP Sender and Receiver for a transceiver.
+  RTCPeerConnection.prototype._transceive = function(transceiver,
+      send, recv) {
+    var params = getCommonCapabilities(transceiver.localCapabilities,
+        transceiver.remoteCapabilities);
+    if (send && transceiver.rtpSender) {
+      params.encodings = transceiver.sendEncodingParameters;
+      params.rtcp = {
+        cname: SDPUtils.localCName,
+        compound: transceiver.rtcpParameters.compound
+      };
+      if (transceiver.recvEncodingParameters.length) {
+        params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
+      }
+      transceiver.rtpSender.send(params);
+    }
+    if (recv && transceiver.rtpReceiver && params.codecs.length > 0) {
+      // remove RTX field in Edge 14942
+      if (transceiver.kind === 'video'
+          && transceiver.recvEncodingParameters
+          && edgeVersion < 15019) {
+        transceiver.recvEncodingParameters.forEach(function(p) {
+          delete p.rtx;
+        });
+      }
+      if (transceiver.recvEncodingParameters.length) {
+        params.encodings = transceiver.recvEncodingParameters;
+      } else {
+        params.encodings = [{}];
+      }
+      params.rtcp = {
+        compound: transceiver.rtcpParameters.compound
+      };
+      if (transceiver.rtcpParameters.cname) {
+        params.rtcp.cname = transceiver.rtcpParameters.cname;
+      }
+      if (transceiver.sendEncodingParameters.length) {
+        params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
+      }
+      transceiver.rtpReceiver.receive(params);
+    }
+  };
+
+  RTCPeerConnection.prototype.setLocalDescription = function(description) {
+    var pc = this;
+
+    // Note: pranswer is not supported.
+    if (['offer', 'answer'].indexOf(description.type) === -1) {
+      return Promise.reject(makeError('TypeError',
+          'Unsupported type "' + description.type + '"'));
+    }
+
+    if (!isActionAllowedInSignalingState('setLocalDescription',
+        description.type, pc.signalingState) || pc._isClosed) {
+      return Promise.reject(makeError('InvalidStateError',
+          'Can not set local ' + description.type +
+          ' in state ' + pc.signalingState));
+    }
+
+    var sections;
+    var sessionpart;
+    if (description.type === 'offer') {
+      // VERY limited support for SDP munging. Limited to:
+      // * changing the order of codecs
+      sections = SDPUtils.splitSections(description.sdp);
+      sessionpart = sections.shift();
+      sections.forEach(function(mediaSection, sdpMLineIndex) {
+        var caps = SDPUtils.parseRtpParameters(mediaSection);
+        pc.transceivers[sdpMLineIndex].localCapabilities = caps;
+      });
+
+      pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
+        pc._gather(transceiver.mid, sdpMLineIndex);
+      });
+    } else if (description.type === 'answer') {
+      sections = SDPUtils.splitSections(pc._remoteDescription.sdp);
+      sessionpart = sections.shift();
+      var isIceLite = SDPUtils.matchPrefix(sessionpart,
+          'a=ice-lite').length > 0;
+      sections.forEach(function(mediaSection, sdpMLineIndex) {
+        var transceiver = pc.transceivers[sdpMLineIndex];
+        var iceGatherer = transceiver.iceGatherer;
+        var iceTransport = transceiver.iceTransport;
+        var dtlsTransport = transceiver.dtlsTransport;
+        var localCapabilities = transceiver.localCapabilities;
+        var remoteCapabilities = transceiver.remoteCapabilities;
+
+        // treat bundle-only as not-rejected.
+        var rejected = SDPUtils.isRejected(mediaSection) &&
+            SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0;
+
+        if (!rejected && !transceiver.rejected) {
+          var remoteIceParameters = SDPUtils.getIceParameters(
+              mediaSection, sessionpart);
+          var remoteDtlsParameters = SDPUtils.getDtlsParameters(
+              mediaSection, sessionpart);
+          if (isIceLite) {
+            remoteDtlsParameters.role = 'server';
+          }
+
+          if (!pc.usingBundle || sdpMLineIndex === 0) {
+            pc._gather(transceiver.mid, sdpMLineIndex);
+            if (iceTransport.state === 'new') {
+              iceTransport.start(iceGatherer, remoteIceParameters,
+                  isIceLite ? 'controlling' : 'controlled');
+            }
+            if (dtlsTransport.state === 'new') {
+              dtlsTransport.start(remoteDtlsParameters);
+            }
+          }
+
+          // Calculate intersection of capabilities.
+          var params = getCommonCapabilities(localCapabilities,
+              remoteCapabilities);
+
+          // Start the RTCRtpSender. The RTCRtpReceiver for this
+          // transceiver has already been started in setRemoteDescription.
+          pc._transceive(transceiver,
+              params.codecs.length > 0,
+              false);
+        }
+      });
+    }
+
+    pc._localDescription = {
+      type: description.type,
+      sdp: description.sdp
+    };
+    if (description.type === 'offer') {
+      pc._updateSignalingState('have-local-offer');
+    } else {
+      pc._updateSignalingState('stable');
+    }
+
+    return Promise.resolve();
+  };
+
+  RTCPeerConnection.prototype.setRemoteDescription = function(description) {
+    var pc = this;
+
+    // Note: pranswer is not supported.
+    if (['offer', 'answer'].indexOf(description.type) === -1) {
+      return Promise.reject(makeError('TypeError',
+          'Unsupported type "' + description.type + '"'));
+    }
+
+    if (!isActionAllowedInSignalingState('setRemoteDescription',
+        description.type, pc.signalingState) || pc._isClosed) {
+      return Promise.reject(makeError('InvalidStateError',
+          'Can not set remote ' + description.type +
+          ' in state ' + pc.signalingState));
+    }
+
+    var streams = {};
+    pc.remoteStreams.forEach(function(stream) {
+      streams[stream.id] = stream;
+    });
+    var receiverList = [];
+    var sections = SDPUtils.splitSections(description.sdp);
+    var sessionpart = sections.shift();
+    var isIceLite = SDPUtils.matchPrefix(sessionpart,
+        'a=ice-lite').length > 0;
+    var usingBundle = SDPUtils.matchPrefix(sessionpart,
+        'a=group:BUNDLE ').length > 0;
+    pc.usingBundle = usingBundle;
+    var iceOptions = SDPUtils.matchPrefix(sessionpart,
+        'a=ice-options:')[0];
+    if (iceOptions) {
+      pc.canTrickleIceCandidates = iceOptions.substr(14).split(' ')
+          .indexOf('trickle') >= 0;
+    } else {
+      pc.canTrickleIceCandidates = false;
+    }
+
+    sections.forEach(function(mediaSection, sdpMLineIndex) {
+      var lines = SDPUtils.splitLines(mediaSection);
+      var kind = SDPUtils.getKind(mediaSection);
+      // treat bundle-only as not-rejected.
+      var rejected = SDPUtils.isRejected(mediaSection) &&
+          SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0;
+      var protocol = lines[0].substr(2).split(' ')[2];
+
+      var direction = SDPUtils.getDirection(mediaSection, sessionpart);
+      var remoteMsid = SDPUtils.parseMsid(mediaSection);
+
+      var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier();
+
+      // Reject datachannels which are not implemented yet.
+      if (rejected || (kind === 'application' && (protocol === 'DTLS/SCTP' ||
+          protocol === 'UDP/DTLS/SCTP'))) {
+        // TODO: this is dangerous in the case where a non-rejected m-line
+        //     becomes rejected.
+        pc.transceivers[sdpMLineIndex] = {
+          mid: mid,
+          kind: kind,
+          protocol: protocol,
+          rejected: true
+        };
+        return;
+      }
+
+      if (!rejected && pc.transceivers[sdpMLineIndex] &&
+          pc.transceivers[sdpMLineIndex].rejected) {
+        // recycle a rejected transceiver.
+        pc.transceivers[sdpMLineIndex] = pc._createTransceiver(kind, true);
+      }
+
+      var transceiver;
+      var iceGatherer;
+      var iceTransport;
+      var dtlsTransport;
+      var rtpReceiver;
+      var sendEncodingParameters;
+      var recvEncodingParameters;
+      var localCapabilities;
+
+      var track;
+      // FIXME: ensure the mediaSection has rtcp-mux set.
+      var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
+      var remoteIceParameters;
+      var remoteDtlsParameters;
+      if (!rejected) {
+        remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
+            sessionpart);
+        remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
+            sessionpart);
+        remoteDtlsParameters.role = 'client';
+      }
+      recvEncodingParameters =
+          SDPUtils.parseRtpEncodingParameters(mediaSection);
+
+      var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);
+
+      var isComplete = SDPUtils.matchPrefix(mediaSection,
+          'a=end-of-candidates', sessionpart).length > 0;
+      var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
+          .map(function(cand) {
+            return SDPUtils.parseCandidate(cand);
+          })
+          .filter(function(cand) {
+            return cand.component === 1;
+          });
+
+      // Check if we can use BUNDLE and dispose transports.
+      if ((description.type === 'offer' || description.type === 'answer') &&
+          !rejected && usingBundle && sdpMLineIndex > 0 &&
+          pc.transceivers[sdpMLineIndex]) {
+        pc._disposeIceAndDtlsTransports(sdpMLineIndex);
+        pc.transceivers[sdpMLineIndex].iceGatherer =
+            pc.transceivers[0].iceGatherer;
+        pc.transceivers[sdpMLineIndex].iceTransport =
+            pc.transceivers[0].iceTransport;
+        pc.transceivers[sdpMLineIndex].dtlsTransport =
+            pc.transceivers[0].dtlsTransport;
+        if (pc.transceivers[sdpMLineIndex].rtpSender) {
+          pc.transceivers[sdpMLineIndex].rtpSender.setTransport(
+              pc.transceivers[0].dtlsTransport);
+        }
+        if (pc.transceivers[sdpMLineIndex].rtpReceiver) {
+          pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
+              pc.transceivers[0].dtlsTransport);
+        }
+      }
+      if (description.type === 'offer' && !rejected) {
+        transceiver = pc.transceivers[sdpMLineIndex] ||
+            pc._createTransceiver(kind);
+        transceiver.mid = mid;
+
+        if (!transceiver.iceGatherer) {
+          transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,
+              usingBundle);
+        }
+
+        if (cands.length && transceiver.iceTransport.state === 'new') {
+          if (isComplete && (!usingBundle || sdpMLineIndex === 0)) {
+            transceiver.iceTransport.setRemoteCandidates(cands);
+          } else {
+            cands.forEach(function(candidate) {
+              maybeAddCandidate(transceiver.iceTransport, candidate);
+            });
+          }
+        }
+
+        localCapabilities = window.RTCRtpReceiver.getCapabilities(kind);
+
+        // filter RTX until additional stuff needed for RTX is implemented
+        // in adapter.js
+        if (edgeVersion < 15019) {
+          localCapabilities.codecs = localCapabilities.codecs.filter(
+              function(codec) {
+                return codec.name !== 'rtx';
+              });
+        }
+
+        sendEncodingParameters = transceiver.sendEncodingParameters || [{
+          ssrc: (2 * sdpMLineIndex + 2) * 1001
+        }];
+
+        // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
+        var isNewTrack = false;
+        if (direction === 'sendrecv' || direction === 'sendonly') {
+          isNewTrack = !transceiver.rtpReceiver;
+          rtpReceiver = transceiver.rtpReceiver ||
+              new window.RTCRtpReceiver(transceiver.dtlsTransport, kind);
+
+          if (isNewTrack) {
+            var stream;
+            track = rtpReceiver.track;
+            // FIXME: does not work with Plan B.
+            if (remoteMsid && remoteMsid.stream === '-') {
+              // no-op. a stream id of '-' means: no associated stream.
+            } else if (remoteMsid) {
+              if (!streams[remoteMsid.stream]) {
+                streams[remoteMsid.stream] = new window.MediaStream();
+                Object.defineProperty(streams[remoteMsid.stream], 'id', {
+                  get: function() {
+                    return remoteMsid.stream;
+                  }
+                });
+              }
+              Object.defineProperty(track, 'id', {
+                get: function() {
+                  return remoteMsid.track;
+                }
+              });
+              stream = streams[remoteMsid.stream];
+            } else {
+              if (!streams.default) {
+                streams.default = new window.MediaStream();
+              }
+              stream = streams.default;
+            }
+            if (stream) {
+              addTrackToStreamAndFireEvent(track, stream);
+              transceiver.associatedRemoteMediaStreams.push(stream);
+            }
+            receiverList.push([track, rtpReceiver, stream]);
+          }
+        } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) {
+          transceiver.associatedRemoteMediaStreams.forEach(function(s) {
+            var nativeTrack = s.getTracks().find(function(t) {
+              return t.id === transceiver.rtpReceiver.track.id;
+            });
+            if (nativeTrack) {
+              removeTrackFromStreamAndFireEvent(nativeTrack, s);
+            }
+          });
+          transceiver.associatedRemoteMediaStreams = [];
+        }
+
+        transceiver.localCapabilities = localCapabilities;
+        transceiver.remoteCapabilities = remoteCapabilities;
+        transceiver.rtpReceiver = rtpReceiver;
+        transceiver.rtcpParameters = rtcpParameters;
+        transceiver.sendEncodingParameters = sendEncodingParameters;
+        transceiver.recvEncodingParameters = recvEncodingParameters;
+
+        // Start the RTCRtpReceiver now. The RTPSender is started in
+        // setLocalDescription.
+        pc._transceive(pc.transceivers[sdpMLineIndex],
+            false,
+            isNewTrack);
+      } else if (description.type === 'answer' && !rejected) {
+        transceiver = pc.transceivers[sdpMLineIndex];
+        iceGatherer = transceiver.iceGatherer;
+        iceTransport = transceiver.iceTransport;
+        dtlsTransport = transceiver.dtlsTransport;
+        rtpReceiver = transceiver.rtpReceiver;
+        sendEncodingParameters = transceiver.sendEncodingParameters;
+        localCapabilities = transceiver.localCapabilities;
+
+        pc.transceivers[sdpMLineIndex].recvEncodingParameters =
+            recvEncodingParameters;
+        pc.transceivers[sdpMLineIndex].remoteCapabilities =
+            remoteCapabilities;
+        pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;
+
+        if (cands.length && iceTransport.state === 'new') {
+          if ((isIceLite || isComplete) &&
+              (!usingBundle || sdpMLineIndex === 0)) {
+            iceTransport.setRemoteCandidates(cands);
+          } else {
+            cands.forEach(function(candidate) {
+              maybeAddCandidate(transceiver.iceTransport, candidate);
+            });
+          }
+        }
+
+        if (!usingBundle || sdpMLineIndex === 0) {
+          if (iceTransport.state === 'new') {
+            iceTransport.start(iceGatherer, remoteIceParameters,
+                'controlling');
+          }
+          if (dtlsTransport.state === 'new') {
+            dtlsTransport.start(remoteDtlsParameters);
+          }
+        }
+
+        // If the offer contained RTX but the answer did not,
+        // remove RTX from sendEncodingParameters.
+        var commonCapabilities = getCommonCapabilities(
+          transceiver.localCapabilities,
+          transceiver.remoteCapabilities);
+
+        var hasRtx = commonCapabilities.codecs.filter(function(c) {
+          return c.name.toLowerCase() === 'rtx';
+        }).length;
+        if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
+          delete transceiver.sendEncodingParameters[0].rtx;
+        }
+
+        pc._transceive(transceiver,
+            direction === 'sendrecv' || direction === 'recvonly',
+            direction === 'sendrecv' || direction === 'sendonly');
+
+        // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
+        if (rtpReceiver &&
+            (direction === 'sendrecv' || direction === 'sendonly')) {
+          track = rtpReceiver.track;
+          if (remoteMsid) {
+            if (!streams[remoteMsid.stream]) {
+              streams[remoteMsid.stream] = new window.MediaStream();
+            }
+            addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]);
+            receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]);
+          } else {
+            if (!streams.default) {
+              streams.default = new window.MediaStream();
+            }
+            addTrackToStreamAndFireEvent(track, streams.default);
+            receiverList.push([track, rtpReceiver, streams.default]);
+          }
+        } else {
+          // FIXME: actually the receiver should be created later.
+          delete transceiver.rtpReceiver;
+        }
+      }
+    });
+
+    if (pc._dtlsRole === undefined) {
+      pc._dtlsRole = description.type === 'offer' ? 'active' : 'passive';
+    }
+
+    pc._remoteDescription = {
+      type: description.type,
+      sdp: description.sdp
+    };
+    if (description.type === 'offer') {
+      pc._updateSignalingState('have-remote-offer');
+    } else {
+      pc._updateSignalingState('stable');
+    }
+    Object.keys(streams).forEach(function(sid) {
+      var stream = streams[sid];
+      if (stream.getTracks().length) {
+        if (pc.remoteStreams.indexOf(stream) === -1) {
+          pc.remoteStreams.push(stream);
+          var event = new Event('addstream');
+          event.stream = stream;
+          window.setTimeout(function() {
+            pc._dispatchEvent('addstream', event);
+          });
+        }
+
+        receiverList.forEach(function(item) {
+          var track = item[0];
+          var receiver = item[1];
+          if (stream.id !== item[2].id) {
+            return;
+          }
+          fireAddTrack(pc, track, receiver, [stream]);
+        });
+      }
+    });
+    receiverList.forEach(function(item) {
+      if (item[2]) {
+        return;
+      }
+      fireAddTrack(pc, item[0], item[1], []);
+    });
+
+    // check whether addIceCandidate({}) was called within four seconds after
+    // setRemoteDescription.
+    window.setTimeout(function() {
+      if (!(pc && pc.transceivers)) {
+        return;
+      }
+      pc.transceivers.forEach(function(transceiver) {
+        if (transceiver.iceTransport &&
+            transceiver.iceTransport.state === 'new' &&
+            transceiver.iceTransport.getRemoteCandidates().length > 0) {
+          console.warn('Timeout for addRemoteCandidate. Consider sending ' +
+              'an end-of-candidates notification');
+          transceiver.iceTransport.addRemoteCandidate({});
+        }
+      });
+    }, 4000);
+
+    return Promise.resolve();
+  };
+
+  RTCPeerConnection.prototype.close = function() {
+    this.transceivers.forEach(function(transceiver) {
+      /* not yet
+      if (transceiver.iceGatherer) {
+        transceiver.iceGatherer.close();
+      }
+      */
+      if (transceiver.iceTransport) {
+        transceiver.iceTransport.stop();
+      }
+      if (transceiver.dtlsTransport) {
+        transceiver.dtlsTransport.stop();
+      }
+      if (transceiver.rtpSender) {
+        transceiver.rtpSender.stop();
+      }
+      if (transceiver.rtpReceiver) {
+        transceiver.rtpReceiver.stop();
+      }
+    });
+    // FIXME: clean up tracks, local streams, remote streams, etc
+    this._isClosed = true;
+    this._updateSignalingState('closed');
+  };
+
+  // Update the signaling state.
+  RTCPeerConnection.prototype._updateSignalingState = function(newState) {
+    this.signalingState = newState;
+    var event = new Event('signalingstatechange');
+    this._dispatchEvent('signalingstatechange', event);
+  };
+
+  // Determine whether to fire the negotiationneeded event.
+  RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
+    var pc = this;
+    if (this.signalingState !== 'stable' || this.needNegotiation === true) {
+      return;
+    }
+    this.needNegotiation = true;
+    window.setTimeout(function() {
+      if (pc.needNegotiation) {
+        pc.needNegotiation = false;
+        var event = new Event('negotiationneeded');
+        pc._dispatchEvent('negotiationneeded', event);
+      }
+    }, 0);
+  };
+
+  // Update the ice connection state.
+  RTCPeerConnection.prototype._updateIceConnectionState = function() {
+    var newState;
+    var states = {
+      'new': 0,
+      closed: 0,
+      checking: 0,
+      connected: 0,
+      completed: 0,
+      disconnected: 0,
+      failed: 0
+    };
+    this.transceivers.forEach(function(transceiver) {
+      if (transceiver.iceTransport && !transceiver.rejected) {
+        states[transceiver.iceTransport.state]++;
+      }
+    });
+
+    newState = 'new';
+    if (states.failed > 0) {
+      newState = 'failed';
+    } else if (states.checking > 0) {
+      newState = 'checking';
+    } else if (states.disconnected > 0) {
+      newState = 'disconnected';
+    } else if (states.new > 0) {
+      newState = 'new';
+    } else if (states.connected > 0) {
+      newState = 'connected';
+    } else if (states.completed > 0) {
+      newState = 'completed';
+    }
+
+    if (newState !== this.iceConnectionState) {
+      this.iceConnectionState = newState;
+      var event = new Event('iceconnectionstatechange');
+      this._dispatchEvent('iceconnectionstatechange', event);
+    }
+  };
+
+  // Update the connection state.
+  RTCPeerConnection.prototype._updateConnectionState = function() {
+    var newState;
+    var states = {
+      'new': 0,
+      closed: 0,
+      connecting: 0,
+      connected: 0,
+      completed: 0,
+      disconnected: 0,
+      failed: 0
+    };
+    this.transceivers.forEach(function(transceiver) {
+      if (transceiver.iceTransport && transceiver.dtlsTransport &&
+          !transceiver.rejected) {
+        states[transceiver.iceTransport.state]++;
+        states[transceiver.dtlsTransport.state]++;
+      }
+    });
+    // ICETransport.completed and connected are the same for this purpose.
+    states.connected += states.completed;
+
+    newState = 'new';
+    if (states.failed > 0) {
+      newState = 'failed';
+    } else if (states.connecting > 0) {
+      newState = 'connecting';
+    } else if (states.disconnected > 0) {
+      newState = 'disconnected';
+    } else if (states.new > 0) {
+      newState = 'new';
+    } else if (states.connected > 0) {
+      newState = 'connected';
+    }
+
+    if (newState !== this.connectionState) {
+      this.connectionState = newState;
+      var event = new Event('connectionstatechange');
+      this._dispatchEvent('connectionstatechange', event);
+    }
+  };
+
+  RTCPeerConnection.prototype.createOffer = function() {
+    var pc = this;
+
+    if (pc._isClosed) {
+      return Promise.reject(makeError('InvalidStateError',
+          'Can not call createOffer after close'));
+    }
+
+    var numAudioTracks = pc.transceivers.filter(function(t) {
+      return t.kind === 'audio';
+    }).length;
+    var numVideoTracks = pc.transceivers.filter(function(t) {
+      return t.kind === 'video';
+    }).length;
+
+    // Determine number of audio and video tracks we need to send/recv.
+    var offerOptions = arguments[0];
+    if (offerOptions) {
+      // Reject Chrome legacy constraints.
+      if (offerOptions.mandatory || offerOptions.optional) {
+        throw new TypeError(
+            'Legacy mandatory/optional constraints not supported.');
+      }
+      if (offerOptions.offerToReceiveAudio !== undefined) {
+        if (offerOptions.offerToReceiveAudio === true) {
+          numAudioTracks = 1;
+        } else if (offerOptions.offerToReceiveAudio === false) {
+          numAudioTracks = 0;
+        } else {
+          numAudioTracks = offerOptions.offerToReceiveAudio;
+        }
+      }
+      if (offerOptions.offerToReceiveVideo !== undefined) {
+        if (offerOptions.offerToReceiveVideo === true) {
+          numVideoTracks = 1;
+        } else if (offerOptions.offerToReceiveVideo === false) {
+          numVideoTracks = 0;
+        } else {
+          numVideoTracks = offerOptions.offerToReceiveVideo;
+        }
+      }
+    }
+
+    pc.transceivers.forEach(function(transceiver) {
+      if (transceiver.kind === 'audio') {
+        numAudioTracks--;
+        if (numAudioTracks < 0) {
+          transceiver.wantReceive = false;
+        }
+      } else if (transceiver.kind === 'video') {
+        numVideoTracks--;
+        if (numVideoTracks < 0) {
+          transceiver.wantReceive = false;
+        }
+      }
+    });
+
+    // Create M-lines for recvonly streams.
+    while (numAudioTracks > 0 || numVideoTracks > 0) {
+      if (numAudioTracks > 0) {
+        pc._createTransceiver('audio');
+        numAudioTracks--;
+      }
+      if (numVideoTracks > 0) {
+        pc._createTransceiver('video');
+        numVideoTracks--;
+      }
+    }
+
+    var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId,
+        pc._sdpSessionVersion++);
+    pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
+      // For each track, create an ice gatherer, ice transport,
+      // dtls transport, potentially rtpsender and rtpreceiver.
+      var track = transceiver.track;
+      var kind = transceiver.kind;
+      var mid = transceiver.mid || SDPUtils.generateIdentifier();
+      transceiver.mid = mid;
+
+      if (!transceiver.iceGatherer) {
+        transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,
+            pc.usingBundle);
+      }
+
+      var localCapabilities = window.RTCRtpSender.getCapabilities(kind);
+      // filter RTX until additional stuff needed for RTX is implemented
+      // in adapter.js
+      if (edgeVersion < 15019) {
+        localCapabilities.codecs = localCapabilities.codecs.filter(
+            function(codec) {
+              return codec.name !== 'rtx';
+            });
+      }
+      localCapabilities.codecs.forEach(function(codec) {
+        // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
+        // by adding level-asymmetry-allowed=1
+        if (codec.name === 'H264' &&
+            codec.parameters['level-asymmetry-allowed'] === undefined) {
+          codec.parameters['level-asymmetry-allowed'] = '1';
+        }
+
+        // for subsequent offers, we might have to re-use the payload
+        // type of the last offer.
+        if (transceiver.remoteCapabilities &&
+            transceiver.remoteCapabilities.codecs) {
+          transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec) {
+            if (codec.name.toLowerCase() === remoteCodec.name.toLowerCase() &&
+                codec.clockRate === remoteCodec.clockRate) {
+              codec.preferredPayloadType = remoteCodec.payloadType;
+            }
+          });
+        }
+      });
+      localCapabilities.headerExtensions.forEach(function(hdrExt) {
+        var remoteExtensions = transceiver.remoteCapabilities &&
+            transceiver.remoteCapabilities.headerExtensions || [];
+        remoteExtensions.forEach(function(rHdrExt) {
+          if (hdrExt.uri === rHdrExt.uri) {
+            hdrExt.id = rHdrExt.id;
+          }
+        });
+      });
+
+      // generate an ssrc now, to be used later in rtpSender.send
+      var sendEncodingParameters = transceiver.sendEncodingParameters || [{
+        ssrc: (2 * sdpMLineIndex + 1) * 1001
+      }];
+      if (track) {
+        // add RTX
+        if (edgeVersion >= 15019 && kind === 'video' &&
+            !sendEncodingParameters[0].rtx) {
+          sendEncodingParameters[0].rtx = {
+            ssrc: sendEncodingParameters[0].ssrc + 1
+          };
+        }
+      }
+
+      if (transceiver.wantReceive) {
+        transceiver.rtpReceiver = new window.RTCRtpReceiver(
+            transceiver.dtlsTransport, kind);
+      }
+
+      transceiver.localCapabilities = localCapabilities;
+      transceiver.sendEncodingParameters = sendEncodingParameters;
+    });
+
+    // always offer BUNDLE and dispose on return if not supported.
+    if (pc._config.bundlePolicy !== 'max-compat') {
+      sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) {
+        return t.mid;
+      }).join(' ') + '\r\n';
+    }
+    sdp += 'a=ice-options:trickle\r\n';
+
+    pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
+      sdp += writeMediaSection(transceiver, transceiver.localCapabilities,
+          'offer', transceiver.stream, pc._dtlsRole);
+      sdp += 'a=rtcp-rsize\r\n';
+
+      if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' &&
+          (sdpMLineIndex === 0 || !pc.usingBundle)) {
+        transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) {
+          cand.component = 1;
+          sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n';
+        });
+
+        if (transceiver.iceGatherer.state === 'completed') {
+          sdp += 'a=end-of-candidates\r\n';
+        }
+      }
+    });
+
+    var desc = new window.RTCSessionDescription({
+      type: 'offer',
+      sdp: sdp
+    });
+    return Promise.resolve(desc);
+  };
+
+  RTCPeerConnection.prototype.createAnswer = function() {
+    var pc = this;
+
+    if (pc._isClosed) {
+      return Promise.reject(makeError('InvalidStateError',
+          'Can not call createAnswer after close'));
+    }
+
+    if (!(pc.signalingState === 'have-remote-offer' ||
+        pc.signalingState === 'have-local-pranswer')) {
+      return Promise.reject(makeError('InvalidStateError',
+          'Can not call createAnswer in signalingState ' + pc.signalingState));
+    }
+
+    var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId,
+        pc._sdpSessionVersion++);
+    if (pc.usingBundle) {
+      sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) {
+        return t.mid;
+      }).join(' ') + '\r\n';
+    }
+    sdp += 'a=ice-options:trickle\r\n';
+
+    var mediaSectionsInOffer = SDPUtils.getMediaSections(
+        pc._remoteDescription.sdp).length;
+    pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
+      if (sdpMLineIndex + 1 > mediaSectionsInOffer) {
+        return;
+      }
+      if (transceiver.rejected) {
+        if (transceiver.kind === 'application') {
+          if (transceiver.protocol === 'DTLS/SCTP') { // legacy fmt
+            sdp += 'm=application 0 DTLS/SCTP 5000\r\n';
+          } else {
+            sdp += 'm=application 0 ' + transceiver.protocol +
+                ' webrtc-datachannel\r\n';
+          }
+        } else if (transceiver.kind === 'audio') {
+          sdp += 'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' +
+              'a=rtpmap:0 PCMU/8000\r\n';
+        } else if (transceiver.kind === 'video') {
+          sdp += 'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' +
+              'a=rtpmap:120 VP8/90000\r\n';
+        }
+        sdp += 'c=IN IP4 0.0.0.0\r\n' +
+            'a=inactive\r\n' +
+            'a=mid:' + transceiver.mid + '\r\n';
+        return;
+      }
+
+      // FIXME: look at direction.
+      if (transceiver.stream) {
+        var localTrack;
+        if (transceiver.kind === 'audio') {
+          localTrack = transceiver.stream.getAudioTracks()[0];
+        } else if (transceiver.kind === 'video') {
+          localTrack = transceiver.stream.getVideoTracks()[0];
+        }
+        if (localTrack) {
+          // add RTX
+          if (edgeVersion >= 15019 && transceiver.kind === 'video' &&
+              !transceiver.sendEncodingParameters[0].rtx) {
+            transceiver.sendEncodingParameters[0].rtx = {
+              ssrc: transceiver.sendEncodingParameters[0].ssrc + 1
+            };
+          }
+        }
+      }
+
+      // Calculate intersection of capabilities.
+      var commonCapabilities = getCommonCapabilities(
+          transceiver.localCapabilities,
+          transceiver.remoteCapabilities);
+
+      var hasRtx = commonCapabilities.codecs.filter(function(c) {
+        return c.name.toLowerCase() === 'rtx';
+      }).length;
+      if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
+        delete transceiver.sendEncodingParameters[0].rtx;
+      }
+
+      sdp += writeMediaSection(transceiver, commonCapabilities,
+          'answer', transceiver.stream, pc._dtlsRole);
+      if (transceiver.rtcpParameters &&
+          transceiver.rtcpParameters.reducedSize) {
+        sdp += 'a=rtcp-rsize\r\n';
+      }
+    });
+
+    var desc = new window.RTCSessionDescription({
+      type: 'answer',
+      sdp: sdp
+    });
+    return Promise.resolve(desc);
+  };
+
+  RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
+    var pc = this;
+    var sections;
+    if (candidate && !(candidate.sdpMLineIndex !== undefined ||
+        candidate.sdpMid)) {
+      return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required'));
+    }
+
+    // TODO: needs to go into ops queue.
+    return new Promise(function(resolve, reject) {
+      if (!pc._remoteDescription) {
+        return reject(makeError('InvalidStateError',
+            'Can not add ICE candidate without a remote description'));
+      } else if (!candidate || candidate.candidate === '') {
+        for (var j = 0; j < pc.transceivers.length; j++) {
+          if (pc.transceivers[j].rejected) {
+            continue;
+          }
+          pc.transceivers[j].iceTransport.addRemoteCandidate({});
+          sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp);
+          sections[j] += 'a=end-of-candidates\r\n';
+          pc._remoteDescription.sdp =
+              SDPUtils.getDescription(pc._remoteDescription.sdp) +
+              sections.join('');
+          if (pc.usingBundle) {
+            break;
+          }
+        }
+      } else {
+        var sdpMLineIndex = candidate.sdpMLineIndex;
+        if (candidate.sdpMid) {
+          for (var i = 0; i < pc.transceivers.length; i++) {
+            if (pc.transceivers[i].mid === candidate.sdpMid) {
+              sdpMLineIndex = i;
+              break;
+            }
+          }
+        }
+        var transceiver = pc.transceivers[sdpMLineIndex];
+        if (transceiver) {
+          if (transceiver.rejected) {
+            return resolve();
+          }
+          var cand = Object.keys(candidate.candidate).length > 0 ?
+              SDPUtils.parseCandidate(candidate.candidate) : {};
+          // Ignore Chrome's invalid candidates since Edge does not like them.
+          if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {
+            return resolve();
+          }
+          // Ignore RTCP candidates, we assume RTCP-MUX.
+          if (cand.component && cand.component !== 1) {
+            return resolve();
+          }
+          // when using bundle, avoid adding candidates to the wrong
+          // ice transport. And avoid adding candidates added in the SDP.
+          if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 &&
+              transceiver.iceTransport !== pc.transceivers[0].iceTransport)) {
+            if (!maybeAddCandidate(transceiver.iceTransport, cand)) {
+              return reject(makeError('OperationError',
+                  'Can not add ICE candidate'));
+            }
+          }
+
+          // update the remoteDescription.
+          var candidateString = candidate.candidate.trim();
+          if (candidateString.indexOf('a=') === 0) {
+            candidateString = candidateString.substr(2);
+          }
+          sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp);
+          sections[sdpMLineIndex] += 'a=' +
+              (cand.type ? candidateString : 'end-of-candidates')
+              + '\r\n';
+          pc._remoteDescription.sdp =
+              SDPUtils.getDescription(pc._remoteDescription.sdp) +
+              sections.join('');
+        } else {
+          return reject(makeError('OperationError',
+              'Can not add ICE candidate'));
+        }
+      }
+      resolve();
+    });
+  };
+
+  RTCPeerConnection.prototype.getStats = function(selector) {
+    if (selector && selector instanceof window.MediaStreamTrack) {
+      var senderOrReceiver = null;
+      this.transceivers.forEach(function(transceiver) {
+        if (transceiver.rtpSender &&
+            transceiver.rtpSender.track === selector) {
+          senderOrReceiver = transceiver.rtpSender;
+        } else if (transceiver.rtpReceiver &&
+            transceiver.rtpReceiver.track === selector) {
+          senderOrReceiver = transceiver.rtpReceiver;
+        }
+      });
+      if (!senderOrReceiver) {
+        throw makeError('InvalidAccessError', 'Invalid selector.');
+      }
+      return senderOrReceiver.getStats();
+    }
+
+    var promises = [];
+    this.transceivers.forEach(function(transceiver) {
+      ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
+          'dtlsTransport'].forEach(function(method) {
+            if (transceiver[method]) {
+              promises.push(transceiver[method].getStats());
+            }
+          });
+    });
+    return Promise.all(promises).then(function(allStats) {
+      var results = new Map();
+      allStats.forEach(function(stats) {
+        stats.forEach(function(stat) {
+          results.set(stat.id, stat);
+        });
+      });
+      return results;
+    });
+  };
+
+  // fix low-level stat names and return Map instead of object.
+  var ortcObjects = ['RTCRtpSender', 'RTCRtpReceiver', 'RTCIceGatherer',
+    'RTCIceTransport', 'RTCDtlsTransport'];
+  ortcObjects.forEach(function(ortcObjectName) {
+    var obj = window[ortcObjectName];
+    if (obj && obj.prototype && obj.prototype.getStats) {
+      var nativeGetstats = obj.prototype.getStats;
+      obj.prototype.getStats = function() {
+        return nativeGetstats.apply(this)
+        .then(function(nativeStats) {
+          var mapStats = new Map();
+          Object.keys(nativeStats).forEach(function(id) {
+            nativeStats[id].type = fixStatsType(nativeStats[id]);
+            mapStats.set(id, nativeStats[id]);
+          });
+          return mapStats;
+        });
+      };
+    }
+  });
+
+  // legacy callback shims. Should be moved to adapter.js some days.
+  var methods = ['createOffer', 'createAnswer'];
+  methods.forEach(function(method) {
+    var nativeMethod = RTCPeerConnection.prototype[method];
+    RTCPeerConnection.prototype[method] = function() {
+      var args = arguments;
+      if (typeof args[0] === 'function' ||
+          typeof args[1] === 'function') { // legacy
+        return nativeMethod.apply(this, [arguments[2]])
+        .then(function(description) {
+          if (typeof args[0] === 'function') {
+            args[0].apply(null, [description]);
+          }
+        }, function(error) {
+          if (typeof args[1] === 'function') {
+            args[1].apply(null, [error]);
+          }
+        });
+      }
+      return nativeMethod.apply(this, arguments);
+    };
+  });
+
+  methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'];
+  methods.forEach(function(method) {
+    var nativeMethod = RTCPeerConnection.prototype[method];
+    RTCPeerConnection.prototype[method] = function() {
+      var args = arguments;
+      if (typeof args[1] === 'function' ||
+          typeof args[2] === 'function') { // legacy
+        return nativeMethod.apply(this, arguments)
+        .then(function() {
+          if (typeof args[1] === 'function') {
+            args[1].apply(null);
+          }
+        }, function(error) {
+          if (typeof args[2] === 'function') {
+            args[2].apply(null, [error]);
+          }
+        });
+      }
+      return nativeMethod.apply(this, arguments);
+    };
+  });
+
+  // getStats is special. It doesn't have a spec legacy method yet we support
+  // getStats(something, cb) without error callbacks.
+  ['getStats'].forEach(function(method) {
+    var nativeMethod = RTCPeerConnection.prototype[method];
+    RTCPeerConnection.prototype[method] = function() {
+      var args = arguments;
+      if (typeof args[1] === 'function') {
+        return nativeMethod.apply(this, arguments)
+        .then(function() {
+          if (typeof args[1] === 'function') {
+            args[1].apply(null);
+          }
+        });
+      }
+      return nativeMethod.apply(this, arguments);
+    };
+  });
+
+  return RTCPeerConnection;
+};
+
+},{"sdp":17}],17:[function(require,module,exports){
+/* eslint-env node */
+'use strict';
+
+// SDP helpers.
+var SDPUtils = {};
+
+// Generate an alphanumeric identifier for cname or mids.
+// TODO: use UUIDs instead? https://gist.github.com/jed/982883
+SDPUtils.generateIdentifier = function() {
+  return Math.random().toString(36).substr(2, 10);
+};
+
+// The RTCP CNAME used by all peerconnections from the same JS.
+SDPUtils.localCName = SDPUtils.generateIdentifier();
+
+// Splits SDP into lines, dealing with both CRLF and LF.
+SDPUtils.splitLines = function(blob) {
+  return blob.trim().split('\n').map(function(line) {
+    return line.trim();
+  });
+};
+// Splits SDP into sessionpart and mediasections. Ensures CRLF.
+SDPUtils.splitSections = function(blob) {
+  var parts = blob.split('\nm=');
+  return parts.map(function(part, index) {
+    return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
+  });
+};
+
+// returns the session description.
+SDPUtils.getDescription = function(blob) {
+  var sections = SDPUtils.splitSections(blob);
+  return sections && sections[0];
+};
+
+// returns the individual media sections.
+SDPUtils.getMediaSections = function(blob) {
+  var sections = SDPUtils.splitSections(blob);
+  sections.shift();
+  return sections;
+};
+
+// Returns lines that start with a certain prefix.
+SDPUtils.matchPrefix = function(blob, prefix) {
+  return SDPUtils.splitLines(blob).filter(function(line) {
+    return line.indexOf(prefix) === 0;
+  });
+};
+
+// Parses an ICE candidate line. Sample input:
+// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
+// rport 55996"
+SDPUtils.parseCandidate = function(line) {
+  var parts;
+  // Parse both variants.
+  if (line.indexOf('a=candidate:') === 0) {
+    parts = line.substring(12).split(' ');
+  } else {
+    parts = line.substring(10).split(' ');
+  }
+
+  var candidate = {
+    foundation: parts[0],
+    component: parseInt(parts[1], 10),
+    protocol: parts[2].toLowerCase(),
+    priority: parseInt(parts[3], 10),
+    ip: parts[4],
+    address: parts[4], // address is an alias for ip.
+    port: parseInt(parts[5], 10),
+    // skip parts[6] == 'typ'
+    type: parts[7]
+  };
+
+  for (var i = 8; i < parts.length; i += 2) {
+    switch (parts[i]) {
+      case 'raddr':
+        candidate.relatedAddress = parts[i + 1];
+        break;
+      case 'rport':
+        candidate.relatedPort = parseInt(parts[i + 1], 10);
+        break;
+      case 'tcptype':
+        candidate.tcpType = parts[i + 1];
+        break;
+      case 'ufrag':
+        candidate.ufrag = parts[i + 1]; // for backward compability.
+        candidate.usernameFragment = parts[i + 1];
+        break;
+      default: // extension handling, in particular ufrag
+        candidate[parts[i]] = parts[i + 1];
+        break;
+    }
+  }
+  return candidate;
+};
+
+// Translates a candidate object into SDP candidate attribute.
+SDPUtils.writeCandidate = function(candidate) {
+  var sdp = [];
+  sdp.push(candidate.foundation);
+  sdp.push(candidate.component);
+  sdp.push(candidate.protocol.toUpperCase());
+  sdp.push(candidate.priority);
+  sdp.push(candidate.address || candidate.ip);
+  sdp.push(candidate.port);
+
+  var type = candidate.type;
+  sdp.push('typ');
+  sdp.push(type);
+  if (type !== 'host' && candidate.relatedAddress &&
+      candidate.relatedPort) {
+    sdp.push('raddr');
+    sdp.push(candidate.relatedAddress);
+    sdp.push('rport');
+    sdp.push(candidate.relatedPort);
+  }
+  if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
+    sdp.push('tcptype');
+    sdp.push(candidate.tcpType);
+  }
+  if (candidate.usernameFragment || candidate.ufrag) {
+    sdp.push('ufrag');
+    sdp.push(candidate.usernameFragment || candidate.ufrag);
+  }
+  return 'candidate:' + sdp.join(' ');
+};
+
+// Parses an ice-options line, returns an array of option tags.
+// a=ice-options:foo bar
+SDPUtils.parseIceOptions = function(line) {
+  return line.substr(14).split(' ');
+};
+
+// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
+// a=rtpmap:111 opus/48000/2
+SDPUtils.parseRtpMap = function(line) {
+  var parts = line.substr(9).split(' ');
+  var parsed = {
+    payloadType: parseInt(parts.shift(), 10) // was: id
+  };
+
+  parts = parts[0].split('/');
+
+  parsed.name = parts[0];
+  parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
+  parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
+  // legacy alias, got renamed back to channels in ORTC.
+  parsed.numChannels = parsed.channels;
+  return parsed;
+};
+
+// Generate an a=rtpmap line from RTCRtpCodecCapability or
+// RTCRtpCodecParameters.
+SDPUtils.writeRtpMap = function(codec) {
+  var pt = codec.payloadType;
+  if (codec.preferredPayloadType !== undefined) {
+    pt = codec.preferredPayloadType;
+  }
+  var channels = codec.channels || codec.numChannels || 1;
+  return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
+      (channels !== 1 ? '/' + channels : '') + '\r\n';
+};
+
+// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
+// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
+// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
+SDPUtils.parseExtmap = function(line) {
+  var parts = line.substr(9).split(' ');
+  return {
+    id: parseInt(parts[0], 10),
+    direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
+    uri: parts[1]
+  };
+};
+
+// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
+// RTCRtpHeaderExtension.
+SDPUtils.writeExtmap = function(headerExtension) {
+  return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
+      (headerExtension.direction && headerExtension.direction !== 'sendrecv'
+        ? '/' + headerExtension.direction
+        : '') +
+      ' ' + headerExtension.uri + '\r\n';
+};
+
+// Parses an ftmp line, returns dictionary. Sample input:
+// a=fmtp:96 vbr=on;cng=on
+// Also deals with vbr=on; cng=on
+SDPUtils.parseFmtp = function(line) {
+  var parsed = {};
+  var kv;
+  var parts = line.substr(line.indexOf(' ') + 1).split(';');
+  for (var j = 0; j < parts.length; j++) {
+    kv = parts[j].trim().split('=');
+    parsed[kv[0].trim()] = kv[1];
+  }
+  return parsed;
+};
+
+// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
+SDPUtils.writeFmtp = function(codec) {
+  var line = '';
+  var pt = codec.payloadType;
+  if (codec.preferredPayloadType !== undefined) {
+    pt = codec.preferredPayloadType;
+  }
+  if (codec.parameters && Object.keys(codec.parameters).length) {
+    var params = [];
+    Object.keys(codec.parameters).forEach(function(param) {
+      if (codec.parameters[param]) {
+        params.push(param + '=' + codec.parameters[param]);
+      } else {
+        params.push(param);
+      }
+    });
+    line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
+  }
+  return line;
+};
+
+// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
+// a=rtcp-fb:98 nack rpsi
+SDPUtils.parseRtcpFb = function(line) {
+  var parts = line.substr(line.indexOf(' ') + 1).split(' ');
+  return {
+    type: parts.shift(),
+    parameter: parts.join(' ')
+  };
+};
+// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
+SDPUtils.writeRtcpFb = function(codec) {
+  var lines = '';
+  var pt = codec.payloadType;
+  if (codec.preferredPayloadType !== undefined) {
+    pt = codec.preferredPayloadType;
+  }
+  if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
+    // FIXME: special handling for trr-int?
+    codec.rtcpFeedback.forEach(function(fb) {
+      lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +
+      (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +
+          '\r\n';
+    });
+  }
+  return lines;
+};
+
+// Parses an RFC 5576 ssrc media attribute. Sample input:
+// a=ssrc:3735928559 cname:something
+SDPUtils.parseSsrcMedia = function(line) {
+  var sp = line.indexOf(' ');
+  var parts = {
+    ssrc: parseInt(line.substr(7, sp - 7), 10)
+  };
+  var colon = line.indexOf(':', sp);
+  if (colon > -1) {
+    parts.attribute = line.substr(sp + 1, colon - sp - 1);
+    parts.value = line.substr(colon + 1);
+  } else {
+    parts.attribute = line.substr(sp + 1);
+  }
+  return parts;
+};
+
+SDPUtils.parseSsrcGroup = function(line) {
+  var parts = line.substr(13).split(' ');
+  return {
+    semantics: parts.shift(),
+    ssrcs: parts.map(function(ssrc) {
+      return parseInt(ssrc, 10);
+    })
+  };
+};
+
+// Extracts the MID (RFC 5888) from a media section.
+// returns the MID or undefined if no mid line was found.
+SDPUtils.getMid = function(mediaSection) {
+  var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
+  if (mid) {
+    return mid.substr(6);
+  }
+};
+
+SDPUtils.parseFingerprint = function(line) {
+  var parts = line.substr(14).split(' ');
+  return {
+    algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
+    value: parts[1]
+  };
+};
+
+// Extracts DTLS parameters from SDP media section or sessionpart.
+// FIXME: for consistency with other functions this should only
+//   get the fingerprint line as input. See also getIceParameters.
+SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
+  var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
+    'a=fingerprint:');
+  // Note: a=setup line is ignored since we use the 'auto' role.
+  // Note2: 'algorithm' is not case sensitive except in Edge.
+  return {
+    role: 'auto',
+    fingerprints: lines.map(SDPUtils.parseFingerprint)
+  };
+};
+
+// Serializes DTLS parameters to SDP.
+SDPUtils.writeDtlsParameters = function(params, setupType) {
+  var sdp = 'a=setup:' + setupType + '\r\n';
+  params.fingerprints.forEach(function(fp) {
+    sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
+  });
+  return sdp;
+};
+
+// Parses a=crypto lines into
+//   https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members
+SDPUtils.parseCryptoLine = function(line) {
+  var parts = line.substr(9).split(' ');
+  return {
+    tag: parseInt(parts[0], 10),
+    cryptoSuite: parts[1],
+    keyParams: parts[2],
+    sessionParams: parts.slice(3),
+  };
+};
+
+SDPUtils.writeCryptoLine = function(parameters) {
+  return 'a=crypto:' + parameters.tag + ' ' +
+    parameters.cryptoSuite + ' ' +
+    (typeof parameters.keyParams === 'object'
+      ? SDPUtils.writeCryptoKeyParams(parameters.keyParams)
+      : parameters.keyParams) +
+    (parameters.sessionParams ? ' ' + parameters.sessionParams.join(' ') : '') +
+    '\r\n';
+};
+
+// Parses the crypto key parameters into
+//   https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam*
+SDPUtils.parseCryptoKeyParams = function(keyParams) {
+  if (keyParams.indexOf('inline:') !== 0) {
+    return null;
+  }
+  var parts = keyParams.substr(7).split('|');
+  return {
+    keyMethod: 'inline',
+    keySalt: parts[0],
+    lifeTime: parts[1],
+    mkiValue: parts[2] ? parts[2].split(':')[0] : undefined,
+    mkiLength: parts[2] ? parts[2].split(':')[1] : undefined,
+  };
+};
+
+SDPUtils.writeCryptoKeyParams = function(keyParams) {
+  return keyParams.keyMethod + ':'
+    + keyParams.keySalt +
+    (keyParams.lifeTime ? '|' + keyParams.lifeTime : '') +
+    (keyParams.mkiValue && keyParams.mkiLength
+      ? '|' + keyParams.mkiValue + ':' + keyParams.mkiLength
+      : '');
+};
+
+// Extracts all SDES paramters.
+SDPUtils.getCryptoParameters = function(mediaSection, sessionpart) {
+  var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
+    'a=crypto:');
+  return lines.map(SDPUtils.parseCryptoLine);
+};
+
+// Parses ICE information from SDP media section or sessionpart.
+// FIXME: for consistency with other functions this should only
+//   get the ice-ufrag and ice-pwd lines as input.
+SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
+  var ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart,
+    'a=ice-ufrag:')[0];
+  var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart,
+    'a=ice-pwd:')[0];
+  if (!(ufrag && pwd)) {
+    return null;
+  }
+  return {
+    usernameFragment: ufrag.substr(12),
+    password: pwd.substr(10),
+  };
+};
+
+// Serializes ICE parameters to SDP.
+SDPUtils.writeIceParameters = function(params) {
+  return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
+      'a=ice-pwd:' + params.password + '\r\n';
+};
+
+// Parses the SDP media section and returns RTCRtpParameters.
+SDPUtils.parseRtpParameters = function(mediaSection) {
+  var description = {
+    codecs: [],
+    headerExtensions: [],
+    fecMechanisms: [],
+    rtcp: []
+  };
+  var lines = SDPUtils.splitLines(mediaSection);
+  var mline = lines[0].split(' ');
+  for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
+    var pt = mline[i];
+    var rtpmapline = SDPUtils.matchPrefix(
+      mediaSection, 'a=rtpmap:' + pt + ' ')[0];
+    if (rtpmapline) {
+      var codec = SDPUtils.parseRtpMap(rtpmapline);
+      var fmtps = SDPUtils.matchPrefix(
+        mediaSection, 'a=fmtp:' + pt + ' ');
+      // Only the first a=fmtp:<pt> is considered.
+      codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
+      codec.rtcpFeedback = SDPUtils.matchPrefix(
+        mediaSection, 'a=rtcp-fb:' + pt + ' ')
+        .map(SDPUtils.parseRtcpFb);
+      description.codecs.push(codec);
+      // parse FEC mechanisms from rtpmap lines.
+      switch (codec.name.toUpperCase()) {
+        case 'RED':
+        case 'ULPFEC':
+          description.fecMechanisms.push(codec.name.toUpperCase());
+          break;
+        default: // only RED and ULPFEC are recognized as FEC mechanisms.
+          break;
+      }
+    }
+  }
+  SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {
+    description.headerExtensions.push(SDPUtils.parseExtmap(line));
+  });
+  // FIXME: parse rtcp.
+  return description;
+};
+
+// Generates parts of the SDP media section describing the capabilities /
+// parameters.
+SDPUtils.writeRtpDescription = function(kind, caps) {
+  var sdp = '';
+
+  // Build the mline.
+  sdp += 'm=' + kind + ' ';
+  sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
+  sdp += ' UDP/TLS/RTP/SAVPF ';
+  sdp += caps.codecs.map(function(codec) {
+    if (codec.preferredPayloadType !== undefined) {
+      return codec.preferredPayloadType;
+    }
+    return codec.payloadType;
+  }).join(' ') + '\r\n';
+
+  sdp += 'c=IN IP4 0.0.0.0\r\n';
+  sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
+
+  // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
+  caps.codecs.forEach(function(codec) {
+    sdp += SDPUtils.writeRtpMap(codec);
+    sdp += SDPUtils.writeFmtp(codec);
+    sdp += SDPUtils.writeRtcpFb(codec);
+  });
+  var maxptime = 0;
+  caps.codecs.forEach(function(codec) {
+    if (codec.maxptime > maxptime) {
+      maxptime = codec.maxptime;
+    }
+  });
+  if (maxptime > 0) {
+    sdp += 'a=maxptime:' + maxptime + '\r\n';
+  }
+  sdp += 'a=rtcp-mux\r\n';
+
+  if (caps.headerExtensions) {
+    caps.headerExtensions.forEach(function(extension) {
+      sdp += SDPUtils.writeExtmap(extension);
+    });
+  }
+  // FIXME: write fecMechanisms.
+  return sdp;
+};
+
+// Parses the SDP media section and returns an array of
+// RTCRtpEncodingParameters.
+SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
+  var encodingParameters = [];
+  var description = SDPUtils.parseRtpParameters(mediaSection);
+  var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
+  var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
+
+  // filter a=ssrc:... cname:, ignore PlanB-msid
+  var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
+    .map(function(line) {
+      return SDPUtils.parseSsrcMedia(line);
+    })
+    .filter(function(parts) {
+      return parts.attribute === 'cname';
+    });
+  var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
+  var secondarySsrc;
+
+  var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
+    .map(function(line) {
+      var parts = line.substr(17).split(' ');
+      return parts.map(function(part) {
+        return parseInt(part, 10);
+      });
+    });
+  if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
+    secondarySsrc = flows[0][1];
+  }
+
+  description.codecs.forEach(function(codec) {
+    if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
+      var encParam = {
+        ssrc: primarySsrc,
+        codecPayloadType: parseInt(codec.parameters.apt, 10)
+      };
+      if (primarySsrc && secondarySsrc) {
+        encParam.rtx = {ssrc: secondarySsrc};
+      }
+      encodingParameters.push(encParam);
+      if (hasRed) {
+        encParam = JSON.parse(JSON.stringify(encParam));
+        encParam.fec = {
+          ssrc: primarySsrc,
+          mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
+        };
+        encodingParameters.push(encParam);
+      }
+    }
+  });
+  if (encodingParameters.length === 0 && primarySsrc) {
+    encodingParameters.push({
+      ssrc: primarySsrc
+    });
+  }
+
+  // we support both b=AS and b=TIAS but interpret AS as TIAS.
+  var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
+  if (bandwidth.length) {
+    if (bandwidth[0].indexOf('b=TIAS:') === 0) {
+      bandwidth = parseInt(bandwidth[0].substr(7), 10);
+    } else if (bandwidth[0].indexOf('b=AS:') === 0) {
+      // use formula from JSEP to convert b=AS to TIAS value.
+      bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95
+          - (50 * 40 * 8);
+    } else {
+      bandwidth = undefined;
+    }
+    encodingParameters.forEach(function(params) {
+      params.maxBitrate = bandwidth;
+    });
+  }
+  return encodingParameters;
+};
+
+// parses http://draft.ortc.org/#rtcrtcpparameters*
+SDPUtils.parseRtcpParameters = function(mediaSection) {
+  var rtcpParameters = {};
+
+  // Gets the first SSRC. Note tha with RTX there might be multiple
+  // SSRCs.
+  var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
+    .map(function(line) {
+      return SDPUtils.parseSsrcMedia(line);
+    })
+    .filter(function(obj) {
+      return obj.attribute === 'cname';
+    })[0];
+  if (remoteSsrc) {
+    rtcpParameters.cname = remoteSsrc.value;
+    rtcpParameters.ssrc = remoteSsrc.ssrc;
+  }
+
+  // Edge uses the compound attribute instead of reducedSize
+  // compound is !reducedSize
+  var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
+  rtcpParameters.reducedSize = rsize.length > 0;
+  rtcpParameters.compound = rsize.length === 0;
+
+  // parses the rtcp-mux attrÑ–bute.
+  // Note that Edge does not support unmuxed RTCP.
+  var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
+  rtcpParameters.mux = mux.length > 0;
+
+  return rtcpParameters;
+};
+
+// parses either a=msid: or a=ssrc:... msid lines and returns
+// the id of the MediaStream and MediaStreamTrack.
+SDPUtils.parseMsid = function(mediaSection) {
+  var parts;
+  var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
+  if (spec.length === 1) {
+    parts = spec[0].substr(7).split(' ');
+    return {stream: parts[0], track: parts[1]};
+  }
+  var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
+    .map(function(line) {
+      return SDPUtils.parseSsrcMedia(line);
+    })
+    .filter(function(msidParts) {
+      return msidParts.attribute === 'msid';
+    });
+  if (planB.length > 0) {
+    parts = planB[0].value.split(' ');
+    return {stream: parts[0], track: parts[1]};
+  }
+};
+
+// SCTP
+// parses draft-ietf-mmusic-sctp-sdp-26 first and falls back
+// to draft-ietf-mmusic-sctp-sdp-05
+SDPUtils.parseSctpDescription = function(mediaSection) {
+  var mline = SDPUtils.parseMLine(mediaSection);
+  var maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:');
+  var maxMessageSize;
+  if (maxSizeLine.length > 0) {
+    maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10);
+  }
+  if (isNaN(maxMessageSize)) {
+    maxMessageSize = 65536;
+  }
+  var sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:');
+  if (sctpPort.length > 0) {
+    return {
+      port: parseInt(sctpPort[0].substr(12), 10),
+      protocol: mline.fmt,
+      maxMessageSize: maxMessageSize
+    };
+  }
+  var sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:');
+  if (sctpMapLines.length > 0) {
+    var parts = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:')[0]
+      .substr(10)
+      .split(' ');
+    return {
+      port: parseInt(parts[0], 10),
+      protocol: parts[1],
+      maxMessageSize: maxMessageSize
+    };
+  }
+};
+
+// SCTP
+// outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers
+// support by now receiving in this format, unless we originally parsed
+// as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line
+// protocol of DTLS/SCTP -- without UDP/ or TCP/)
+SDPUtils.writeSctpDescription = function(media, sctp) {
+  var output = [];
+  if (media.protocol !== 'DTLS/SCTP') {
+    output = [
+      'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.protocol + '\r\n',
+      'c=IN IP4 0.0.0.0\r\n',
+      'a=sctp-port:' + sctp.port + '\r\n'
+    ];
+  } else {
+    output = [
+      'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.port + '\r\n',
+      'c=IN IP4 0.0.0.0\r\n',
+      'a=sctpmap:' + sctp.port + ' ' + sctp.protocol + ' 65535\r\n'
+    ];
+  }
+  if (sctp.maxMessageSize !== undefined) {
+    output.push('a=max-message-size:' + sctp.maxMessageSize + '\r\n');
+  }
+  return output.join('');
+};
+
+// Generate a session ID for SDP.
+// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
+// recommends using a cryptographically random +ve 64-bit value
+// but right now this should be acceptable and within the right range
+SDPUtils.generateSessionId = function() {
+  return Math.random().toString().substr(2, 21);
+};
+
+// Write boilder plate for start of SDP
+// sessId argument is optional - if not supplied it will
+// be generated randomly
+// sessVersion is optional and defaults to 2
+// sessUser is optional and defaults to 'thisisadapterortc'
+SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) {
+  var sessionId;
+  var version = sessVer !== undefined ? sessVer : 2;
+  if (sessId) {
+    sessionId = sessId;
+  } else {
+    sessionId = SDPUtils.generateSessionId();
+  }
+  var user = sessUser || 'thisisadapterortc';
+  // FIXME: sess-id should be an NTP timestamp.
+  return 'v=0\r\n' +
+      'o=' + user + ' ' + sessionId + ' ' + version +
+        ' IN IP4 127.0.0.1\r\n' +
+      's=-\r\n' +
+      't=0 0\r\n';
+};
+
+SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
+  var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
+
+  // Map ICE parameters (ufrag, pwd) to SDP.
+  sdp += SDPUtils.writeIceParameters(
+    transceiver.iceGatherer.getLocalParameters());
+
+  // Map DTLS parameters to SDP.
+  sdp += SDPUtils.writeDtlsParameters(
+    transceiver.dtlsTransport.getLocalParameters(),
+    type === 'offer' ? 'actpass' : 'active');
+
+  sdp += 'a=mid:' + transceiver.mid + '\r\n';
+
+  if (transceiver.direction) {
+    sdp += 'a=' + transceiver.direction + '\r\n';
+  } else if (transceiver.rtpSender && transceiver.rtpReceiver) {
+    sdp += 'a=sendrecv\r\n';
+  } else if (transceiver.rtpSender) {
+    sdp += 'a=sendonly\r\n';
+  } else if (transceiver.rtpReceiver) {
+    sdp += 'a=recvonly\r\n';
+  } else {
+    sdp += 'a=inactive\r\n';
+  }
+
+  if (transceiver.rtpSender) {
+    // spec.
+    var msid = 'msid:' + stream.id + ' ' +
+        transceiver.rtpSender.track.id + '\r\n';
+    sdp += 'a=' + msid;
+
+    // for Chrome.
+    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
+        ' ' + msid;
+    if (transceiver.sendEncodingParameters[0].rtx) {
+      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
+          ' ' + msid;
+      sdp += 'a=ssrc-group:FID ' +
+          transceiver.sendEncodingParameters[0].ssrc + ' ' +
+          transceiver.sendEncodingParameters[0].rtx.ssrc +
+          '\r\n';
+    }
+  }
+  // FIXME: this should be written by writeRtpDescription.
+  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
+      ' cname:' + SDPUtils.localCName + '\r\n';
+  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
+    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
+        ' cname:' + SDPUtils.localCName + '\r\n';
+  }
+  return sdp;
+};
+
+// Gets the direction from the mediaSection or the sessionpart.
+SDPUtils.getDirection = function(mediaSection, sessionpart) {
+  // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
+  var lines = SDPUtils.splitLines(mediaSection);
+  for (var i = 0; i < lines.length; i++) {
+    switch (lines[i]) {
+      case 'a=sendrecv':
+      case 'a=sendonly':
+      case 'a=recvonly':
+      case 'a=inactive':
+        return lines[i].substr(2);
+      default:
+        // FIXME: What should happen here?
+    }
+  }
+  if (sessionpart) {
+    return SDPUtils.getDirection(sessionpart);
+  }
+  return 'sendrecv';
+};
+
+SDPUtils.getKind = function(mediaSection) {
+  var lines = SDPUtils.splitLines(mediaSection);
+  var mline = lines[0].split(' ');
+  return mline[0].substr(2);
+};
+
+SDPUtils.isRejected = function(mediaSection) {
+  return mediaSection.split(' ', 2)[1] === '0';
+};
+
+SDPUtils.parseMLine = function(mediaSection) {
+  var lines = SDPUtils.splitLines(mediaSection);
+  var parts = lines[0].substr(2).split(' ');
+  return {
+    kind: parts[0],
+    port: parseInt(parts[1], 10),
+    protocol: parts[2],
+    fmt: parts.slice(3).join(' ')
+  };
+};
+
+SDPUtils.parseOLine = function(mediaSection) {
+  var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0];
+  var parts = line.substr(2).split(' ');
+  return {
+    username: parts[0],
+    sessionId: parts[1],
+    sessionVersion: parseInt(parts[2], 10),
+    netType: parts[3],
+    addressType: parts[4],
+    address: parts[5]
+  };
+};
+
+// a very naive interpretation of a valid SDP.
+SDPUtils.isValidSDP = function(blob) {
+  if (typeof blob !== 'string' || blob.length === 0) {
+    return false;
+  }
+  var lines = SDPUtils.splitLines(blob);
+  for (var i = 0; i < lines.length; i++) {
+    if (lines[i].length < 2 || lines[i].charAt(1) !== '=') {
+      return false;
+    }
+    // TODO: check the modifier a bit more.
+  }
+  return true;
+};
+
+// Expose public methods.
+if (typeof module === 'object') {
+  module.exports = SDPUtils;
+}
+
+},{}]},{},[1])(1)
+});
diff --git a/tools/perf/page_sets/webrtc_cases/adapter.js b/tools/perf/page_sets/webrtc_cases/adapter.js
deleted file mode 100644
index a3c0b08..0000000
--- a/tools/perf/page_sets/webrtc_cases/adapter.js
+++ /dev/null
@@ -1,3976 +0,0 @@
-/*
- * Copyright 2017 The Chromium Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
- /* eslint-env node */
-'use strict';
-
-// SDP helpers.
-var SDPUtils = {};
-
-// Generate an alphanumeric identifier for cname or mids.
-// TODO: use UUIDs instead? https://gist.github.com/jed/982883
-SDPUtils.generateIdentifier = function() {
-  return Math.random().toString(36).substr(2, 10);
-};
-
-// The RTCP CNAME used by all peerconnections from the same JS.
-SDPUtils.localCName = SDPUtils.generateIdentifier();
-
-// Splits SDP into lines, dealing with both CRLF and LF.
-SDPUtils.splitLines = function(blob) {
-  return blob.trim().split('\n').map(function(line) {
-    return line.trim();
-  });
-};
-// Splits SDP into sessionpart and mediasections. Ensures CRLF.
-SDPUtils.splitSections = function(blob) {
-  var parts = blob.split('\nm=');
-  return parts.map(function(part, index) {
-    return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
-  });
-};
-
-// Returns lines that start with a certain prefix.
-SDPUtils.matchPrefix = function(blob, prefix) {
-  return SDPUtils.splitLines(blob).filter(function(line) {
-    return line.indexOf(prefix) === 0;
-  });
-};
-
-// Parses an ICE candidate line. Sample input:
-// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
-// rport 55996"
-SDPUtils.parseCandidate = function(line) {
-  var parts;
-  // Parse both variants.
-  if (line.indexOf('a=candidate:') === 0) {
-    parts = line.substring(12).split(' ');
-  } else {
-    parts = line.substring(10).split(' ');
-  }
-
-  var candidate = {
-    foundation: parts[0],
-    component: parseInt(parts[1], 10),
-    protocol: parts[2].toLowerCase(),
-    priority: parseInt(parts[3], 10),
-    ip: parts[4],
-    port: parseInt(parts[5], 10),
-    // skip parts[6] == 'typ'
-    type: parts[7]
-  };
-
-  for (var i = 8; i < parts.length; i += 2) {
-    switch (parts[i]) {
-      case 'raddr':
-        candidate.relatedAddress = parts[i + 1];
-        break;
-      case 'rport':
-        candidate.relatedPort = parseInt(parts[i + 1], 10);
-        break;
-      case 'tcptype':
-        candidate.tcpType = parts[i + 1];
-        break;
-      default: // extension handling, in particular ufrag
-        candidate[parts[i]] = parts[i + 1];
-        break;
-    }
-  }
-  return candidate;
-};
-
-// Translates a candidate object into SDP candidate attribute.
-SDPUtils.writeCandidate = function(candidate) {
-  var sdp = [];
-  sdp.push(candidate.foundation);
-  sdp.push(candidate.component);
-  sdp.push(candidate.protocol.toUpperCase());
-  sdp.push(candidate.priority);
-  sdp.push(candidate.ip);
-  sdp.push(candidate.port);
-
-  var type = candidate.type;
-  sdp.push('typ');
-  sdp.push(type);
-  if (type !== 'host' && candidate.relatedAddress &&
-      candidate.relatedPort) {
-    sdp.push('raddr');
-    sdp.push(candidate.relatedAddress); // was: relAddr
-    sdp.push('rport');
-    sdp.push(candidate.relatedPort); // was: relPort
-  }
-  if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
-    sdp.push('tcptype');
-    sdp.push(candidate.tcpType);
-  }
-  if (candidate.ufrag) {
-    sdp.push('ufrag');
-    sdp.push(candidate.ufrag);
-  }
-  return 'candidate:' + sdp.join(' ');
-};
-
-// Parses an ice-options line, returns an array of option tags.
-// a=ice-options:foo bar
-SDPUtils.parseIceOptions = function(line) {
-  return line.substr(14).split(' ');
-}
-
-// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
-// a=rtpmap:111 opus/48000/2
-SDPUtils.parseRtpMap = function(line) {
-  var parts = line.substr(9).split(' ');
-  var parsed = {
-    payloadType: parseInt(parts.shift(), 10) // was: id
-  };
-
-  parts = parts[0].split('/');
-
-  parsed.name = parts[0];
-  parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
-  // was: channels
-  parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
-  return parsed;
-};
-
-// Generate an a=rtpmap line from RTCRtpCodecCapability or
-// RTCRtpCodecParameters.
-SDPUtils.writeRtpMap = function(codec) {
-  var pt = codec.payloadType;
-  if (codec.preferredPayloadType !== undefined) {
-    pt = codec.preferredPayloadType;
-  }
-  return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
-      (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
-};
-
-// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
-// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
-// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
-SDPUtils.parseExtmap = function(line) {
-  var parts = line.substr(9).split(' ');
-  return {
-    id: parseInt(parts[0], 10),
-    direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
-    uri: parts[1]
-  };
-};
-
-// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
-// RTCRtpHeaderExtension.
-SDPUtils.writeExtmap = function(headerExtension) {
-  return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
-      (headerExtension.direction && headerExtension.direction !== 'sendrecv'
-          ? '/' + headerExtension.direction
-          : '') +
-      ' ' + headerExtension.uri + '\r\n';
-};
-
-// Parses an ftmp line, returns dictionary. Sample input:
-// a=fmtp:96 vbr=on;cng=on
-// Also deals with vbr=on; cng=on
-SDPUtils.parseFmtp = function(line) {
-  var parsed = {};
-  var kv;
-  var parts = line.substr(line.indexOf(' ') + 1).split(';');
-  for (var j = 0; j < parts.length; j++) {
-    kv = parts[j].trim().split('=');
-    parsed[kv[0].trim()] = kv[1];
-  }
-  return parsed;
-};
-
-// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
-SDPUtils.writeFmtp = function(codec) {
-  var line = '';
-  var pt = codec.payloadType;
-  if (codec.preferredPayloadType !== undefined) {
-    pt = codec.preferredPayloadType;
-  }
-  if (codec.parameters && Object.keys(codec.parameters).length) {
-    var params = [];
-    Object.keys(codec.parameters).forEach(function(param) {
-      params.push(param + '=' + codec.parameters[param]);
-    });
-    line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
-  }
-  return line;
-};
-
-// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
-// a=rtcp-fb:98 nack rpsi
-SDPUtils.parseRtcpFb = function(line) {
-  var parts = line.substr(line.indexOf(' ') + 1).split(' ');
-  return {
-    type: parts.shift(),
-    parameter: parts.join(' ')
-  };
-};
-// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
-SDPUtils.writeRtcpFb = function(codec) {
-  var lines = '';
-  var pt = codec.payloadType;
-  if (codec.preferredPayloadType !== undefined) {
-    pt = codec.preferredPayloadType;
-  }
-  if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
-    // FIXME: special handling for trr-int?
-    codec.rtcpFeedback.forEach(function(fb) {
-      lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +
-      (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +
-          '\r\n';
-    });
-  }
-  return lines;
-};
-
-// Parses an RFC 5576 ssrc media attribute. Sample input:
-// a=ssrc:3735928559 cname:something
-SDPUtils.parseSsrcMedia = function(line) {
-  var sp = line.indexOf(' ');
-  var parts = {
-    ssrc: parseInt(line.substr(7, sp - 7), 10)
-  };
-  var colon = line.indexOf(':', sp);
-  if (colon > -1) {
-    parts.attribute = line.substr(sp + 1, colon - sp - 1);
-    parts.value = line.substr(colon + 1);
-  } else {
-    parts.attribute = line.substr(sp + 1);
-  }
-  return parts;
-};
-
-// Extracts the MID (RFC 5888) from a media section.
-// returns the MID or undefined if no mid line was found.
-SDPUtils.getMid = function(mediaSection) {
-  var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
-  if (mid) {
-    return mid.substr(6);
-  }
-}
-
-SDPUtils.parseFingerprint = function(line) {
-  var parts = line.substr(14).split(' ');
-  return {
-    algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
-    value: parts[1]
-  };
-};
-
-// Extracts DTLS parameters from SDP media section or sessionpart.
-// FIXME: for consistency with other functions this should only
-//   get the fingerprint line as input. See also getIceParameters.
-SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
-  var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
-      'a=fingerprint:');
-  // Note: a=setup line is ignored since we use the 'auto' role.
-  // Note2: 'algorithm' is not case sensitive except in Edge.
-  return {
-    role: 'auto',
-    fingerprints: lines.map(SDPUtils.parseFingerprint)
-  };
-};
-
-// Serializes DTLS parameters to SDP.
-SDPUtils.writeDtlsParameters = function(params, setupType) {
-  var sdp = 'a=setup:' + setupType + '\r\n';
-  params.fingerprints.forEach(function(fp) {
-    sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
-  });
-  return sdp;
-};
-// Parses ICE information from SDP media section or sessionpart.
-// FIXME: for consistency with other functions this should only
-//   get the ice-ufrag and ice-pwd lines as input.
-SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
-  var lines = SDPUtils.splitLines(mediaSection);
-  // Search in session part, too.
-  lines = lines.concat(SDPUtils.splitLines(sessionpart));
-  var iceParameters = {
-    usernameFragment: lines.filter(function(line) {
-      return line.indexOf('a=ice-ufrag:') === 0;
-    })[0].substr(12),
-    password: lines.filter(function(line) {
-      return line.indexOf('a=ice-pwd:') === 0;
-    })[0].substr(10)
-  };
-  return iceParameters;
-};
-
-// Serializes ICE parameters to SDP.
-SDPUtils.writeIceParameters = function(params) {
-  return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
-      'a=ice-pwd:' + params.password + '\r\n';
-};
-
-// Parses the SDP media section and returns RTCRtpParameters.
-SDPUtils.parseRtpParameters = function(mediaSection) {
-  var description = {
-    codecs: [],
-    headerExtensions: [],
-    fecMechanisms: [],
-    rtcp: []
-  };
-  var lines = SDPUtils.splitLines(mediaSection);
-  var mline = lines[0].split(' ');
-  for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
-    var pt = mline[i];
-    var rtpmapline = SDPUtils.matchPrefix(
-        mediaSection, 'a=rtpmap:' + pt + ' ')[0];
-    if (rtpmapline) {
-      var codec = SDPUtils.parseRtpMap(rtpmapline);
-      var fmtps = SDPUtils.matchPrefix(
-          mediaSection, 'a=fmtp:' + pt + ' ');
-      // Only the first a=fmtp:<pt> is considered.
-      codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
-      codec.rtcpFeedback = SDPUtils.matchPrefix(
-          mediaSection, 'a=rtcp-fb:' + pt + ' ')
-        .map(SDPUtils.parseRtcpFb);
-      description.codecs.push(codec);
-      // parse FEC mechanisms from rtpmap lines.
-      switch (codec.name.toUpperCase()) {
-        case 'RED':
-        case 'ULPFEC':
-          description.fecMechanisms.push(codec.name.toUpperCase());
-          break;
-        default: // only RED and ULPFEC are recognized as FEC mechanisms.
-          break;
-      }
-    }
-  }
-  SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {
-    description.headerExtensions.push(SDPUtils.parseExtmap(line));
-  });
-  // FIXME: parse rtcp.
-  return description;
-};
-
-// Generates parts of the SDP media section describing the capabilities /
-// parameters.
-SDPUtils.writeRtpDescription = function(kind, caps) {
-  var sdp = '';
-
-  // Build the mline.
-  sdp += 'm=' + kind + ' ';
-  sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
-  sdp += ' UDP/TLS/RTP/SAVPF ';
-  sdp += caps.codecs.map(function(codec) {
-    if (codec.preferredPayloadType !== undefined) {
-      return codec.preferredPayloadType;
-    }
-    return codec.payloadType;
-  }).join(' ') + '\r\n';
-
-  sdp += 'c=IN IP4 0.0.0.0\r\n';
-  sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
-
-  // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
-  caps.codecs.forEach(function(codec) {
-    sdp += SDPUtils.writeRtpMap(codec);
-    sdp += SDPUtils.writeFmtp(codec);
-    sdp += SDPUtils.writeRtcpFb(codec);
-  });
-  var maxptime = 0;
-  caps.codecs.forEach(function(codec) {
-    if (codec.maxptime > maxptime) {
-      maxptime = codec.maxptime;
-    }
-  });
-  if (maxptime > 0) {
-    sdp += 'a=maxptime:' + maxptime + '\r\n';
-  }
-  sdp += 'a=rtcp-mux\r\n';
-
-  caps.headerExtensions.forEach(function(extension) {
-    sdp += SDPUtils.writeExtmap(extension);
-  });
-  // FIXME: write fecMechanisms.
-  return sdp;
-};
-
-// Parses the SDP media section and returns an array of
-// RTCRtpEncodingParameters.
-SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
-  var encodingParameters = [];
-  var description = SDPUtils.parseRtpParameters(mediaSection);
-  var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
-  var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
-
-  // filter a=ssrc:... cname:, ignore PlanB-msid
-  var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
-  .map(function(line) {
-    return SDPUtils.parseSsrcMedia(line);
-  })
-  .filter(function(parts) {
-    return parts.attribute === 'cname';
-  });
-  var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
-  var secondarySsrc;
-
-  var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
-  .map(function(line) {
-    var parts = line.split(' ');
-    parts.shift();
-    return parts.map(function(part) {
-      return parseInt(part, 10);
-    });
-  });
-  if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
-    secondarySsrc = flows[0][1];
-  }
-
-  description.codecs.forEach(function(codec) {
-    if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
-      var encParam = {
-        ssrc: primarySsrc,
-        codecPayloadType: parseInt(codec.parameters.apt, 10),
-        rtx: {
-          ssrc: secondarySsrc
-        }
-      };
-      encodingParameters.push(encParam);
-      if (hasRed) {
-        encParam = JSON.parse(JSON.stringify(encParam));
-        encParam.fec = {
-          ssrc: secondarySsrc,
-          mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
-        };
-        encodingParameters.push(encParam);
-      }
-    }
-  });
-  if (encodingParameters.length === 0 && primarySsrc) {
-    encodingParameters.push({
-      ssrc: primarySsrc
-    });
-  }
-
-  // we support both b=AS and b=TIAS but interpret AS as TIAS.
-  var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
-  if (bandwidth.length) {
-    if (bandwidth[0].indexOf('b=TIAS:') === 0) {
-      bandwidth = parseInt(bandwidth[0].substr(7), 10);
-    } else if (bandwidth[0].indexOf('b=AS:') === 0) {
-      // use formula from JSEP to convert b=AS to TIAS value.
-      bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95
-          - (50 * 40 * 8);
-    } else {
-      bandwidth = undefined;
-    }
-    encodingParameters.forEach(function(params) {
-      params.maxBitrate = bandwidth;
-    });
-  }
-  return encodingParameters;
-};
-
-// parses http://draft.ortc.org/#rtcrtcpparameters*
-SDPUtils.parseRtcpParameters = function(mediaSection) {
-  var rtcpParameters = {};
-
-  var cname;
-  // Gets the first SSRC. Note that with RTX there might be multiple
-  // SSRCs.
-  var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
-      .map(function(line) {
-        return SDPUtils.parseSsrcMedia(line);
-      })
-      .filter(function(obj) {
-        return obj.attribute === 'cname';
-      })[0];
-  if (remoteSsrc) {
-    rtcpParameters.cname = remoteSsrc.value;
-    rtcpParameters.ssrc = remoteSsrc.ssrc;
-  }
-
-  // Edge uses the compound attribute instead of reducedSize
-  // compound is !reducedSize
-  var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
-  rtcpParameters.reducedSize = rsize.length > 0;
-  rtcpParameters.compound = rsize.length === 0;
-
-  // parses the rtcp-mux attrÑ–bute.
-  // Note that Edge does not support unmuxed RTCP.
-  var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
-  rtcpParameters.mux = mux.length > 0;
-
-  return rtcpParameters;
-};
-
-// parses either a=msid: or a=ssrc:... msid lines and returns
-// the id of the MediaStream and MediaStreamTrack.
-SDPUtils.parseMsid = function(mediaSection) {
-  var parts;
-  var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
-  if (spec.length === 1) {
-    parts = spec[0].substr(7).split(' ');
-    return {stream: parts[0], track: parts[1]};
-  }
-  var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
-  .map(function(line) {
-    return SDPUtils.parseSsrcMedia(line);
-  })
-  .filter(function(parts) {
-    return parts.attribute === 'msid';
-  });
-  if (planB.length > 0) {
-    parts = planB[0].value.split(' ');
-    return {stream: parts[0], track: parts[1]};
-  }
-};
-
-// Generate a session ID for SDP.
-// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
-// recommends using a cryptographically random +ve 64-bit value
-// but right now this should be acceptable and within the right range
-SDPUtils.generateSessionId = function() {
-  return Math.random().toString().substr(2, 21);
-};
-
-// Write boilder plate for start of SDP
-// sessId argument is optional - if not supplied it will
-// be generated randomly
-// sessVersion is optional and defaults to 2
-SDPUtils.writeSessionBoilerplate = function(sessId, sessVer) {
-  var sessionId;
-  var version = sessVer !== undefined ? sessVer : 2;
-  if (sessId) {
-    sessionId = sessId;
-  } else {
-    sessionId = SDPUtils.generateSessionId();
-  }
-  // FIXME: sess-id should be an NTP timestamp.
-  return 'v=0\r\n' +
-      'o=thisisadapterortc ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' +
-      's=-\r\n' +
-      't=0 0\r\n';
-};
-
-SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
-  var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
-
-  // Map ICE parameters (ufrag, pwd) to SDP.
-  sdp += SDPUtils.writeIceParameters(
-      transceiver.iceGatherer.getLocalParameters());
-
-  // Map DTLS parameters to SDP.
-  sdp += SDPUtils.writeDtlsParameters(
-      transceiver.dtlsTransport.getLocalParameters(),
-      type === 'offer' ? 'actpass' : 'active');
-
-  sdp += 'a=mid:' + transceiver.mid + '\r\n';
-
-  if (transceiver.direction) {
-    sdp += 'a=' + transceiver.direction + '\r\n';
-  } else if (transceiver.rtpSender && transceiver.rtpReceiver) {
-    sdp += 'a=sendrecv\r\n';
-  } else if (transceiver.rtpSender) {
-    sdp += 'a=sendonly\r\n';
-  } else if (transceiver.rtpReceiver) {
-    sdp += 'a=recvonly\r\n';
-  } else {
-    sdp += 'a=inactive\r\n';
-  }
-
-  if (transceiver.rtpSender) {
-    // spec.
-    var msid = 'msid:' + stream.id + ' ' +
-        transceiver.rtpSender.track.id + '\r\n';
-    sdp += 'a=' + msid;
-
-    // for Chrome.
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-        ' ' + msid;
-    if (transceiver.sendEncodingParameters[0].rtx) {
-      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
-          ' ' + msid;
-      sdp += 'a=ssrc-group:FID ' +
-          transceiver.sendEncodingParameters[0].ssrc + ' ' +
-          transceiver.sendEncodingParameters[0].rtx.ssrc +
-          '\r\n';
-    }
-  }
-  // FIXME: this should be written by writeRtpDescription.
-  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-      ' cname:' + SDPUtils.localCName + '\r\n';
-  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
-        ' cname:' + SDPUtils.localCName + '\r\n';
-  }
-  return sdp;
-};
-
-// Gets the direction from the mediaSection or the sessionpart.
-SDPUtils.getDirection = function(mediaSection, sessionpart) {
-  // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
-  var lines = SDPUtils.splitLines(mediaSection);
-  for (var i = 0; i < lines.length; i++) {
-    switch (lines[i]) {
-      case 'a=sendrecv':
-      case 'a=sendonly':
-      case 'a=recvonly':
-      case 'a=inactive':
-        return lines[i].substr(2);
-      default:
-        // FIXME: What should happen here?
-    }
-  }
-  if (sessionpart) {
-    return SDPUtils.getDirection(sessionpart);
-  }
-  return 'sendrecv';
-};
-
-SDPUtils.getKind = function(mediaSection) {
-  var lines = SDPUtils.splitLines(mediaSection);
-  var mline = lines[0].split(' ');
-  return mline[0].substr(2);
-};
-
-SDPUtils.isRejected = function(mediaSection) {
-  return mediaSection.split(' ', 2)[1] === '0';
-};
-
-// Expose public methods.
-module.exports = SDPUtils;
-
-},{}],2:[function(require,module,exports){
-(function (global){
-/*
- *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
- /* eslint-env node */
-
-'use strict';
-
-var adapterFactory = require('./adapter_factory.js');
-module.exports = adapterFactory({window: global.window});
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"./adapter_factory.js":3}],3:[function(require,module,exports){
-/*
- *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
- /* eslint-env node */
-
-'use strict';
-
-// Shimming starts here.
-module.exports = function(dependencies, opts) {
-  var window = dependencies && dependencies.window;
-
-  var options = {
-    shimChrome: true,
-    shimFirefox: true,
-    shimEdge: true,
-    shimSafari: true,
-  };
-
-  for (var key in opts) {
-    if (hasOwnProperty.call(opts, key)) {
-      options[key] = opts[key];
-    }
-  }
-
-  // Utils.
-  var utils = require('./utils');
-  var logging = utils.log;
-  var browserDetails = utils.detectBrowser(window);
-
-  // Export to the adapter global object visible in the browser.
-  var adapter = {
-    browserDetails: browserDetails,
-    extractVersion: utils.extractVersion,
-    disableLog: utils.disableLog,
-    disableWarnings: utils.disableWarnings
-  };
-
-  // Uncomment the line below if you want logging to occur, including logging
-  // for the switch statement below. Can also be turned on in the browser via
-  // adapter.disableLog(false), but then logging from the switch statement below
-  // will not appear.
-  // require('./utils').disableLog(false);
-
-  // Browser shims.
-  var chromeShim = require('./chrome/chrome_shim') || null;
-  var edgeShim = require('./edge/edge_shim') || null;
-  var firefoxShim = require('./firefox/firefox_shim') || null;
-  var safariShim = require('./safari/safari_shim') || null;
-
-  // Shim browser if found.
-  switch (browserDetails.browser) {
-    case 'chrome':
-      if (!chromeShim || !chromeShim.shimPeerConnection ||
-          !options.shimChrome) {
-        logging('Chrome shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming chrome.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = chromeShim;
-
-      chromeShim.shimGetUserMedia(window);
-      chromeShim.shimMediaStream(window);
-      utils.shimCreateObjectURL(window);
-      chromeShim.shimSourceObject(window);
-      chromeShim.shimPeerConnection(window);
-      chromeShim.shimOnTrack(window);
-      chromeShim.shimAddTrackRemoveTrack(window);
-      chromeShim.shimGetSendersWithDtmf(window);
-      break;
-    case 'firefox':
-      if (!firefoxShim || !firefoxShim.shimPeerConnection ||
-          !options.shimFirefox) {
-        logging('Firefox shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming firefox.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = firefoxShim;
-
-      firefoxShim.shimGetUserMedia(window);
-      utils.shimCreateObjectURL(window);
-      firefoxShim.shimSourceObject(window);
-      firefoxShim.shimPeerConnection(window);
-      firefoxShim.shimOnTrack(window);
-      break;
-    case 'edge':
-      if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) {
-        logging('MS edge shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming edge.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = edgeShim;
-
-      edgeShim.shimGetUserMedia(window);
-      utils.shimCreateObjectURL(window);
-      edgeShim.shimPeerConnection(window);
-      edgeShim.shimReplaceTrack(window);
-      break;
-    case 'safari':
-      if (!safariShim || !options.shimSafari) {
-        logging('Safari shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming safari.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = safariShim;
-      // shim window.URL.createObjectURL Safari (technical preview)
-      utils.shimCreateObjectURL(window);
-      safariShim.shimRTCIceServerUrls(window);
-      safariShim.shimCallbacksAPI(window);
-      safariShim.shimLocalStreamsAPI(window);
-      safariShim.shimRemoteStreamsAPI(window);
-      safariShim.shimGetUserMedia(window);
-      break;
-    default:
-      logging('Unsupported browser!');
-      break;
-  }
-
-  return adapter;
-};
-
-},{"./chrome/chrome_shim":4,"./edge/edge_shim":6,"./firefox/firefox_shim":9,"./safari/safari_shim":11,"./utils":12}],4:[function(require,module,exports){
-
-/*
- *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
- /* eslint-env node */
-'use strict';
-var utils = require('../utils.js');
-var logging = utils.log;
-
-var chromeShim = {
-  shimMediaStream: function(window) {
-    window.MediaStream = window.MediaStream || window.webkitMediaStream;
-  },
-
-  shimOnTrack: function(window) {
-    if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
-        window.RTCPeerConnection.prototype)) {
-      Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
-        get: function() {
-          return this._ontrack;
-        },
-        set: function(f) {
-          if (this._ontrack) {
-            this.removeEventListener('track', this._ontrack);
-          }
-          this.addEventListener('track', this._ontrack = f);
-        }
-      });
-      var origSetRemoteDescription =
-          window.RTCPeerConnection.prototype.setRemoteDescription;
-      window.RTCPeerConnection.prototype.setRemoteDescription = function() {
-        var pc = this;
-        if (!pc._ontrackpoly) {
-          pc._ontrackpoly = function(e) {
-            // onaddstream does not fire when a track is added to an existing
-            // stream. But stream.onaddtrack is implemented so we use that.
-            e.stream.addEventListener('addtrack', function(te) {
-              var receiver;
-              if (window.RTCPeerConnection.prototype.getReceivers) {
-                receiver = pc.getReceivers().find(function(r) {
-                  return r.track.id === te.track.id;
-                });
-              } else {
-                receiver = {track: te.track};
-              }
-
-              var event = new Event('track');
-              event.track = te.track;
-              event.receiver = receiver;
-              event.streams = [e.stream];
-              pc.dispatchEvent(event);
-            });
-            e.stream.getTracks().forEach(function(track) {
-              var receiver;
-              if (window.RTCPeerConnection.prototype.getReceivers) {
-                receiver = pc.getReceivers().find(function(r) {
-                  return r.track.id === track.id;
-                });
-              } else {
-                receiver = {track: track};
-              }
-              var event = new Event('track');
-              event.track = track;
-              event.receiver = receiver;
-              event.streams = [e.stream];
-              pc.dispatchEvent(event);
-            });
-          };
-          pc.addEventListener('addstream', pc._ontrackpoly);
-        }
-        return origSetRemoteDescription.apply(pc, arguments);
-      };
-    }
-  },
-
-  shimGetSendersWithDtmf: function(window) {
-    // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
-    if (typeof window === 'object' && window.RTCPeerConnection &&
-        !('getSenders' in window.RTCPeerConnection.prototype) &&
-        'createDTMFSender' in window.RTCPeerConnection.prototype) {
-      var shimSenderWithDtmf = function(pc, track) {
-        return {
-          track: track,
-          get dtmf() {
-            if (this._dtmf === undefined) {
-              if (track.kind === 'audio') {
-                this._dtmf = pc.createDTMFSender(track);
-              } else {
-                this._dtmf = null;
-              }
-            }
-            return this._dtmf;
-          },
-          _pc: pc
-        };
-      };
-
-      // augment addTrack when getSenders is not available.
-      if (!window.RTCPeerConnection.prototype.getSenders) {
-        window.RTCPeerConnection.prototype.getSenders = function() {
-          this._senders = this._senders || [];
-          return this._senders.slice(); // return a copy of the internal state.
-        };
-        var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
-        window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-          var pc = this;
-          var sender = origAddTrack.apply(pc, arguments);
-          if (!sender) {
-            sender = shimSenderWithDtmf(pc, track);
-            pc._senders.push(sender);
-          }
-          return sender;
-        };
-
-        var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
-        window.RTCPeerConnection.prototype.removeTrack = function(sender) {
-          var pc = this;
-          origRemoveTrack.apply(pc, arguments);
-          var idx = pc._senders.indexOf(sender);
-          if (idx !== -1) {
-            pc._senders.splice(idx, 1);
-          }
-        };
-      }
-      var origAddStream = window.RTCPeerConnection.prototype.addStream;
-      window.RTCPeerConnection.prototype.addStream = function(stream) {
-        var pc = this;
-        pc._senders = pc._senders || [];
-        origAddStream.apply(pc, [stream]);
-        stream.getTracks().forEach(function(track) {
-          pc._senders.push(shimSenderWithDtmf(pc, track));
-        });
-      };
-
-      var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
-      window.RTCPeerConnection.prototype.removeStream = function(stream) {
-        var pc = this;
-        pc._senders = pc._senders || [];
-        origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]);
-
-        stream.getTracks().forEach(function(track) {
-          var sender = pc._senders.find(function(s) {
-            return s.track === track;
-          });
-          if (sender) {
-            pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender
-          }
-        });
-      };
-    } else if (typeof window === 'object' && window.RTCPeerConnection &&
-               'getSenders' in window.RTCPeerConnection.prototype &&
-               'createDTMFSender' in window.RTCPeerConnection.prototype &&
-               window.RTCRtpSender &&
-               !('dtmf' in window.RTCRtpSender.prototype)) {
-      var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
-      window.RTCPeerConnection.prototype.getSenders = function() {
-        var pc = this;
-        var senders = origGetSenders.apply(pc, []);
-        senders.forEach(function(sender) {
-          sender._pc = pc;
-        });
-        return senders;
-      };
-
-      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
-        get: function() {
-          if (this._dtmf === undefined) {
-            if (this.track.kind === 'audio') {
-              this._dtmf = this._pc.createDTMFSender(this.track);
-            } else {
-              this._dtmf = null;
-            }
-          }
-          return this._dtmf;
-        }
-      });
-    }
-  },
-
-  shimSourceObject: function(window) {
-    var URL = window && window.URL;
-
-    if (typeof window === 'object') {
-      if (window.HTMLMediaElement &&
-        !('srcObject' in window.HTMLMediaElement.prototype)) {
-        // Shim the srcObject property, once, when HTMLMediaElement is found.
-        Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
-          get: function() {
-            return this._srcObject;
-          },
-          set: function(stream) {
-            var self = this;
-            // Use _srcObject as a private property for this shim
-            this._srcObject = stream;
-            if (this.src) {
-              URL.revokeObjectURL(this.src);
-            }
-
-            if (!stream) {
-              this.src = '';
-              return undefined;
-            }
-            this.src = URL.createObjectURL(stream);
-            // We need to recreate the blob url when a track is added or
-            // removed. Doing it manually since we want to avoid a recursion.
-            stream.addEventListener('addtrack', function() {
-              if (self.src) {
-                URL.revokeObjectURL(self.src);
-              }
-              self.src = URL.createObjectURL(stream);
-            });
-            stream.addEventListener('removetrack', function() {
-              if (self.src) {
-                URL.revokeObjectURL(self.src);
-              }
-              self.src = URL.createObjectURL(stream);
-            });
-          }
-        });
-      }
-    }
-  },
-
-  shimAddTrackRemoveTrack: function(window) {
-    // shim addTrack and removeTrack.
-    if (window.RTCPeerConnection.prototype.addTrack) {
-      return;
-    }
-
-    // also shim pc.getLocalStreams when addTrack is shimmed
-    // to return the original streams.
-    var origGetLocalStreams = window.RTCPeerConnection.prototype
-        .getLocalStreams;
-    window.RTCPeerConnection.prototype.getLocalStreams = function() {
-      var self = this;
-      var nativeStreams = origGetLocalStreams.apply(this);
-      self._reverseStreams = self._reverseStreams || {};
-      return nativeStreams.map(function(stream) {
-        return self._reverseStreams[stream.id];
-      });
-    };
-
-    var origAddStream = window.RTCPeerConnection.prototype.addStream;
-    window.RTCPeerConnection.prototype.addStream = function(stream) {
-      var pc = this;
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-
-      stream.getTracks().forEach(function(track) {
-        var alreadyExists = pc.getSenders().find(function(s) {
-          return s.track === track;
-        });
-        if (alreadyExists) {
-          throw new DOMException('Track already exists.',
-              'InvalidAccessError');
-        }
-      });
-      // Add identity mapping for consistency with addTrack.
-      // Unless this is being used with a stream from addTrack.
-      if (!pc._reverseStreams[stream.id]) {
-        var newStream = new window.MediaStream(stream.getTracks());
-        pc._streams[stream.id] = newStream;
-        pc._reverseStreams[newStream.id] = stream;
-        stream = newStream;
-      }
-      origAddStream.apply(pc, [stream]);
-    };
-
-    var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
-    window.RTCPeerConnection.prototype.removeStream = function(stream) {
-      var pc = this;
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-
-      origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]);
-      delete pc._reverseStreams[(pc._streams[stream.id] ?
-          pc._streams[stream.id].id : stream.id)];
-      delete pc._streams[stream.id];
-    };
-
-    window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-      var pc = this;
-      if (pc.signalingState === 'closed') {
-        throw new DOMException(
-          'The RTCPeerConnection\'s signalingState is \'closed\'.',
-          'InvalidStateError');
-      }
-      var streams = [].slice.call(arguments, 1);
-      if (streams.length !== 1 ||
-          !streams[0].getTracks().find(function(t) {
-            return t === track;
-          })) {
-        // this is not fully correct but all we can manage without
-        // [[associated MediaStreams]] internal slot.
-        throw new DOMException(
-          'The adapter.js addTrack polyfill only supports a single ' +
-          ' stream which is associated with the specified track.',
-          'NotSupportedError');
-      }
-
-      var alreadyExists = pc.getSenders().find(function(s) {
-        return s.track === track;
-      });
-      if (alreadyExists) {
-        throw new DOMException('Track already exists.',
-            'InvalidAccessError');
-      }
-
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-      var oldStream = pc._streams[stream.id];
-      if (oldStream) {
-        // this is using odd Chrome behaviour, use with caution:
-        // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
-        // Note: we rely on the high-level addTrack/dtmf shim to
-        // create the sender with a dtmf sender.
-        oldStream.addTrack(track);
-        pc.dispatchEvent(new Event('negotiationneeded'));
-      } else {
-        var newStream = new window.MediaStream([track]);
-        pc._streams[stream.id] = newStream;
-        pc._reverseStreams[newStream.id] = stream;
-        pc.addStream(newStream);
-      }
-      return pc.getSenders().find(function(s) {
-        return s.track === track;
-      });
-    };
-
-    window.RTCPeerConnection.prototype.removeTrack = function(sender) {
-      var pc = this;
-      if (pc.signalingState === 'closed') {
-        throw new DOMException(
-          'The RTCPeerConnection\'s signalingState is \'closed\'.',
-          'InvalidStateError');
-      }
-      var isLocal = sender._pc === pc;
-      if (!isLocal) {
-        throw new DOMException('Sender was not created by this connection.',
-            'InvalidAccessError');
-      }
-
-      // Search for the native stream the senders track belongs to.
-      pc._streams = pc._streams || {};
-      var stream;
-      Object.keys(pc._streams).forEach(function(streamid) {
-        var hasTrack = pc._streams[streamid].getTracks().find(function(track) {
-          return sender.track === track;
-        });
-        if (hasTrack) {
-          stream = pc._streams[streamid];
-        }
-      });
-
-      if (stream) {
-        if (stream.getTracks().length === 1) {
-          // if this is the last track of the stream, remove the stream. This
-          // takes care of any shimmed _senders.
-          pc.removeStream(stream);
-        } else {
-          // relying on the same odd chrome behaviour as above.
-          stream.removeTrack(sender.track);
-        }
-        pc.dispatchEvent(new Event('negotiationneeded'));
-      }
-    };
-  },
-
-  shimPeerConnection: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    // The RTCPeerConnection object.
-    if (!window.RTCPeerConnection) {
-      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-        // Translate iceTransportPolicy to iceTransports,
-        // see https://code.google.com/p/webrtc/issues/detail?id=4869
-        // this was fixed in M56 along with unprefixing RTCPeerConnection.
-        logging('PeerConnection');
-        if (pcConfig && pcConfig.iceTransportPolicy) {
-          pcConfig.iceTransports = pcConfig.iceTransportPolicy;
-        }
-
-        return new window.webkitRTCPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype =
-          window.webkitRTCPeerConnection.prototype;
-      // wrap static methods. Currently just generateCertificate.
-      if (window.webkitRTCPeerConnection.generateCertificate) {
-        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-          get: function() {
-            return window.webkitRTCPeerConnection.generateCertificate;
-          }
-        });
-      }
-    } else {
-      // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
-      var OrigPeerConnection = window.RTCPeerConnection;
-      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-        if (pcConfig && pcConfig.iceServers) {
-          var newIceServers = [];
-          for (var i = 0; i < pcConfig.iceServers.length; i++) {
-            var server = pcConfig.iceServers[i];
-            if (!server.hasOwnProperty('urls') &&
-                server.hasOwnProperty('url')) {
-              console.warn('RTCIceServer.url is deprecated! Use urls instead.');
-              server = JSON.parse(JSON.stringify(server));
-              server.urls = server.url;
-              newIceServers.push(server);
-            } else {
-              newIceServers.push(pcConfig.iceServers[i]);
-            }
-          }
-          pcConfig.iceServers = newIceServers;
-        }
-        return new OrigPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
-      // wrap static methods. Currently just generateCertificate.
-      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-        get: function() {
-          return OrigPeerConnection.generateCertificate;
-        }
-      });
-    }
-
-    var origGetStats = window.RTCPeerConnection.prototype.getStats;
-    window.RTCPeerConnection.prototype.getStats = function(selector,
-        successCallback, errorCallback) {
-      var self = this;
-      var args = arguments;
-
-      // If selector is a function then we are in the old style stats so just
-      // pass back the original getStats format to avoid breaking old users.
-      if (arguments.length > 0 && typeof selector === 'function') {
-        return origGetStats.apply(this, arguments);
-      }
-
-      // When spec-style getStats is supported, return those when called with
-      // either no arguments or the selector argument is null.
-      if (origGetStats.length === 0 && (arguments.length === 0 ||
-          typeof arguments[0] !== 'function')) {
-        return origGetStats.apply(this, []);
-      }
-
-      var fixChromeStats_ = function(response) {
-        var standardReport = {};
-        var reports = response.result();
-        reports.forEach(function(report) {
-          var standardStats = {
-            id: report.id,
-            timestamp: report.timestamp,
-            type: {
-              localcandidate: 'local-candidate',
-              remotecandidate: 'remote-candidate'
-            }[report.type] || report.type
-          };
-          report.names().forEach(function(name) {
-            standardStats[name] = report.stat(name);
-          });
-          standardReport[standardStats.id] = standardStats;
-        });
-
-        return standardReport;
-      };
-
-      // shim getStats with maplike support
-      var makeMapStats = function(stats) {
-        return new Map(Object.keys(stats).map(function(key) {
-          return [key, stats[key]];
-        }));
-      };
-
-      if (arguments.length >= 2) {
-        var successCallbackWrapper_ = function(response) {
-          args[1](makeMapStats(fixChromeStats_(response)));
-        };
-
-        return origGetStats.apply(this, [successCallbackWrapper_,
-          arguments[0]]);
-      }
-
-      // promise-support
-      return new Promise(function(resolve, reject) {
-        origGetStats.apply(self, [
-          function(response) {
-            resolve(makeMapStats(fixChromeStats_(response)));
-          }, reject]);
-      }).then(successCallback, errorCallback);
-    };
-
-    // add promise support -- natively available in Chrome 51
-    if (browserDetails.version < 51) {
-      ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-          .forEach(function(method) {
-            var nativeMethod = window.RTCPeerConnection.prototype[method];
-            window.RTCPeerConnection.prototype[method] = function() {
-              var args = arguments;
-              var self = this;
-              var promise = new Promise(function(resolve, reject) {
-                nativeMethod.apply(self, [args[0], resolve, reject]);
-              });
-              if (args.length < 2) {
-                return promise;
-              }
-              return promise.then(function() {
-                args[1].apply(null, []);
-              },
-              function(err) {
-                if (args.length >= 3) {
-                  args[2].apply(null, [err]);
-                }
-              });
-            };
-          });
-    }
-
-    // promise support for createOffer and createAnswer. Available (without
-    // bugs) since M52: crbug/619289
-    if (browserDetails.version < 52) {
-      ['createOffer', 'createAnswer'].forEach(function(method) {
-        var nativeMethod = window.RTCPeerConnection.prototype[method];
-        window.RTCPeerConnection.prototype[method] = function() {
-          var self = this;
-          if (arguments.length < 1 || (arguments.length === 1 &&
-              typeof arguments[0] === 'object')) {
-            var opts = arguments.length === 1 ? arguments[0] : undefined;
-            return new Promise(function(resolve, reject) {
-              nativeMethod.apply(self, [resolve, reject, opts]);
-            });
-          }
-          return nativeMethod.apply(this, arguments);
-        };
-      });
-    }
-
-    // shim implicit creation of RTCSessionDescription/RTCIceCandidate
-    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-        .forEach(function(method) {
-          var nativeMethod = window.RTCPeerConnection.prototype[method];
-          window.RTCPeerConnection.prototype[method] = function() {
-            arguments[0] = new ((method === 'addIceCandidate') ?
-                window.RTCIceCandidate :
-                window.RTCSessionDescription)(arguments[0]);
-            return nativeMethod.apply(this, arguments);
-          };
-        });
-
-    // support for addIceCandidate(null or undefined)
-    var nativeAddIceCandidate =
-        window.RTCPeerConnection.prototype.addIceCandidate;
-    window.RTCPeerConnection.prototype.addIceCandidate = function() {
-      if (!arguments[0]) {
-        if (arguments[1]) {
-          arguments[1].apply(null);
-        }
-        return Promise.resolve();
-      }
-      return nativeAddIceCandidate.apply(this, arguments);
-    };
-  }
-};
-
-
-// Expose public methods.
-module.exports = {
-  shimMediaStream: chromeShim.shimMediaStream,
-  shimOnTrack: chromeShim.shimOnTrack,
-  shimAddTrackRemoveTrack: chromeShim.shimAddTrackRemoveTrack,
-  shimGetSendersWithDtmf: chromeShim.shimGetSendersWithDtmf,
-  shimSourceObject: chromeShim.shimSourceObject,
-  shimPeerConnection: chromeShim.shimPeerConnection,
-  shimGetUserMedia: require('./getusermedia')
-};
-
-},{"../utils.js":12,"./getusermedia":5}],5:[function(require,module,exports){
-/*
- *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
- /* eslint-env node */
-'use strict';
-var utils = require('../utils.js');
-var logging = utils.log;
-
-// Expose public methods.
-module.exports = function(window) {
-  var browserDetails = utils.detectBrowser(window);
-  var navigator = window && window.navigator;
-
-  var constraintsToChrome_ = function(c) {
-    if (typeof c !== 'object' || c.mandatory || c.optional) {
-      return c;
-    }
-    var cc = {};
-    Object.keys(c).forEach(function(key) {
-      if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
-        return;
-      }
-      var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
-      if (r.exact !== undefined && typeof r.exact === 'number') {
-        r.min = r.max = r.exact;
-      }
-      var oldname_ = function(prefix, name) {
-        if (prefix) {
-          return prefix + name.charAt(0).toUpperCase() + name.slice(1);
-        }
-        return (name === 'deviceId') ? 'sourceId' : name;
-      };
-      if (r.ideal !== undefined) {
-        cc.optional = cc.optional || [];
-        var oc = {};
-        if (typeof r.ideal === 'number') {
-          oc[oldname_('min', key)] = r.ideal;
-          cc.optional.push(oc);
-          oc = {};
-          oc[oldname_('max', key)] = r.ideal;
-          cc.optional.push(oc);
-        } else {
-          oc[oldname_('', key)] = r.ideal;
-          cc.optional.push(oc);
-        }
-      }
-      if (r.exact !== undefined && typeof r.exact !== 'number') {
-        cc.mandatory = cc.mandatory || {};
-        cc.mandatory[oldname_('', key)] = r.exact;
-      } else {
-        ['min', 'max'].forEach(function(mix) {
-          if (r[mix] !== undefined) {
-            cc.mandatory = cc.mandatory || {};
-            cc.mandatory[oldname_(mix, key)] = r[mix];
-          }
-        });
-      }
-    });
-    if (c.advanced) {
-      cc.optional = (cc.optional || []).concat(c.advanced);
-    }
-    return cc;
-  };
-
-  var shimConstraints_ = function(constraints, func) {
-    constraints = JSON.parse(JSON.stringify(constraints));
-    if (constraints && typeof constraints.audio === 'object') {
-      var remap = function(obj, a, b) {
-        if (a in obj && !(b in obj)) {
-          obj[b] = obj[a];
-          delete obj[a];
-        }
-      };
-      constraints = JSON.parse(JSON.stringify(constraints));
-      remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');
-      remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');
-      constraints.audio = constraintsToChrome_(constraints.audio);
-    }
-    if (constraints && typeof constraints.video === 'object') {
-      // Shim facingMode for mobile & surface pro.
-      var face = constraints.video.facingMode;
-      face = face && ((typeof face === 'object') ? face : {ideal: face});
-      var getSupportedFacingModeLies = browserDetails.version < 61;
-
-      if ((face && (face.exact === 'user' || face.exact === 'environment' ||
-                    face.ideal === 'user' || face.ideal === 'environment')) &&
-          !(navigator.mediaDevices.getSupportedConstraints &&
-            navigator.mediaDevices.getSupportedConstraints().facingMode &&
-            !getSupportedFacingModeLies)) {
-        delete constraints.video.facingMode;
-        var matches;
-        if (face.exact === 'environment' || face.ideal === 'environment') {
-          matches = ['back', 'rear'];
-        } else if (face.exact === 'user' || face.ideal === 'user') {
-          matches = ['front'];
-        }
-        if (matches) {
-          // Look for matches in label, or use last cam for back (typical).
-          return navigator.mediaDevices.enumerateDevices()
-          .then(function(devices) {
-            devices = devices.filter(function(d) {
-              return d.kind === 'videoinput';
-            });
-            var dev = devices.find(function(d) {
-              return matches.some(function(match) {
-                return d.label.toLowerCase().indexOf(match) !== -1;
-              });
-            });
-            if (!dev && devices.length && matches.indexOf('back') !== -1) {
-              dev = devices[devices.length - 1]; // more likely the back cam
-            }
-            if (dev) {
-              constraints.video.deviceId = face.exact ? {exact: dev.deviceId} :
-                                                        {ideal: dev.deviceId};
-            }
-            constraints.video = constraintsToChrome_(constraints.video);
-            logging('chrome: ' + JSON.stringify(constraints));
-            return func(constraints);
-          });
-        }
-      }
-      constraints.video = constraintsToChrome_(constraints.video);
-    }
-    logging('chrome: ' + JSON.stringify(constraints));
-    return func(constraints);
-  };
-
-  var shimError_ = function(e) {
-    return {
-      name: {
-        PermissionDeniedError: 'NotAllowedError',
-        InvalidStateError: 'NotReadableError',
-        DevicesNotFoundError: 'NotFoundError',
-        ConstraintNotSatisfiedError: 'OverconstrainedError',
-        TrackStartError: 'NotReadableError',
-        MediaDeviceFailedDueToShutdown: 'NotReadableError',
-        MediaDeviceKillSwitchOn: 'NotReadableError'
-      }[e.name] || e.name,
-      message: e.message,
-      constraint: e.constraintName,
-      toString: function() {
-        return this.name + (this.message && ': ') + this.message;
-      }
-    };
-  };
-
-  var getUserMedia_ = function(constraints, onSuccess, onError) {
-    shimConstraints_(constraints, function(c) {
-      navigator.webkitGetUserMedia(c, onSuccess, function(e) {
-        onError(shimError_(e));
-      });
-    });
-  };
-
-  navigator.getUserMedia = getUserMedia_;
-
-  // Returns the result of getUserMedia as a Promise.
-  var getUserMediaPromise_ = function(constraints) {
-    return new Promise(function(resolve, reject) {
-      navigator.getUserMedia(constraints, resolve, reject);
-    });
-  };
-
-  if (!navigator.mediaDevices) {
-    navigator.mediaDevices = {
-      getUserMedia: getUserMediaPromise_,
-      enumerateDevices: function() {
-        return new Promise(function(resolve) {
-          var kinds = {audio: 'audioinput', video: 'videoinput'};
-          return window.MediaStreamTrack.getSources(function(devices) {
-            resolve(devices.map(function(device) {
-              return {label: device.label,
-                kind: kinds[device.kind],
-                deviceId: device.id,
-                groupId: ''};
-            }));
-          });
-        });
-      },
-      getSupportedConstraints: function() {
-        return {
-          deviceId: true, echoCancellation: true, facingMode: true,
-          frameRate: true, height: true, width: true
-        };
-      }
-    };
-  }
-
-  // A shim for getUserMedia method on the mediaDevices object.
-  // TODO(KaptenJansson) remove once implemented in Chrome stable.
-  if (!navigator.mediaDevices.getUserMedia) {
-    navigator.mediaDevices.getUserMedia = function(constraints) {
-      return getUserMediaPromise_(constraints);
-    };
-  } else {
-    // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
-    // function which returns a Promise, it does not accept spec-style
-    // constraints.
-    var origGetUserMedia = navigator.mediaDevices.getUserMedia.
-        bind(navigator.mediaDevices);
-    navigator.mediaDevices.getUserMedia = function(cs) {
-      return shimConstraints_(cs, function(c) {
-        return origGetUserMedia(c).then(function(stream) {
-          if (c.audio && !stream.getAudioTracks().length ||
-              c.video && !stream.getVideoTracks().length) {
-            stream.getTracks().forEach(function(track) {
-              track.stop();
-            });
-            throw new DOMException('', 'NotFoundError');
-          }
-          return stream;
-        }, function(e) {
-          return Promise.reject(shimError_(e));
-        });
-      });
-    };
-  }
-
-  // Dummy devicechange event methods.
-  // TODO(KaptenJansson) remove once implemented in Chrome stable.
-  if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
-    navigator.mediaDevices.addEventListener = function() {
-      logging('Dummy mediaDevices.addEventListener called.');
-    };
-  }
-  if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
-    navigator.mediaDevices.removeEventListener = function() {
-      logging('Dummy mediaDevices.removeEventListener called.');
-    };
-  }
-};
-
-},{"../utils.js":12}],6:[function(require,module,exports){
-/*
- *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
- /* eslint-env node */
-'use strict';
-
-var utils = require('../utils');
-var shimRTCPeerConnection = require('./rtcpeerconnection_shim');
-
-module.exports = {
-  shimGetUserMedia: require('./getusermedia'),
-  shimPeerConnection: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    if (window.RTCIceGatherer) {
-      // ORTC defines an RTCIceCandidate object but no constructor.
-      // Not implemented in Edge.
-      if (!window.RTCIceCandidate) {
-        window.RTCIceCandidate = function(args) {
-          return args;
-        };
-      }
-      // ORTC does not have a session description object but
-      // other browsers (i.e. Chrome) that will support both PC and ORTC
-      // in the future might have this defined already.
-      if (!window.RTCSessionDescription) {
-        window.RTCSessionDescription = function(args) {
-          return args;
-        };
-      }
-      // this adds an additional event listener to MediaStrackTrack that signals
-      // when a tracks enabled property was changed. Workaround for a bug in
-      // addStream, see below. No longer required in 15025+
-      if (browserDetails.version < 15025) {
-        var origMSTEnabled = Object.getOwnPropertyDescriptor(
-            window.MediaStreamTrack.prototype, 'enabled');
-        Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', {
-          set: function(value) {
-            origMSTEnabled.set.call(this, value);
-            var ev = new Event('enabled');
-            ev.enabled = value;
-            this.dispatchEvent(ev);
-          }
-        });
-      }
-    }
-
-    // ORTC defines the DTMF sender a bit different.
-    // https://github.com/w3c/ortc/issues/714
-    if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) {
-      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
-        get: function() {
-          if (this._dtmf === undefined) {
-            if (this.track.kind === 'audio') {
-              this._dtmf = new window.RTCDtmfSender(this);
-            } else if (this.track.kind === 'video') {
-              this._dtmf = null;
-            }
-          }
-          return this._dtmf;
-        }
-      });
-    }
-
-    window.RTCPeerConnection =
-        shimRTCPeerConnection(window, browserDetails.version);
-  },
-  shimReplaceTrack: function(window) {
-    // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
-    if (window.RTCRtpSender &&
-        !('replaceTrack' in window.RTCRtpSender.prototype)) {
-      window.RTCRtpSender.prototype.replaceTrack =
-          window.RTCRtpSender.prototype.setTrack;
-    }
-  }
-};
-
-},{"../utils":12,"./getusermedia":7,"./rtcpeerconnection_shim":8}],7:[function(require,module,exports){
-/*
- *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
- /* eslint-env node */
-'use strict';
-
-// Expose public methods.
-module.exports = function(window) {
-  var navigator = window && window.navigator;
-
-  var shimError_ = function(e) {
-    return {
-      name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name,
-      message: e.message,
-      constraint: e.constraint,
-      toString: function() {
-        return this.name;
-      }
-    };
-  };
-
-  // getUserMedia error shim.
-  var origGetUserMedia = navigator.mediaDevices.getUserMedia.
-      bind(navigator.mediaDevices);
-  navigator.mediaDevices.getUserMedia = function(c) {
-    return origGetUserMedia(c).catch(function(e) {
-      return Promise.reject(shimError_(e));
-    });
-  };
-};
-
-},{}],8:[function(require,module,exports){
-/*
- *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
- /* eslint-env node */
-'use strict';
-
-var SDPUtils = require('sdp');
-
-// sort tracks such that they follow an a-v-a-v...
-// pattern.
-function sortTracks(tracks) {
-  var audioTracks = tracks.filter(function(track) {
-    return track.kind === 'audio';
-  });
-  var videoTracks = tracks.filter(function(track) {
-    return track.kind === 'video';
-  });
-  tracks = [];
-  while (audioTracks.length || videoTracks.length) {
-    if (audioTracks.length) {
-      tracks.push(audioTracks.shift());
-    }
-    if (videoTracks.length) {
-      tracks.push(videoTracks.shift());
-    }
-  }
-  return tracks;
-}
-
-// Edge does not like
-// 1) stun:
-// 2) turn: that does not have all of turn:host:port?transport=udp
-// 3) turn: with ipv6 addresses
-// 4) turn: occurring muliple times
-function filterIceServers(iceServers, edgeVersion) {
-  var hasTurn = false;
-  iceServers = JSON.parse(JSON.stringify(iceServers));
-  return iceServers.filter(function(server) {
-    if (server && (server.urls || server.url)) {
-      var urls = server.urls || server.url;
-      if (server.url && !server.urls) {
-        console.warn('RTCIceServer.url is deprecated! Use urls instead.');
-      }
-      var isString = typeof urls === 'string';
-      if (isString) {
-        urls = [urls];
-      }
-      urls = urls.filter(function(url) {
-        var validTurn = url.indexOf('turn:') === 0 &&
-            url.indexOf('transport=udp') !== -1 &&
-            url.indexOf('turn:[') === -1 &&
-            !hasTurn;
-
-        if (validTurn) {
-          hasTurn = true;
-          return true;
-        }
-        return url.indexOf('stun:') === 0 && edgeVersion >= 14393;
-      });
-
-      delete server.url;
-      server.urls = isString ? urls[0] : urls;
-      return !!urls.length;
-    }
-    return false;
-  });
-}
-
-// Determines the intersection of local and remote capabilities.
-function getCommonCapabilities(localCapabilities, remoteCapabilities) {
-  var commonCapabilities = {
-    codecs: [],
-    headerExtensions: [],
-    fecMechanisms: []
-  };
-
-  var findCodecByPayloadType = function(pt, codecs) {
-    pt = parseInt(pt, 10);
-    for (var i = 0; i < codecs.length; i++) {
-      if (codecs[i].payloadType === pt ||
-          codecs[i].preferredPayloadType === pt) {
-        return codecs[i];
-      }
-    }
-  };
-
-  var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
-    var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
-    var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
-    return lCodec && rCodec &&
-        lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
-  };
-
-  localCapabilities.codecs.forEach(function(lCodec) {
-    for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
-      var rCodec = remoteCapabilities.codecs[i];
-      if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
-          lCodec.clockRate === rCodec.clockRate) {
-        if (lCodec.name.toLowerCase() === 'rtx' &&
-            lCodec.parameters && rCodec.parameters.apt) {
-          // for RTX we need to find the local rtx that has a apt
-          // which points to the same local codec as the remote one.
-          if (!rtxCapabilityMatches(lCodec, rCodec,
-              localCapabilities.codecs, remoteCapabilities.codecs)) {
-            continue;
-          }
-        }
-        rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
-        // number of channels is the highest common number of channels
-        rCodec.numChannels = Math.min(lCodec.numChannels,
-            rCodec.numChannels);
-        // push rCodec so we reply with offerer payload type
-        commonCapabilities.codecs.push(rCodec);
-
-        // determine common feedback mechanisms
-        rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) {
-          for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
-            if (lCodec.rtcpFeedback[j].type === fb.type &&
-                lCodec.rtcpFeedback[j].parameter === fb.parameter) {
-              return true;
-            }
-          }
-          return false;
-        });
-        // FIXME: also need to determine .parameters
-        //  see https://github.com/openpeer/ortc/issues/569
-        break;
-      }
-    }
-  });
-
-  localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {
-    for (var i = 0; i < remoteCapabilities.headerExtensions.length;
-         i++) {
-      var rHeaderExtension = remoteCapabilities.headerExtensions[i];
-      if (lHeaderExtension.uri === rHeaderExtension.uri) {
-        commonCapabilities.headerExtensions.push(rHeaderExtension);
-        break;
-      }
-    }
-  });
-
-  // FIXME: fecMechanisms
-  return commonCapabilities;
-}
-
-// is action=setLocalDescription with type allowed in signalingState
-function isActionAllowedInSignalingState(action, type, signalingState) {
-  return {
-    offer: {
-      setLocalDescription: ['stable', 'have-local-offer'],
-      setRemoteDescription: ['stable', 'have-remote-offer']
-    },
-    answer: {
-      setLocalDescription: ['have-remote-offer', 'have-local-pranswer'],
-      setRemoteDescription: ['have-local-offer', 'have-remote-pranswer']
-    }
-  }[type][action].indexOf(signalingState) !== -1;
-}
-
-module.exports = function(window, edgeVersion) {
-  var RTCPeerConnection = function(config) {
-    var self = this;
-
-    var _eventTarget = document.createDocumentFragment();
-    ['addEventListener', 'removeEventListener', 'dispatchEvent']
-        .forEach(function(method) {
-          self[method] = _eventTarget[method].bind(_eventTarget);
-        });
-
-    this.needNegotiation = false;
-
-    this.onicecandidate = null;
-    this.onaddstream = null;
-    this.ontrack = null;
-    this.onremovestream = null;
-    this.onsignalingstatechange = null;
-    this.oniceconnectionstatechange = null;
-    this.onicegatheringstatechange = null;
-    this.onnegotiationneeded = null;
-    this.ondatachannel = null;
-    this.canTrickleIceCandidates = null;
-
-    this.localStreams = [];
-    this.remoteStreams = [];
-    this.getLocalStreams = function() {
-      return self.localStreams;
-    };
-    this.getRemoteStreams = function() {
-      return self.remoteStreams;
-    };
-
-    this.localDescription = new window.RTCSessionDescription({
-      type: '',
-      sdp: ''
-    });
-    this.remoteDescription = new window.RTCSessionDescription({
-      type: '',
-      sdp: ''
-    });
-    this.signalingState = 'stable';
-    this.iceConnectionState = 'new';
-    this.iceGatheringState = 'new';
-
-    this.iceOptions = {
-      gatherPolicy: 'all',
-      iceServers: []
-    };
-    if (config && config.iceTransportPolicy) {
-      switch (config.iceTransportPolicy) {
-        case 'all':
-        case 'relay':
-          this.iceOptions.gatherPolicy = config.iceTransportPolicy;
-          break;
-        default:
-          // don't set iceTransportPolicy.
-          break;
-      }
-    }
-    this.usingBundle = config && config.bundlePolicy === 'max-bundle';
-
-    if (config && config.iceServers) {
-      this.iceOptions.iceServers = filterIceServers(config.iceServers,
-          edgeVersion);
-    }
-    this._config = config || {};
-
-    // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
-    // everything that is needed to describe a SDP m-line.
-    this.transceivers = [];
-
-    // since the iceGatherer is currently created in createOffer but we
-    // must not emit candidates until after setLocalDescription we buffer
-    // them in this array.
-    this._localIceCandidatesBuffer = [];
-
-    this._sdpSessionId = SDPUtils.generateSessionId();
-  };
-
-  RTCPeerConnection.prototype._emitGatheringStateChange = function() {
-    var event = new Event('icegatheringstatechange');
-    this.dispatchEvent(event);
-    if (this.onicegatheringstatechange !== null) {
-      this.onicegatheringstatechange(event);
-    }
-  };
-
-  RTCPeerConnection.prototype._emitBufferedCandidates = function() {
-    var self = this;
-    var sections = SDPUtils.splitSections(self.localDescription.sdp);
-    // FIXME: need to apply ice candidates in a way which is async but
-    // in-order
-    this._localIceCandidatesBuffer.forEach(function(event) {
-      var end = !event.candidate || Object.keys(event.candidate).length === 0;
-      if (end) {
-        for (var j = 1; j < sections.length; j++) {
-          if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) {
-            sections[j] += 'a=end-of-candidates\r\n';
-          }
-        }
-      } else {
-        sections[event.candidate.sdpMLineIndex + 1] +=
-            'a=' + event.candidate.candidate + '\r\n';
-      }
-      self.localDescription.sdp = sections.join('');
-      self.dispatchEvent(event);
-      if (self.onicecandidate !== null) {
-        self.onicecandidate(event);
-      }
-      if (!event.candidate && self.iceGatheringState !== 'complete') {
-        var complete = self.transceivers.every(function(transceiver) {
-          return transceiver.iceGatherer &&
-              transceiver.iceGatherer.state === 'completed';
-        });
-        if (complete && self.iceGatheringStateChange !== 'complete') {
-          self.iceGatheringState = 'complete';
-          self._emitGatheringStateChange();
-        }
-      }
-    });
-    this._localIceCandidatesBuffer = [];
-  };
-
-  RTCPeerConnection.prototype.getConfiguration = function() {
-    return this._config;
-  };
-
-  // internal helper to create a transceiver object.
-  // (whih is not yet the same as the WebRTC 1.0 transceiver)
-  RTCPeerConnection.prototype._createTransceiver = function(kind) {
-    var hasBundleTransport = this.transceivers.length > 0;
-    var transceiver = {
-      track: null,
-      iceGatherer: null,
-      iceTransport: null,
-      dtlsTransport: null,
-      localCapabilities: null,
-      remoteCapabilities: null,
-      rtpSender: null,
-      rtpReceiver: null,
-      kind: kind,
-      mid: null,
-      sendEncodingParameters: null,
-      recvEncodingParameters: null,
-      stream: null,
-      wantReceive: true
-    };
-    if (this.usingBundle && hasBundleTransport) {
-      transceiver.iceTransport = this.transceivers[0].iceTransport;
-      transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;
-    } else {
-      var transports = this._createIceAndDtlsTransports();
-      transceiver.iceTransport = transports.iceTransport;
-      transceiver.dtlsTransport = transports.dtlsTransport;
-    }
-    this.transceivers.push(transceiver);
-    return transceiver;
-  };
-
-  RTCPeerConnection.prototype.addTrack = function(track, stream) {
-    var transceiver;
-    for (var i = 0; i < this.transceivers.length; i++) {
-      if (!this.transceivers[i].track &&
-          this.transceivers[i].kind === track.kind) {
-        transceiver = this.transceivers[i];
-      }
-    }
-    if (!transceiver) {
-      transceiver = this._createTransceiver(track.kind);
-    }
-
-    transceiver.track = track;
-    transceiver.stream = stream;
-    transceiver.rtpSender = new window.RTCRtpSender(track,
-        transceiver.dtlsTransport);
-
-    this._maybeFireNegotiationNeeded();
-    return transceiver.rtpSender;
-  };
-
-  RTCPeerConnection.prototype.addStream = function(stream) {
-    var self = this;
-    if (edgeVersion >= 15025) {
-      this.localStreams.push(stream);
-      stream.getTracks().forEach(function(track) {
-        self.addTrack(track, stream);
-      });
-    } else {
-      // Clone is necessary for local demos mostly, attaching directly
-      // to two different senders does not work (build 10547).
-      // Fixed in 15025 (or earlier)
-      var clonedStream = stream.clone();
-      stream.getTracks().forEach(function(track, idx) {
-        var clonedTrack = clonedStream.getTracks()[idx];
-        track.addEventListener('enabled', function(event) {
-          clonedTrack.enabled = event.enabled;
-        });
-      });
-      clonedStream.getTracks().forEach(function(track) {
-        self.addTrack(track, clonedStream);
-      });
-      this.localStreams.push(clonedStream);
-    }
-    this._maybeFireNegotiationNeeded();
-  };
-
-  RTCPeerConnection.prototype.removeStream = function(stream) {
-    var idx = this.localStreams.indexOf(stream);
-    if (idx > -1) {
-      this.localStreams.splice(idx, 1);
-      this._maybeFireNegotiationNeeded();
-    }
-  };
-
-  RTCPeerConnection.prototype.getSenders = function() {
-    return this.transceivers.filter(function(transceiver) {
-      return !!transceiver.rtpSender;
-    })
-    .map(function(transceiver) {
-      return transceiver.rtpSender;
-    });
-  };
-
-  RTCPeerConnection.prototype.getReceivers = function() {
-    return this.transceivers.filter(function(transceiver) {
-      return !!transceiver.rtpReceiver;
-    })
-    .map(function(transceiver) {
-      return transceiver.rtpReceiver;
-    });
-  };
-
-  // Create ICE gatherer and hook it up.
-  RTCPeerConnection.prototype._createIceGatherer = function(mid,
-      sdpMLineIndex) {
-    var self = this;
-    var iceGatherer = new window.RTCIceGatherer(self.iceOptions);
-    iceGatherer.onlocalcandidate = function(evt) {
-      var event = new Event('icecandidate');
-      event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
-
-      var cand = evt.candidate;
-      var end = !cand || Object.keys(cand).length === 0;
-      // Edge emits an empty object for RTCIceCandidateComplete‥
-      if (end) {
-        // polyfill since RTCIceGatherer.state is not implemented in
-        // Edge 10547 yet.
-        if (iceGatherer.state === undefined) {
-          iceGatherer.state = 'completed';
-        }
-      } else {
-        // RTCIceCandidate doesn't have a component, needs to be added
-        cand.component = 1;
-        event.candidate.candidate = SDPUtils.writeCandidate(cand);
-      }
-
-      // update local description.
-      var sections = SDPUtils.splitSections(self.localDescription.sdp);
-      if (!end) {
-        sections[event.candidate.sdpMLineIndex + 1] +=
-            'a=' + event.candidate.candidate + '\r\n';
-      } else {
-        sections[event.candidate.sdpMLineIndex + 1] +=
-            'a=end-of-candidates\r\n';
-      }
-      self.localDescription.sdp = sections.join('');
-      var transceivers = self._pendingOffer ? self._pendingOffer :
-          self.transceivers;
-      var complete = transceivers.every(function(transceiver) {
-        return transceiver.iceGatherer &&
-            transceiver.iceGatherer.state === 'completed';
-      });
-
-      // Emit candidate if localDescription is set.
-      // Also emits null candidate when all gatherers are complete.
-      switch (self.iceGatheringState) {
-        case 'new':
-          if (!end) {
-            self._localIceCandidatesBuffer.push(event);
-          }
-          if (end && complete) {
-            self._localIceCandidatesBuffer.push(
-                new Event('icecandidate'));
-          }
-          break;
-        case 'gathering':
-          self._emitBufferedCandidates();
-          if (!end) {
-            self.dispatchEvent(event);
-            if (self.onicecandidate !== null) {
-              self.onicecandidate(event);
-            }
-          }
-          if (complete) {
-            self.dispatchEvent(new Event('icecandidate'));
-            if (self.onicecandidate !== null) {
-              self.onicecandidate(new Event('icecandidate'));
-            }
-            self.iceGatheringState = 'complete';
-            self._emitGatheringStateChange();
-          }
-          break;
-        case 'complete':
-          // should not happen... currently!
-          break;
-        default: // no-op.
-          break;
-      }
-    };
-    return iceGatherer;
-  };
-
-  // Create ICE transport and DTLS transport.
-  RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {
-    var self = this;
-    var iceTransport = new window.RTCIceTransport(null);
-    iceTransport.onicestatechange = function() {
-      self._updateConnectionState();
-    };
-
-    var dtlsTransport = new window.RTCDtlsTransport(iceTransport);
-    dtlsTransport.ondtlsstatechange = function() {
-      self._updateConnectionState();
-    };
-    dtlsTransport.onerror = function() {
-      // onerror does not set state to failed by itself.
-      Object.defineProperty(dtlsTransport, 'state',
-          {value: 'failed', writable: true});
-      self._updateConnectionState();
-    };
-
-    return {
-      iceTransport: iceTransport,
-      dtlsTransport: dtlsTransport
-    };
-  };
-
-  // Destroy ICE gatherer, ICE transport and DTLS transport.
-  // Without triggering the callbacks.
-  RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
-      sdpMLineIndex) {
-    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
-    if (iceGatherer) {
-      delete iceGatherer.onlocalcandidate;
-      delete this.transceivers[sdpMLineIndex].iceGatherer;
-    }
-    var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
-    if (iceTransport) {
-      delete iceTransport.onicestatechange;
-      delete this.transceivers[sdpMLineIndex].iceTransport;
-    }
-    var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;
-    if (dtlsTransport) {
-      delete dtlsTransport.ondtlsstatechange;
-      delete dtlsTransport.onerror;
-      delete this.transceivers[sdpMLineIndex].dtlsTransport;
-    }
-  };
-
-  // Start the RTP Sender and Receiver for a transceiver.
-  RTCPeerConnection.prototype._transceive = function(transceiver,
-      send, recv) {
-    var params = getCommonCapabilities(transceiver.localCapabilities,
-        transceiver.remoteCapabilities);
-    if (send && transceiver.rtpSender) {
-      params.encodings = transceiver.sendEncodingParameters;
-      params.rtcp = {
-        cname: SDPUtils.localCName,
-        compound: transceiver.rtcpParameters.compound
-      };
-      if (transceiver.recvEncodingParameters.length) {
-        params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
-      }
-      transceiver.rtpSender.send(params);
-    }
-    if (recv && transceiver.rtpReceiver) {
-      // remove RTX field in Edge 14942
-      if (transceiver.kind === 'video'
-          && transceiver.recvEncodingParameters
-          && edgeVersion < 15019) {
-        transceiver.recvEncodingParameters.forEach(function(p) {
-          delete p.rtx;
-        });
-      }
-      params.encodings = transceiver.recvEncodingParameters;
-      params.rtcp = {
-        cname: transceiver.rtcpParameters.cname,
-        compound: transceiver.rtcpParameters.compound
-      };
-      if (transceiver.sendEncodingParameters.length) {
-        params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
-      }
-      transceiver.rtpReceiver.receive(params);
-    }
-  };
-
-  RTCPeerConnection.prototype.setLocalDescription = function(description) {
-    var self = this;
-
-    if (!isActionAllowedInSignalingState('setLocalDescription',
-        description.type, this.signalingState)) {
-      var e = new Error('Can not set local ' + description.type +
-          ' in state ' + this.signalingState);
-      e.name = 'InvalidStateError';
-      if (arguments.length > 2 && typeof arguments[2] === 'function') {
-        window.setTimeout(arguments[2], 0, e);
-      }
-      return Promise.reject(e);
-    }
-
-    var sections;
-    var sessionpart;
-    if (description.type === 'offer') {
-      // FIXME: What was the purpose of this empty if statement?
-      // if (!this._pendingOffer) {
-      // } else {
-      if (this._pendingOffer) {
-        // VERY limited support for SDP munging. Limited to:
-        // * changing the order of codecs
-        sections = SDPUtils.splitSections(description.sdp);
-        sessionpart = sections.shift();
-        sections.forEach(function(mediaSection, sdpMLineIndex) {
-          var caps = SDPUtils.parseRtpParameters(mediaSection);
-          self._pendingOffer[sdpMLineIndex].localCapabilities = caps;
-        });
-        this.transceivers = this._pendingOffer;
-        delete this._pendingOffer;
-      }
-    } else if (description.type === 'answer') {
-      sections = SDPUtils.splitSections(self.remoteDescription.sdp);
-      sessionpart = sections.shift();
-      var isIceLite = SDPUtils.matchPrefix(sessionpart,
-          'a=ice-lite').length > 0;
-      sections.forEach(function(mediaSection, sdpMLineIndex) {
-        var transceiver = self.transceivers[sdpMLineIndex];
-        var iceGatherer = transceiver.iceGatherer;
-        var iceTransport = transceiver.iceTransport;
-        var dtlsTransport = transceiver.dtlsTransport;
-        var localCapabilities = transceiver.localCapabilities;
-        var remoteCapabilities = transceiver.remoteCapabilities;
-
-        var rejected = SDPUtils.isRejected(mediaSection);
-
-        if (!rejected && !transceiver.isDatachannel) {
-          var remoteIceParameters = SDPUtils.getIceParameters(
-              mediaSection, sessionpart);
-          var remoteDtlsParameters = SDPUtils.getDtlsParameters(
-              mediaSection, sessionpart);
-          if (isIceLite) {
-            remoteDtlsParameters.role = 'server';
-          }
-
-          if (!self.usingBundle || sdpMLineIndex === 0) {
-            iceTransport.start(iceGatherer, remoteIceParameters,
-                isIceLite ? 'controlling' : 'controlled');
-            dtlsTransport.start(remoteDtlsParameters);
-          }
-
-          // Calculate intersection of capabilities.
-          var params = getCommonCapabilities(localCapabilities,
-              remoteCapabilities);
-
-          // Start the RTCRtpSender. The RTCRtpReceiver for this
-          // transceiver has already been started in setRemoteDescription.
-          self._transceive(transceiver,
-              params.codecs.length > 0,
-              false);
-        }
-      });
-    }
-
-    this.localDescription = {
-      type: description.type,
-      sdp: description.sdp
-    };
-    switch (description.type) {
-      case 'offer':
-        this._updateSignalingState('have-local-offer');
-        break;
-      case 'answer':
-        this._updateSignalingState('stable');
-        break;
-      default:
-        throw new TypeError('unsupported type "' + description.type +
-            '"');
-    }
-
-    // If a success callback was provided, emit ICE candidates after it
-    // has been executed. Otherwise, emit callback after the Promise is
-    // resolved.
-    var hasCallback = arguments.length > 1 &&
-      typeof arguments[1] === 'function';
-    if (hasCallback) {
-      var cb = arguments[1];
-      window.setTimeout(function() {
-        cb();
-        if (self.iceGatheringState === 'new') {
-          self.iceGatheringState = 'gathering';
-          self._emitGatheringStateChange();
-        }
-        self._emitBufferedCandidates();
-      }, 0);
-    }
-    var p = Promise.resolve();
-    p.then(function() {
-      if (!hasCallback) {
-        if (self.iceGatheringState === 'new') {
-          self.iceGatheringState = 'gathering';
-          self._emitGatheringStateChange();
-        }
-        // Usually candidates will be emitted earlier.
-        window.setTimeout(self._emitBufferedCandidates.bind(self), 500);
-      }
-    });
-    return p;
-  };
-
-  RTCPeerConnection.prototype.setRemoteDescription = function(description) {
-    var self = this;
-
-    if (!isActionAllowedInSignalingState('setRemoteDescription',
-        description.type, this.signalingState)) {
-      var e = new Error('Can not set remote ' + description.type +
-          ' in state ' + this.signalingState);
-      e.name = 'InvalidStateError';
-      if (arguments.length > 2 && typeof arguments[2] === 'function') {
-        window.setTimeout(arguments[2], 0, e);
-      }
-      return Promise.reject(e);
-    }
-
-    var streams = {};
-    var receiverList = [];
-    var sections = SDPUtils.splitSections(description.sdp);
-    var sessionpart = sections.shift();
-    var isIceLite = SDPUtils.matchPrefix(sessionpart,
-        'a=ice-lite').length > 0;
-    var usingBundle = SDPUtils.matchPrefix(sessionpart,
-        'a=group:BUNDLE ').length > 0;
-    this.usingBundle = usingBundle;
-    var iceOptions = SDPUtils.matchPrefix(sessionpart,
-        'a=ice-options:')[0];
-    if (iceOptions) {
-      this.canTrickleIceCandidates = iceOptions.substr(14).split(' ')
-          .indexOf('trickle') >= 0;
-    } else {
-      this.canTrickleIceCandidates = false;
-    }
-
-    sections.forEach(function(mediaSection, sdpMLineIndex) {
-      var lines = SDPUtils.splitLines(mediaSection);
-      var kind = SDPUtils.getKind(mediaSection);
-      var rejected = SDPUtils.isRejected(mediaSection);
-      var protocol = lines[0].substr(2).split(' ')[2];
-
-      var direction = SDPUtils.getDirection(mediaSection, sessionpart);
-      var remoteMsid = SDPUtils.parseMsid(mediaSection);
-
-      var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier();
-
-      // Reject datachannels which are not implemented yet.
-      if (kind === 'application' && protocol === 'DTLS/SCTP') {
-        self.transceivers[sdpMLineIndex] = {
-          mid: mid,
-          isDatachannel: true
-        };
-        return;
-      }
-
-      var transceiver;
-      var iceGatherer;
-      var iceTransport;
-      var dtlsTransport;
-      var rtpReceiver;
-      var sendEncodingParameters;
-      var recvEncodingParameters;
-      var localCapabilities;
-
-      var track;
-      // FIXME: ensure the mediaSection has rtcp-mux set.
-      var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
-      var remoteIceParameters;
-      var remoteDtlsParameters;
-      if (!rejected) {
-        remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
-            sessionpart);
-        remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
-            sessionpart);
-        remoteDtlsParameters.role = 'client';
-      }
-      recvEncodingParameters =
-          SDPUtils.parseRtpEncodingParameters(mediaSection);
-
-      var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);
-
-      var isComplete = SDPUtils.matchPrefix(mediaSection,
-          'a=end-of-candidates', sessionpart).length > 0;
-      var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
-          .map(function(cand) {
-            return SDPUtils.parseCandidate(cand);
-          })
-          .filter(function(cand) {
-            return cand.component === '1' || cand.component === 1;
-          });
-
-      // Check if we can use BUNDLE and dispose transports.
-      if ((description.type === 'offer' || description.type === 'answer') &&
-          !rejected && usingBundle && sdpMLineIndex > 0 &&
-          self.transceivers[sdpMLineIndex]) {
-        self._disposeIceAndDtlsTransports(sdpMLineIndex);
-        self.transceivers[sdpMLineIndex].iceGatherer =
-            self.transceivers[0].iceGatherer;
-        self.transceivers[sdpMLineIndex].iceTransport =
-            self.transceivers[0].iceTransport;
-        self.transceivers[sdpMLineIndex].dtlsTransport =
-            self.transceivers[0].dtlsTransport;
-        if (self.transceivers[sdpMLineIndex].rtpSender) {
-          self.transceivers[sdpMLineIndex].rtpSender.setTransport(
-              self.transceivers[0].dtlsTransport);
-        }
-        if (self.transceivers[sdpMLineIndex].rtpReceiver) {
-          self.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
-              self.transceivers[0].dtlsTransport);
-        }
-      }
-      if (description.type === 'offer' && !rejected) {
-        transceiver = self.transceivers[sdpMLineIndex] ||
-            self._createTransceiver(kind);
-        transceiver.mid = mid;
-
-        if (!transceiver.iceGatherer) {
-          transceiver.iceGatherer = usingBundle && sdpMLineIndex > 0 ?
-              self.transceivers[0].iceGatherer :
-              self._createIceGatherer(mid, sdpMLineIndex);
-        }
-
-        if (isComplete && cands.length &&
-            (!usingBundle || sdpMLineIndex === 0)) {
-          transceiver.iceTransport.setRemoteCandidates(cands);
-        }
-
-        localCapabilities = window.RTCRtpReceiver.getCapabilities(kind);
-
-        // filter RTX until additional stuff needed for RTX is implemented
-        // in adapter.js
-        if (edgeVersion < 15019) {
-          localCapabilities.codecs = localCapabilities.codecs.filter(
-              function(codec) {
-                return codec.name !== 'rtx';
-              });
-        }
-
-        sendEncodingParameters = [{
-          ssrc: (2 * sdpMLineIndex + 2) * 1001
-        }];
-
-        if (direction === 'sendrecv' || direction === 'sendonly') {
-          rtpReceiver = new window.RTCRtpReceiver(transceiver.dtlsTransport,
-              kind);
-
-          track = rtpReceiver.track;
-          // FIXME: does not work with Plan B.
-          if (remoteMsid) {
-            if (!streams[remoteMsid.stream]) {
-              streams[remoteMsid.stream] = new window.MediaStream();
-              Object.defineProperty(streams[remoteMsid.stream], 'id', {
-                get: function() {
-                  return remoteMsid.stream;
-                }
-              });
-            }
-            Object.defineProperty(track, 'id', {
-              get: function() {
-                return remoteMsid.track;
-              }
-            });
-            streams[remoteMsid.stream].addTrack(track);
-            receiverList.push([track, rtpReceiver,
-              streams[remoteMsid.stream]]);
-          } else {
-            if (!streams.default) {
-              streams.default = new window.MediaStream();
-            }
-            streams.default.addTrack(track);
-            receiverList.push([track, rtpReceiver, streams.default]);
-          }
-        }
-
-        transceiver.localCapabilities = localCapabilities;
-        transceiver.remoteCapabilities = remoteCapabilities;
-        transceiver.rtpReceiver = rtpReceiver;
-        transceiver.rtcpParameters = rtcpParameters;
-        transceiver.sendEncodingParameters = sendEncodingParameters;
-        transceiver.recvEncodingParameters = recvEncodingParameters;
-
-        // Start the RTCRtpReceiver now. The RTPSender is started in
-        // setLocalDescription.
-        self._transceive(self.transceivers[sdpMLineIndex],
-            false,
-            direction === 'sendrecv' || direction === 'sendonly');
-      } else if (description.type === 'answer' && !rejected) {
-        transceiver = self.transceivers[sdpMLineIndex];
-        iceGatherer = transceiver.iceGatherer;
-        iceTransport = transceiver.iceTransport;
-        dtlsTransport = transceiver.dtlsTransport;
-        rtpReceiver = transceiver.rtpReceiver;
-        sendEncodingParameters = transceiver.sendEncodingParameters;
-        localCapabilities = transceiver.localCapabilities;
-
-        self.transceivers[sdpMLineIndex].recvEncodingParameters =
-            recvEncodingParameters;
-        self.transceivers[sdpMLineIndex].remoteCapabilities =
-            remoteCapabilities;
-        self.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;
-
-        if (!usingBundle || sdpMLineIndex === 0) {
-          if ((isIceLite || isComplete) && cands.length) {
-            iceTransport.setRemoteCandidates(cands);
-          }
-          iceTransport.start(iceGatherer, remoteIceParameters,
-              'controlling');
-          dtlsTransport.start(remoteDtlsParameters);
-        }
-
-        self._transceive(transceiver,
-            direction === 'sendrecv' || direction === 'recvonly',
-            direction === 'sendrecv' || direction === 'sendonly');
-
-        if (rtpReceiver &&
-            (direction === 'sendrecv' || direction === 'sendonly')) {
-          track = rtpReceiver.track;
-          if (remoteMsid) {
-            if (!streams[remoteMsid.stream]) {
-              streams[remoteMsid.stream] = new window.MediaStream();
-            }
-            streams[remoteMsid.stream].addTrack(track);
-            receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]);
-          } else {
-            if (!streams.default) {
-              streams.default = new window.MediaStream();
-            }
-            streams.default.addTrack(track);
-            receiverList.push([track, rtpReceiver, streams.default]);
-          }
-        } else {
-          // FIXME: actually the receiver should be created later.
-          delete transceiver.rtpReceiver;
-        }
-      }
-    });
-
-    this.remoteDescription = {
-      type: description.type,
-      sdp: description.sdp
-    };
-    switch (description.type) {
-      case 'offer':
-        this._updateSignalingState('have-remote-offer');
-        break;
-      case 'answer':
-        this._updateSignalingState('stable');
-        break;
-      default:
-        throw new TypeError('unsupported type "' + description.type +
-            '"');
-    }
-    Object.keys(streams).forEach(function(sid) {
-      var stream = streams[sid];
-      if (stream.getTracks().length) {
-        self.remoteStreams.push(stream);
-        var event = new Event('addstream');
-        event.stream = stream;
-        self.dispatchEvent(event);
-        if (self.onaddstream !== null) {
-          window.setTimeout(function() {
-            self.onaddstream(event);
-          }, 0);
-        }
-
-        receiverList.forEach(function(item) {
-          var track = item[0];
-          var receiver = item[1];
-          if (stream.id !== item[2].id) {
-            return;
-          }
-          var trackEvent = new Event('track');
-          trackEvent.track = track;
-          trackEvent.receiver = receiver;
-          trackEvent.streams = [stream];
-          self.dispatchEvent(trackEvent);
-          if (self.ontrack !== null) {
-            window.setTimeout(function() {
-              self.ontrack(trackEvent);
-            }, 0);
-          }
-        });
-      }
-    });
-
-    // check whether addIceCandidate({}) was called within four seconds after
-    // setRemoteDescription.
-    window.setTimeout(function() {
-      if (!(self && self.transceivers)) {
-        return;
-      }
-      self.transceivers.forEach(function(transceiver) {
-        if (transceiver.iceTransport &&
-            transceiver.iceTransport.state === 'new' &&
-            transceiver.iceTransport.getRemoteCandidates().length > 0) {
-          console.warn('Timeout for addRemoteCandidate. Consider sending ' +
-              'an end-of-candidates notification');
-          transceiver.iceTransport.addRemoteCandidate({});
-        }
-      });
-    }, 4000);
-
-    if (arguments.length > 1 && typeof arguments[1] === 'function') {
-      window.setTimeout(arguments[1], 0);
-    }
-    return Promise.resolve();
-  };
-
-  RTCPeerConnection.prototype.close = function() {
-    this.transceivers.forEach(function(transceiver) {
-      /* not yet
-      if (transceiver.iceGatherer) {
-        transceiver.iceGatherer.close();
-      }
-      */
-      if (transceiver.iceTransport) {
-        transceiver.iceTransport.stop();
-      }
-      if (transceiver.dtlsTransport) {
-        transceiver.dtlsTransport.stop();
-      }
-      if (transceiver.rtpSender) {
-        transceiver.rtpSender.stop();
-      }
-      if (transceiver.rtpReceiver) {
-        transceiver.rtpReceiver.stop();
-      }
-    });
-    // FIXME: clean up tracks, local streams, remote streams, etc
-    this._updateSignalingState('closed');
-  };
-
-  // Update the signaling state.
-  RTCPeerConnection.prototype._updateSignalingState = function(newState) {
-    this.signalingState = newState;
-    var event = new Event('signalingstatechange');
-    this.dispatchEvent(event);
-    if (this.onsignalingstatechange !== null) {
-      this.onsignalingstatechange(event);
-    }
-  };
-
-  // Determine whether to fire the negotiationneeded event.
-  RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
-    var self = this;
-    if (this.signalingState !== 'stable' || this.needNegotiation === true) {
-      return;
-    }
-    this.needNegotiation = true;
-    window.setTimeout(function() {
-      if (self.needNegotiation === false) {
-        return;
-      }
-      self.needNegotiation = false;
-      var event = new Event('negotiationneeded');
-      self.dispatchEvent(event);
-      if (self.onnegotiationneeded !== null) {
-        self.onnegotiationneeded(event);
-      }
-    }, 0);
-  };
-
-  // Update the connection state.
-  RTCPeerConnection.prototype._updateConnectionState = function() {
-    var self = this;
-    var newState;
-    var states = {
-      'new': 0,
-      closed: 0,
-      connecting: 0,
-      checking: 0,
-      connected: 0,
-      completed: 0,
-      disconnected: 0,
-      failed: 0
-    };
-    this.transceivers.forEach(function(transceiver) {
-      states[transceiver.iceTransport.state]++;
-      states[transceiver.dtlsTransport.state]++;
-    });
-    // ICETransport.completed and connected are the same for this purpose.
-    states.connected += states.completed;
-
-    newState = 'new';
-    if (states.failed > 0) {
-      newState = 'failed';
-    } else if (states.connecting > 0 || states.checking > 0) {
-      newState = 'connecting';
-    } else if (states.disconnected > 0) {
-      newState = 'disconnected';
-    } else if (states.new > 0) {
-      newState = 'new';
-    } else if (states.connected > 0 || states.completed > 0) {
-      newState = 'connected';
-    }
-
-    if (newState !== self.iceConnectionState) {
-      self.iceConnectionState = newState;
-      var event = new Event('iceconnectionstatechange');
-      this.dispatchEvent(event);
-      if (this.oniceconnectionstatechange !== null) {
-        this.oniceconnectionstatechange(event);
-      }
-    }
-  };
-
-  RTCPeerConnection.prototype.createOffer = function() {
-    var self = this;
-    if (this._pendingOffer) {
-      throw new Error('createOffer called while there is a pending offer.');
-    }
-    var offerOptions;
-    if (arguments.length === 1 && typeof arguments[0] !== 'function') {
-      offerOptions = arguments[0];
-    } else if (arguments.length === 3) {
-      offerOptions = arguments[2];
-    }
-
-    var numAudioTracks = this.transceivers.filter(function(t) {
-      return t.kind === 'audio';
-    }).length;
-    var numVideoTracks = this.transceivers.filter(function(t) {
-      return t.kind === 'video';
-    }).length;
-
-    // Determine number of audio and video tracks we need to send/recv.
-    if (offerOptions) {
-      // Reject Chrome legacy constraints.
-      if (offerOptions.mandatory || offerOptions.optional) {
-        throw new TypeError(
-            'Legacy mandatory/optional constraints not supported.');
-      }
-      if (offerOptions.offerToReceiveAudio !== undefined) {
-        if (offerOptions.offerToReceiveAudio === true) {
-          numAudioTracks = 1;
-        } else if (offerOptions.offerToReceiveAudio === false) {
-          numAudioTracks = 0;
-        } else {
-          numAudioTracks = offerOptions.offerToReceiveAudio;
-        }
-      }
-      if (offerOptions.offerToReceiveVideo !== undefined) {
-        if (offerOptions.offerToReceiveVideo === true) {
-          numVideoTracks = 1;
-        } else if (offerOptions.offerToReceiveVideo === false) {
-          numVideoTracks = 0;
-        } else {
-          numVideoTracks = offerOptions.offerToReceiveVideo;
-        }
-      }
-    }
-
-    this.transceivers.forEach(function(transceiver) {
-      if (transceiver.kind === 'audio') {
-        numAudioTracks--;
-        if (numAudioTracks < 0) {
-          transceiver.wantReceive = false;
-        }
-      } else if (transceiver.kind === 'video') {
-        numVideoTracks--;
-        if (numVideoTracks < 0) {
-          transceiver.wantReceive = false;
-        }
-      }
-    });
-
-    // Create M-lines for recvonly streams.
-    while (numAudioTracks > 0 || numVideoTracks > 0) {
-      if (numAudioTracks > 0) {
-        this._createTransceiver('audio');
-        numAudioTracks--;
-      }
-      if (numVideoTracks > 0) {
-        this._createTransceiver('video');
-        numVideoTracks--;
-      }
-    }
-    // reorder tracks
-    var transceivers = sortTracks(this.transceivers);
-
-    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId);
-    transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      // For each track, create an ice gatherer, ice transport,
-      // dtls transport, potentially rtpsender and rtpreceiver.
-      var track = transceiver.track;
-      var kind = transceiver.kind;
-      var mid = SDPUtils.generateIdentifier();
-      transceiver.mid = mid;
-
-      if (!transceiver.iceGatherer) {
-        transceiver.iceGatherer = self.usingBundle && sdpMLineIndex > 0 ?
-            transceivers[0].iceGatherer :
-            self._createIceGatherer(mid, sdpMLineIndex);
-      }
-
-      var localCapabilities = window.RTCRtpSender.getCapabilities(kind);
-      // filter RTX until additional stuff needed for RTX is implemented
-      // in adapter.js
-      if (edgeVersion < 15019) {
-        localCapabilities.codecs = localCapabilities.codecs.filter(
-            function(codec) {
-              return codec.name !== 'rtx';
-            });
-      }
-      localCapabilities.codecs.forEach(function(codec) {
-        // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
-        // by adding level-asymmetry-allowed=1
-        if (codec.name === 'H264' &&
-            codec.parameters['level-asymmetry-allowed'] === undefined) {
-          codec.parameters['level-asymmetry-allowed'] = '1';
-        }
-      });
-
-      // generate an ssrc now, to be used later in rtpSender.send
-      var sendEncodingParameters = [{
-        ssrc: (2 * sdpMLineIndex + 1) * 1001
-      }];
-      if (track) {
-        // add RTX
-        if (edgeVersion >= 15019 && kind === 'video') {
-          sendEncodingParameters[0].rtx = {
-            ssrc: (2 * sdpMLineIndex + 1) * 1001 + 1
-          };
-        }
-      }
-
-      if (transceiver.wantReceive) {
-        transceiver.rtpReceiver = new window.RTCRtpReceiver(
-          transceiver.dtlsTransport,
-          kind
-        );
-      }
-
-      transceiver.localCapabilities = localCapabilities;
-      transceiver.sendEncodingParameters = sendEncodingParameters;
-    });
-
-    // always offer BUNDLE and dispose on return if not supported.
-    if (this._config.bundlePolicy !== 'max-compat') {
-      sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) {
-        return t.mid;
-      }).join(' ') + '\r\n';
-    }
-    sdp += 'a=ice-options:trickle\r\n';
-
-    transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      sdp += SDPUtils.writeMediaSection(transceiver,
-          transceiver.localCapabilities, 'offer', transceiver.stream);
-      sdp += 'a=rtcp-rsize\r\n';
-    });
-
-    this._pendingOffer = transceivers;
-    var desc = new window.RTCSessionDescription({
-      type: 'offer',
-      sdp: sdp
-    });
-    if (arguments.length && typeof arguments[0] === 'function') {
-      window.setTimeout(arguments[0], 0, desc);
-    }
-    return Promise.resolve(desc);
-  };
-
-  RTCPeerConnection.prototype.createAnswer = function() {
-    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId);
-    if (this.usingBundle) {
-      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
-        return t.mid;
-      }).join(' ') + '\r\n';
-    }
-    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      if (transceiver.isDatachannel) {
-        sdp += 'm=application 0 DTLS/SCTP 5000\r\n' +
-            'c=IN IP4 0.0.0.0\r\n' +
-            'a=mid:' + transceiver.mid + '\r\n';
-        return;
-      }
-
-      // FIXME: look at direction.
-      if (transceiver.stream) {
-        var localTrack;
-        if (transceiver.kind === 'audio') {
-          localTrack = transceiver.stream.getAudioTracks()[0];
-        } else if (transceiver.kind === 'video') {
-          localTrack = transceiver.stream.getVideoTracks()[0];
-        }
-        if (localTrack) {
-          // add RTX
-          if (edgeVersion >= 15019 && transceiver.kind === 'video') {
-            transceiver.sendEncodingParameters[0].rtx = {
-              ssrc: (2 * sdpMLineIndex + 2) * 1001 + 1
-            };
-          }
-        }
-      }
-
-      // Calculate intersection of capabilities.
-      var commonCapabilities = getCommonCapabilities(
-          transceiver.localCapabilities,
-          transceiver.remoteCapabilities);
-
-      var hasRtx = commonCapabilities.codecs.filter(function(c) {
-        return c.name.toLowerCase() === 'rtx';
-      }).length;
-      if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
-        delete transceiver.sendEncodingParameters[0].rtx;
-      }
-
-      sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities,
-          'answer', transceiver.stream);
-      if (transceiver.rtcpParameters &&
-          transceiver.rtcpParameters.reducedSize) {
-        sdp += 'a=rtcp-rsize\r\n';
-      }
-    });
-
-    var desc = new window.RTCSessionDescription({
-      type: 'answer',
-      sdp: sdp
-    });
-    if (arguments.length && typeof arguments[0] === 'function') {
-      window.setTimeout(arguments[0], 0, desc);
-    }
-    return Promise.resolve(desc);
-  };
-
-  RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
-    if (!candidate) {
-      for (var j = 0; j < this.transceivers.length; j++) {
-        this.transceivers[j].iceTransport.addRemoteCandidate({});
-        if (this.usingBundle) {
-          return Promise.resolve();
-        }
-      }
-    } else {
-      var mLineIndex = candidate.sdpMLineIndex;
-      if (candidate.sdpMid) {
-        for (var i = 0; i < this.transceivers.length; i++) {
-          if (this.transceivers[i].mid === candidate.sdpMid) {
-            mLineIndex = i;
-            break;
-          }
-        }
-      }
-      var transceiver = this.transceivers[mLineIndex];
-      if (transceiver) {
-        var cand = Object.keys(candidate.candidate).length > 0 ?
-            SDPUtils.parseCandidate(candidate.candidate) : {};
-        // Ignore Chrome's invalid candidates since Edge does not like them.
-        if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {
-          return Promise.resolve();
-        }
-        // Ignore RTCP candidates, we assume RTCP-MUX.
-        if (cand.component &&
-            !(cand.component === '1' || cand.component === 1)) {
-          return Promise.resolve();
-        }
-        transceiver.iceTransport.addRemoteCandidate(cand);
-
-        // update the remoteDescription.
-        var sections = SDPUtils.splitSections(this.remoteDescription.sdp);
-        sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim()
-            : 'a=end-of-candidates') + '\r\n';
-        this.remoteDescription.sdp = sections.join('');
-      }
-    }
-    if (arguments.length > 1 && typeof arguments[1] === 'function') {
-      window.setTimeout(arguments[1], 0);
-    }
-    return Promise.resolve();
-  };
-
-  RTCPeerConnection.prototype.getStats = function() {
-    var promises = [];
-    this.transceivers.forEach(function(transceiver) {
-      ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
-        'dtlsTransport'].forEach(function(method) {
-          if (transceiver[method]) {
-            promises.push(transceiver[method].getStats());
-          }
-        });
-    });
-    var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
-        arguments[1];
-    var fixStatsType = function(stat) {
-      return {
-        inboundrtp: 'inbound-rtp',
-        outboundrtp: 'outbound-rtp',
-        candidatepair: 'candidate-pair',
-        localcandidate: 'local-candidate',
-        remotecandidate: 'remote-candidate'
-      }[stat.type] || stat.type;
-    };
-    return new Promise(function(resolve) {
-      // shim getStats with maplike support
-      var results = new Map();
-      Promise.all(promises).then(function(res) {
-        res.forEach(function(result) {
-          Object.keys(result).forEach(function(id) {
-            result[id].type = fixStatsType(result[id]);
-            results.set(id, result[id]);
-          });
-        });
-        if (cb) {
-          window.setTimeout(cb, 0, results);
-        }
-        resolve(results);
-      });
-    });
-  };
-  return RTCPeerConnection;
-};
-
-},{"sdp":1}],9:[function(require,module,exports){
-/*
- *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
- /* eslint-env node */
-'use strict';
-
-var utils = require('../utils');
-
-var firefoxShim = {
-  shimOnTrack: function(window) {
-    if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
-        window.RTCPeerConnection.prototype)) {
-      Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
-        get: function() {
-          return this._ontrack;
-        },
-        set: function(f) {
-          if (this._ontrack) {
-            this.removeEventListener('track', this._ontrack);
-            this.removeEventListener('addstream', this._ontrackpoly);
-          }
-          this.addEventListener('track', this._ontrack = f);
-          this.addEventListener('addstream', this._ontrackpoly = function(e) {
-            e.stream.getTracks().forEach(function(track) {
-              var event = new Event('track');
-              event.track = track;
-              event.receiver = {track: track};
-              event.streams = [e.stream];
-              this.dispatchEvent(event);
-            }.bind(this));
-          }.bind(this));
-        }
-      });
-    }
-  },
-
-  shimSourceObject: function(window) {
-    // Firefox has supported mozSrcObject since FF22, unprefixed in 42.
-    if (typeof window === 'object') {
-      if (window.HTMLMediaElement &&
-        !('srcObject' in window.HTMLMediaElement.prototype)) {
-        // Shim the srcObject property, once, when HTMLMediaElement is found.
-        Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
-          get: function() {
-            return this.mozSrcObject;
-          },
-          set: function(stream) {
-            this.mozSrcObject = stream;
-          }
-        });
-      }
-    }
-  },
-
-  shimPeerConnection: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    if (typeof window !== 'object' || !(window.RTCPeerConnection ||
-        window.mozRTCPeerConnection)) {
-      return; // probably media.peerconnection.enabled=false in about:config
-    }
-    // The RTCPeerConnection object.
-    if (!window.RTCPeerConnection) {
-      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-        if (browserDetails.version < 38) {
-          // .urls is not supported in FF < 38.
-          // create RTCIceServers with a single url.
-          if (pcConfig && pcConfig.iceServers) {
-            var newIceServers = [];
-            for (var i = 0; i < pcConfig.iceServers.length; i++) {
-              var server = pcConfig.iceServers[i];
-              if (server.hasOwnProperty('urls')) {
-                for (var j = 0; j < server.urls.length; j++) {
-                  var newServer = {
-                    url: server.urls[j]
-                  };
-                  if (server.urls[j].indexOf('turn') === 0) {
-                    newServer.username = server.username;
-                    newServer.credential = server.credential;
-                  }
-                  newIceServers.push(newServer);
-                }
-              } else {
-                newIceServers.push(pcConfig.iceServers[i]);
-              }
-            }
-            pcConfig.iceServers = newIceServers;
-          }
-        }
-        return new window.mozRTCPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype =
-          window.mozRTCPeerConnection.prototype;
-
-      // wrap static methods. Currently just generateCertificate.
-      if (window.mozRTCPeerConnection.generateCertificate) {
-        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-          get: function() {
-            return window.mozRTCPeerConnection.generateCertificate;
-          }
-        });
-      }
-
-      window.RTCSessionDescription = window.mozRTCSessionDescription;
-      window.RTCIceCandidate = window.mozRTCIceCandidate;
-    }
-
-    // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
-    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-        .forEach(function(method) {
-          var nativeMethod = window.RTCPeerConnection.prototype[method];
-          window.RTCPeerConnection.prototype[method] = function() {
-            arguments[0] = new ((method === 'addIceCandidate') ?
-                window.RTCIceCandidate :
-                window.RTCSessionDescription)(arguments[0]);
-            return nativeMethod.apply(this, arguments);
-          };
-        });
-
-    // support for addIceCandidate(null or undefined)
-    var nativeAddIceCandidate =
-        window.RTCPeerConnection.prototype.addIceCandidate;
-    window.RTCPeerConnection.prototype.addIceCandidate = function() {
-      if (!arguments[0]) {
-        if (arguments[1]) {
-          arguments[1].apply(null);
-        }
-        return Promise.resolve();
-      }
-      return nativeAddIceCandidate.apply(this, arguments);
-    };
-
-    // shim getStats with maplike support
-    var makeMapStats = function(stats) {
-      var map = new Map();
-      Object.keys(stats).forEach(function(key) {
-        map.set(key, stats[key]);
-        map[key] = stats[key];
-      });
-      return map;
-    };
-
-    var modernStatsTypes = {
-      inboundrtp: 'inbound-rtp',
-      outboundrtp: 'outbound-rtp',
-      candidatepair: 'candidate-pair',
-      localcandidate: 'local-candidate',
-      remotecandidate: 'remote-candidate'
-    };
-
-    var nativeGetStats = window.RTCPeerConnection.prototype.getStats;
-    window.RTCPeerConnection.prototype.getStats = function(
-      selector,
-      onSucc,
-      onErr
-    ) {
-      return nativeGetStats.apply(this, [selector || null])
-        .then(function(stats) {
-          if (browserDetails.version < 48) {
-            stats = makeMapStats(stats);
-          }
-          if (browserDetails.version < 53 && !onSucc) {
-            // Shim only promise getStats with spec-hyphens in type names
-            // Leave callback version alone; misc old uses of forEach before Map
-            try {
-              stats.forEach(function(stat) {
-                stat.type = modernStatsTypes[stat.type] || stat.type;
-              });
-            } catch (e) {
-              if (e.name !== 'TypeError') {
-                throw e;
-              }
-              // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
-              stats.forEach(function(stat, i) {
-                stats.set(i, Object.assign({}, stat, {
-                  type: modernStatsTypes[stat.type] || stat.type
-                }));
-              });
-            }
-          }
-          return stats;
-        })
-        .then(onSucc, onErr);
-    };
-  }
-};
-
-// Expose public methods.
-module.exports = {
-  shimOnTrack: firefoxShim.shimOnTrack,
-  shimSourceObject: firefoxShim.shimSourceObject,
-  shimPeerConnection: firefoxShim.shimPeerConnection,
-  shimGetUserMedia: require('./getusermedia')
-};
-
-},{"../utils":12,"./getusermedia":10}],10:[function(require,module,exports){
-/*
- *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
- /* eslint-env node */
-'use strict';
-
-var utils = require('../utils');
-var logging = utils.log;
-
-// Expose public methods.
-module.exports = function(window) {
-  var browserDetails = utils.detectBrowser(window);
-  var navigator = window && window.navigator;
-  var MediaStreamTrack = window && window.MediaStreamTrack;
-
-  var shimError_ = function(e) {
-    return {
-      name: {
-        InternalError: 'NotReadableError',
-        NotSupportedError: 'TypeError',
-        PermissionDeniedError: 'NotAllowedError',
-        SecurityError: 'NotAllowedError'
-      }[e.name] || e.name,
-      message: {
-        'The operation is insecure.': 'The request is not allowed by the ' +
-        'user agent or the platform in the current context.'
-      }[e.message] || e.message,
-      constraint: e.constraint,
-      toString: function() {
-        return this.name + (this.message && ': ') + this.message;
-      }
-    };
-  };
-
-  // getUserMedia constraints shim.
-  var getUserMedia_ = function(constraints, onSuccess, onError) {
-    var constraintsToFF37_ = function(c) {
-      if (typeof c !== 'object' || c.require) {
-        return c;
-      }
-      var require = [];
-      Object.keys(c).forEach(function(key) {
-        if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
-          return;
-        }
-        var r = c[key] = (typeof c[key] === 'object') ?
-            c[key] : {ideal: c[key]};
-        if (r.min !== undefined ||
-            r.max !== undefined || r.exact !== undefined) {
-          require.push(key);
-        }
-        if (r.exact !== undefined) {
-          if (typeof r.exact === 'number') {
-            r. min = r.max = r.exact;
-          } else {
-            c[key] = r.exact;
-          }
-          delete r.exact;
-        }
-        if (r.ideal !== undefined) {
-          c.advanced = c.advanced || [];
-          var oc = {};
-          if (typeof r.ideal === 'number') {
-            oc[key] = {min: r.ideal, max: r.ideal};
-          } else {
-            oc[key] = r.ideal;
-          }
-          c.advanced.push(oc);
-          delete r.ideal;
-          if (!Object.keys(r).length) {
-            delete c[key];
-          }
-        }
-      });
-      if (require.length) {
-        c.require = require;
-      }
-      return c;
-    };
-    constraints = JSON.parse(JSON.stringify(constraints));
-    if (browserDetails.version < 38) {
-      logging('spec: ' + JSON.stringify(constraints));
-      if (constraints.audio) {
-        constraints.audio = constraintsToFF37_(constraints.audio);
-      }
-      if (constraints.video) {
-        constraints.video = constraintsToFF37_(constraints.video);
-      }
-      logging('ff37: ' + JSON.stringify(constraints));
-    }
-    return navigator.mozGetUserMedia(constraints, onSuccess, function(e) {
-      onError(shimError_(e));
-    });
-  };
-
-  // Returns the result of getUserMedia as a Promise.
-  var getUserMediaPromise_ = function(constraints) {
-    return new Promise(function(resolve, reject) {
-      getUserMedia_(constraints, resolve, reject);
-    });
-  };
-
-  // Shim for mediaDevices on older versions.
-  if (!navigator.mediaDevices) {
-    navigator.mediaDevices = {getUserMedia: getUserMediaPromise_,
-      addEventListener: function() { },
-      removeEventListener: function() { }
-    };
-  }
-  navigator.mediaDevices.enumerateDevices =
-      navigator.mediaDevices.enumerateDevices || function() {
-        return new Promise(function(resolve) {
-          var infos = [
-            {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
-            {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
-          ];
-          resolve(infos);
-        });
-      };
-
-  if (browserDetails.version < 41) {
-    // Work around http://bugzil.la/1169665
-    var orgEnumerateDevices =
-        navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
-    navigator.mediaDevices.enumerateDevices = function() {
-      return orgEnumerateDevices().then(undefined, function(e) {
-        if (e.name === 'NotFoundError') {
-          return [];
-        }
-        throw e;
-      });
-    };
-  }
-  if (browserDetails.version < 49) {
-    var origGetUserMedia = navigator.mediaDevices.getUserMedia.
-        bind(navigator.mediaDevices);
-    navigator.mediaDevices.getUserMedia = function(c) {
-      return origGetUserMedia(c).then(function(stream) {
-        // Work around https://bugzil.la/802326
-        if (c.audio && !stream.getAudioTracks().length ||
-            c.video && !stream.getVideoTracks().length) {
-          stream.getTracks().forEach(function(track) {
-            track.stop();
-          });
-          throw new DOMException('The object can not be found here.',
-                                 'NotFoundError');
-        }
-        return stream;
-      }, function(e) {
-        return Promise.reject(shimError_(e));
-      });
-    };
-  }
-  if (!(browserDetails.version > 55 &&
-      'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) {
-    var remap = function(obj, a, b) {
-      if (a in obj && !(b in obj)) {
-        obj[b] = obj[a];
-        delete obj[a];
-      }
-    };
-
-    var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.
-        bind(navigator.mediaDevices);
-    navigator.mediaDevices.getUserMedia = function(c) {
-      if (typeof c === 'object' && typeof c.audio === 'object') {
-        c = JSON.parse(JSON.stringify(c));
-        remap(c.audio, 'autoGainControl', 'mozAutoGainControl');
-        remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');
-      }
-      return nativeGetUserMedia(c);
-    };
-
-    if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {
-      var nativeGetSettings = MediaStreamTrack.prototype.getSettings;
-      MediaStreamTrack.prototype.getSettings = function() {
-        var obj = nativeGetSettings.apply(this, arguments);
-        remap(obj, 'mozAutoGainControl', 'autoGainControl');
-        remap(obj, 'mozNoiseSuppression', 'noiseSuppression');
-        return obj;
-      };
-    }
-
-    if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) {
-      var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints;
-      MediaStreamTrack.prototype.applyConstraints = function(c) {
-        if (this.kind === 'audio' && typeof c === 'object') {
-          c = JSON.parse(JSON.stringify(c));
-          remap(c, 'autoGainControl', 'mozAutoGainControl');
-          remap(c, 'noiseSuppression', 'mozNoiseSuppression');
-        }
-        return nativeApplyConstraints.apply(this, [c]);
-      };
-    }
-  }
-  navigator.getUserMedia = function(constraints, onSuccess, onError) {
-    if (browserDetails.version < 44) {
-      return getUserMedia_(constraints, onSuccess, onError);
-    }
-    // Replace Firefox 44+'s deprecation warning with unprefixed version.
-    console.warn('navigator.getUserMedia has been replaced by ' +
-                 'navigator.mediaDevices.getUserMedia');
-    navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
-  };
-};
-
-},{"../utils":12}],11:[function(require,module,exports){
-/*
- *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
-'use strict';
-var utils = require('../utils');
-
-var safariShim = {
-  // TODO: DrAlex, should be here, double check against LayoutTests
-
-  // TODO: once the back-end for the mac port is done, add.
-  // TODO: check for webkitGTK+
-  // shimPeerConnection: function() { },
-
-  shimLocalStreamsAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getLocalStreams = function() {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        return this._localStreams;
-      };
-    }
-    if (!('getStreamById' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getStreamById = function(id) {
-        var result = null;
-        if (this._localStreams) {
-          this._localStreams.forEach(function(stream) {
-            if (stream.id === id) {
-              result = stream;
-            }
-          });
-        }
-        if (this._remoteStreams) {
-          this._remoteStreams.forEach(function(stream) {
-            if (stream.id === id) {
-              result = stream;
-            }
-          });
-        }
-        return result;
-      };
-    }
-    if (!('addStream' in window.RTCPeerConnection.prototype)) {
-      var _addTrack = window.RTCPeerConnection.prototype.addTrack;
-      window.RTCPeerConnection.prototype.addStream = function(stream) {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        if (this._localStreams.indexOf(stream) === -1) {
-          this._localStreams.push(stream);
-        }
-        var self = this;
-        stream.getTracks().forEach(function(track) {
-          _addTrack.call(self, track, stream);
-        });
-      };
-
-      window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-        if (stream) {
-          if (!this._localStreams) {
-            this._localStreams = [stream];
-          } else if (this._localStreams.indexOf(stream) === -1) {
-            this._localStreams.push(stream);
-          }
-        }
-        _addTrack.call(this, track, stream);
-      };
-    }
-    if (!('removeStream' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.removeStream = function(stream) {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        var index = this._localStreams.indexOf(stream);
-        if (index === -1) {
-          return;
-        }
-        this._localStreams.splice(index, 1);
-        var self = this;
-        var tracks = stream.getTracks();
-        this.getSenders().forEach(function(sender) {
-          if (tracks.indexOf(sender.track) !== -1) {
-            self.removeTrack(sender);
-          }
-        });
-      };
-    }
-  },
-  shimRemoteStreamsAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getRemoteStreams = function() {
-        return this._remoteStreams ? this._remoteStreams : [];
-      };
-    }
-    if (!('onaddstream' in window.RTCPeerConnection.prototype)) {
-      Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {
-        get: function() {
-          return this._onaddstream;
-        },
-        set: function(f) {
-          if (this._onaddstream) {
-            this.removeEventListener('addstream', this._onaddstream);
-            this.removeEventListener('track', this._onaddstreampoly);
-          }
-          this.addEventListener('addstream', this._onaddstream = f);
-          this.addEventListener('track', this._onaddstreampoly = function(e) {
-            var stream = e.streams[0];
-            if (!this._remoteStreams) {
-              this._remoteStreams = [];
-            }
-            if (this._remoteStreams.indexOf(stream) >= 0) {
-              return;
-            }
-            this._remoteStreams.push(stream);
-            var event = new Event('addstream');
-            event.stream = e.streams[0];
-            this.dispatchEvent(event);
-          }.bind(this));
-        }
-      });
-    }
-  },
-  shimCallbacksAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    var prototype = window.RTCPeerConnection.prototype;
-    var createOffer = prototype.createOffer;
-    var createAnswer = prototype.createAnswer;
-    var setLocalDescription = prototype.setLocalDescription;
-    var setRemoteDescription = prototype.setRemoteDescription;
-    var addIceCandidate = prototype.addIceCandidate;
-
-    prototype.createOffer = function(successCallback, failureCallback) {
-      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
-      var promise = createOffer.apply(this, [options]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-
-    prototype.createAnswer = function(successCallback, failureCallback) {
-      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
-      var promise = createAnswer.apply(this, [options]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-
-    var withCallback = function(description, successCallback, failureCallback) {
-      var promise = setLocalDescription.apply(this, [description]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.setLocalDescription = withCallback;
-
-    withCallback = function(description, successCallback, failureCallback) {
-      var promise = setRemoteDescription.apply(this, [description]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.setRemoteDescription = withCallback;
-
-    withCallback = function(candidate, successCallback, failureCallback) {
-      var promise = addIceCandidate.apply(this, [candidate]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.addIceCandidate = withCallback;
-  },
-  shimGetUserMedia: function(window) {
-    var navigator = window && window.navigator;
-
-    if (!navigator.getUserMedia) {
-      if (navigator.webkitGetUserMedia) {
-        navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
-      } else if (navigator.mediaDevices &&
-          navigator.mediaDevices.getUserMedia) {
-        navigator.getUserMedia = function(constraints, cb, errcb) {
-          navigator.mediaDevices.getUserMedia(constraints)
-          .then(cb, errcb);
-        }.bind(navigator);
-      }
-    }
-  },
-  shimRTCIceServerUrls: function(window) {
-    // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
-    var OrigPeerConnection = window.RTCPeerConnection;
-    window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-      if (pcConfig && pcConfig.iceServers) {
-        var newIceServers = [];
-        for (var i = 0; i < pcConfig.iceServers.length; i++) {
-          var server = pcConfig.iceServers[i];
-          if (!server.hasOwnProperty('urls') &&
-              server.hasOwnProperty('url')) {
-            utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
-            server = JSON.parse(JSON.stringify(server));
-            server.urls = server.url;
-            delete server.url;
-            newIceServers.push(server);
-          } else {
-            newIceServers.push(pcConfig.iceServers[i]);
-          }
-        }
-        pcConfig.iceServers = newIceServers;
-      }
-      return new OrigPeerConnection(pcConfig, pcConstraints);
-    };
-    window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
-    // wrap static methods. Currently just generateCertificate.
-    Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-      get: function() {
-        return OrigPeerConnection.generateCertificate;
-      }
-    });
-  }
-};
-
-// Expose public methods.
-module.exports = {
-  shimCallbacksAPI: safariShim.shimCallbacksAPI,
-  shimLocalStreamsAPI: safariShim.shimLocalStreamsAPI,
-  shimRemoteStreamsAPI: safariShim.shimRemoteStreamsAPI,
-  shimGetUserMedia: safariShim.shimGetUserMedia,
-  shimRTCIceServerUrls: safariShim.shimRTCIceServerUrls
-  // TODO
-  // shimPeerConnection: safariShim.shimPeerConnection
-};
-
-},{"../utils":12}],12:[function(require,module,exports){
-/*
- *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
- /* eslint-env node */
-'use strict';
-
-var logDisabled_ = true;
-var deprecationWarnings_ = true;
-
-// Utility methods.
-var utils = {
-  disableLog: function(bool) {
-    if (typeof bool !== 'boolean') {
-      return new Error('Argument type: ' + typeof bool +
-          '. Please use a boolean.');
-    }
-    logDisabled_ = bool;
-    return (bool) ? 'adapter.js logging disabled' :
-        'adapter.js logging enabled';
-  },
-
-  /**
-   * Disable or enable deprecation warnings
-   * @param {!boolean} bool set to true to disable warnings.
-   */
-  disableWarnings: function(bool) {
-    if (typeof bool !== 'boolean') {
-      return new Error('Argument type: ' + typeof bool +
-          '. Please use a boolean.');
-    }
-    deprecationWarnings_ = !bool;
-    return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
-  },
-
-  log: function() {
-    if (typeof window === 'object') {
-      if (logDisabled_) {
-        return;
-      }
-      if (typeof console !== 'undefined' && typeof console.log === 'function') {
-        console.log.apply(console, arguments);
-      }
-    }
-  },
-
-  /**
-   * Shows a deprecation warning suggesting the modern and spec-compatible API.
-   */
-  deprecated: function(oldMethod, newMethod) {
-    if (!deprecationWarnings_) {
-      return;
-    }
-    console.warn(oldMethod + ' is deprecated, please use ' + newMethod +
-        ' instead.');
-  },
-
-  /**
-   * Extract browser version out of the provided user agent string.
-   *
-   * @param {!string} uastring userAgent string.
-   * @param {!string} expr Regular expression used as match criteria.
-   * @param {!number} pos position in the version string to be returned.
-   * @return {!number} browser version.
-   */
-  extractVersion: function(uastring, expr, pos) {
-    var match = uastring.match(expr);
-    return match && match.length >= pos && parseInt(match[pos], 10);
-  },
-
-  /**
-   * Browser detector.
-   *
-   * @return {object} result containing browser and version
-   *     properties.
-   */
-  detectBrowser: function(window) {
-    var navigator = window && window.navigator;
-
-    // Returned result object.
-    var result = {};
-    result.browser = null;
-    result.version = null;
-
-    // Fail early if it's not a browser
-    if (typeof window === 'undefined' || !window.navigator) {
-      result.browser = 'Not a browser.';
-      return result;
-    }
-
-    // Firefox.
-    if (navigator.mozGetUserMedia) {
-      result.browser = 'firefox';
-      result.version = this.extractVersion(navigator.userAgent,
-          /Firefox\/(\d+)\./, 1);
-    } else if (navigator.webkitGetUserMedia) {
-      // Chrome, Chromium, Webview, Opera, all use the chrome shim for now
-      if (window.webkitRTCPeerConnection) {
-        result.browser = 'chrome';
-        result.version = this.extractVersion(navigator.userAgent,
-          /Chrom(e|ium)\/(\d+)\./, 2);
-      } else { // Safari (in an unpublished version) or unknown webkit-based.
-        if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) {
-          result.browser = 'safari';
-          result.version = this.extractVersion(navigator.userAgent,
-            /AppleWebKit\/(\d+)\./, 1);
-        } else { // unknown webkit-based browser.
-          result.browser = 'Unsupported webkit-based browser ' +
-              'with GUM support but no WebRTC support.';
-          return result;
-        }
-      }
-    } else if (navigator.mediaDevices &&
-        navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge.
-      result.browser = 'edge';
-      result.version = this.extractVersion(navigator.userAgent,
-          /Edge\/(\d+).(\d+)$/, 2);
-    } else if (navigator.mediaDevices &&
-        navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) {
-        // Safari, with webkitGetUserMedia removed.
-      result.browser = 'safari';
-      result.version = this.extractVersion(navigator.userAgent,
-          /AppleWebKit\/(\d+)\./, 1);
-    } else { // Default fallthrough: not supported.
-      result.browser = 'Not a supported browser.';
-      return result;
-    }
-
-    return result;
-  },
-
-  // shimCreateObjectURL must be called before shimSourceObject to avoid loop.
-
-  shimCreateObjectURL: function(window) {
-    var URL = window && window.URL;
-
-    if (!(typeof window === 'object' && window.HTMLMediaElement &&
-          'srcObject' in window.HTMLMediaElement.prototype)) {
-      // Only shim CreateObjectURL using srcObject if srcObject exists.
-      return undefined;
-    }
-
-    var nativeCreateObjectURL = URL.createObjectURL.bind(URL);
-    var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL);
-    var streams = new Map(), newId = 0;
-
-    URL.createObjectURL = function(stream) {
-      if ('getTracks' in stream) {
-        var url = 'polyblob:' + (++newId);
-        streams.set(url, stream);
-        utils.deprecated('URL.createObjectURL(stream)',
-            'elem.srcObject = stream');
-        return url;
-      }
-      return nativeCreateObjectURL(stream);
-    };
-    URL.revokeObjectURL = function(url) {
-      nativeRevokeObjectURL(url);
-      streams.delete(url);
-    };
-
-    var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,
-                                              'src');
-    Object.defineProperty(window.HTMLMediaElement.prototype, 'src', {
-      get: function() {
-        return dsc.get.apply(this);
-      },
-      set: function(url) {
-        this.srcObject = streams.get(url) || null;
-        return dsc.set.apply(this, [url]);
-      }
-    });
-
-    var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute;
-    window.HTMLMediaElement.prototype.setAttribute = function() {
-      if (arguments.length === 2 &&
-          ('' + arguments[0]).toLowerCase() === 'src') {
-        this.srcObject = streams.get(arguments[1]) || null;
-      }
-      return nativeSetAttribute.apply(this, arguments);
-    };
-  }
-};
-
-// Export.
-module.exports = {
-  log: utils.log,
-  deprecated: utils.deprecated,
-  disableLog: utils.disableLog,
-  disableWarnings: utils.disableWarnings,
-  extractVersion: utils.extractVersion,
-  shimCreateObjectURL: utils.shimCreateObjectURL,
-  detectBrowser: utils.detectBrowser.bind(utils)
-};
-
-},{}]},{},[2])(2)
-});
\ No newline at end of file
diff --git a/tools/perf/page_sets/webrtc_cases/audio.html b/tools/perf/page_sets/webrtc_cases/audio.html
deleted file mode 100644
index 035888d..0000000
--- a/tools/perf/page_sets/webrtc_cases/audio.html
+++ /dev/null
@@ -1,63 +0,0 @@
-<!DOCTYPE html>
-<!--
- * Copyright 2017 The Chromium Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
--->
-<html>
-<head>
-
-
-  <base target="_blank">
-
-  <title>Peer connection: audio only</title>
-
-
-</head>
-
-<body>
-
-  <div id="container">
-
-    <h1><a href="//webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC samples</a> <span>Peer connection: audio only</span></h1>
-
-    <div id="audio">
-      <div>
-        <div class="label">Local audio:</div><audio id="audio1" autoplay controls muted></audio>
-      </div>
-      <div>
-        <div class="label">Remote audio:</div><audio id="audio2" autoplay controls></audio>
-      </div>
-    </div>
-
-    <div id="buttons">
-      <select id="codec">
-        <!-- Codec values are matched with how they appear in the SDP.
-        For instance, opus matches opus/48000/2 in Chrome, and ISAC/16000
-        matches 16K iSAC (but not 32K iSAC). -->
-        <option value="opus">Opus</option>
-        <option value="ISAC">iSAC 16K</option>
-        <option value="G722">G722</option>
-        <option value="PCMU">PCMU</option>
-      </select>
-      <button id="callButton">Call</button>
-      <button id="hangupButton">Hang Up</button>
-    </div>
-    <div class="graph-container" id="bitrateGraph">
-      <div>Bitrate</div>
-      <canvas id="bitrateCanvas"></canvas>
-    </div>
-    <div class="graph-container" id="packetGraph">
-      <div>Packets sent per second</div>
-      <canvas id="packetCanvas"></canvas>
-    </div>
-
-    <a href="https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/audio" title="View source for this page on GitHub" id="viewSource">View source on GitHub</a>
-
-  </div>
-
-
-<script src="audio.js"></script>
-<script src="adapter.js"></script>
-<script src="common.js"></script>
-</body></html>
diff --git a/tools/perf/page_sets/webrtc_cases/audio.js b/tools/perf/page_sets/webrtc_cases/audio.js
deleted file mode 100644
index 588f643..0000000
--- a/tools/perf/page_sets/webrtc_cases/audio.js
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright 2017 The Chromium Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-'use strict';
-
-var audio2 = document.querySelector('audio#audio2');
-var callButton = document.querySelector('button#callButton');
-var hangupButton = document.querySelector('button#hangupButton');
-var codecSelector = document.querySelector('select#codec');
-hangupButton.disabled = true;
-callButton.onclick = call;
-hangupButton.onclick = hangup;
-
-var pc1;
-var pc2;
-var localStream;
-
-var bitrateGraph;
-var bitrateSeries;
-
-var packetGraph;
-var packetSeries;
-
-var lastResult;
-
-var offerOptions = {
-  offerToReceiveAudio: 1,
-  offerToReceiveVideo: 0,
-  voiceActivityDetection: false
-};
-
-function gotStream(stream) {
-  hangupButton.disabled = false;
-  trace('Received local stream');
-  localStream = stream;
-  var audioTracks = localStream.getAudioTracks();
-  if (audioTracks.length > 0) {
-    trace('Using Audio device: ' + audioTracks[0].label);
-  }
-  localStream.getTracks().forEach(
-    function(track) {
-      pc1.addTrack(
-        track,
-        localStream
-      );
-    }
-  );
-  trace('Adding Local Stream to peer connection');
-
-  pc1.createOffer(
-    offerOptions
-  ).then(
-    gotDescription1,
-    onCreateSessionDescriptionError
-  );
-
-  bitrateSeries = new TimelineDataSeries();
-  bitrateGraph = new TimelineGraphView('bitrateGraph', 'bitrateCanvas');
-  bitrateGraph.updateEndDate();
-
-  packetSeries = new TimelineDataSeries();
-  packetGraph = new TimelineGraphView('packetGraph', 'packetCanvas');
-  packetGraph.updateEndDate();
-}
-
-function onCreateSessionDescriptionError(error) {
-  trace('Failed to create session description: ' + error.toString());
-}
-
-function call() {
-  callButton.disabled = true;
-  codecSelector.disabled = true;
-  trace('Starting call');
-  var servers = null;
-  var pcConstraints = {
-    'optional': []
-  };
-  pc1 = new RTCPeerConnection(servers, pcConstraints);
-  trace('Created local peer connection object pc1');
-  pc1.onicecandidate = function(e) {
-    onIceCandidate(pc1, e);
-  };
-  pc2 = new RTCPeerConnection(servers, pcConstraints);
-  trace('Created remote peer connection object pc2');
-  pc2.onicecandidate = function(e) {
-    onIceCandidate(pc2, e);
-  };
-  pc2.ontrack = gotRemoteStream;
-  trace('Requesting local stream');
-  navigator.mediaDevices.getUserMedia({
-    audio: true,
-    video: false
-  })
-  .then(gotStream)
-  .catch(function(e) {
-    alert('getUserMedia() error: ' + e.name);
-  });
-}
-
-function gotDescription1(desc) {
-  trace('Offer from pc1 \n' + desc.sdp);
-  pc1.setLocalDescription(desc).then(
-    function() {
-      desc.sdp = forceChosenAudioCodec(desc.sdp);
-      pc2.setRemoteDescription(desc).then(
-        function() {
-          pc2.createAnswer().then(
-            gotDescription2,
-            onCreateSessionDescriptionError
-          );
-        },
-        onSetSessionDescriptionError
-      );
-    },
-    onSetSessionDescriptionError
-  );
-}
-
-function gotDescription2(desc) {
-  trace('Answer from pc2 \n' + desc.sdp);
-  pc2.setLocalDescription(desc).then(
-    function() {
-      desc.sdp = forceChosenAudioCodec(desc.sdp);
-      pc1.setRemoteDescription(desc).then(
-        function() {
-        },
-        onSetSessionDescriptionError
-      );
-    },
-    onSetSessionDescriptionError
-  );
-}
-
-function hangup() {
-  trace('Ending call');
-  localStream.getTracks().forEach(function(track) {
-    track.stop();
-  });
-  pc1.close();
-  pc2.close();
-  pc1 = null;
-  pc2 = null;
-  hangupButton.disabled = true;
-  callButton.disabled = false;
-  codecSelector.disabled = false;
-}
-
-function gotRemoteStream(e) {
-  if (audio2.srcObject !== e.streams[0]) {
-    audio2.srcObject = e.streams[0];
-    trace('Received remote stream');
-  }
-}
-
-function getOtherPc(pc) {
-  return (pc === pc1) ? pc2 : pc1;
-}
-
-function getName(pc) {
-  return (pc === pc1) ? 'pc1' : 'pc2';
-}
-
-function onIceCandidate(pc, event) {
-  getOtherPc(pc).addIceCandidate(event.candidate)
-  .then(
-    function() {
-      onAddIceCandidateSuccess(pc);
-    },
-    function(err) {
-      onAddIceCandidateError(pc, err);
-    }
-  );
-  trace(getName(pc) + ' ICE candidate: \n' + (event.candidate ?
-      event.candidate.candidate : '(null)'));
-}
-
-function onAddIceCandidateSuccess() {
-  trace('AddIceCandidate success.');
-}
-
-function onAddIceCandidateError(error) {
-  trace('Failed to add ICE Candidate: ' + error.toString());
-}
-
-function onSetSessionDescriptionError(error) {
-  trace('Failed to set session description: ' + error.toString());
-}
-
-function forceChosenAudioCodec(sdp) {
-  return maybePreferCodec(sdp, 'audio', 'send', codecSelector.value);
-}
-
-// Copied from AppRTC's sdputils.js:
-
-// Sets |codec| as the default |type| codec if it's present.
-// The format of |codec| is 'NAME/RATE', e.g. 'opus/48000'.
-function maybePreferCodec(sdp, type, dir, codec) {
-  var str = type + ' ' + dir + ' codec';
-  if (codec === '') {
-    trace('No preference on ' + str + '.');
-    return sdp;
-  }
-
-  trace('Prefer ' + str + ': ' + codec);
-
-  var sdpLines = sdp.split('\r\n');
-
-  // Search for m line.
-  var mLineIndex = findLine(sdpLines, 'm=', type);
-  if (mLineIndex === null) {
-    return sdp;
-  }
-
-  // If the codec is available, set it as the default in m line.
-  var codecIndex = findLine(sdpLines, 'a=rtpmap', codec);
-  console.log('codecIndex', codecIndex);
-  if (codecIndex) {
-    var payload = getCodecPayloadType(sdpLines[codecIndex]);
-    if (payload) {
-      sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], payload);
-    }
-  }
-
-  sdp = sdpLines.join('\r\n');
-  return sdp;
-}
-
-// Find the line in sdpLines that starts with |prefix|, and, if specified,
-// contains |substr| (case-insensitive search).
-function findLine(sdpLines, prefix, substr) {
-  return findLineInRange(sdpLines, 0, -1, prefix, substr);
-}
-
-// Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
-// and, if specified, contains |substr| (case-insensitive search).
-function findLineInRange(sdpLines, startLine, endLine, prefix, substr) {
-  var realEndLine = endLine !== -1 ? endLine : sdpLines.length;
-  for (var i = startLine; i < realEndLine; ++i) {
-    if (sdpLines[i].indexOf(prefix) === 0) {
-      if (!substr ||
-          sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) {
-        return i;
-      }
-    }
-  }
-  return null;
-}
-
-// Gets the codec payload type from an a=rtpmap:X line.
-function getCodecPayloadType(sdpLine) {
-  var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');
-  var result = sdpLine.match(pattern);
-  return (result && result.length === 2) ? result[1] : null;
-}
-
-// Returns a new m= line with the specified codec as the first one.
-function setDefaultCodec(mLine, payload) {
-  var elements = mLine.split(' ');
-
-  // Just copy the first three parameters; codec order starts on fourth.
-  var newLine = elements.slice(0, 3);
-
-  // Put target payload first and copy in the rest.
-  newLine.push(payload);
-  for (var i = 3; i < elements.length; i++) {
-    if (elements[i] !== payload) {
-      newLine.push(elements[i]);
-    }
-  }
-  return newLine.join(' ');
-}
-
-// query getStats every second
-window.setInterval(function() {
-  if (!window.pc1) {
-    return;
-  }
-  window.pc1.getStats(null).then(function(res) {
-    res.forEach(function(report) {
-      var bytes;
-      var packets;
-      var now = report.timestamp;
-      if ((report.type === 'outboundrtp') ||
-          (report.type === 'outbound-rtp') ||
-          (report.type === 'ssrc' && report.bytesSent)) {
-        bytes = report.bytesSent;
-        packets = report.packetsSent;
-        if (lastResult && lastResult.get(report.id)) {
-          // calculate bitrate
-          var bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) /
-              (now - lastResult.get(report.id).timestamp);
-
-          // append to chart
-          bitrateSeries.addPoint(now, bitrate);
-          bitrateGraph.setDataSeries([bitrateSeries]);
-          bitrateGraph.updateEndDate();
-
-          // calculate number of packets and append to chart
-          packetSeries.addPoint(now, packets -
-              lastResult.get(report.id).packetsSent);
-          packetGraph.setDataSeries([packetSeries]);
-          packetGraph.updateEndDate();
-        }
-      }
-    });
-    lastResult = res;
-  });
-}, 1000);
diff --git a/tools/perf/page_sets/webrtc_cases/canvas-capture.html b/tools/perf/page_sets/webrtc_cases/canvas-capture.html
index 018cb25..2fd7288 100644
--- a/tools/perf/page_sets/webrtc_cases/canvas-capture.html
+++ b/tools/perf/page_sets/webrtc_cases/canvas-capture.html
@@ -21,6 +21,5 @@
 
 
 <script src="canvas-capture.js"></script>
-<script src="adapter.js"></script>
-<script src="common.js"></script>
+<script src="adapter-latest.js"></script>
 </body></html>
diff --git a/tools/perf/page_sets/webrtc_cases/codec_constraints.html b/tools/perf/page_sets/webrtc_cases/codec_constraints.html
index 1e85c49..d524a11 100644
--- a/tools/perf/page_sets/webrtc_cases/codec_constraints.html
+++ b/tools/perf/page_sets/webrtc_cases/codec_constraints.html
@@ -51,6 +51,5 @@
 
 
 <script src="codec_constraints.js"></script>
-<script src="adapter.js"></script>
-<script src="common.js"></script>
+<script src="adapter-latest.js"></script>
 </body></html>
diff --git a/tools/perf/page_sets/webrtc_cases/codec_constraints.js b/tools/perf/page_sets/webrtc_cases/codec_constraints.js
index 1a1cf91..5909c64 100644
--- a/tools/perf/page_sets/webrtc_cases/codec_constraints.js
+++ b/tools/perf/page_sets/webrtc_cases/codec_constraints.js
@@ -90,10 +90,7 @@
   startButton.disabled = true;
   navigator.mediaDevices.getUserMedia({
     audio: true,
-    video: {
-      width: {exact: 1280},
-      height: {exact: 720},
-    },
+    video: true
   })
     .then(gotStream)
     .catch(function(e) {
diff --git a/tools/perf/page_sets/webrtc_cases/common.js b/tools/perf/page_sets/webrtc_cases/common.js
deleted file mode 100644
index 047a000..0000000
--- a/tools/perf/page_sets/webrtc_cases/common.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * Copyright 2017 The Chromium Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
- /* exported trace */
-
-// Logging utility function.
-function trace(arg) {
-  var now = (window.performance.now() / 1000).toFixed(3);
-  console.log(now + ': ', arg);
-}
diff --git a/tools/perf/page_sets/webrtc_cases/datatransfer.html b/tools/perf/page_sets/webrtc_cases/datatransfer.html
index 2c6315e..32e04d6 100644
--- a/tools/perf/page_sets/webrtc_cases/datatransfer.html
+++ b/tools/perf/page_sets/webrtc_cases/datatransfer.html
@@ -8,66 +8,75 @@
 <head>
 
 
-  <base target="_blank">
+    <base target="_blank">
 
-  <title>Generate and transfer data</title>
+    <title>Generate and transfer data</title>
 
 
 </head>
 
 <body>
 
-  <div id="container">
+<div id="container">
 
-    <h1><a href="https://webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC samples</a> <span>Generate and transfer data</span></h1>
+    <h1><a href="https://webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC samples</a> <span>Generate and transfer data</span>
+    </h1>
     <section>
 
-      <p>This page generates and sends the specified amount of data via WebRTC datachannels.</p>
+        <p>This page generates and sends the specified amount of data via WebRTC datachannels.</p>
 
-      <p>To accomplish this in an interoperable way, the data is split into chunks which are then transferred via the datachannel. The datachannel is reliable and ordered by default which is well-suited to filetransfers.</p>
+        <p>To accomplish this in an interoperable way, the data is split into chunks which are then transferred via the
+            datachannel. The datachannel is reliable and ordered by default which is well-suited to filetransfers.</p>
 
-      <p>Send and receive progress is monitored using HTML5 <i>progress</i> elements.</p>
+        <p>Send and receive progress is monitored using HTML5 <i>progress</i> elements.</p>
 
     </section>
 
     <section>
-      <div id="button">
-        <button id="sendTheData" type="button">Generate and send data</button>
-      </div>
-      <div class="input">
-        <input type="number" id="megsToSend" min="1" name="megs" value="128"/>
-        <label for="megsToSend">MB</label>
-        <div id="errorMsg"></div>
-      </div>
-      <div class="input">
-        <input type="checkbox" id="ordered" checked>
-        <label for="ordered">Ordered mode</label>
-      </div>
-      <div class="progress">
-        <div class="label">Send progress: </div>
-        <progress id="sendProgress" max="0" value="0"></progress>
-      </div>
+        <div id="button">
+            <button id="sendTheData" type="button">Generate and send data</button>
+        </div>
+        <div class="input">
+            <input type="number" id="megsToSend" min="1" name="megs" value="16"/>
+            <label for="megsToSend">MB <b>(warning: very large values will potentially cause memory problems)</b></label>
+            <div id="errorMsg"></div>
+        </div>
+        <div class="input">
+            <input type="checkbox" id="ordered" checked>
+            <label for="ordered">Ordered mode</label>
+        </div>
+        <div class="progress">
+            <div class="label">Send progress:</div>
+            <progress id="sendProgress" max="0" value="0"></progress>
+        </div>
 
-      <div class="progress">
-        <div class="label">Receive progress: </div>
-        <progress id="receiveProgress" max="0" value="0"></progress>
-      </div>
+        <div class="progress">
+            <div class="label">Receive progress:</div>
+            <progress id="receiveProgress" max="0" value="0"></progress>
+        </div>
+
+        <div>
+            <span id="transferStatus"></span>
+        </div>
     </section>
 
     <section>
-      <p>View the console to see logging.</p>
+        <p>View the console to see logging.</p>
 
-      <p>The <code>RTCPeerConnection</code> objects <code>localConnection</code> and <code>remoteConnection</code> are in global scope, so you can inspect them in the console as well.</p>
+        <p>The <code>RTCPeerConnection</code> objects <code>localConnection</code> and <code>remoteConnection</code> are
+            in global scope, so you can inspect them in the console as well.</p>
 
-      <p>For more information about RTCDataChannel, see <a href="http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcdatachannel" title="RTCDataChannel section of HTML5 Rocks article about WebRTC">Getting Started With WebRTC</a>.</p>
+        <p>For more information about RTCDataChannel, see <a
+                href="http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcdatachannel"
+                title="RTCDataChannel section of HTML5 Rocks article about WebRTC">Getting Started With WebRTC</a>.</p>
     </section>
 
-    <a href="https://github.com/webrtc/samples/tree/gh-pages/src/content/datachannel/datatransfer" title="View source for this page on GitHub" id="viewSource">View source on GitHub</a>
-  </div>
+    <a href="https://github.com/webrtc/samples/tree/gh-pages/src/content/datachannel/datatransfer"
+       title="View source for this page on GitHub" id="viewSource">View source on GitHub</a>
+</div>
 
 
 
 <script src="datatransfer.js"></script>
-<script src="adapter.js"></script>
-<script src="common.js"></script>
+<script src="adapter-latest.js"></script>
 </body></html>
diff --git a/tools/perf/page_sets/webrtc_cases/datatransfer.js b/tools/perf/page_sets/webrtc_cases/datatransfer.js
index 7c50507..fa589365 100644
--- a/tools/perf/page_sets/webrtc_cases/datatransfer.js
+++ b/tools/perf/page_sets/webrtc_cases/datatransfer.js
@@ -4,168 +4,162 @@
  * found in the LICENSE file.
  */
 'use strict';
+const MAX_CHUNK_SIZE = 262144;
 
-var localConnection;
-var remoteConnection;
-var sendChannel;
-var receiveChannel;
-var pcConstraint;
-var megsToSend = document.querySelector('input#megsToSend');
-var sendButton = document.querySelector('button#sendTheData');
-var orderedCheckbox = document.querySelector('input#ordered');
-var sendProgress = document.querySelector('progress#sendProgress');
-var receiveProgress = document.querySelector('progress#receiveProgress');
-var errorMessage = document.querySelector('div#errorMsg');
+let localConnection;
+let remoteConnection;
+let sendChannel;
+let receiveChannel;
+let chunkSize;
+let lowWaterMark;
+let highWaterMark;
+let dataString;
+let timeoutHandle = null;
+const megsToSend = document.querySelector('input#megsToSend');
+const sendButton = document.querySelector('button#sendTheData');
+const orderedCheckbox = document.querySelector('input#ordered');
+const sendProgress = document.querySelector('progress#sendProgress');
+const receiveProgress = document.querySelector('progress#receiveProgress');
+const errorMessage = document.querySelector('div#errorMsg');
+const transferStatus = document.querySelector('span#transferStatus');
 
-var receivedSize = 0;
-var bytesToSend = 0;
+let bytesToSend = 0;
+let totalTimeUsedInSend = 0;
+let numberOfSendCalls = 0;
+let maxTimeUsedInSend = 0;
+let sendStartTime = 0;
 
-sendButton.onclick = createConnection;
+sendButton.addEventListener('click', createConnection);
 
 // Prevent data sent to be set to 0.
-megsToSend.addEventListener('change', function(e) {
-  if (this.value <= 0) {
+megsToSend.addEventListener('change', function() {
+  const number = this.value;
+  if (Number.isNaN(number)) {
+    errorMessage.innerHTML = `Invalid value for MB to send: ${number}`;
+  } else if (number <= 0) {
     sendButton.disabled = true;
     errorMessage.innerHTML = '<p>Please enter a number greater than zero.</p>';
+  } else if (number > 64) {
+    sendButton.disabled = true;
+    errorMessage.innerHTML = '<p>Please enter a number lower or equal than 64.</p>';
   } else {
     errorMessage.innerHTML = '';
     sendButton.disabled = false;
   }
 });
 
-function createConnection() {
+async function createConnection() {
   sendButton.disabled = true;
   megsToSend.disabled = true;
-  var servers = null;
-  pcConstraint = null;
 
-  bytesToSend = Math.round(megsToSend.value) * 1024 * 1024;
+  const servers = null;
 
-  // Add localConnection to global scope to make it visible
-  // from the browser console.
-  window.localConnection = localConnection = new RTCPeerConnection(servers,
-      pcConstraint);
-  trace('Created local peer connection object localConnection');
+  const number = Number.parseInt(megsToSend.value);
+  bytesToSend = number * 1024 * 1024;
 
-  var dataChannelParams = {ordered: false};
+  localConnection = new RTCPeerConnection(servers);
+
+  // Let's make a data channel!
+  const dataChannelParams = {ordered: false};
   if (orderedCheckbox.checked) {
     dataChannelParams.ordered = true;
   }
+  sendChannel = localConnection.createDataChannel('sendDataChannel', dataChannelParams);
+  sendChannel.addEventListener('open', onSendChannelOpen);
+  sendChannel.addEventListener('close', onSendChannelClosed);
+  console.log('Created send data channel: ', sendChannel);
 
-  sendChannel = localConnection.createDataChannel(
-      'sendDataChannel', dataChannelParams);
-  sendChannel.binaryType = 'arraybuffer';
-  trace('Created send data channel');
+  console.log('Created local peer connection object localConnection: ', localConnection);
 
-  sendChannel.onopen = onSendChannelStateChange;
-  sendChannel.onclose = onSendChannelStateChange;
-  localConnection.onicecandidate = function(e) {
-    onIceCandidate(localConnection, e);
-  };
+  localConnection.addEventListener('icecandidate', e => onIceCandidate(localConnection, e));
 
-  localConnection.createOffer().then(
-    gotDescription1,
-    onCreateSessionDescriptionError
-  );
+  remoteConnection = new RTCPeerConnection(servers);
+  remoteConnection.addEventListener('icecandidate', e => onIceCandidate(remoteConnection, e));
+  remoteConnection.addEventListener('datachannel', receiveChannelCallback);
 
-  // Add remoteConnection to global scope to make it visible
-  // from the browser console.
-  window.remoteConnection = remoteConnection = new RTCPeerConnection(servers,
-      pcConstraint);
-  trace('Created remote peer connection object remoteConnection');
-
-  remoteConnection.onicecandidate = function(e) {
-    onIceCandidate(remoteConnection, e);
-  };
-  remoteConnection.ondatachannel = receiveChannelCallback;
-}
-
-function onCreateSessionDescriptionError(error) {
-  trace('Failed to create session description: ' + error.toString());
-}
-
-function randomAsciiString(length) {
-  var result = '';
-  for (var i = 0; i < length; i++) {
-    // Visible ASCII chars are between 33 and 126.
-    result += String.fromCharCode(33 + Math.random() * 93);
+  try {
+    const localOffer = await localConnection.createOffer();
+    await handleLocalDescription(localOffer);
+  } catch (e) {
+    console.error('Failed to create session description: ', e);
   }
-  return result;
+
+  transferStatus.innerHTML = 'Peer connection setup complete.';
 }
 
-function sendGeneratedData() {
+function sendData() {
+  // Stop scheduled timer if any (part of the workaround introduced below)
+  if (timeoutHandle !== null) {
+    clearTimeout(timeoutHandle);
+    timeoutHandle = null;
+  }
+
+  let bufferedAmount = sendChannel.bufferedAmount;
+  while (sendProgress.value < sendProgress.max) {
+    transferStatus.innerText = 'Sending data...';
+    const timeBefore = performance.now();
+    sendChannel.send(dataString);
+    const timeUsed = performance.now() - timeBefore;
+    if (timeUsed > maxTimeUsedInSend) {
+      maxTimeUsedInSend = timeUsed;
+      totalTimeUsedInSend += timeUsed;
+    }
+    numberOfSendCalls += 1;
+    bufferedAmount += chunkSize;
+    sendProgress.value += chunkSize;
+
+    // Pause sending if we reach the high water mark
+    if (bufferedAmount >= highWaterMark) {
+      // This is a workaround due to the bug that all browsers are incorrectly calculating the
+      // amount of buffered data. Therefore, the 'bufferedamountlow' event would not fire.
+      if (sendChannel.bufferedAmount < lowWaterMark) {
+        timeoutHandle = setTimeout(() => sendData(), 0);
+      }
+      console.log(`Paused sending, buffered amount: ${bufferedAmount} (announced: ${sendChannel.bufferedAmount})`);
+      break;
+    }
+  }
+
+  if (sendProgress.value === sendProgress.max) {
+    transferStatus.innerHTML = 'Data transfer completed successfully!';
+  }
+}
+
+function startSendingData() {
+  transferStatus.innerHTML = 'Start sending data.';
   sendProgress.max = bytesToSend;
   receiveProgress.max = sendProgress.max;
   sendProgress.value = 0;
   receiveProgress.value = 0;
+  sendStartTime = performance.now();
+  maxTimeUsedInSend = 0;
+  totalTimeUsedInSend = 0;
+  numberOfSendCalls = 0;
+  sendData();
+}
 
-  var chunkSize = 16384;
-  var stringToSendRepeatedly = randomAsciiString(chunkSize);
-  var bufferFullThreshold = 5 * chunkSize;
-  var usePolling = true;
-  if (typeof sendChannel.bufferedAmountLowThreshold === 'number') {
-    trace('Using the bufferedamountlow event for flow control');
-    usePolling = false;
-
-    // Reduce the buffer fullness threshold, since we now have more efficient
-    // buffer management.
-    bufferFullThreshold = chunkSize / 2;
-
-    // This is "overcontrol": our high and low thresholds are the same.
-    sendChannel.bufferedAmountLowThreshold = bufferFullThreshold;
+function maybeReset() {
+  if (localConnection === null && remoteConnection === null) {
+    sendButton.disabled = false;
+    megsToSend.disabled = false;
   }
-  // Listen for one bufferedamountlow event.
-  var listener = function() {
-    sendChannel.removeEventListener('bufferedamountlow', listener);
-    sendAllData();
-  };
-  var sendAllData = function() {
-    // Try to queue up a bunch of data and back off when the channel starts to
-    // fill up. We don't setTimeout after each send since this lowers our
-    // throughput quite a bit (setTimeout(fn, 0) can take hundreds of milli-
-    // seconds to execute).
-    while (sendProgress.value < sendProgress.max) {
-      if (sendChannel.bufferedAmount > bufferFullThreshold) {
-        if (usePolling) {
-          setTimeout(sendAllData, 250);
-        } else {
-          sendChannel.addEventListener('bufferedamountlow', listener);
-        }
-        return;
-      }
-      sendProgress.value += chunkSize;
-      sendChannel.send(stringToSendRepeatedly);
-    }
-  };
-  setTimeout(sendAllData, 0);
 }
 
-function closeDataChannels() {
-  trace('Closing data channels');
-  sendChannel.close();
-  trace('Closed data channel with label: ' + sendChannel.label);
-  receiveChannel.close();
-  trace('Closed data channel with label: ' + receiveChannel.label);
-  localConnection.close();
-  remoteConnection.close();
-  localConnection = null;
-  remoteConnection = null;
-  trace('Closed peer connections');
-}
-
-function gotDescription1(desc) {
+async function handleLocalDescription(desc) {
   localConnection.setLocalDescription(desc);
-  trace('Offer from localConnection \n' + desc.sdp);
+  console.log('Offer from localConnection:\n', desc.sdp);
   remoteConnection.setRemoteDescription(desc);
-  remoteConnection.createAnswer().then(
-    gotDescription2,
-    onCreateSessionDescriptionError
-  );
+  try {
+    const remoteAnswer = await remoteConnection.createAnswer();
+    handleRemoteAnswer(remoteAnswer);
+  } catch (e) {
+    console.error('Error when creating remote answer: ', e);
+  }
 }
 
-function gotDescription2(desc) {
+function handleRemoteAnswer(desc) {
   remoteConnection.setLocalDescription(desc);
-  trace('Answer from remoteConnection \n' + desc.sdp);
+  console.log('Answer from remoteConnection:\n', desc.sdp);
   localConnection.setRemoteDescription(desc);
 }
 
@@ -173,57 +167,76 @@
   return (pc === localConnection) ? remoteConnection : localConnection;
 }
 
-function getName(pc) {
-  return (pc === localConnection) ? 'localPeerConnection' :
-      'remotePeerConnection';
-}
-
-function onIceCandidate(pc, event) {
-  getOtherPc(pc).addIceCandidate(event.candidate)
-  .then(
-    function() {
-      onAddIceCandidateSuccess(pc);
-    },
-    function(err) {
-      onAddIceCandidateError(pc, err);
-    }
-  );
-  trace(getName(pc) + ' ICE candidate: \n' + (event.candidate ?
-      event.candidate.candidate : '(null)'));
-}
-
-function onAddIceCandidateSuccess() {
-  trace('AddIceCandidate success.');
-}
-
-function onAddIceCandidateError(error) {
-  trace('Failed to add Ice Candidate: ' + error.toString());
+async function onIceCandidate(pc, event) {
+  const candidate = event.candidate;
+  if (candidate === null) {
+    return;
+  } // Ignore null candidates
+  try {
+    await getOtherPc(pc).addIceCandidate(candidate);
+    console.log('AddIceCandidate successful: ', candidate);
+  } catch (e) {
+    console.error('Failed to add Ice Candidate: ', e);
+  }
 }
 
 function receiveChannelCallback(event) {
-  trace('Receive Channel Callback');
+  console.log('Receive Channel Callback');
   receiveChannel = event.channel;
   receiveChannel.binaryType = 'arraybuffer';
-  receiveChannel.onmessage = onReceiveMessageCallback;
-
-  receivedSize = 0;
+  receiveChannel.addEventListener('close', onReceiveChannelClosed);
+  receiveChannel.addEventListener('message', onReceiveMessageCallback);
 }
 
 function onReceiveMessageCallback(event) {
-  receivedSize += event.data.length;
-  receiveProgress.value = receivedSize;
+  receiveProgress.value += event.data.length;
 
-  if (receivedSize === bytesToSend) {
-    closeDataChannels();
-    sendButton.disabled = false;
-    megsToSend.disabled = false;
+  // Workaround for a bug in Chrome which prevents the closing event from being raised by the
+  // remote side. Also a workaround for Firefox which does not send all pending data when closing
+  // the channel.
+  if (receiveProgress.value === receiveProgress.max) {
+    sendChannel.close();
+    receiveChannel.close();
   }
 }
 
-function onSendChannelStateChange() {
-  var readyState = sendChannel.readyState;
-  trace('Send channel state is: ' + readyState);
-  if (readyState === 'open') {
-    sendGeneratedData();
-  }
+function onSendChannelOpen() {
+  console.log('Send channel is open');
+
+  chunkSize = Math.min(localConnection.sctp.maxMessageSize, MAX_CHUNK_SIZE);
+  console.log('Determined chunk size: ', chunkSize);
+  dataString = new Array(chunkSize).fill('X').join('');
+  lowWaterMark = chunkSize; // A single chunk
+  highWaterMark = Math.max(chunkSize * 8, 1048576); // 8 chunks or at least 1 MiB
+  console.log('Send buffer low water threshold: ', lowWaterMark);
+  console.log('Send buffer high water threshold: ', highWaterMark);
+  sendChannel.bufferedAmountLowThreshold = lowWaterMark;
+  sendChannel.addEventListener('bufferedamountlow', (e) => {
+    console.log('BufferedAmountLow event:', e);
+    sendData();
+  });
+
+  startSendingData();
+}
+
+function onSendChannelClosed() {
+  console.log('Send channel is closed');
+  localConnection.close();
+  localConnection = null;
+  console.log('Closed local peer connection');
+  maybeReset();
+  console.log('Average time spent in send() (ms): ' +
+              totalTimeUsedInSend / numberOfSendCalls);
+  console.log('Max time spent in send() (ms): ' + maxTimeUsedInSend);
+  const spentTime = performance.now() - sendStartTime;
+  console.log('Total time spent: ' + spentTime);
+  console.log('MBytes/Sec: ' + (bytesToSend / 1000) / spentTime);
+}
+
+function onReceiveChannelClosed() {
+  console.log('Receive channel is closed');
+  remoteConnection.close();
+  remoteConnection = null;
+  console.log('Closed remote peer connection');
+  maybeReset();
 }
diff --git a/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.html b/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.html
index dff5a91..5a7eac2 100644
--- a/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.html
+++ b/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.html
@@ -40,6 +40,5 @@
 
 
 <script src="multiple-peerconnections.js"></script>
-<script src="adapter.js"></script>
-<script src="common.js"></script>
+<script src="adapter-latest.js"></script>
 </body></html>
diff --git a/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.js b/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.js
index ef93404..3af6c63 100644
--- a/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.js
+++ b/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.js
@@ -17,7 +17,7 @@
 startTestButton.onclick = startTest;
 
 function logError(err) {
-  console.error(err);
+  console.err(err);
 }
 
 function addNewVideoElement() {
diff --git a/tools/perf/page_sets/webrtc_cases/pause-play.html b/tools/perf/page_sets/webrtc_cases/pause-play.html
index 5b5825d..84dc4e4 100644
--- a/tools/perf/page_sets/webrtc_cases/pause-play.html
+++ b/tools/perf/page_sets/webrtc_cases/pause-play.html
@@ -12,6 +12,5 @@
     <table border="0" id="test-table"></table>
 
 <script src="pause-play.js"></script>
-<script src="adapter.js"></script>
-<script src="common.js"></script>
+<script src="adapter-latest.js"></script>
 </body></html>
diff --git a/tools/perf/page_sets/webrtc_cases/resolution.html b/tools/perf/page_sets/webrtc_cases/resolution.html
index e92e0799..5f02eec 100644
--- a/tools/perf/page_sets/webrtc_cases/resolution.html
+++ b/tools/perf/page_sets/webrtc_cases/resolution.html
@@ -8,73 +8,97 @@
 <head>
 
 
-  <base target="_blank">
+    <base target="_blank">
 
-  <title>getUserMedia: select resolution</title>
+    <title>getUserMedia: select resolution</title>
 
 
-  <style>
-    body, html {
-      height: 100%;
-    }
+    <style>
+        body, html {
+            height: 100%;
+        }
 
-    button {
-      margin: 0 10px 20px 0;
-      width: 90px;
-    }
+        button {
+            margin: 0 10px 20px 0;
+            width: 90px;
+        }
 
-    div#buttons {
-      margin: 0 0 1em 0;
-    }
+        div#buttons {
+            margin: 0 0 1em 0;
+        }
 
-    div#container {
-      max-width: 100%;
-    }
+        div#container {
+            max-width: 100%;
+        }
 
-    p#dimensions {
-      height: 1em;
-      margin: 0 0 1.5em 0;
-    }
+        #errormessage {
+            display: none;
+            font-size: 300%;
+        }
 
-    video {
-      background: none;
-      height: auto;
-      width: auto;
-    }
-  </style>
+        #videoblock {
+            display: none;
+        }
+
+        p#dimensions {
+            height: 1em;
+            margin: 0 0 1.5em 0;
+        }
+
+        video {
+            background: none;
+            height: auto;
+            width: auto;
+        }
+    </style>
 
 </head>
 
 <body>
 
-  <div id="container">
+<div id="container">
 
-    <h1><a href="//webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC samples</a> <span>getUserMedia: select resolution</span></h1>
+    <h1><a href="//webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC samples</a> <span>getUserMedia: select resolution</span>
+    </h1>
     <p></p>
 
-    <p>This example uses <a href="https://w3c.github.io/mediacapture-main/getusermedia.html#media-track-constraints" title="W3C getusermedia specification - constraints section">constraints</a>.</p>
+    <p>This example uses <a href="https://w3c.github.io/mediacapture-main/getusermedia.html#media-track-constraints"
+                            title="W3C getusermedia specification - constraints section">constraints</a>.</p>
 
     <p>Click a button to call <code>getUserMedia()</code> with appropriate resolution.</p>
 
     <div id="buttons">
-      <button id="qvga">QVGA</button>
-      <button id="vga">VGA</button>
-      <button id="hd">HD</button>
-      <button id="full-hd">Full HD</button>
+        <button id="qvga">QVGA</button>
+        <button id="vga">VGA</button>
+        <button id="hd">HD</button>
+        <button id="full-hd">Full HD</button>
+        <button id="fourK">4K</button>
+        <button id="eightK">8K</button>
     </div>
 
-    <p id="dimensions"></p>
+    <div id="videoblock">
+        <p id="dimensions"></p>
 
-    <video id="gum-res-local" autoplay></video>
+        <video id="gum-res-local" playsinline autoplay></video>
+        <div id="width">
+            <label>Width <span></span>px:</label>
+            <input type="range" min="0" max="7680" value="0">
+        </div>
+        <input id="sizelock" type="checkbox">Lock video size<br>
+        <input id="aspectlock" type="checkbox">Lock aspect ratio<br>
+    </div>
+    <p id="errormessage"></p>
 
-    <p>For more information, see <a href="http://www.html5rocks.com/en/tutorials/getusermedia/intro/" title="Media capture article by Eric Bidelman on HTML5 Rocks">Capturing Audio &amp; Video in HTML5</a> on HTML5 Rocks.</p>
+    <p>For more information, see <a href="http://www.html5rocks.com/en/tutorials/getusermedia/intro/"
+                                    title="Media capture article by Eric Bidelman on HTML5 Rocks">Capturing Audio &amp;
+        Video in HTML5</a> on HTML5 Rocks.</p>
 
-    <a href="https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/resolution" title="View source for this page on GitHub" id="viewSource">View source on GitHub</a>
-  </div>
+    <a href="https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/resolution"
+       title="View source for this page on GitHub" id="viewSource">View source on GitHub</a>
+</div>
 
 
 
 <script src="resolution.js"></script>
-<script src="adapter.js"></script>
-<script src="common.js"></script>
+<script src="adapter-latest.js"></script>
 </body></html>
diff --git a/tools/perf/page_sets/webrtc_cases/resolution.js b/tools/perf/page_sets/webrtc_cases/resolution.js
index 4b807b0..0f275bb 100644
--- a/tools/perf/page_sets/webrtc_cases/resolution.js
+++ b/tools/perf/page_sets/webrtc_cases/resolution.js
@@ -5,74 +5,177 @@
  */
 'use strict';
 
-var dimensions = document.querySelector('#dimensions');
-var video = document.querySelector('video');
-var stream;
+const dimensions = document.querySelector('#dimensions');
+const video = document.querySelector('video');
+let stream;
 
-var vgaButton = document.querySelector('#vga');
-var qvgaButton = document.querySelector('#qvga');
-var hdButton = document.querySelector('#hd');
-var fullHdButton = document.querySelector('#full-hd');
+const vgaButton = document.querySelector('#vga');
+const qvgaButton = document.querySelector('#qvga');
+const hdButton = document.querySelector('#hd');
+const fullHdButton = document.querySelector('#full-hd');
+const fourKButton = document.querySelector('#fourK');
+const eightKButton = document.querySelector('#eightK');
 
-vgaButton.onclick = function() {
+const videoblock = document.querySelector('#videoblock');
+const messagebox = document.querySelector('#errormessage');
+
+const widthInput = document.querySelector('div#width input');
+const widthOutput = document.querySelector('div#width span');
+const aspectLock = document.querySelector('#aspectlock');
+const sizeLock = document.querySelector('#sizelock');
+
+let currentWidth = 0;
+let currentHeight = 0;
+
+vgaButton.onclick = () => {
   getMedia(vgaConstraints);
 };
 
-qvgaButton.onclick = function() {
+qvgaButton.onclick = () => {
   getMedia(qvgaConstraints);
 };
 
-hdButton.onclick = function() {
+hdButton.onclick = () => {
   getMedia(hdConstraints);
 };
 
-fullHdButton.onclick = function() {
+fullHdButton.onclick = () => {
   getMedia(fullHdConstraints);
 };
 
-var qvgaConstraints = {
+fourKButton.onclick = () => {
+  getMedia(fourKConstraints);
+};
+
+eightKButton.onclick = () => {
+  getMedia(eightKConstraints);
+};
+
+const qvgaConstraints = {
   video: {width: {exact: 320}, height: {exact: 240}}
 };
 
-var vgaConstraints = {
+const vgaConstraints = {
   video: {width: {exact: 640}, height: {exact: 480}}
 };
 
-var hdConstraints = {
+const hdConstraints = {
   video: {width: {exact: 1280}, height: {exact: 720}}
 };
 
-var fullHdConstraints = {
+const fullHdConstraints = {
   video: {width: {exact: 1920}, height: {exact: 1080}}
 };
 
+const fourKConstraints = {
+  video: {width: {exact: 4096}, height: {exact: 2160}}
+};
+
+const eightKConstraints = {
+  video: {width: {exact: 7680}, height: {exact: 4320}}
+};
+
 function gotStream(mediaStream) {
-  window.stream = mediaStream; // stream available to console
+  stream = window.stream = mediaStream; // stream available to console
   video.srcObject = mediaStream;
-}
-
-function displayVideoDimensions() {
-  if (!video.videoWidth) {
-    setTimeout(displayVideoDimensions, 500);
+  messagebox.style.display = 'none';
+  videoblock.style.display = 'block';
+  const track = mediaStream.getVideoTracks()[0];
+  const constraints = track.getConstraints();
+  console.log('Result constraints: ' + JSON.stringify(constraints));
+  if (constraints && constraints.width && constraints.width.exact) {
+    widthInput.value = constraints.width.exact;
+    widthOutput.textContent = constraints.width.exact;
+  } else if (constraints && constraints.width && constraints.width.min) {
+    widthInput.value = constraints.width.min;
+    widthOutput.textContent = constraints.width.min;
   }
-  dimensions.innerHTML = 'Actual video dimensions: ' + video.videoWidth +
-    'x' + video.videoHeight + 'px.';
 }
 
-video.onloadedmetadata = displayVideoDimensions;
+function errorMessage(who, what) {
+  const message = who + ': ' + what;
+  messagebox.innerText = message;
+  messagebox.style.display = 'block';
+  console.log(message);
+}
+
+function clearErrorMessage() {
+  messagebox.style.display = 'none';
+}
+
+function displayVideoDimensions(whereSeen) {
+  if (video.videoWidth) {
+    dimensions.innerText = 'Actual video dimensions: ' + video.videoWidth +
+      'x' + video.videoHeight + 'px.';
+    if (currentWidth !== video.videoWidth ||
+      currentHeight !== video.videoHeight) {
+      console.log(whereSeen + ': ' + dimensions.innerText);
+      currentWidth = video.videoWidth;
+      currentHeight = video.videoHeight;
+    }
+  } else {
+    dimensions.innerText = 'Video not ready';
+  }
+}
+
+video.onloadedmetadata = () => {
+  displayVideoDimensions('loadedmetadata');
+};
+
+video.onresize = () => {
+  displayVideoDimensions('resize');
+};
+
+function constraintChange(e) {
+  widthOutput.textContent = e.target.value;
+  const track = window.stream.getVideoTracks()[0];
+  let constraints;
+  if (aspectLock.checked) {
+    constraints = {
+      width: {exact: e.target.value},
+      aspectRatio: {
+        exact: video.videoWidth / video.videoHeight
+      }
+    };
+  } else {
+    constraints = {width: {exact: e.target.value}};
+  }
+  clearErrorMessage();
+  console.log('applying ' + JSON.stringify(constraints));
+  track.applyConstraints(constraints)
+      .then(() => {
+        console.log('applyConstraint success');
+        displayVideoDimensions('applyConstraints');
+      })
+      .catch(err => {
+        errorMessage('applyConstraints', err.name);
+      });
+}
+
+widthInput.onchange = constraintChange;
+
+sizeLock.onchange = () => {
+  if (sizeLock.checked) {
+    console.log('Setting fixed size');
+    video.style.width = '100%';
+  } else {
+    console.log('Setting auto size');
+    video.style.width = 'auto';
+  }
+};
 
 function getMedia(constraints) {
   if (stream) {
-    stream.getTracks().forEach(function(track) {
+    stream.getTracks().forEach(track => {
       track.stop();
     });
   }
 
+  clearErrorMessage();
+  videoblock.style.display = 'none';
   navigator.mediaDevices.getUserMedia(constraints)
-  .then(gotStream)
-  .catch(function(e) {
-    var message = 'getUserMedia error: ' + e.name;
-    alert(message);
-    console.log(message);
-  });
+      .then(gotStream)
+      .catch(e => {
+        errorMessage('getUserMedia', e.message, e.name);
+      });
 }
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index db06d8d..e4f8453f 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -198,7 +198,7 @@
  <item id="ntp_icon_source" added_in_milestone="69" hash_code="29197139" type="0" content_hash_code="16399294" os_list="linux,windows" file_path="chrome/browser/search/ntp_icon_source.cc"/>
  <item id="ntp_snippets_fetch" added_in_milestone="62" hash_code="15418154" type="0" content_hash_code="10078959" os_list="linux,windows" file_path="components/ntp_snippets/remote/json_request.cc"/>
  <item id="nux_ntp_background_preview" added_in_milestone="74" hash_code="124847649" type="0" content_hash_code="31404656" os_list="linux,windows" file_path="chrome/browser/ui/webui/welcome/ntp_background_fetcher.cc"/>
- <item id="oauth2_access_token_fetcher" added_in_milestone="62" hash_code="27915688" type="0" content_hash_code="33501872" os_list="linux,windows" file_path="google_apis/gaia/oauth2_access_token_fetcher_impl.cc"/>
+ <item id="oauth2_access_token_fetcher" added_in_milestone="62" hash_code="27915688" type="0" content_hash_code="33501872" os_list="linux,windows" file_path="google_apis/gaia/gaia_access_token_fetcher.cc"/>
  <item id="oauth2_api_call_flow" added_in_milestone="65" hash_code="29188932" type="2" content_hash_code="108831236" os_list="linux,windows" policy_fields="-1" file_path="google_apis/gaia/oauth2_api_call_flow.cc"/>
  <item id="oauth2_mint_token_flow" added_in_milestone="62" hash_code="1112842" type="1" second_id="29188932" content_hash_code="91581432" os_list="linux,windows" semantics_fields="1,2,3,4,5" policy_fields="3,4" file_path="google_apis/gaia/oauth2_mint_token_flow.cc"/>
  <item id="ocsp_start_url_loader" added_in_milestone="81" hash_code="3646641" type="0" deprecated="2020-04-28" content_hash_code="106270072" file_path=""/>
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
index ab23bce..73017647 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
@@ -7,6 +7,7 @@
 #include <cstdint>
 
 #include "base/check.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/notreached.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/clipboard/clipboard_constants.h"
@@ -160,7 +161,7 @@
     received_data_ = std::make_unique<OSExchangeData>(
         std::make_unique<OSExchangeDataProviderNonBacked>());
     last_drag_location_ = location;
-    HandleUnprocessedMimeTypes();
+    HandleUnprocessedMimeTypes(base::TimeTicks::Now());
   }
 }
 
@@ -281,21 +282,23 @@
 // more unprocessed mime types on the |unprocessed_mime_types_| queue. Once this
 // process is finished, OnDataTransferFinished is called to deliver the
 // |received_data_| to the drop handler.
-void WaylandDataDragController::HandleUnprocessedMimeTypes() {
+void WaylandDataDragController::HandleUnprocessedMimeTypes(
+    base::TimeTicks start_time) {
   DCHECK_EQ(state_, State::kTransferring);
   std::string mime_type = GetNextUnprocessedMimeType();
   if (mime_type.empty() || is_leave_pending_) {
-    OnDataTransferFinished(std::move(received_data_));
+    OnDataTransferFinished(start_time, std::move(received_data_));
   } else {
     DCHECK(data_offer_);
     data_device_->RequestData(
         data_offer_.get(), mime_type,
         base::BindOnce(&WaylandDataDragController::OnMimeTypeDataTransferred,
-                       weak_factory_.GetWeakPtr()));
+                       weak_factory_.GetWeakPtr(), start_time));
   }
 }
 
 void WaylandDataDragController::OnMimeTypeDataTransferred(
+    base::TimeTicks start_time,
     PlatformClipboard::Data contents) {
   DCHECK_EQ(state_, State::kTransferring);
   DCHECK(contents);
@@ -306,10 +309,11 @@
   unprocessed_mime_types_.pop_front();
 
   // Continue reading data for other negotiated mime types.
-  HandleUnprocessedMimeTypes();
+  HandleUnprocessedMimeTypes(start_time);
 }
 
 void WaylandDataDragController::OnDataTransferFinished(
+    base::TimeTicks start_time,
     std::unique_ptr<OSExchangeData> received_data) {
   unprocessed_mime_types_.clear();
   state_ = State::kIdle;
@@ -329,6 +333,9 @@
     return;
   }
 
+  UMA_HISTOGRAM_TIMES("Event.WaylandDragDrop.IncomingDataTransferTime",
+                      base::TimeTicks::Now() - start_time);
+
   PropagateOnDragEnter(last_drag_location_, std::move(received_data));
 }
 
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.h b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.h
index 79589467..efb6965 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.h
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.h
@@ -105,9 +105,11 @@
 
   void Offer(const OSExchangeData& data, int operation);
   void CreateIconSurfaceIfNeeded(const OSExchangeData& data);
-  void HandleUnprocessedMimeTypes();
-  void OnMimeTypeDataTransferred(PlatformClipboard::Data contents);
+  void HandleUnprocessedMimeTypes(base::TimeTicks start_time);
+  void OnMimeTypeDataTransferred(base::TimeTicks start_time,
+                                 PlatformClipboard::Data contents);
   void OnDataTransferFinished(
+      base::TimeTicks start_time,
       std::unique_ptr<ui::OSExchangeData> received_data);
   std::string GetNextUnprocessedMimeType();
   // Calls the window's OnDragEnter with the given location and data,
diff --git a/ui/webui/resources/js/cr/ui/list.js b/ui/webui/resources/js/cr/ui/list.js
index d68e316..d65b21a 100644
--- a/ui/webui/resources/js/cr/ui/list.js
+++ b/ui/webui/resources/js/cr/ui/list.js
@@ -1401,9 +1401,6 @@
   function handleMouseDown(e) {
     e.target = /** @type {!HTMLElement} */ (e.target);
     const listItem = this.getListItemAncestor(e.target);
-    if (!listItem) {
-      return;
-    }
     const wasSelected = listItem && listItem.selected;
     this.handlePointerDownUp_(e);
 
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
index 6746cbe8..392038b8 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
@@ -21,6 +21,8 @@
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.weblayer.Browser;
 import org.chromium.weblayer.NavigateParams;
+import org.chromium.weblayer.Navigation;
+import org.chromium.weblayer.NavigationCallback;
 import org.chromium.weblayer.Tab;
 import org.chromium.weblayer.TabListCallback;
 import org.chromium.weblayer.shell.InstrumentationActivity;
@@ -45,6 +47,13 @@
         }
     }
 
+    private static final boolean EXPECT_NAVIGATION_COMPLETION = true;
+    private static final boolean EXPECT_NAVIGATION_FAILURE = false;
+    private static final boolean RESULTS_IN_EXTERNAL_INTENT = true;
+    private static final boolean DOESNT_RESULT_IN_EXTERNAL_INTENT = false;
+    private static final boolean RESULTS_IN_USER_DECIDING_EXTERNAL_INTENT = true;
+    private static final boolean DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT = false;
+
     private static final String ABOUT_BLANK_URL = "about:blank";
     private static final String CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER =
             "weblayer://weblayertest/intent";
@@ -126,6 +135,80 @@
         }
     }
 
+    /*
+     * Navigates to |urlToNavigateTo| and waits for a completed/failed navigation to |urlToWaitFor|
+     * as appropriate. In the callback verifies that the values of the relevant params on the
+     * Navigation match the passed-in expected values.
+     */
+    private void navigateAndCheckExternalIntentParams(String urlToNavigateTo, String urlToWaitFor,
+            boolean expectNavigationCompletion, boolean resultsInExternalIntent,
+            boolean resultsInUserDecidingIntentLaunch) throws Throwable {
+        Tab tab = mActivityTestRule.getActivity().getTab();
+
+        CallbackHelper navigationCompletedCallbackHelper = new CallbackHelper();
+        CallbackHelper navigationFailedCallbackHelper = new CallbackHelper();
+
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationCompleted(Navigation navigation) {
+                String url = navigation.getUri().toString();
+                if (!url.equals(urlToWaitFor)) return;
+
+                Assert.assertEquals(true, expectNavigationCompletion);
+
+                // A navigation should never be expected to both complete and result in an external
+                // intent.
+                Assert.assertEquals(false, resultsInExternalIntent);
+                Assert.assertEquals(false, navigation.wasIntentLaunched());
+                Assert.assertEquals(false, resultsInUserDecidingIntentLaunch);
+                Assert.assertEquals(false, navigation.isUserDecidingIntentLaunch());
+
+                navigationCompletedCallbackHelper.notifyCalled();
+            }
+
+            @Override
+            public void onNavigationFailed(Navigation navigation) {
+                String url = navigation.getUri().toString();
+                if (!url.equals(urlToWaitFor)) return;
+
+                Assert.assertEquals(false, expectNavigationCompletion);
+
+                Assert.assertEquals(resultsInExternalIntent, navigation.wasIntentLaunched());
+                Assert.assertEquals(
+                        resultsInUserDecidingIntentLaunch, navigation.isUserDecidingIntentLaunch());
+
+                navigationFailedCallbackHelper.notifyCalled();
+            }
+        };
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().registerNavigationCallback(navigationCallback);
+            tab.getNavigationController().navigate(Uri.parse(urlToNavigateTo));
+        });
+
+        if (expectNavigationCompletion) {
+            navigationCompletedCallbackHelper.waitForFirst();
+        } else {
+            navigationFailedCallbackHelper.waitForFirst();
+        }
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().unregisterNavigationCallback(navigationCallback);
+        });
+    }
+
+    /*
+     * A convenience variant of the above method that navigates to and waits for the same URL. See
+     * comments on the above method.
+     */
+    private void navigateAndCheckExternalIntentParams(String urlToNavigateTo,
+            boolean expectNavigationCompletion, boolean resultsInExternalIntent,
+            boolean resultsInUserDecidingIntentLaunch) throws Throwable {
+        navigateAndCheckExternalIntentParams(urlToNavigateTo, urlToNavigateTo,
+                expectNavigationCompletion, resultsInExternalIntent,
+                resultsInUserDecidingIntentLaunch);
+    }
+
     /**
      * Verifies that for a navigation to a URI that WebLayer can handle internally, there
      * is no external intent triggered.
@@ -170,6 +253,124 @@
     }
 
     /**
+     * Tests that external intent-related navigation params are not set on browser navigations.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void testExternalIntentNavigationParamsNotSetOnBrowserNavigations() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+
+        navigateAndCheckExternalIntentParams(mTestServerSiteUrl, EXPECT_NAVIGATION_COMPLETION,
+                DOESNT_RESULT_IN_EXTERNAL_INTENT, DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+
+        // Navigating to an unresolvable intent with a fallback URL should result in a followup
+        // browser navigation to the fallback URL.
+        navigateAndCheckExternalIntentParams(mNonResolvableIntentWithFallbackUrl,
+                mTestServerSiteUrl, EXPECT_NAVIGATION_COMPLETION, DOESNT_RESULT_IN_EXTERNAL_INTENT,
+                DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+    }
+
+    /**
+     * Tests that Navigation#wasIntentLaunched() is correctly set on embedder navigations that
+     * resolve to intents.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void testExternalIntentNavigationParamSetOnNavigationsToIntents() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        navigateAndCheckExternalIntentParams(INTENT_TO_CHROME_URL, EXPECT_NAVIGATION_FAILURE,
+                RESULTS_IN_EXTERNAL_INTENT, DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+        navigateAndCheckExternalIntentParams(mIntentToChromeWithFallbackUrl,
+                EXPECT_NAVIGATION_FAILURE, RESULTS_IN_EXTERNAL_INTENT,
+                DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+        navigateAndCheckExternalIntentParams(mRedirectToIntentToChromeURL, INTENT_TO_CHROME_URL,
+                EXPECT_NAVIGATION_FAILURE, RESULTS_IN_EXTERNAL_INTENT,
+                DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+        navigateAndCheckExternalIntentParams(mRedirectToCustomSchemeUrlWithDefaultExternalHandler,
+                CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER, EXPECT_NAVIGATION_FAILURE,
+                RESULTS_IN_EXTERNAL_INTENT, DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+
+        // A navigation that results in an intent that cannot be launched should still fail, but
+        // should not have the wasIntentLaunched() parameter set.
+        navigateAndCheckExternalIntentParams(MALFORMED_INTENT_URL, EXPECT_NAVIGATION_FAILURE,
+                DOESNT_RESULT_IN_EXTERNAL_INTENT, DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+
+        // The presence of a fallback URL should not impact the state in the navigation failure
+        // callback for a navigation that results in an unresolvable intent.
+        navigateAndCheckExternalIntentParams(mNonResolvableIntentWithFallbackUrl,
+                EXPECT_NAVIGATION_FAILURE, DOESNT_RESULT_IN_EXTERNAL_INTENT,
+                DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+    }
+
+    /**
+     * Tests that Navigation#isUserDecidingIntentLaunch() is correctly set on embedder navigations
+     * that resolve to intents in incognito mode.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void testUserDecidingExternalIntentNavigationParamSetOnNavigationsToIntentsInIncognito()
+            throws Throwable {
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_IS_INCOGNITO, true);
+        mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL, extras);
+
+        navigateAndCheckExternalIntentParams(INTENT_TO_CHROME_URL, EXPECT_NAVIGATION_FAILURE,
+                DOESNT_RESULT_IN_EXTERNAL_INTENT, RESULTS_IN_USER_DECIDING_EXTERNAL_INTENT);
+    }
+
+    /**
+     * Tests that Navigation#wasIntentLaunched() is correctly set on a navigation to an intent that
+     * is initiated via a link click.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void testExternalIntentNavigationParamSetOnIntentLaunchViaLinkClick() throws Throwable {
+        // Set up all the prerequisites.
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+
+        CallbackHelper navigationFailureCallbackHelper = new CallbackHelper();
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationFailed(Navigation navigation) {
+                Assert.assertEquals(INTENT_TO_CHROME_URL, navigation.getUri().toString());
+                Assert.assertEquals(true, navigation.wasIntentLaunched());
+                Assert.assertEquals(false, navigation.isUserDecidingIntentLaunch());
+
+                navigationFailureCallbackHelper.notifyCalled();
+            }
+        };
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().registerNavigationCallback(navigationCallback);
+        });
+
+        // Navigate to a URL that has a link to an intent, click on the link, and verify via the
+        // callback that the navigation to the intent fails with the expected state set.
+        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_CHROME_IN_SAME_TAB_FILE);
+        mActivityTestRule.navigateAndWait(url);
+        mActivityTestRule.executeScriptSync(
+                "document.onclick = function() {document.getElementById('link').click()}",
+                true /* useSeparateIsolate */);
+        EventUtils.simulateTouchCenterOfView(
+                mActivityTestRule.getActivity().getWindow().getDecorView());
+        navigationFailureCallbackHelper.waitForFirst();
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().unregisterNavigationCallback(navigationCallback);
+        });
+    }
+
+    /**
      * Tests that a navigation that redirects to an external intent results in the external intent
      * being launched.
      */
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateClientImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateClientImpl.java
index 7a9a730..6a2f3e64 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateClientImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateClientImpl.java
@@ -10,6 +10,9 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.components.external_intents.AuthenticatorNavigationInterceptor;
 import org.chromium.components.external_intents.ExternalNavigationHandler;
+import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingAsyncActionType;
+import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
+import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResultType;
 import org.chromium.components.external_intents.InterceptNavigationDelegateClient;
 import org.chromium.components.external_intents.InterceptNavigationDelegateImpl;
 import org.chromium.components.external_intents.RedirectHandler;
@@ -135,6 +138,33 @@
         }
     }
 
+    @Override
+    public void onDecisionReachedForNavigation(
+            NavigationParams params, OverrideUrlLoadingResult overrideUrlLoadingResult) {
+        NavigationImpl navigation =
+                mTab.getNavigationControllerImpl().getNavigationImplFromId(params.navigationId);
+
+        // As the navigation is still ongoing at this point there should be a NavigationImpl
+        // instance for it.
+        assert navigation != null;
+
+        switch (overrideUrlLoadingResult.getResultType()) {
+            case OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT:
+                navigation.setIntentLaunched();
+                break;
+            case OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION:
+                if (overrideUrlLoadingResult.getAsyncActionType()
+                        == OverrideUrlLoadingAsyncActionType.UI_GATING_INTENT_LAUNCH) {
+                    navigation.setIsUserDecidingIntentLaunch();
+                }
+                break;
+            case OverrideUrlLoadingResultType.OVERRIDE_WITH_CLOBBERING_TAB:
+            case OverrideUrlLoadingResultType.NO_OVERRIDE:
+            default:
+                break;
+        }
+    }
+
     static void closeTab(TabImpl tab) {
         // Prior to 84 the client was not equipped to handle the case of WebLayer initiating the
         // last tab being closed, so we simply short-circuit out here in that case.
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java
index 3151068..705cd25 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java
@@ -168,6 +168,12 @@
                 mNativeNavigationController, index);
     }
 
+    public NavigationImpl getNavigationImplFromId(long id) {
+        StrictModeWorkaround.apply();
+        return NavigationControllerImplJni.get().getNavigationImplFromId(
+                mNativeNavigationController, id);
+    }
+
     @CalledByNative
     private NavigationImpl createNavigation(long nativeNavigationImpl) {
         return new NavigationImpl(mNavigationControllerClient, nativeNavigationImpl);
@@ -294,5 +300,6 @@
         String getNavigationEntryDisplayUri(long nativeNavigationControllerImpl, int index);
         String getNavigationEntryTitle(long nativeNavigationControllerImpl, int index);
         boolean isNavigationEntrySkippable(long nativeNavigationControllerImpl, int index);
+        NavigationImpl getNavigationImplFromId(long nativeNavigationControllerImpl, long id);
     }
 }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
index 247ceeec..a3d9e82 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
@@ -29,6 +29,14 @@
     // WARNING: NavigationImpl may outlive the native side, in which case this member is set to 0.
     private long mNativeNavigationImpl;
 
+    // Set to true if/when it is determined that an external intent was launched for this
+    // navigation.
+    private boolean mIntentLaunched;
+
+    // Set to true if/when it is determined that this navigation result in UI being presented to the
+    // user via which the user will determine whether an intent should be launched.
+    private boolean mIsUserDecidingIntentLaunch;
+
     public NavigationImpl(INavigationControllerClient client, long nativeNavigationImpl) {
         mNativeNavigationImpl = nativeNavigationImpl;
         try {
@@ -141,6 +149,16 @@
     }
 
     @Override
+    public boolean wasIntentLaunched() {
+        return mIntentLaunched;
+    }
+
+    @Override
+    public boolean isUserDecidingIntentLaunch() {
+        return mIsUserDecidingIntentLaunch;
+    }
+
+    @Override
     public boolean wasStopCalled() {
         StrictModeWorkaround.apply();
         throwIfNativeDestroyed();
@@ -161,6 +179,14 @@
         return NavigationImplJni.get().isReload(mNativeNavigationImpl);
     }
 
+    public void setIntentLaunched() {
+        mIntentLaunched = true;
+    }
+
+    public void setIsUserDecidingIntentLaunch() {
+        mIsUserDecidingIntentLaunch = true;
+    }
+
     private void throwIfNativeDestroyed() {
         if (mNativeNavigationImpl == 0) {
             throw new IllegalStateException("Using Navigation after native destroyed");
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
index 88e3b88..f04f0ab 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
@@ -511,6 +511,10 @@
         return mWebContents;
     }
 
+    public NavigationControllerImpl getNavigationControllerImpl() {
+        return mNavigationController;
+    }
+
     // Public for tests.
     @VisibleForTesting
     public long getNativeTab() {
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl
index 5c94e34..de8dcb7 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl
@@ -33,4 +33,8 @@
   // @since 86
   boolean isPageInitiated() = 11;
   boolean isReload() = 12;
+
+  // @since 89
+  boolean wasIntentLaunched() = 13;
+  boolean isUserDecidingIntentLaunch() = 14;
 }
diff --git a/weblayer/browser/navigation_controller_impl.cc b/weblayer/browser/navigation_controller_impl.cc
index 51baf80..800cb57 100644
--- a/weblayer/browser/navigation_controller_impl.cc
+++ b/weblayer/browser/navigation_controller_impl.cc
@@ -216,6 +216,13 @@
                                                           int index) {
   return IsNavigationEntrySkippable(index);
 }
+
+base::android::ScopedJavaGlobalRef<jobject>
+NavigationControllerImpl::GetNavigationImplFromId(JNIEnv* env, int64_t id) {
+  auto* navigation_impl = GetNavigationImplFromId(id);
+  return navigation_impl ? navigation_impl->java_navigation() : nullptr;
+}
+
 #endif
 
 void NavigationControllerImpl::WillRedirectRequest(
diff --git a/weblayer/browser/navigation_controller_impl.h b/weblayer/browser/navigation_controller_impl.h
index 8a2669ec..5b6d5ebf 100644
--- a/weblayer/browser/navigation_controller_impl.h
+++ b/weblayer/browser/navigation_controller_impl.h
@@ -77,6 +77,9 @@
       JNIEnv* env,
       int index);
   bool IsNavigationEntrySkippable(JNIEnv* env, int index);
+  base::android::ScopedJavaGlobalRef<jobject> GetNavigationImplFromId(
+      JNIEnv* env,
+      int64_t id);
 #endif
 
   bool should_delay_web_contents_deletion() {
diff --git a/weblayer/browser/prefetch_browsertest.cc b/weblayer/browser/prefetch_browsertest.cc
index df3bcaf..62ab5ce 100644
--- a/weblayer/browser/prefetch_browsertest.cc
+++ b/weblayer/browser/prefetch_browsertest.cc
@@ -4,7 +4,9 @@
 
 #include "base/files/file_path.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
 #include "base/test/bind.h"
+#include "base/thread_annotations.h"
 #include "build/build_config.h"
 #include "components/network_session_configurator/common/network_switches.h"
 #include "content/public/browser/web_contents.h"
@@ -57,6 +59,12 @@
 
  protected:
   bool prefetch_target_request_seen_ = false;
+  base::Lock lock_;
+
+  // |requests_| is accessed on the UI thread by the test body and on the IO
+  // thread by the test server's request handler, so must be guarded by a lock
+  // to avoid data races.
+  std::vector<net::test_server::HttpRequest> requests_ GUARDED_BY(lock_);
 
  private:
   void MonitorRequest(const net::test_server::HttpRequest& request) {
@@ -80,21 +88,15 @@
 // https://crbug.com/922362: When the prefetched request is redirected, DCHECKs
 // in PrefetchURLLoader::FollowRedirect() failed due to "X-Client-Data" in
 // removed_headers. Verify that it no longer does.
-// TODO(https://crbug.com/1144142): Fails on TSan due to data race.
-#if defined(THREAD_SANITIZER)
-#define MAYBE_RedirectedPrefetch DISABLED_RedirectedPrefetch
-#else
-#define MAYBE_RedirectedPrefetch RedirectedPrefetch
-#endif
-IN_PROC_BROWSER_TEST_F(PrefetchBrowserTest, MAYBE_RedirectedPrefetch) {
-  std::vector<net::test_server::HttpRequest> requests;
+IN_PROC_BROWSER_TEST_F(PrefetchBrowserTest, RedirectedPrefetch) {
   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
   https_server.RegisterRequestHandler(base::BindLambdaForTesting(
-      [&requests](const net::test_server::HttpRequest& request)
+      [this](const net::test_server::HttpRequest& request)
           -> std::unique_ptr<net::test_server::HttpResponse> {
         auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+        base::AutoLock auto_lock(lock_);
         if (request.relative_url == std::string(kRedirectPrefetchPage)) {
-          requests.push_back(request);
+          requests_.push_back(request);
           response->set_content_type("text/html");
           response->set_content(
               base::StringPrintf("<link rel=\"prefetch\" href=\"%s\" "
@@ -102,7 +104,7 @@
                                  kRedirectPrefetchUrl));
           return response;
         } else if (request.relative_url == std::string(kRedirectPrefetchUrl)) {
-          requests.push_back(request);
+          requests_.push_back(request);
           response->set_code(net::HTTP_MOVED_PERMANENTLY);
           response->AddCustomHeader(
               "Location", base::StringPrintf("https://example.com:%s%s",
@@ -111,7 +113,7 @@
           return response;
         } else if (request.relative_url ==
                    std::string(kRedirectedPrefetchUrl)) {
-          requests.push_back(request);
+          requests_.push_back(request);
           return response;
         }
         return nullptr;
@@ -119,15 +121,21 @@
 
   https_server.ServeFilesFromSourceDirectory(
       base::FilePath(FILE_PATH_LITERAL("weblayer/test/data")));
+  {
+    base::AutoLock auto_lock(lock_);
+    requests_.clear();
+  }
   ASSERT_TRUE(https_server.Start());
 
   GURL url = https_server.GetURL("www.google.com", kRedirectPrefetchPage);
   EXPECT_TRUE(RunPrefetchExperiment(url, base::ASCIIToUTF16("done")));
-  ASSERT_EQ(3U, requests.size());
-
-  EXPECT_EQ(base::StringPrintf("www.google.com:%u", https_server.port()),
-            requests[0].headers["Host"]);
-  EXPECT_EQ(kRedirectPrefetchPage, requests[0].relative_url);
+  {
+    base::AutoLock auto_lock(lock_);
+    ASSERT_EQ(3U, requests_.size());
+    EXPECT_EQ(base::StringPrintf("www.google.com:%u", https_server.port()),
+              requests_[0].headers["Host"]);
+    EXPECT_EQ(kRedirectPrefetchPage, requests_[0].relative_url);
+  }
 }
 
 }  // namespace weblayer
diff --git a/weblayer/public/java/org/chromium/weblayer/Navigation.java b/weblayer/public/java/org/chromium/weblayer/Navigation.java
index e302c50..e312642 100644
--- a/weblayer/public/java/org/chromium/weblayer/Navigation.java
+++ b/weblayer/public/java/org/chromium/weblayer/Navigation.java
@@ -145,6 +145,43 @@
     }
 
     /**
+     * Whether this navigation resulted in an external intent being launched. Returns false if this
+     * navigation did not do so, or if that status is not yet known for this navigation.  This
+     * status is determined for a navigation when processing final (post redirect) HTTP response
+     * headers. This means the only time the embedder can know if the navigation resulted in an
+     * external intent being launched is in NavigationCallback.onNavigationFailed.
+     *
+     * @since 89
+     */
+    public boolean wasIntentLaunched() {
+        ThreadCheck.ensureOnUiThread();
+        try {
+            return mNavigationImpl.wasIntentLaunched();
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
+
+    /**
+     * Whether this navigation resulted in the user deciding whether an external intent should be
+     * launched (e.g., via a dialog). Returns false if this navigation did not resolve to such a
+     * user decision, or if that status is not yet known for this navigation.  This status is
+     * determined for a navigation when processing final (post redirect) HTTP response headers. This
+     * means the only time the embedder can know this status definitively is in
+     * NavigationCallback.onNavigationFailed.
+     *
+     * @since 89
+     */
+    public boolean isUserDecidingIntentLaunch() {
+        ThreadCheck.ensureOnUiThread();
+        try {
+            return mNavigationImpl.isUserDecidingIntentLaunch();
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
+
+    /**
      * Whether this navigation was stopped before it could complete because
      * NavigationController.stop() was called.
      *