diff --git a/DEPS b/DEPS
index c96d90d..56c4cf97 100644
--- a/DEPS
+++ b/DEPS
@@ -179,7 +179,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': '56ff573a6c1416dad552ed26b1724a30a632f4d3',
+  'v8_revision': 'e18618490c3aeddf96e36c11b4bffcb5c7f12320',
   # 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.
@@ -187,7 +187,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': '9e9493f29dbf3afcd962c56f66cb752efe3c7ee8',
+  'angle_revision': '3f283eb209adf9405721a05e8323bd80069912ad',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -246,7 +246,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': 'f92fd4849f47c9ad4a26c1864138f73418d60ddf',
+  'devtools_frontend_revision': '07fd18460d096895e12a7f8b228cf9d2fbf117d6',
   # 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.
@@ -286,7 +286,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.
-  'spv_tools_revision': 'e1688b60caf77e7efd9e440e57cca429ca7c5a1e',
+  'spv_tools_revision': 'a6d3a2dd41d4efcafaefb4821ad491262be6b8d3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -302,7 +302,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': '9b5ecdf536e0da8740eba61fc925f172d51d3c12',
+  'dawn_revision': '35645a601a73c26e40e9ddf20f2f685acacf7bde',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -532,7 +532,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '844112ed66147768bb88c48ad5af1179ab3ed7c8',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '8a6d4f9ab70a581253c8c2fb89ba6e076b71431a',
       'condition': 'checkout_ios',
   },
 
@@ -944,7 +944,7 @@
     Var('chromium_git') + '/codecs/libgav1.git' + '@' + 'fa1c3c4e673cf12ffa22b8fbe4a7c79314571f1b',
 
   'src/third_party/glslang/src':
-    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + '8985fc91089f3117d338f0ebd683596a197b2d92',
+    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + 'c6a4c6d3d8b5b85b93308336534adf9c1ef0ae66',
 
   'src/third_party/google_toolbox_for_mac/src': {
       'url': Var('chromium_git') + '/external/github.com/google/google-toolbox-for-mac.git' + '@' + Var('google_toolbox_for_mac_revision'),
@@ -1239,7 +1239,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '024aa887785036dd7f1012cc97ce131ed40304b4',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '0fd4f5cda74d29ee13a8942f973c827ec73d7b0d',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1440,7 +1440,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'ec18cc3262922e7dcdbe70243c6f40606f979144',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '80636981052754dcdaaf90a5d83a1cd9eb786dfb',
+    Var('webrtc_git') + '/src.git' + '@' + '4518a20e14ecb3415c52aecf132d9bc83edaa9c4',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1515,7 +1515,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@2fdaf67e552951e7d4c1d0fa1b688942caf25baa',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3ac359c9ea04878a029df2e976aa4bcf11a273fc',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/autotest_private_api_utils.cc b/ash/autotest_private_api_utils.cc
index 141b681..245a48f 100644
--- a/ash/autotest_private_api_utils.cc
+++ b/ash/autotest_private_api_utils.cc
@@ -81,8 +81,7 @@
 
 std::vector<aura::Window*> GetAppWindowList() {
   ScopedSkipUserSessionBlockedCheck skip_session_blocked;
-  return Shell::Get()->mru_window_tracker()->BuildWindowForCycleWithPipList(
-      kAllDesks);
+  return Shell::Get()->mru_window_tracker()->BuildAppWindowList(kAllDesks);
 }
 
 bool WaitForLauncherState(AppListViewState target_state,
diff --git a/ash/wm/mru_window_tracker.cc b/ash/wm/mru_window_tracker.cc
index fb5f785a..645d4a0 100644
--- a/ash/wm/mru_window_tracker.cc
+++ b/ash/wm/mru_window_tracker.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 
+#include "ash/public/cpp/app_types.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/session/session_controller_impl.h"
@@ -208,6 +209,15 @@
     window->RemoveObserver(this);
 }
 
+MruWindowTracker::WindowList MruWindowTracker::BuildAppWindowList(
+    DesksMruType desks_mru_type) const {
+  return BuildWindowListInternal(
+      &mru_windows_, desks_mru_type, [](aura::Window* w) {
+        return w->GetProperty(aura::client::kAppType) !=
+               static_cast<int>(ash::AppType::NON_APP);
+      });
+}
+
 MruWindowTracker::WindowList MruWindowTracker::BuildMruWindowList(
     DesksMruType desks_mru_type) const {
   return BuildWindowListInternal(&mru_windows_, desks_mru_type,
diff --git a/ash/wm/mru_window_tracker.h b/ash/wm/mru_window_tracker.h
index 81e1ab7b..65e6f2a 100644
--- a/ash/wm/mru_window_tracker.h
+++ b/ash/wm/mru_window_tracker.h
@@ -49,6 +49,14 @@
   MruWindowTracker();
   ~MruWindowTracker() override;
 
+  // Returns the set windows in the mru list regardless of whether they can be
+  // included in the cycler or not.
+  // |desks_mru_type| determines whether to include or exclude windows from the
+  // inactive desks.
+  // TODO(oshima|afakhry): Investigate if we can consolidate BuildXXXList
+  // methods with parameters.
+  WindowList BuildAppWindowList(DesksMruType desks_mru_type) const;
+
   // Returns the set of windows which can be cycled through using the tracked
   // list of most recently used windows.
   // |desks_mru_type| determines whether to include or exclude windows from the
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 146f433..e084b3f 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -383,7 +383,6 @@
     "//cc/base",
     "//cc/paint",
     "//components/viz/common",
-    "//services/tracing/public/cpp:cpp",
     "//skia",
   ]
   deps = [
@@ -399,6 +398,7 @@
     "//mojo/public/cpp/bindings:struct_traits",
     "//services/metrics/public/cpp:ukm_builders",
     "//services/metrics/public/mojom",
+    "//services/tracing/public/cpp:cpp",
     "//ui/events:events_base",
     "//ui/gfx",
     "//ui/gfx/geometry",
diff --git a/cc/trees/latency_info_swap_promise.cc b/cc/trees/latency_info_swap_promise.cc
index 3f96946..2edc793 100644
--- a/cc/trees/latency_info_swap_promise.cc
+++ b/cc/trees/latency_info_swap_promise.cc
@@ -8,6 +8,8 @@
 
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
+#include "services/tracing/public/cpp/perfetto/flow_event_utils.h"
+#include "services/tracing/public/cpp/perfetto/macros.h"
 
 namespace cc {
 
@@ -38,10 +40,19 @@
 
 // Trace the original LatencyInfo of a LatencyInfoSwapPromise
 void LatencyInfoSwapPromise::OnCommit() {
-  TRACE_EVENT_WITH_FLOW1("input,benchmark", "LatencyInfo.Flow",
-                         TRACE_ID_GLOBAL(TraceId()),
-                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
-                         "step", "HandleInputEventMainCommit");
+  using perfetto::protos::pbzero::ChromeLatencyInfo;
+  using perfetto::protos::pbzero::TrackEvent;
+
+  TRACE_EVENT("input,benchmark", "LatencyInfo.Flow",
+              [this](perfetto::EventContext ctx) {
+                ChromeLatencyInfo* latency_info =
+                    ctx.event()->set_chrome_latency_info();
+                latency_info->set_trace_id(TraceId());
+                latency_info->set_step(
+                    ChromeLatencyInfo::STEP_HANDLE_INPUT_EVENT_MAIN_COMMIT);
+                tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_INOUT,
+                                       TraceId());
+              });
 }
 
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 13573155..584aff4 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -61,6 +61,8 @@
 #include "cc/trees/tree_synchronizer.h"
 #include "cc/trees/ukm_manager.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
+#include "services/tracing/public/cpp/perfetto/flow_event_utils.h"
+#include "services/tracing/public/cpp/perfetto/macros.h"
 #include "ui/gfx/geometry/size_conversions.h"
 #include "ui/gfx/geometry/vector2d_conversions.h"
 #include "ui/gfx/presentation_feedback.h"
@@ -943,11 +945,20 @@
 void LayerTreeHost::ApplyScrollAndScale(ScrollAndScaleSet* info) {
   DCHECK(info);
   TRACE_EVENT0("cc", "LayerTreeHost::ApplyScrollAndScale");
+
+  using perfetto::protos::pbzero::ChromeLatencyInfo;
+  using perfetto::protos::pbzero::TrackEvent;
+
   for (auto& swap_promise : info->swap_promises) {
-    TRACE_EVENT_WITH_FLOW1("input,benchmark", "LatencyInfo.Flow",
-                           TRACE_ID_GLOBAL(swap_promise->TraceId()),
-                           TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
-                           "step", "Main thread scroll update");
+    TRACE_EVENT(
+        "input,benchmark", "LatencyInfo.Flow",
+        [&swap_promise](perfetto::EventContext ctx) {
+          ChromeLatencyInfo* info = ctx.event()->set_chrome_latency_info();
+          info->set_trace_id(swap_promise->TraceId());
+          info->set_step(ChromeLatencyInfo::STEP_MAIN_THREAD_SCROLL_UPDATE);
+          tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_INOUT,
+                                 swap_promise->TraceId());
+        });
     swap_promise_manager_.QueueSwapPromise(std::move(swap_promise));
   }
 
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 35a18b6..5f1693c 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -112,6 +112,7 @@
 #include "gpu/command_buffer/client/shared_image_interface.h"
 #include "gpu/command_buffer/common/shared_image_usage.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
+#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.pbzero.h"
 #include "third_party/skia/include/gpu/GrContext.h"
 #include "ui/gfx/display_color_spaces.h"
 #include "ui/gfx/geometry/point_conversions.h"
@@ -2471,8 +2472,9 @@
           ui::INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT, draw_time);
     }
   }
-  ui::LatencyInfo::TraceIntermediateFlowEvents(metadata.latency_info,
-                                               "SwapBuffers");
+  ui::LatencyInfo::TraceIntermediateFlowEvents(
+      metadata.latency_info,
+      perfetto::protos::pbzero::ChromeLatencyInfo::STEP_SWAP_BUFFERS);
 
   // Collect all resource ids in the render passes into a single array.
   std::vector<viz::ResourceId> resources;
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 3194c7c..123f2a6c 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -1395,6 +1395,7 @@
       "//chrome/browser/resources:component_extension_resources",
       "//chrome/browser/resources:dev_ui_paks",
       "//chrome/browser/resources:downloads_resources",
+      "//chrome/browser/resources:gaia_auth_host_resources",
       "//chrome/browser/resources:history_resources",
       "//chrome/browser/resources:local_ntp_resources",
       "//chrome/browser/resources:new_tab_page_resources",
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java
index d01f873..eace6bd2 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java
@@ -86,8 +86,10 @@
                     // TODO(b/143517837) Merge autofill assistant image fetcher UMA names.
                     mImageFetcher.fetchImage(image.mImageUrl,
                             ImageFetcher.ASSISTANT_DETAILS_UMA_CLIENT_NAME, result -> {
-                                image.mImageBitmap = Bitmap.createScaledBitmap(result,
-                                        image.mImageSizeInPixels, image.mImageSizeInPixels, true);
+                                image.mImageBitmap = result != null ? Bitmap.createScaledBitmap(
+                                                             result, image.mImageSizeInPixels,
+                                                             image.mImageSizeInPixels, true)
+                                                                    : null;
                                 mDrawable.setFullOverlayImage(image);
                             });
                 } else {
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantBottomsheetTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantBottomsheetTest.java
index f0e2ce5..e422eb0b 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantBottomsheetTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantBottomsheetTest.java
@@ -551,7 +551,7 @@
                     float y = GeneralLocation.TOP_CENTER.calculateCoordinates(
                             mTestRule.getActivity().findViewById(
                                     R.id.autofill_assistant_bottom_sheet_toolbar))[1];
-                    Rect el = getAbsoluteBoundingRect(elementId, mTestRule);
+                    Rect el = getAbsoluteBoundingRect(mTestRule, elementId);
                     return el.bottom > y == shouldBeCovered;
                 } catch (Exception e) {
                     throw new RuntimeException(e);
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java
index 45cbb2c..6b79139 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java
@@ -181,11 +181,11 @@
         onView(withId(R.id.card_unmask_input)).perform(typeText("123"));
         onView(withId(R.id.positive_button)).perform(click());
         waitUntilViewMatchesCondition(withText("Prompt"), isCompletelyDisplayed());
-        assertThat(getElementValue("name", getWebContents()), is("John Doe"));
-        assertThat(getElementValue("card_number", getWebContents()), is("4111111111111111"));
-        assertThat(getElementValue("cv2_number", getWebContents()), is("123"));
-        assertThat(getElementValue("exp_month", getWebContents()), is("12"));
-        assertThat(getElementValue("exp_year", getWebContents()), is("2050"));
+        assertThat(getElementValue(getWebContents(), "name"), is("John Doe"));
+        assertThat(getElementValue(getWebContents(), "card_number"), is("4111111111111111"));
+        assertThat(getElementValue(getWebContents(), "cv2_number"), is("123"));
+        assertThat(getElementValue(getWebContents(), "exp_month"), is("12"));
+        assertThat(getElementValue(getWebContents(), "exp_year"), is("2050"));
     }
 
     /**
@@ -236,8 +236,8 @@
         startAutofillAssistant(mTestRule.getActivity(), testService);
 
         waitUntilViewMatchesCondition(withText("Prompt"), isCompletelyDisplayed());
-        String password = getElementValue("password", getWebContents());
-        String confirmation_password = getElementValue("password-conf", getWebContents());
+        String password = getElementValue(getWebContents(), "password");
+        String confirmation_password = getElementValue(getWebContents(), "password-conf");
         assertThat(password.length(), greaterThan(0));
         assertThat(password, is(confirmation_password));
     }
@@ -307,12 +307,12 @@
         startAutofillAssistant(mTestRule.getActivity(), testService);
 
         waitUntilViewMatchesCondition(withText("Continue"), isDisplayed());
-        tapElement("button", mTestRule);
+        tapElement(mTestRule, "button");
         onView(withText("Continue")).perform(click());
         waitUntilViewMatchesCondition(withText("Toggle"), isDisplayed());
 
         // Verify that in the next step the touchable window is not present anymore.
-        tapElement("button", mTestRule);
+        tapElement(mTestRule, "button");
         onView(withText("Toggle")).check(matches(isDisplayed()));
     }
 
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFormActionTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFormActionTest.java
index 25ec7dc..7f23b2ad 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFormActionTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFormActionTest.java
@@ -6,6 +6,7 @@
 
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.scrollTo;
 import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.intent.Intents.intended;
@@ -176,44 +177,45 @@
 
         waitUntilViewMatchesCondition(withText("Continue"), isCompletelyDisplayed());
         // TODO(b/144690738) Remove the isDisplayed() condition.
-        onView(allOf(isDisplayed(), withId(R.id.value),
+        onView(allOf(withId(R.id.value), withEffectiveVisibility(VISIBLE),
                        hasSibling(hasDescendant(withText("Counter 1")))))
                 .check(matches(hasTextColor(R.color.modern_grey_800_alpha_38)));
-        onView(allOf(isDisplayed(), withId(R.id.increase_button),
+        onView(allOf(withId(R.id.increase_button), withEffectiveVisibility(VISIBLE),
                        hasSibling(hasDescendant(withText("Counter 1")))))
                 .check(matches(hasTintColor(R.color.modern_blue_600)));
-        onView(allOf(isDisplayed(), withId(R.id.decrease_button),
+        onView(allOf(withId(R.id.decrease_button), withEffectiveVisibility(VISIBLE),
                        hasSibling(hasDescendant(withText("Counter 1")))))
                 .check(matches(hasTintColor(R.color.modern_grey_800_alpha_38)));
         // Click on Counter 1 +, increase from 0 to 1.
-        onView(allOf(isDisplayed(), withId(R.id.increase_button),
+        onView(allOf(withId(R.id.increase_button), withEffectiveVisibility(VISIBLE),
                        hasSibling(hasDescendant(withText("Counter 1")))))
-                .perform(click());
-        onView(allOf(isDisplayed(), withId(R.id.value),
+                .perform(scrollTo(), click());
+        onView(allOf(withId(R.id.value), withEffectiveVisibility(VISIBLE),
                        hasSibling(hasDescendant(withText("Counter 1")))))
                 .check(matches(hasTextColor(R.color.modern_blue_600)));
-        onView(allOf(isDisplayed(), withId(R.id.increase_button),
+        onView(allOf(withId(R.id.increase_button), withEffectiveVisibility(VISIBLE),
                        hasSibling(hasDescendant(withText("Counter 1")))))
                 .check(matches(hasTintColor(R.color.modern_grey_800_alpha_38)));
         // Decrease button is still disabled due to the minCountersSum requirement.
 
         // Click expand label to make Counter 2 visible.
-        onView(allOf(isDisplayed(), withId(R.id.expand_label))).perform(click());
+        onView(allOf(withId(R.id.expand_label), withEffectiveVisibility(VISIBLE)))
+                .perform(scrollTo(), click());
         // Click on Counter 3 +, increase from 0 to 1.
-        onView(allOf(isDisplayed(), withId(R.id.increase_button),
+        onView(allOf(withId(R.id.increase_button), withEffectiveVisibility(VISIBLE),
                        hasSibling(hasDescendant(withText("Counter 3")))))
-                .perform(click());
+                .perform(scrollTo(), click());
 
         // Click on Choice 1, then Choice 2, then back to Choice 1.
         onView(allOf(withClassName(is(RadioButton.class.getName())), withParentIndex(0),
                        withEffectiveVisibility(VISIBLE)))
-                .perform(click());
+                .perform(scrollTo(), click());
         onView(allOf(withClassName(is(RadioButton.class.getName())), withParentIndex(3),
                        withEffectiveVisibility(VISIBLE)))
-                .perform(click());
+                .perform(scrollTo(), click());
         onView(allOf(withClassName(is(RadioButton.class.getName())), withParentIndex(0),
                        withEffectiveVisibility(VISIBLE)))
-                .perform(click());
+                .perform(scrollTo(), click());
 
         // Check that choice 1 is visually selected and choice 2 is de-selected.
         onView(allOf(withClassName(is(RadioButton.class.getName())), withParentIndex(0),
@@ -224,9 +226,9 @@
                 .check(matches(not(isChecked())));
 
         // Click on Counter 2 +, increase from 0 to 1.
-        onView(allOf(isDisplayed(), withId(R.id.increase_button),
+        onView(allOf(withId(R.id.increase_button), withEffectiveVisibility(VISIBLE),
                        hasSibling(hasDescendant(withText("Counter 2")))))
-                .perform(click());
+                .perform(scrollTo(), click());
 
         // Finish form action, wait for response and prepare next set of actions.
         List<ActionProto> nextActions = new ArrayList<>();
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayIntegrationTest.java
index 5ba558b5c..e334ce326 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayIntegrationTest.java
@@ -103,12 +103,12 @@
         waitUntilViewMatchesCondition(withText("Prompt"), isCompletelyDisplayed());
 
         // Tapping on the element should remove it from the DOM.
-        assertThat(checkElementExists("touch_area_one", mTestRule.getWebContents()), is(true));
-        tapElement("touch_area_one", mTestRule);
-        assertThat(checkElementExists("touch_area_one", mTestRule.getWebContents()), is(false));
+        assertThat(checkElementExists(mTestRule.getWebContents(), "touch_area_one"), is(true));
+        tapElement(mTestRule, "touch_area_one");
+        assertThat(checkElementExists(mTestRule.getWebContents(), "touch_area_one"), is(false));
         // Tapping on the element should be blocked by the overlay.
-        tapElement("touch_area_four", mTestRule);
-        assertThat(checkElementExists("touch_area_four", mTestRule.getWebContents()), is(true));
+        tapElement(mTestRule, "touch_area_four");
+        assertThat(checkElementExists(mTestRule.getWebContents(), "touch_area_four"), is(true));
     }
 
     /**
@@ -149,15 +149,112 @@
 
         // Tapping on the element should remove it from the DOM. The element should be after a
         // big element forcing the page to scroll.
-        assertThat(checkElementExists("touch_area_five", mTestRule.getWebContents()), is(true));
-        tapElement("touch_area_five", mTestRule);
-        assertThat(checkElementExists("touch_area_five", mTestRule.getWebContents()), is(false));
+        assertThat(checkElementExists(mTestRule.getWebContents(), "touch_area_five"), is(true));
+        tapElement(mTestRule, "touch_area_five");
+        assertThat(checkElementExists(mTestRule.getWebContents(), "touch_area_five"), is(false));
         // Tapping on the element should be blocked by the overlay.
-        tapElement("touch_area_six", mTestRule);
-        assertThat(checkElementExists("touch_area_six", mTestRule.getWebContents()), is(true));
+        tapElement(mTestRule, "touch_area_six");
+        assertThat(checkElementExists(mTestRule.getWebContents(), "touch_area_six"), is(true));
     }
 
-    // TODO(b/143942385): Write a test for an element within an iFrame.
+    /**
+     * Tests that clicking on an iFrame element works with a showcast.
+     */
+    @Test
+    @MediumTest
+    public void testShowCastOnIFrameElement() throws Exception {
+        ElementReferenceProto element = (ElementReferenceProto) ElementReferenceProto.newBuilder()
+                                                .addSelectors("#iframe")
+                                                .addSelectors("#touch_area_1")
+                                                .build();
+
+        ArrayList<ActionProto> list = new ArrayList<>();
+        list.add(
+                (ActionProto) ActionProto.newBuilder()
+                        .setFocusElement(FocusElementProto.newBuilder()
+                                                 .setElement(element)
+                                                 .setTouchableElementArea(
+                                                         ElementAreaProto.newBuilder().addTouchable(
+                                                                 Rectangle.newBuilder().addElements(
+                                                                         element))))
+                        .build());
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setPrompt(PromptProto.newBuilder().setMessage("Prompt").addChoices(
+                                 PromptProto.Choice.newBuilder()))
+                         .build());
+
+        AutofillAssistantTestScript script = new AutofillAssistantTestScript(
+                (SupportedScriptProto) SupportedScriptProto.newBuilder()
+                        .setPath("autofill_assistant_target_website.html")
+                        .setPresentation(PresentationProto.newBuilder().setAutostart(true).setChip(
+                                ChipProto.newBuilder().setText("Done")))
+                        .build(),
+                list);
+        runScript(script);
+
+        waitUntilViewMatchesCondition(withText("Prompt"), isCompletelyDisplayed());
+
+        // Tapping on the element should remove it from the DOM.
+        assertThat(
+                checkElementExists(mTestRule.getWebContents(), "iframe", "touch_area_1"), is(true));
+        tapElement(mTestRule, "iframe", "touch_area_1");
+        assertThat(checkElementExists(mTestRule.getWebContents(), "iframe", "touch_area_1"),
+                is(false));
+        // Tapping on the element should be blocked by the overlay.
+        tapElement(mTestRule, "iframe", "touch_area_2");
+        assertThat(
+                checkElementExists(mTestRule.getWebContents(), "iframe", "touch_area_2"), is(true));
+    }
+
+    /**
+     * Tests that clicking on an iFrame element works with a showcast in a scrolled iFrame.
+     */
+    @Test
+    @MediumTest
+    public void testShowCastOnIFrameElementInScrollIFrame() throws Exception {
+        ElementReferenceProto element = (ElementReferenceProto) ElementReferenceProto.newBuilder()
+                                                .addSelectors("#iframe")
+                                                .addSelectors("#touch_area_3")
+                                                .build();
+
+        ArrayList<ActionProto> list = new ArrayList<>();
+        list.add(
+                (ActionProto) ActionProto.newBuilder()
+                        .setFocusElement(FocusElementProto.newBuilder()
+                                                 .setElement(element)
+                                                 .setTouchableElementArea(
+                                                         ElementAreaProto.newBuilder().addTouchable(
+                                                                 Rectangle.newBuilder().addElements(
+                                                                         element))))
+                        .build());
+        list.add((ActionProto) ActionProto.newBuilder()
+                         .setPrompt(PromptProto.newBuilder().setMessage("Prompt").addChoices(
+                                 PromptProto.Choice.newBuilder()))
+                         .build());
+
+        AutofillAssistantTestScript script = new AutofillAssistantTestScript(
+                (SupportedScriptProto) SupportedScriptProto.newBuilder()
+                        .setPath("autofill_assistant_target_website.html")
+                        .setPresentation(PresentationProto.newBuilder().setAutostart(true).setChip(
+                                ChipProto.newBuilder().setText("Done")))
+                        .build(),
+                list);
+        runScript(script);
+
+        waitUntilViewMatchesCondition(withText("Prompt"), isCompletelyDisplayed());
+
+        // Tapping on the element should remove it from the DOM. The element should be after a
+        // big element forcing the page to scroll.
+        assertThat(
+                checkElementExists(mTestRule.getWebContents(), "iframe", "touch_area_3"), is(true));
+        tapElement(mTestRule, "iframe", "touch_area_3");
+        assertThat(checkElementExists(mTestRule.getWebContents(), "iframe", "touch_area_3"),
+                is(false));
+        // Tapping on the element should be blocked by the overlay.
+        tapElement(mTestRule, "iframe", "touch_area_4");
+        assertThat(
+                checkElementExists(mTestRule.getWebContents(), "iframe", "touch_area_4"), is(true));
+    }
 
     private void runScript(AutofillAssistantTestScript script) {
         AutofillAssistantTestService testService =
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayUiTest.java
index 733c9c0..a30a5ba 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayUiTest.java
@@ -12,6 +12,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
 
 import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.checkElementExists;
 import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.getBoundingRectForElement;
@@ -23,6 +24,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 
@@ -71,16 +73,22 @@
         return mTestRule.getWebContents();
     }
 
-    /** Creates a coordinator for use in UI tests. */
+    /** Creates a coordinator for use in UI tests with a default, non-null overlay image. */
     private AssistantOverlayCoordinator createCoordinator(AssistantOverlayModel model)
             throws ExecutionException {
-        Bitmap testImage = BitmapFactory.decodeResource(mTestRule.getActivity().getResources(),
-                org.chromium.chrome.autofill_assistant.R.drawable.btn_close);
+        return createCoordinator(model,
+                BitmapFactory.decodeResource(mTestRule.getActivity().getResources(),
+                        org.chromium.chrome.autofill_assistant.R.drawable.btn_close));
+    }
 
+    /** Creates a coordinator for use in UI tests with a custom overlay image. */
+    private AssistantOverlayCoordinator createCoordinator(
+            AssistantOverlayModel model, @Nullable Bitmap overlayImage) throws ExecutionException {
         return runOnUiThreadBlocking(
                 ()
                         -> new AssistantOverlayCoordinator(mTestRule.getActivity(), model,
-                                new AutofillAssistantUiTestUtil.MockImageFetcher(testImage, null)));
+                                new AutofillAssistantUiTestUtil.MockImageFetcher(
+                                        overlayImage, null)));
     }
 
     /** Tests assumptions about the initial state of the infobox. */
@@ -92,7 +100,7 @@
 
         assertScrimDisplayed(false);
         tapElement("touch_area_one");
-        assertThat(checkElementExists("touch_area_one", getWebContents()), is(false));
+        assertThat(checkElementExists(getWebContents(), "touch_area_one"), is(false));
     }
 
     /** Tests assumptions about the full overlay. */
@@ -106,13 +114,13 @@
                 () -> model.set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL));
         assertScrimDisplayed(true);
         tapElement("touch_area_one");
-        assertThat(checkElementExists("touch_area_one", getWebContents()), is(true));
+        assertThat(checkElementExists(getWebContents(), "touch_area_one"), is(true));
 
         runOnUiThreadBlocking(
                 () -> model.set(AssistantOverlayModel.STATE, AssistantOverlayState.HIDDEN));
         assertScrimDisplayed(false);
         tapElement("touch_area_one");
-        assertThat(checkElementExists("touch_area_one", getWebContents()), is(false));
+        assertThat(checkElementExists(getWebContents(), "touch_area_one"), is(false));
     }
 
     /** Tests assumptions about the full overlay. */
@@ -144,16 +152,16 @@
                 () -> model.set(AssistantOverlayModel.STATE, AssistantOverlayState.PARTIAL));
         assertScrimDisplayed(true);
         tapElement("touch_area_one");
-        assertThat(checkElementExists("touch_area_one", getWebContents()), is(true));
+        assertThat(checkElementExists(getWebContents(), "touch_area_one"), is(true));
 
-        Rect rect = getBoundingRectForElement("touch_area_one", getWebContents());
+        Rect rect = getBoundingRectForElement(getWebContents(), "touch_area_one");
         runOnUiThreadBlocking(()
                                       -> model.set(AssistantOverlayModel.TOUCHABLE_AREA,
                                               Collections.singletonList(new RectF(rect))));
 
         // Touchable area set, but no viewport given: equivalent to full overlay.
         tapElement("touch_area_one");
-        assertThat(checkElementExists("touch_area_one", getWebContents()), is(true));
+        assertThat(checkElementExists(getWebContents(), "touch_area_one"), is(true));
 
         // Set viewport.
         Rect viewport = getViewport(getWebContents());
@@ -162,12 +170,12 @@
 
         // Now the partial overlay allows tapping the highlighted touch area.
         tapElement("touch_area_one");
-        assertThat(checkElementExists("touch_area_one", getWebContents()), is(false));
+        assertThat(checkElementExists(getWebContents(), "touch_area_one"), is(false));
 
         runOnUiThreadBlocking(
                 () -> model.set(AssistantOverlayModel.TOUCHABLE_AREA, Collections.emptyList()));
         tapElement("touch_area_three");
-        assertThat(checkElementExists("touch_area_three", getWebContents()), is(true));
+        assertThat(checkElementExists(getWebContents(), "touch_area_three"), is(true));
     }
 
     /** Scrolls a touchable area into view and then taps it. */
@@ -177,7 +185,7 @@
         AssistantOverlayModel model = new AssistantOverlayModel();
         AssistantOverlayCoordinator coordinator = createCoordinator(model);
 
-        Rect rect = getBoundingRectForElement("touch_area_two", getWebContents());
+        Rect rect = getBoundingRectForElement(getWebContents(), "touch_area_two");
         Rect viewport = getViewport(getWebContents());
         runOnUiThreadBlocking(() -> {
             model.set(AssistantOverlayModel.STATE, AssistantOverlayState.PARTIAL);
@@ -190,7 +198,49 @@
         runOnUiThreadBlocking(
                 () -> model.set(AssistantOverlayModel.VISUAL_VIEWPORT, new RectF(newViewport)));
         tapElement("touch_area_two");
-        assertThat(checkElementExists("touch_area_two", getWebContents()), is(false));
+        assertThat(checkElementExists(getWebContents(), "touch_area_two"), is(false));
+    }
+
+    /**
+     * Regular overlay image test. Since there is no easy way to test whether the image is actually
+     * rendered, this is simply checking that nothing crashes.
+     */
+    @Test
+    @MediumTest
+    public void testOverlayImageDoesNotCrashIfValid() throws Exception {
+        AssistantOverlayModel model = new AssistantOverlayModel();
+        Bitmap bitmap = BitmapFactory.decodeResource(mTestRule.getActivity().getResources(),
+                org.chromium.chrome.autofill_assistant.R.drawable.btn_close);
+        assertThat(bitmap, notNullValue());
+        AssistantOverlayCoordinator coordinator =
+                createCoordinator(model, /* overlayImage = */ bitmap);
+
+        runOnUiThreadBlocking(() -> {
+            model.set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL);
+            model.set(AssistantOverlayModel.OVERLAY_IMAGE,
+                    new AssistantOverlayImage("https://www.example.com/example.png", 32, 32, 12,
+                            "Text", Color.RED, 20));
+        });
+
+        assertScrimDisplayed(true);
+    }
+
+    /** Simulates what would happen if the overlay image fetcher returned null. */
+    @Test
+    @MediumTest
+    public void testOverlayDoesNotCrashIfImageFailsToLoad() throws Exception {
+        AssistantOverlayModel model = new AssistantOverlayModel();
+        AssistantOverlayCoordinator coordinator =
+                createCoordinator(model, /* overlayImage = */ null);
+
+        runOnUiThreadBlocking(() -> {
+            model.set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL);
+            model.set(AssistantOverlayModel.OVERLAY_IMAGE,
+                    new AssistantOverlayImage("https://www.example.com/example.png", 32, 32, 12,
+                            "Text", Color.RED, 20));
+        });
+
+        assertScrimDisplayed(true);
     }
 
     private void assertScrimDisplayed(boolean expected) throws Exception {
@@ -214,7 +264,7 @@
     }
 
     void tapElement(String elementId) throws Exception {
-        AutofillAssistantUiTestUtil.tapElement(elementId, mTestRule);
+        AutofillAssistantUiTestUtil.tapElement(mTestRule, elementId);
     }
 
     /**
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
index d4aab0c..744354f3 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
@@ -165,8 +165,8 @@
                 .check(matches(allOf(withText("johndoe@google.com"), isDisplayed())));
         onView(withText("Continue")).perform(click());
         waitUntilViewMatchesCondition(withText("Prompt"), isCompletelyDisplayed());
-        assertThat(getElementValue("profile_name", getWebContents()), is("John Doe"));
-        assertThat(getElementValue("email", getWebContents()), is("johndoe@google.com"));
+        assertThat(getElementValue(getWebContents(), "profile_name"), is("John Doe"));
+        assertThat(getElementValue(getWebContents(), "email"), is("johndoe@google.com"));
     }
 
     /**
@@ -217,8 +217,8 @@
         waitUntilViewMatchesCondition(withContentDescription("Continue"), isEnabled());
         onView(withContentDescription("Continue")).perform(click());
         waitUntilViewMatchesCondition(withText("Prompt"), isCompletelyDisplayed());
-        assertThat(getElementValue("profile_name", getWebContents()), is("John Doe"));
-        assertThat(getElementValue("email", getWebContents()), is("johndoe@google.com"));
+        assertThat(getElementValue(getWebContents(), "profile_name"), is("John Doe"));
+        assertThat(getElementValue(getWebContents(), "email"), is("johndoe@google.com"));
     }
 
     /**
@@ -276,8 +276,8 @@
         onView(withText("Continue")).perform(click());
         waitUntilViewMatchesCondition(withText("Prompt"), isCompletelyDisplayed());
         // Make sure it's not Adam West that was selected.
-        assertThat(getElementValue("profile_name", getWebContents()), is("John Doe"));
-        assertThat(getElementValue("email", getWebContents()), is("johndoe@google.com"));
+        assertThat(getElementValue(getWebContents(), "profile_name"), is("John Doe"));
+        assertThat(getElementValue(getWebContents(), "email"), is("johndoe@google.com"));
     }
 
     /**
@@ -395,8 +395,8 @@
         onView(withText("Continue")).perform(click());
         waitUntilViewMatchesCondition(withText("Prompt"), isCompletelyDisplayed());
         // Make sure it's now Jane Doe.
-        assertThat(getElementValue("profile_name", getWebContents()), is("Jane Doe"));
-        assertThat(getElementValue("email", getWebContents()), is("janedoe@google.com"));
+        assertThat(getElementValue(getWebContents(), "profile_name"), is("Jane Doe"));
+        assertThat(getElementValue(getWebContents(), "email"), is("janedoe@google.com"));
     }
 
     /**
@@ -477,11 +477,11 @@
         onView(withId(R.id.card_unmask_input)).perform(typeText("123"));
         onView(withId(R.id.positive_button)).perform(click());
         waitUntilViewMatchesCondition(withText("Prompt"), isCompletelyDisplayed());
-        assertThat(getElementValue("name", getWebContents()), is("John Doe"));
-        assertThat(getElementValue("card_number", getWebContents()), is("4111111111111111"));
-        assertThat(getElementValue("cv2_number", getWebContents()), is("123"));
-        assertThat(getElementValue("exp_month", getWebContents()), is("01"));
-        assertThat(getElementValue("exp_year", getWebContents()), is(String.valueOf(year + 2)));
+        assertThat(getElementValue(getWebContents(), "name"), is("John Doe"));
+        assertThat(getElementValue(getWebContents(), "card_number"), is("4111111111111111"));
+        assertThat(getElementValue(getWebContents(), "cv2_number"), is("123"));
+        assertThat(getElementValue(getWebContents(), "exp_month"), is("01"));
+        assertThat(getElementValue(getWebContents(), "exp_year"), is(String.valueOf(year + 2)));
     }
 
     /**
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java
index 2fdcb2b..a4a51d6 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java
@@ -65,6 +65,7 @@
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import jp.tomorrowkey.android.gifplayer.BaseGifImage;
@@ -429,27 +430,28 @@
     }
 
     /** Performs a single tap on the center of the specified element. */
-    public static void tapElement(String elementId, CustomTabActivityTestRule testRule)
+    public static void tapElement(CustomTabActivityTestRule testRule, String... elementIds)
             throws Exception {
-        Rect coords = getAbsoluteBoundingRect(elementId, testRule);
+        Rect coords = getAbsoluteBoundingRect(testRule, elementIds);
         float x = coords.left + 0.5f * (coords.right - coords.left);
         float y = coords.top + 0.5f * (coords.bottom - coords.top);
 
         // Sanity check, can only click on coordinates on screen.
         DisplayMetrics displayMetrics = testRule.getActivity().getResources().getDisplayMetrics();
         if (x < 0 || x > displayMetrics.widthPixels || y < 0 || y > displayMetrics.heightPixels) {
-            throw new IllegalArgumentException(elementId + " not on screen: tried to tap x=" + x
-                    + ", y=" + y + ", which is outside of display with w="
-                    + displayMetrics.widthPixels + ", h=" + displayMetrics.heightPixels);
+            throw new IllegalArgumentException(Arrays.toString(elementIds)
+                    + " not on screen: tried to tap x=" + x + ", y=" + y
+                    + ", which is outside of display with w=" + displayMetrics.widthPixels
+                    + ", h=" + displayMetrics.heightPixels);
         }
         TestTouchUtils.singleClick(InstrumentationRegistry.getInstrumentation(), x, y);
     }
 
     /** Computes the bounding rectangle of the specified DOM element in absolute screen space. */
-    public static Rect getAbsoluteBoundingRect(String elementId, CustomTabActivityTestRule testRule)
-            throws Exception {
+    public static Rect getAbsoluteBoundingRect(
+            CustomTabActivityTestRule testRule, String... elementIds) throws Exception {
         // Get bounding rectangle in viewport space.
-        Rect elementRect = getBoundingRectForElement(elementId, testRule.getWebContents());
+        Rect elementRect = getBoundingRectForElement(testRule.getWebContents(), elementIds);
 
         /*
          * Conversion from viewport space to screen space is done in two steps:
@@ -475,34 +477,45 @@
      * Retrieves the bounding rectangle for the specified element in the DOM tree in CSS pixel
      * coordinates.
      */
-    public static Rect getBoundingRectForElement(String elementId, WebContents webContents)
+    public static Rect getBoundingRectForElement(WebContents webContents, String... elementIds)
             throws Exception {
-        if (!checkElementExists(elementId, webContents)) {
-            throw new IllegalArgumentException(elementId + " does not exist");
+        if (!checkElementExists(webContents, elementIds)) {
+            throw new IllegalArgumentException(Arrays.toString(elementIds) + " does not exist");
         }
         TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper javascriptHelper =
                 new TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper();
-        javascriptHelper.evaluateJavaScriptForTests(webContents,
-                "(function() {"
-                        + " rect = document.getElementById('" + elementId
-                        + "').getBoundingClientRect();"
-                        + " return [window.scrollX + rect.left, window.scrollY + rect.top, "
-                        + "         window.scrollX + rect.right, window.scrollY + rect.bottom];"
-                        + "})()");
-        javascriptHelper.waitUntilHasValue();
-        JSONArray rectJson = new JSONArray(javascriptHelper.getJsonResultAndClear());
-        return new Rect(
-                rectJson.getInt(0), rectJson.getInt(1), rectJson.getInt(2), rectJson.getInt(3));
+        Rect rect = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
+        for (int i = 0; i < elementIds.length; ++i) {
+            String offsetX = i == 0 ? "window.scrollX" : "0";
+            String offsetY = i == 0 ? "window.scrollY" : "0";
+            String elementSelector =
+                    getElementSelectorString(Arrays.copyOfRange(elementIds, 0, i + 1));
+            javascriptHelper.evaluateJavaScriptForTests(webContents,
+                    "(function() {"
+                            + " rect = " + elementSelector + ".getBoundingClientRect();"
+                            + " return [" + offsetX + " + rect.left, " + offsetY + " + rect.top, "
+                            + "         " + offsetX + " + rect.right, " + offsetY
+                            + " + rect.bottom];"
+                            + "})()");
+            javascriptHelper.waitUntilHasValue();
+            JSONArray rectJson = new JSONArray(javascriptHelper.getJsonResultAndClear());
+
+            rect = new Rect(Math.min(rect.right, rect.left + rectJson.getInt(0)),
+                    Math.min(rect.bottom, rect.top + rectJson.getInt(1)),
+                    Math.min(rect.right, rect.left + rectJson.getInt(2)),
+                    Math.min(rect.bottom, rect.top + rectJson.getInt(3)));
+        }
+        return rect;
     }
 
     /** Checks whether the specified element exists in the DOM tree. */
-    public static boolean checkElementExists(String elementId, WebContents webContents)
+    public static boolean checkElementExists(WebContents webContents, String... elementIds)
             throws Exception {
         TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper javascriptHelper =
                 new TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper();
         javascriptHelper.evaluateJavaScriptForTests(webContents,
                 "(function() {"
-                        + " return [document.getElementById('" + elementId + "') != null]; "
+                        + " return [" + getElementSelectorString(elementIds) + " != null]; "
                         + "})()");
         javascriptHelper.waitUntilHasValue();
         JSONArray result = new JSONArray(javascriptHelper.getJsonResultAndClear());
@@ -510,14 +523,14 @@
     }
 
     /** Checks whether the specified element is displayed in the DOM tree. */
-    public static boolean checkElementIsDisplayed(String elementId, WebContents webContents)
+    public static boolean checkElementIsDisplayed(WebContents webContents, String... elementIds)
             throws Exception {
         TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper javascriptHelper =
                 new TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper();
         javascriptHelper.evaluateJavaScriptForTests(webContents,
                 "(function() {"
-                        + " return [document.getElementById('" + elementId
-                        + "').style.display != \"none\"]; "
+                        + " return [" + getElementSelectorString(elementIds)
+                        + ".style.display != \"none\"]; "
                         + "})()");
         javascriptHelper.waitUntilHasValue();
         JSONArray result = new JSONArray(javascriptHelper.getJsonResultAndClear());
@@ -543,19 +556,37 @@
     /**
      * Retrieves the value of the specified element.
      */
-    public static String getElementValue(String elementId, WebContents webContents)
+    public static String getElementValue(WebContents webContents, String... elementIds)
             throws Exception {
-        if (!checkElementExists(elementId, webContents)) {
-            throw new IllegalArgumentException(elementId + " does not exist");
+        if (!checkElementExists(webContents, elementIds)) {
+            throw new IllegalArgumentException(Arrays.toString(elementIds) + " does not exist");
         }
         TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper javascriptHelper =
                 new TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper();
         javascriptHelper.evaluateJavaScriptForTests(webContents,
                 "(function() {"
-                        + " return [document.getElementById('" + elementId + "').value]"
+                        + " return [" + getElementSelectorString(elementIds) + ".value]"
                         + "})()");
         javascriptHelper.waitUntilHasValue();
         JSONArray result = new JSONArray(javascriptHelper.getJsonResultAndClear());
         return result.getString(0);
     }
+
+    private static String getElementSelectorString(String[] elementIds) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("document");
+
+        for (int i = 0; i < elementIds.length; ++i) {
+            builder.append(".getElementById('");
+            builder.append(elementIds[i]);
+            builder.append("')");
+            if (i != elementIds.length - 1) {
+                // Get the iFrame document. This only works for local iFrames, OutOfProcess iFrames
+                // may respond with an error.
+                builder.append(".contentWindow.document");
+            }
+        }
+
+        return builder.toString();
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunUtils.java
index eadb9e83..aeadbdb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunUtils.java
@@ -90,7 +90,7 @@
 
     @VisibleForTesting
     static boolean hasGoogleAccounts() {
-        return AccountManagerFacade.get().hasGoogleAccounts();
+        return !AccountManagerFacade.get().tryGetGoogleAccounts().isEmpty();
     }
 
     @SuppressLint("InlinedApi")
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
index d2a4913..193e3d7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
@@ -46,10 +46,11 @@
 import org.chromium.chrome.browser.payments.ui.PaymentRequestSection.LineItemBreakdownSection;
 import org.chromium.chrome.browser.payments.ui.PaymentRequestSection.OptionSection;
 import org.chromium.chrome.browser.payments.ui.PaymentRequestSection.SectionSeparator;
+import org.chromium.chrome.browser.signin.IdentityServicesProvider;
 import org.chromium.components.browser_ui.widget.FadingEdgeScrollView;
 import org.chromium.components.browser_ui.widget.animation.FocusAnimator;
 import org.chromium.components.browser_ui.widget.animation.Interpolators;
-import org.chromium.components.signin.ChromeSigninController;
+import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.ui.text.NoUnderlineClickableSpan;
 import org.chromium.ui.text.SpanApplier;
 import org.chromium.ui.text.SpanApplier.SpanInfo;
@@ -1106,11 +1107,16 @@
         String message;
         if (!mShowDataSource) {
             message = mContext.getString(R.string.payments_card_and_address_settings);
-        } else if (ChromeSigninController.get().isSignedIn()) {
-            message = mContext.getString(R.string.payments_card_and_address_settings_signed_in,
-                    ChromeSigninController.get().getSignedInAccountName());
         } else {
-            message = mContext.getString(R.string.payments_card_and_address_settings_signed_out);
+            CoreAccountInfo coreAccountInfo =
+                    IdentityServicesProvider.get().getIdentityManager().getPrimaryAccountInfo();
+            if (coreAccountInfo != null) {
+                message = mContext.getString(R.string.payments_card_and_address_settings_signed_in,
+                        coreAccountInfo.getEmail());
+            } else {
+                message =
+                        mContext.getString(R.string.payments_card_and_address_settings_signed_out);
+            }
         }
 
         NoUnderlineClickableSpan settingsSpan = new NoUnderlineClickableSpan(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/settings/OWNERS
index de5743e..ed50038 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/settings/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/OWNERS
@@ -6,5 +6,6 @@
 # Site settings:
 finnur@chromium.org
 hkamila@chromium.org
+andypaicu@chromium.org
 
 # COMPONENT: UI>Browser>Mobile>Settings
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/ConfirmManagedSyncDataDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/ConfirmManagedSyncDataDialog.java
index e040255..9d371014 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/ConfirmManagedSyncDataDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/ConfirmManagedSyncDataDialog.java
@@ -55,13 +55,13 @@
     }
 
     private void setListener(Listener listener) {
-        assert mListener == null;
+        assert listener != null;
         mListener = listener;
     }
 
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        if (savedInstanceState != null) {
+        if (mListener == null) {
             dismiss();
         }
         String title = getString(R.string.sign_in_managed_account);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java
index 3003c0b..fc0d674d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java
@@ -135,7 +135,7 @@
         if (isSyncEnabled) {
             mProfileSyncService.requestStart();
         } else {
-            if (Profile.getLastUsedProfile().isChild()) {
+            if (Profile.getLastUsedRegularProfile().isChild()) {
                 // For child accounts, Sync needs to stay enabled, so we reenable it in settings.
                 // TODO(bauerb): Remove the dependency on child account code and instead go through
                 // prefs (here and in the Sync customization UI).
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseCreationDialogFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseCreationDialogFragment.java
index 9fb1589..00a3950c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseCreationDialogFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseCreationDialogFragment.java
@@ -78,7 +78,7 @@
                     public void onClick(View view) {
                         HelpAndFeedback.getInstance().show(activity,
                                 activity.getString(R.string.help_context_change_sync_passphrase),
-                                Profile.getLastUsedProfile(), null);
+                                Profile.getLastUsedRegularProfile(), null);
                     }
                 }));
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseDialogFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseDialogFragment.java
index 596bb778..7291d82 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseDialogFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseDialogFragment.java
@@ -182,10 +182,6 @@
                 new SpanInfo("<learnmore>", "</learnmore>", new ClickableSpan() {
                     @Override
                     public void onClick(View view) {
-                        // TODO(https://crbug.com/1048632): It works correctly now, but unsafe
-                        // if sync runs in incognito mode ever. Use the current profile (i.e.,
-                        // regular profile or incognito profile) instead of always using regular
-                        // profile.
                         HelpAndFeedback.getInstance().show(getActivity(), helpContext,
                                 Profile.getLastUsedRegularProfile(), null);
                     }
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 3a48354..c2a5056 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
@@ -1307,6 +1307,7 @@
     @Test
     @SmallTest
     @Feature({"ContextualSearch"})
+    @DisabledTest(message = "crbug.com/1058297")
     public void testTapCausesOneLowPriorityRequest() throws TimeoutException {
         mFakeServer.reset();
         clickWordNode("states");
@@ -3010,6 +3011,7 @@
     @Test
     @SmallTest
     @Feature({"ContextualSearch"})
+    @DisabledTest(message = "crbug.com/1058297")
     public void testAllInternalStatesVisitedResolvingTap()
             throws InterruptedException, TimeoutException {
         // Set up a tracking version of the Internal State Controller.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java
index 5474585..38646df 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java
@@ -46,7 +46,7 @@
     private static class FakeAuthenticationAccountManager extends FakeAccountManagerDelegate {
         private final String mAccountType;
 
-        public FakeAuthenticationAccountManager(String accountType) {
+        FakeAuthenticationAccountManager(String accountType) {
             super(FakeAccountManagerDelegate.DISABLE_PROFILE_DATA_SOURCE);
             mAccountType = accountType;
         }
@@ -70,11 +70,6 @@
                 AccountHolder.builder(mTestAccount).alwaysAccept(true).build());
     }
 
-    // This test previously flaked on the try bot: http://crbug.com/543160.
-    // Re-enabling this test since there has been related cleanup/refactoring
-    // during the time the test was disabled. If the test starts flaking again,
-    // re-open the bug.
-    // TODO(nyquist): Remove this if the test is not flaky anymore.
     @Test
     @SmallTest
     @Feature({"GoogleAccounts"})
@@ -85,20 +80,10 @@
         addTestAccount();
 
         ContextUtils.initApplicationContextForTests(mAccountTestingContext);
-        boolean hasAccounts = FirstRunUtils.hasGoogleAccounts();
-
-        Assert.assertTrue(hasAccounts);
-
-        boolean hasAuthenticator = FirstRunUtils.hasGoogleAccountAuthenticator();
-
-        Assert.assertTrue(hasAuthenticator);
+        Assert.assertTrue(FirstRunUtils.hasGoogleAccounts());
+        Assert.assertTrue(FirstRunUtils.hasGoogleAccountAuthenticator());
     }
 
-    // This test previously flaked on the try bot: http://crbug.com/543160.
-    // Re-enabling this test since there has been related cleanup/refactoring
-    // during the time the test was disabled. If the test starts flaking again,
-    // re-open the bug.
-    // TODO(nyquist): Remove this if the test is not flaky anymore.
     @Test
     @SmallTest
     @Feature({"GoogleAccounts"})
@@ -108,12 +93,7 @@
         setUpAccountManager("Not A Google Account");
 
         ContextUtils.initApplicationContextForTests(mAccountTestingContext);
-        boolean hasAccounts = FirstRunUtils.hasGoogleAccounts();
-
-        Assert.assertFalse(hasAccounts);
-
-        boolean hasAuthenticator = FirstRunUtils.hasGoogleAccountAuthenticator();
-
-        Assert.assertFalse(hasAuthenticator);
+        Assert.assertFalse(FirstRunUtils.hasGoogleAccounts());
+        Assert.assertFalse(FirstRunUtils.hasGoogleAccountAuthenticator());
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/ConfirmManagedSyncDataDialogIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/ConfirmManagedSyncDataDialogIntegrationTest.java
index 68ce23c..300baed 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/ConfirmManagedSyncDataDialogIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/ConfirmManagedSyncDataDialogIntegrationTest.java
@@ -17,7 +17,6 @@
 import org.mockito.Mock;
 
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -42,7 +41,6 @@
 
     @Test
     @LargeTest
-    @DisabledTest(message = "Flaky crbug.com/1054855")
     public void testDialogIsDismissedWhenRecreated() {
         ConfirmManagedSyncDataDialog dialog =
                 ConfirmManagedSyncDataDialog.create(mListenerMock, TEST_DOMAIN);
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 9e914e2..888fd51e 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-82.0.4072.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-82.0.4075.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index f4b77ca..d5240107 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -633,11 +633,14 @@
     Select timezone
   </message>
   <message name="IDS_OOBE_EULA_SECTION_TITLE" desc="Title of Terms of Service screen">
-    Google Chrome OS Terms
+    Google universal Terms of Service (uToS)
   </message>
   <message name="IDS_OOBE_EULA_IFRAME_LABEL" desc="Accessibility label on an iframe with full Chrome OS Terms text">
     Google Chrome OS Terms contents
   </message>
+  <message name="IDS_OOBE_EULA_ADDITIONAL_TERMS" desc="Text of the Link to Google Chrome and Chrome OS additional terms">
+    Google Chrome and Chrome OS Additional Terms
+  </message>
   <message name="IDS_OOBE_EULA_ACCEPT_AND_CONTINUE_BUTTON_TEXT" desc="Label on a button on the Title of Terms of Service OOBE screen to accept EULA and continue.">
     Accept and continue
   </message>
diff --git a/chrome/app/chromeos_strings_grdp/IDS_OOBE_EULA_ADDITIONAL_TERMS.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_OOBE_EULA_ADDITIONAL_TERMS.png.sha1
new file mode 100644
index 0000000..1136b235
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_OOBE_EULA_ADDITIONAL_TERMS.png.sha1
@@ -0,0 +1 @@
+21d717ee171285a99bad56f5c920f11d4ea75df0
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_OOBE_EULA_SECTION_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_OOBE_EULA_SECTION_TITLE.png.sha1
new file mode 100644
index 0000000..1136b235
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_OOBE_EULA_SECTION_TITLE.png.sha1
@@ -0,0 +1 @@
+21d717ee171285a99bad56f5c920f11d4ea75df0
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 19ee0b4..ba4710a3 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1527,9 +1527,6 @@
   <message name="IDS_SETTINGS_SAFETY_CHECK_UPDATES_PRIMARY_LABEL" desc="'Updates' is an element in safety check that shows the update status of Chrome.">
     Updates
   </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_PRIMARY_LABEL" desc="'Passwords' is an element in safety check that allows users to check for their passwords being compromised.">
-    Passwords
-  </message>
   <message name="IDS_SAFETY_CHECK_PASSWORDS_SUB_LABEL_SAFE" desc="This text points out that the safety check password check has not found any compromised passwords.">
     No compromised passwords found
   </message>
@@ -1545,6 +1542,21 @@
   <message name="IDS_SAFETY_CHECK_PASSWORDS_BUTTON_ERROR" desc="This button allows users to try the password check again.">
     Try again
   </message>
+  <message name="IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_SUB_LABEL_ENABLED" desc="This text points out that Safe Browsing is enabled and that the user is protected.">
+    Safe Browsing is up to date and protecting you from harmful sites and downloads
+  </message>
+  <message name="IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_SUB_LABEL_DISABLED" desc="This text points out that Safe Browsing is disabled and that the user is not protected.">
+    Safe Browsing is off. To stay safe on the web, turn it on.
+  </message>
+  <message name="IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_SUB_LABEL_DISABLED_BY_ADMIN" desc="This text points out that Safe Browsing is disabled by an administrator.">
+    Your administrator has turned off Safe Browsing
+  </message>
+  <message name="IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_SUB_LABEL_DISABLED_BY_EXTENSION" desc="This text points out that Safe Browsing is disabled by an extension.">
+    An extension has turned off Safe Browsing
+  </message>
+  <message name="IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_BUTTON" desc="This is the text for the button that allows users to manage their Safe Browsing settings.">
+    Manage
+  </message>
   <message name="IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_PRIMARY_LABEL" desc="'Extensions' is an element in safety check that shows the safety check status of installed extensions.">
     Extensions
   </message>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 3451aa00..0fd1c87 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -5564,6 +5564,7 @@
   if (is_win || is_mac || is_desktop_linux || is_chromeos) {
     deps += [
       "//chrome/browser/resources/discards:discards_resources_gen",
+      "//chrome/browser/resources/gaia_auth_host:modulize",
       "//chrome/browser/resources/management:polymer3_elements",
       "//chrome/browser/resources/signin:polymer3_elements",
       "//chrome/browser/ui/webui/discards:mojo_bindings_js",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 2220cd1..e527043d 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4881,6 +4881,14 @@
      FEATURE_VALUE_TYPE(features::kUserDataSnapshot)},
 #endif
 
+#if defined(OS_WIN)
+    {"run-video-capture-service-in-browser",
+     flag_descriptions::kRunVideoCaptureServiceInBrowserProcessName,
+     flag_descriptions::kRunVideoCaptureServiceInBrowserProcessDescription,
+     kOsWin,
+     FEATURE_VALUE_TYPE(features::kRunVideoCaptureServiceInBrowserProcess)},
+#endif  // defined(OS_WIN)
+
     {"legacy-tls-enforced", flag_descriptions::kLegacyTLSEnforcedName,
      flag_descriptions::kLegacyTLSEnforcedDescription, kOsDesktop | kOsAndroid,
      FEATURE_VALUE_TYPE(net::features::kLegacyTLSEnforced)},
diff --git a/chrome/browser/autofill/mock_autofill_popup_controller.cc b/chrome/browser/autofill/mock_autofill_popup_controller.cc
index 06dc6e4c..fe41477b 100644
--- a/chrome/browser/autofill/mock_autofill_popup_controller.cc
+++ b/chrome/browser/autofill/mock_autofill_popup_controller.cc
@@ -9,7 +9,7 @@
 MockAutofillPopupController::MockAutofillPopupController() {
   gfx::FontList::SetDefaultFontDescription("Arial, Times New Roman, 15px");
   layout_model_ = std::make_unique<autofill::AutofillPopupLayoutModel>(
-      this, false /* is_credit_card_field */);
+      false /* is_credit_card_field */);
 }
 
 MockAutofillPopupController::~MockAutofillPopupController() = default;
diff --git a/chrome/browser/autofill/mock_autofill_popup_controller.h b/chrome/browser/autofill/mock_autofill_popup_controller.h
index e5212fc..7e1ab6a 100644
--- a/chrome/browser/autofill/mock_autofill_popup_controller.h
+++ b/chrome/browser/autofill/mock_autofill_popup_controller.h
@@ -40,17 +40,13 @@
     return *bounds;
   }
   MOCK_CONST_METHOD0(IsRTL, bool());
-  const std::vector<autofill::Suggestion> GetSuggestions() override {
-    return suggestions_;
-  }
-#if !defined(OS_ANDROID)
-  MOCK_METHOD1(GetElidedValueWidthForRow, int(int row));
-  MOCK_METHOD1(GetElidedLabelWidthForRow, int(int row));
-#endif
 
   // AutofillPopupController
   MOCK_METHOD0(OnSuggestionsChanged, void());
   MOCK_METHOD1(AcceptSuggestion, void(int index));
+  const std::vector<autofill::Suggestion> GetSuggestions() override {
+    return suggestions_;
+  }
 
   int GetLineCount() const override { return suggestions_.size(); }
 
diff --git a/chrome/browser/bluetooth/bluetooth_chooser_context.cc b/chrome/browser/bluetooth/bluetooth_chooser_context.cc
index 04a5b08..bc579e6 100644
--- a/chrome/browser/bluetooth/bluetooth_chooser_context.cc
+++ b/chrome/browser/bluetooth/bluetooth_chooser_context.cc
@@ -291,13 +291,6 @@
   return *object.FindStringKey(kDeviceNameKey);
 }
 
-// static
-WebBluetoothDeviceId BluetoothChooserContext::GetObjectDeviceId(
-    const base::Value& object) {
-  std::string device_id_str = *object.FindStringKey(kWebBluetoothDeviceIdKey);
-  return WebBluetoothDeviceId(device_id_str);
-}
-
 bool BluetoothChooserContext::IsValidObject(const base::Value& object) {
   return object.FindStringKey(kDeviceAddressKey) &&
          object.FindStringKey(kDeviceNameKey) &&
diff --git a/chrome/browser/bluetooth/bluetooth_chooser_context.h b/chrome/browser/bluetooth/bluetooth_chooser_context.h
index b29083bf..37581114 100644
--- a/chrome/browser/bluetooth/bluetooth_chooser_context.h
+++ b/chrome/browser/bluetooth/bluetooth_chooser_context.h
@@ -79,8 +79,6 @@
 
   // Returns the human readable string representing the given object.
   static std::string GetObjectName(const base::Value& object);
-  static blink::WebBluetoothDeviceId GetObjectDeviceId(
-      const base::Value& object);
 
  protected:
   // ChooserContextBase implementation;
diff --git a/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc b/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc
index 8ae43ec..4a74e69 100644
--- a/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc
+++ b/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc
@@ -204,7 +204,7 @@
   // Remove |this| as a SessionActivationObserver.
   auto* session_controller = ash::SessionController::Get();
   if (session_controller) {
-    session_controller->AddSessionActivationObserverForAccountId(
+    session_controller->RemoveSessionActivationObserverForAccountId(
         primary_user_->GetAccountId(), this);
   }
 }
diff --git a/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc b/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc
index d8ddf6e..d3c4881 100644
--- a/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc
@@ -290,14 +290,15 @@
                          SyncConsentPolicyDisabledTest,
                          testing::Bool());
 
-// Tests of the consent dialog with the SplitSettingsSync flag enabled.
-class SyncConsentSplitSettingsSyncTest : public SyncConsentTest {
+// Tests of the consent dialog with the SplitSyncConsent flag enabled.
+class SyncConsentSplitSyncConsentTest : public SyncConsentTest {
  public:
-  SyncConsentSplitSettingsSyncTest() {
-    sync_feature_list_.InitAndEnableFeature(
-        chromeos::features::kSplitSettingsSync);
+  SyncConsentSplitSyncConsentTest() {
+    sync_feature_list_.InitWithFeatures({chromeos::features::kSplitSettingsSync,
+                                         chromeos::features::kSplitSyncConsent},
+                                        {});
   }
-  ~SyncConsentSplitSettingsSyncTest() override = default;
+  ~SyncConsentSplitSyncConsentTest() override = default;
 
  private:
   base::test::ScopedFeatureList sync_feature_list_;
@@ -309,7 +310,7 @@
 #else
 #define MAYBE_DefaultFlow DefaultFlow
 #endif
-IN_PROC_BROWSER_TEST_F(SyncConsentSplitSettingsSyncTest, MAYBE_DefaultFlow) {
+IN_PROC_BROWSER_TEST_F(SyncConsentSplitSyncConsentTest, MAYBE_DefaultFlow) {
   LoginToSyncConsentScreen();
 
   // OS sync is disabled by default.
@@ -364,7 +365,7 @@
 #else
 #define MAYBE_UserCanDisable UserCanDisable
 #endif
-IN_PROC_BROWSER_TEST_F(SyncConsentSplitSettingsSyncTest, MAYBE_UserCanDisable) {
+IN_PROC_BROWSER_TEST_F(SyncConsentSplitSyncConsentTest, MAYBE_UserCanDisable) {
   LoginToSyncConsentScreen();
 
   // Wait for content to load.
diff --git a/chrome/browser/chromeos/login/screens/sync_consent_screen.cc b/chrome/browser/chromeos/login/screens/sync_consent_screen.cc
index fc6543d..6171258 100644
--- a/chrome/browser/chromeos/login/screens/sync_consent_screen.cc
+++ b/chrome/browser/chromeos/login/screens/sync_consent_screen.cc
@@ -127,7 +127,7 @@
     const std::vector<int>& consent_description,
     int consent_confirmation,
     bool enable_os_sync) {
-  DCHECK(chromeos::features::IsSplitSettingsSyncEnabled());
+  DCHECK(chromeos::features::IsSplitSyncConsentEnabled());
   // The user only consented to the feature if they left the toggle on.
   RecordConsent(enable_os_sync ? CONSENT_GIVEN : CONSENT_NOT_GIVEN,
                 consent_description, consent_confirmation);
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc
index 1e68513..246ad20c 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -1353,7 +1353,7 @@
               ->FindExtendedAccountInfoForAccountWithRefreshTokenByGaiaId(
                   gaia_id);
       DCHECK(account_info.has_value());
-      if (features::IsSplitSettingsSyncEnabled()) {
+      if (features::IsSplitSyncConsentEnabled()) {
         if (is_new_profile) {
           if (!identity_manager->HasPrimaryAccount(ConsentLevel::kSync)) {
             // Set the account without recording browser sync consent.
@@ -1368,7 +1368,7 @@
             gaia_id);
       } else {
         // Set a primary account here because the profile might have been
-        // created with the feature SplitSettingsSync enabled. Then the
+        // created with the feature SplitSyncConsent enabled. Then the
         // profile might only have an unconsented primary account.
         identity_manager->GetPrimaryAccountMutator()->SetPrimaryAccount(
             account_info->account_id);
@@ -1677,7 +1677,7 @@
   // If needed, create browser observer to display first run OOBE Goodies page.
   first_run::GoodiesDisplayer::Init();
 
-  if (chromeos::features::IsSplitSettingsSyncEnabled() &&
+  if (chromeos::features::IsSplitSyncConsentEnabled() &&
       ProfileSyncServiceFactory::IsSyncAllowed(profile)) {
     turn_sync_on_helper_ = std::make_unique<TurnSyncOnHelper>(profile);
   }
diff --git a/chrome/browser/chromeos/sync/turn_sync_on_helper.cc b/chrome/browser/chromeos/sync/turn_sync_on_helper.cc
index 4988a78..b9aa2c9 100644
--- a/chrome/browser/chromeos/sync/turn_sync_on_helper.cc
+++ b/chrome/browser/chromeos/sync/turn_sync_on_helper.cc
@@ -86,7 +86,7 @@
       delegate_(std::move(delegate)) {
   DCHECK(profile_);
   DCHECK(identity_manager_);
-  DCHECK(chromeos::features::IsSplitSettingsSyncEnabled());
+  DCHECK(chromeos::features::IsSplitSyncConsentEnabled());
   Init();
 }
 
diff --git a/chrome/browser/chromeos/sync/turn_sync_on_helper_unittest.cc b/chrome/browser/chromeos/sync/turn_sync_on_helper_unittest.cc
index 83e77d9..447bb2d 100644
--- a/chrome/browser/chromeos/sync/turn_sync_on_helper_unittest.cc
+++ b/chrome/browser/chromeos/sync/turn_sync_on_helper_unittest.cc
@@ -49,7 +49,9 @@
 class TurnSyncOnHelperTest : public BrowserWithTestWindowTest {
  public:
   TurnSyncOnHelperTest() {
-    feature_list_.InitAndEnableFeature(chromeos::features::kSplitSettingsSync);
+    feature_list_.InitWithFeatures({chromeos::features::kSplitSettingsSync,
+                                    chromeos::features::kSplitSyncConsent},
+                                   {});
   }
   ~TurnSyncOnHelperTest() override = default;
 
diff --git a/chrome/browser/content_settings/tab_specific_content_settings.cc b/chrome/browser/content_settings/tab_specific_content_settings.cc
index df87f900..bad544c 100644
--- a/chrome/browser/content_settings/tab_specific_content_settings.cc
+++ b/chrome/browser/content_settings/tab_specific_content_settings.cc
@@ -753,10 +753,6 @@
   }
 
   ClearNavigationRelatedContentSettings();
-  ClearGeolocationContentSettings();
-  ClearMidiContentSettings();
-  ClearPendingProtocolHandler();
-  ClearContentSettingsChangedViaPageInfo();
 }
 
 void TabSpecificContentSettings::ReadyToCommitNavigation(
@@ -794,10 +790,11 @@
     return;
   }
 
-  // Clear "blocked" flags.
   ClearContentSettingsExceptForNavigationRelatedSettings();
   GeolocationDidNavigate(navigation_handle);
   MidiDidNavigate(navigation_handle);
+  ClearPendingProtocolHandler();
+  ClearContentSettingsChangedViaPageInfo();
 
   if (web_contents()->GetVisibleURL().SchemeIsHTTPOrHTTPS()) {
     content_settings::RecordPluginsAction(
@@ -833,26 +830,20 @@
     observer.OnSiteDataAccessed();
 }
 
-void TabSpecificContentSettings::ClearGeolocationContentSettings() {
-  geolocation_usages_state_.ClearStateMap();
-}
-
-void TabSpecificContentSettings::ClearMidiContentSettings() {
-  midi_usages_state_.ClearStateMap();
-}
-
 void TabSpecificContentSettings::ClearContentSettingsChangedViaPageInfo() {
   content_settings_changed_via_page_info_.clear();
 }
 
 void TabSpecificContentSettings::GeolocationDidNavigate(
     content::NavigationHandle* navigation_handle) {
+  geolocation_usages_state_.ClearStateMap();
   geolocation_usages_state_.DidNavigate(navigation_handle->GetURL(),
                                         navigation_handle->GetPreviousURL());
 }
 
 void TabSpecificContentSettings::MidiDidNavigate(
     content::NavigationHandle* navigation_handle) {
+  midi_usages_state_.ClearStateMap();
   midi_usages_state_.DidNavigate(navigation_handle->GetURL(),
                                  navigation_handle->GetPreviousURL());
 }
diff --git a/chrome/browser/content_settings/tab_specific_content_settings.h b/chrome/browser/content_settings/tab_specific_content_settings.h
index 7f62118..5b20cd89 100644
--- a/chrome/browser/content_settings/tab_specific_content_settings.h
+++ b/chrome/browser/content_settings/tab_specific_content_settings.h
@@ -381,12 +381,6 @@
   // Notifies all registered |SiteDataObserver|s.
   void NotifySiteDataObservers();
 
-  // Clears the Geolocation settings.
-  void ClearGeolocationContentSettings();
-
-  // Clears the MIDI settings.
-  void ClearMidiContentSettings();
-
   // Clears settings changed by the user via PageInfo since the last navigation.
   void ClearContentSettingsChangedViaPageInfo();
 
diff --git a/chrome/browser/enterprise_reporting/browser_report_generator.cc b/chrome/browser/enterprise_reporting/browser_report_generator.cc
index 063221f1..551d25b 100644
--- a/chrome/browser/enterprise_reporting/browser_report_generator.cc
+++ b/chrome/browser/enterprise_reporting/browser_report_generator.cc
@@ -10,11 +10,13 @@
 #include "base/files/file_path.h"
 #include "base/path_service.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/version.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/upgrade_detector/build_state.h"
 #include "chrome/common/channel_info.h"
 #include "components/policy/core/common/cloud/cloud_policy_util.h"
 #include "components/version_info/channel.h"
@@ -60,6 +62,12 @@
 #if !defined(OS_CHROMEOS)
   report->set_browser_version(version_info::GetVersionNumber());
   report->set_channel(policy::ConvertToProtoChannel(chrome::GetChannel()));
+  const auto* const build_state = g_browser_process->GetBuildState();
+  if (build_state->update_type() != BuildState::UpdateType::kNone) {
+    const auto& installed_version = build_state->installed_version();
+    if (installed_version)
+      report->set_installed_browser_version(installed_version->GetString());
+  }
 #endif
 
   report->set_executable_path(GetExecutablePath());
diff --git a/chrome/browser/enterprise_reporting/browser_report_generator_unittest.cc b/chrome/browser/enterprise_reporting/browser_report_generator_unittest.cc
index d459a070..29777f6 100644
--- a/chrome/browser/enterprise_reporting/browser_report_generator_unittest.cc
+++ b/chrome/browser/enterprise_reporting/browser_report_generator_unittest.cc
@@ -10,8 +10,11 @@
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind_test_util.h"
+#include "base/version.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/enterprise_reporting/profile_report_generator.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/upgrade_detector/build_state.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/account_id/account_id.h"
@@ -52,6 +55,12 @@
     content::PluginService::GetInstance()->Init();
   }
 
+  void InitializeUpdate() {
+    auto* build_state = g_browser_process->GetBuildState();
+    build_state->SetUpdate(BuildState::UpdateType::kNormalUpdate,
+                           base::Version("1.2.3.4"), base::nullopt);
+  }
+
   void InitializeProfile() {
     profile_manager_.profile_attributes_storage()->AddProfile(
         profile_manager()->profiles_dir().AppendASCII(kProfileId),
@@ -93,9 +102,18 @@
 #if defined(OS_CHROMEOS)
           EXPECT_FALSE(report->has_browser_version());
           EXPECT_FALSE(report->has_channel());
+          EXPECT_FALSE(report->has_installed_browser_version());
 #else
           EXPECT_NE(std::string(), report->browser_version());
           EXPECT_TRUE(report->has_channel());
+          const auto* build_state = g_browser_process->GetBuildState();
+          if (build_state->update_type() == BuildState::UpdateType::kNone ||
+              !build_state->installed_version()) {
+            EXPECT_FALSE(report->has_installed_browser_version());
+          } else {
+            EXPECT_EQ(report->installed_browser_version(),
+                      build_state->installed_version()->GetString());
+          }
 #endif
 
           EXPECT_NE(std::string(), report->executable_path());
@@ -139,4 +157,14 @@
   GenerateAndVerify();
 }
 
+#if !defined(OS_CHROMEOS)
+TEST_F(BrowserReportGeneratorTest, GenerateBasicReportWithUpdate) {
+  InitializeUpdate();
+  InitializeProfile();
+  InitializeIrregularProfiles();
+  InitializePlugin();
+  GenerateAndVerify();
+}
+#endif
+
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 25df656c..2d88cb1 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3145,6 +3145,11 @@
     "expiry_milestone": 75
   },
   {
+    "name": "run-video-capture-service-in-browser",
+    "owners": [ "armax", "guidou" ],
+    "expiry_milestone": 90
+  },
+  {
     "name": "safety-tips",
     "owners": [ "jdeblasio", "estark", "meacer" ],
     "expiry_milestone": 82
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 954ae61d..b5fe8feb 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3759,6 +3759,15 @@
 
 #endif  // !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
 
+#if defined(OS_WIN)
+
+const char kRunVideoCaptureServiceInBrowserProcessName[] =
+    "Run video capture service in browser";
+const char kRunVideoCaptureServiceInBrowserProcessDescription[] =
+    "Run the video capture service in the browser process.";
+
+#endif  // defined(OS_WIN)
+
 // ============================================================================
 // Don't just add flags to the end, put them in the right section in
 // alphabetical order just like the header file.
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 2d778875..81635cd 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2218,6 +2218,13 @@
 
 #endif  // !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
 
+#if defined(OS_WIN)
+
+extern const char kRunVideoCaptureServiceInBrowserProcessName[];
+extern const char kRunVideoCaptureServiceInBrowserProcessDescription[];
+
+#endif  // defined(OS_WIN)
+
 // ============================================================================
 // Don't just add flags to the end, put them in the right section in
 // alphabetical order. See top instructions for more.
diff --git a/chrome/browser/push_messaging/push_messaging_service_impl.cc b/chrome/browser/push_messaging/push_messaging_service_impl.cc
index ee390b8..2e6049ec 100644
--- a/chrome/browser/push_messaging/push_messaging_service_impl.cc
+++ b/chrome/browser/push_messaging/push_messaging_service_impl.cc
@@ -559,6 +559,15 @@
   content::RenderFrameHost* render_frame_host =
       content::RenderFrameHost::FromID(render_process_id, render_frame_id);
 
+  if (!render_frame_host) {
+    // It is possible for `render_frame_host` to be nullptr here due to a race
+    // (crbug.com/1057981).
+    SubscribeEndWithError(
+        std::move(callback),
+        blink::mojom::PushRegistrationStatus::RENDERER_SHUTDOWN);
+    return;
+  }
+
   if (!options->user_visible_only) {
     content::RenderFrameHost* main_frame =
         GetMainFrameForRenderFrameHost(render_frame_host);
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index 53f15559..24e11e9 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -26,6 +26,7 @@
         "discards:closure_compile",
         "download_internals:closure_compile",
         "downloads:closure_compile",
+        "gaia_auth_host:closure_compile",
         "history:closure_compile",
         "local_ntp:closure_compile",
         "local_state:closure_compile",
@@ -138,6 +139,27 @@
     output_dir = "$root_gen_dir/chrome"
   }
 
+  grit("gaia_auth_host_resources") {
+    # The .grd contains references to generated files.
+    source_is_generated = true
+    grit_flags = [
+      "-E",
+      "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
+    ]
+
+    source = "gaia_auth_host/gaia_auth_host_resources.grd"
+    deps = [ "//chrome/browser/resources/gaia_auth_host:modulize" ]
+
+    defines = chrome_grit_defines
+    outputs = [
+      "grit/gaia_auth_host_resources.h",
+      "grit/gaia_auth_host_resources_map.cc",
+      "grit/gaia_auth_host_resources_map.h",
+      "gaia_auth_host_resources.pak",
+    ]
+    output_dir = "$root_gen_dir/chrome"
+  }
+
   grit("history_resources") {
     # The .grd contains references to generated files.
     source_is_generated = true
diff --git a/chrome/browser/resources/chromeos/login/oobe_dialog_host.css b/chrome/browser/resources/chromeos/login/oobe_dialog_host.css
index baf49be..4358c9d 100644
--- a/chrome/browser/resources/chromeos/login/oobe_dialog_host.css
+++ b/chrome/browser/resources/chromeos/login/oobe_dialog_host.css
@@ -30,7 +30,7 @@
 }
 
 .oobe-tos-webview {
-  border: 0.5px solid rgb(218, 220, 224); /* #DADCE0 */
+  border: 1px solid rgb(218, 220, 224); /* #DADCE0 */
   border-radius: 4px;
   box-shadow: inset 0 0 4px 4px rgba(241, 243, 244, 0.5); /* #F1F3F4 */
 }
diff --git a/chrome/browser/resources/chromeos/login/oobe_eula.css b/chrome/browser/resources/chromeos/login/oobe_eula.css
index d28d03e..ddc0995 100644
--- a/chrome/browser/resources/chromeos/login/oobe_eula.css
+++ b/chrome/browser/resources/chromeos/login/oobe_eula.css
@@ -9,7 +9,8 @@
 }
 
 #crosEulaFrame {
-  min-height: 200px;
+  /* minimal height which allows popup dialogs looks fine. */
+  min-height: 300px;
 }
 
 #installationSettings,
@@ -18,15 +19,18 @@
   min-height: unset;
 }
 
-#installationSettings {
-  margin-inline-start: 16px;
+#footer-more {
+  padding-inline-start: 16px;
+}
+
+#additionalTerms,
+#installationSettings,
+#logging {
   margin-top: 16px;
 }
 
 #logging {
   color: rgba(0, 0, 0, 0.54);
-  margin-inline-start: 16px;
-  margin-top: 16px;
 }
 
 cr-toggle {
diff --git a/chrome/browser/resources/chromeos/login/oobe_eula.html b/chrome/browser/resources/chromeos/login/oobe_eula.html
index f8ba3f6..69af9c8 100644
--- a/chrome/browser/resources/chromeos/login/oobe_eula.html
+++ b/chrome/browser/resources/chromeos/login/oobe_eula.html
@@ -33,22 +33,27 @@
             i18n-values="aria-label:oobeEulaIframeLabel"
             on-contentload="onFrameLoad_">
         </webview>
-        <a id="installationSettings" href="#"
-            on-tap="onInstallationSettingsClicked_">
-          [[i18nDynamic(locale, 'eulaSystemInstallationSettings')]]
-        </a>
-        <div id="logging" class="layout horizontal">
-          <cr-toggle id="usageStats" checked="{{usageStatsChecked}}"
-              on-change="onUsageChanged_" aria-labelledby="usageStatsLabel">
-          </cr-toggle>
-          <div id="usageStatsLabelContainer">
-            <span id="usageStatsLabel" on-tap="usageStatsLabelClicked_">
-              [[i18nDynamic(locale, 'checkboxLogging')]]
-            </span>
-            <a id="learn-more" href="#" on-tap="onUsageStatsHelpLinkClicked_"
-                class="oobe-local-link">
-              [[i18nDynamic(locale, 'learnMore')]]
-            </a>
+        <div id="footer-more" class="layout vertical">
+          <a href="#" id="additionalTerms" on-tap="onAdditionalTermsClicked_">
+            [[i18nDynamic(locale, 'oobeEulaAditionalTerms')]]
+          </a>
+          <a id="installationSettings" href="#"
+              on-tap="onInstallationSettingsClicked_">
+            [[i18nDynamic(locale, 'eulaSystemInstallationSettings')]]
+          </a>
+          <div id="logging" class="layout horizontal">
+            <cr-toggle id="usageStats" checked="{{usageStatsChecked}}"
+                on-change="onUsageChanged_" aria-labelledby="usageStatsLabel">
+            </cr-toggle>
+            <div id="usageStatsLabelContainer">
+              <span id="usageStatsLabel" on-tap="usageStatsLabelClicked_">
+                [[i18nDynamic(locale, 'checkboxLogging')]]
+              </span>
+              <a id="learn-more" href="#" on-tap="onUsageStatsHelpLinkClicked_"
+                  class="oobe-local-link">
+                [[i18nDynamic(locale, 'learnMore')]]
+              </a>
+            </div>
           </div>
         </div>
       </div>
@@ -104,5 +109,10 @@
             on-tap="onInstallationSettingsCloseClicked_"></oobe-text-button>
       </div>
     </oobe-dialog>
+    <oobe-help-dialog id="additional-tos">
+      <webview slot="content" role="document" class="flex oobe-tos-webview"
+          id="additionalChromeToS">
+      </webview>
+    </oobe-help-dialog>
   </template>
 </dom-module>
diff --git a/chrome/browser/resources/chromeos/login/oobe_eula.js b/chrome/browser/resources/chromeos/login/oobe_eula.js
index b9b9e2405..b1eaff4 100644
--- a/chrome/browser/resources/chromeos/login/oobe_eula.js
+++ b/chrome/browser/resources/chromeos/login/oobe_eula.js
@@ -48,7 +48,7 @@
     /**
      * Reference to OOBE screen object.
      * @type {!{
-     *     loadEulaToWebview_: function(Element),
+     *     loadEulaToWebview_: function(Element, string, boolean),
      *     onUsageStatsClicked_: function(boolean),
      * }}
      */
@@ -121,7 +121,15 @@
    */
   updateLocalizedContent(event) {
     // This forces frame to reload.
-    this.screen.loadEulaToWebview_(this.$.crosEulaFrame);
+    const onlineEulaUrl = loadTimeData.getString('eulaOnlineUrl');
+
+    this.screen.loadEulaToWebview_(
+        this.$.crosEulaFrame, onlineEulaUrl, false /* clear_anchors */);
+
+    const additionalToSUrl =
+        loadTimeData.getString('eulaAdditionalToSOnlineUrl');
+    this.screen.loadEulaToWebview_(
+        this.$.additionalChromeToS, additionalToSUrl, true /* clear_anchors */);
     this.i18nUpdateLocale();
   },
 
@@ -143,6 +151,10 @@
     this.screen.onUsageStatsClicked_(this.$.usageStats.checked);
   },
 
+  onAdditionalTermsClicked_() {
+    this.$['additional-tos'].showDialog();
+  },
+
   /**
    * On-tap event handler for installationSettings.
    *
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_eula.js b/chrome/browser/resources/chromeos/login/oobe_screen_eula.js
index 3a49c755..773866c1 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_eula.js
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_eula.js
@@ -12,7 +12,9 @@
         'for(var i = 0; i < A.length; ++i) {' +
         '  const el = A[i];' +
         '  let e = document.createElement("span");' +
-        '  e.textContent=el.textContent;' +
+        '  if (el.textContent.trim().length > 0) {' +
+        '    e.textContent=el.textContent + "(" + el.href + ")";' +
+        '  }' +
         '  el.parentNode.replaceChild(e,el);' +
         '}'
   };
@@ -26,12 +28,12 @@
   // the current requests fail. This prevents webview-loadAbort events from
   // being fired and unnecessary reloads.
   class EulaLoader {
-    constructor(webview, timeout, load_offline_callback) {
+    constructor(webview, timeout, load_offline_callback, clear_anchors) {
       assert(webview.tagName === 'WEBVIEW');
 
       // Do not create multiple loaders.
-      if (EulaLoader.instance_) {
-        return EulaLoader.instance_;
+      if (EulaLoader.instances[webview.id]) {
+        return EulaLoader.instances[webview.id];
       }
 
       this.webview_ = webview;
@@ -41,16 +43,18 @@
       this.loadOfflineCallback_ = load_offline_callback;
       this.loadTimer_ = 0;
 
-      // Add the CLEAR_ANCHORS_CONTENT_SCRIPT that will clear <a><\a> (anchors)
-      // in order to prevent any navigation in the webview itself.
-      webview.addContentScripts([{
-        name: 'clearAnchors',
-        matches: ['<all_urls>'],
-        js: CLEAR_ANCHORS_CONTENT_SCRIPT,
-      }]);
-      webview.addEventListener('contentload', () => {
-        webview.executeScript(CLEAR_ANCHORS_CONTENT_SCRIPT);
-      });
+      if (clear_anchors) {
+        // Add the CLEAR_ANCHORS_CONTENT_SCRIPT that will clear <a><\a>
+        // (anchors) in order to prevent any navigation in the webview itself.
+        webview.addContentScripts([{
+          name: 'clearAnchors',
+          matches: ['<all_urls>'],
+          js: CLEAR_ANCHORS_CONTENT_SCRIPT,
+        }]);
+        webview.addEventListener('contentload', () => {
+          webview.executeScript(CLEAR_ANCHORS_CONTENT_SCRIPT);
+        });
+      }
 
       // Monitor webRequests API events
       this.webview_.request.onCompleted.addListener(
@@ -61,7 +65,7 @@
           {urls: ['<all_urls>'], types: ['main_frame']});
 
       // The only instance of the EulaLoader.
-      EulaLoader.instance_ = this;
+      EulaLoader.instances[webview.id] = this;
     }
 
     // Clears the internal state of the EULA loader. Stops the timeout timer
@@ -165,6 +169,7 @@
       }
     }
   }
+  EulaLoader.instances = {};
 
   return {
     /** @override */
@@ -230,8 +235,11 @@
      * privileged webui bindings.
      *
      * @param {!WebView} webview Webview element to host the terms.
+     * @param {!string} onlineEulaUrl
+     * @param {boolean} clear_anchors if true the script will clear anchors
+     *                                from the loaded page.
      */
-    loadEulaToWebview_(webview) {
+    loadEulaToWebview_(webview, onlineEulaUrl, clear_anchors) {
       assert(webview.tagName === 'WEBVIEW');
 
       /**
@@ -251,16 +259,11 @@
             webview, TERMS_URL, WebViewHelper.ContentType.HTML);
       };
 
-      var onlineEulaUrl = loadTimeData.getString('eulaOnlineUrl');
-      if (!onlineEulaUrl) {
-        loadBundledEula();
-        return;
-      }
-
       // Load online Eula with a timeout to fallback to the offline version.
       // This won't construct multiple EulaLoaders. Single instance.
       var eulaLoader = new EulaLoader(
-          webview, ONLINE_EULA_LOAD_TIMEOUT_IN_MS, loadBundledEula);
+          webview, ONLINE_EULA_LOAD_TIMEOUT_IN_MS, loadBundledEula,
+          clear_anchors);
       eulaLoader.setUrl(onlineEulaUrl);
     },
 
diff --git a/chrome/browser/resources/chromeos/login/sync_consent.html b/chrome/browser/resources/chromeos/login/sync_consent.html
index 3760c05..aa2a164 100644
--- a/chrome/browser/resources/chromeos/login/sync_consent.html
+++ b/chrome/browser/resources/chromeos/login/sync_consent.html
@@ -69,7 +69,7 @@
       </div>
     </oobe-dialog>
 
-    <!-- Dialog used with SplitSettingsSync. -->
+    <!-- Dialog used with the SplitSyncConsent feature. -->
     <oobe-dialog id="osSyncConsentDialog" role="dialog" has-buttons
         aria-label$="[[i18nDynamic(locale, 'osSyncConsentTitle')]]"
         hidden>
diff --git a/chrome/browser/resources/chromeos/login/sync_consent.js b/chrome/browser/resources/chromeos/login/sync_consent.js
index f4364d9f..a2a6c7bb 100644
--- a/chrome/browser/resources/chromeos/login/sync_consent.js
+++ b/chrome/browser/resources/chromeos/login/sync_consent.js
@@ -65,8 +65,8 @@
    * Reacts to changes in loadTimeData.
    */
   updateLocalizedContent() {
-    if (loadTimeData.getBoolean('splitSettingsSync')) {
-      // SplitSettingsSync version.
+    if (loadTimeData.getBoolean('splitSyncConsent')) {
+      // SplitSyncConsent version.
       this.showScreen_('osSyncConsentDialog');
     } else {
       // Regular version.
@@ -96,7 +96,7 @@
    * @private
    */
   onOsSyncAcceptAndContinue_(event) {
-    assert(loadTimeData.getBoolean('splitSettingsSync'));
+    assert(loadTimeData.getBoolean('splitSyncConsent'));
     assert(event.path);
     let enableOsSync = !!this.$.enableOsSyncToggle.checked;
     chrome.send('login.SyncConsentScreen.osSyncAcceptAndContinue', [
diff --git a/chrome/browser/resources/gaia_auth_host/BUILD.gn b/chrome/browser/resources/gaia_auth_host/BUILD.gn
index 05df31e..8f422cc1 100644
--- a/chrome/browser/resources/gaia_auth_host/BUILD.gn
+++ b/chrome/browser/resources/gaia_auth_host/BUILD.gn
@@ -3,6 +3,8 @@
 # found in the LICENSE file.
 
 import("//chrome/test/base/js2gtest.gni")
+import("//third_party/closure_compiler/compile_js.gni")
+import("//ui/webui/resources/tools/js_modulizer.gni")
 
 js2gtest("login_unitjs_tests") {
   # These could be unit tests, except they need a browser context in order
@@ -24,3 +26,95 @@
   testonly = true
   deps = [ ":login_unitjs_tests" ]
 }
+
+js_type_check("closure_compile") {
+  uses_js_modules = true
+  deps = [
+    ":authenticator.m",
+    ":channel.m",
+    ":post_message_channel.m",
+    ":saml_handler.m",
+    ":saml_password_attributes.m",
+    ":saml_timestamps.m",
+    ":webview_event_manager.m",
+  ]
+}
+
+js_library("channel.m") {
+  sources =
+      [ "$root_gen_dir/chrome/browser/resources/gaia_auth_host/channel.m.js" ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_library("webview_event_manager.m") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/gaia_auth_host/webview_event_manager.m.js" ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_library("post_message_channel.m") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/gaia_auth_host/post_message_channel.m.js" ]
+  deps = [ ":channel.m" ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_library("saml_password_attributes.m") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/gaia_auth_host/saml_password_attributes.m.js" ]
+  deps = [ ":saml_timestamps.m" ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_library("saml_timestamps.m") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/gaia_auth_host/saml_timestamps.m.js" ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_library("saml_handler.m") {
+  sources = [
+    "$root_gen_dir/chrome/browser/resources/gaia_auth_host/saml_handler.m.js",
+  ]
+  deps = [
+    ":channel.m",
+    ":post_message_channel.m",
+    ":webview_event_manager.m",
+  ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_library("authenticator.m") {
+  sources = [
+    "$root_gen_dir/chrome/browser/resources/gaia_auth_host/authenticator.m.js",
+  ]
+  deps = [
+    ":saml_handler.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:util.m",
+    "//ui/webui/resources/js/cr:event_target.m",
+  ]
+  externs_list = [
+    "$externs_path/chrome_extensions.js",
+    "$externs_path/webview_tag.js",
+  ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_modulizer("modulize") {
+  input_files = [
+    "authenticator.js",
+    "channel.js",
+    "post_message_channel.js",
+    "saml_handler.js",
+    "saml_password_attributes.js",
+    "saml_timestamps.js",
+    "webview_event_manager.js",
+  ]
+  namespace_rewrites = [
+    "DOMWindow|Object",
+    "cr.EventTarget|EventTarget",
+    "cr.login.SamlHandler|SamlHandler",
+    "samlPasswordAttributes.PasswordAttributes|PasswordAttributes",
+    "samlPasswordAttributes.readPasswordAttributes|readPasswordAttributes",
+    "samlTimestamps.decodeTimestamp|decodeTimestamp",
+    "XMLDocument|Object",
+  ]
+}
diff --git a/chrome/browser/resources/gaia_auth_host/authenticator.js b/chrome/browser/resources/gaia_auth_host/authenticator.js
index 627c6bbc..31ae361 100644
--- a/chrome/browser/resources/gaia_auth_host/authenticator.js
+++ b/chrome/browser/resources/gaia_auth_host/authenticator.js
@@ -5,6 +5,16 @@
 // <include src="saml_handler.js">
 // Note: webview_event_manager.js is already included by saml_handler.js.
 
+// clang-format off
+// #import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js'
+// #import {assert} from 'chrome://resources/js/assert.m.js';
+// #import {$, appendParam} from 'chrome://resources/js/util.m.js';
+// #import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
+
+// #import {SamlHandler, OnHeadersReceivedDetails} from './saml_handler.m.js';
+// #import {WebviewEventManager} from './webview_event_manager.m.js';
+// clang-format on
+
 /**
  * @fileoverview An UI component to authenticate to Chrome. The component hosts
  * IdP web pages in a webview. A client who is interested in monitoring
@@ -15,7 +25,33 @@
  */
 
 cr.define('cr.login', function() {
-  'use strict';
+  /* #ignore */ 'use strict';
+
+  /**
+   * Parameters for the authorization flow.
+   * @typedef {{
+   *   hl: string,
+   *   gaiaUrl: string,
+   *   authMode: Number,
+   *   isLoginPrimaryAccount: boolean,
+   *   email: string,
+   *   constrained: string,
+   *   readOnlyEmail: boolean,
+   *   service: string,
+   *   dontResizeNonEmbeddedPages: boolean,
+   *   clientId: string,
+   *   gaiaPath: string,
+   *   emailDomain: string,
+   *   showTos: string,
+   *   extractSamlPasswordAttributes: boolean,
+   *   flow: string,
+   *   ignoreCrOSIdpSetting: boolean,
+   *   enableGaiaActionButtons: boolean,
+   *   enterpriseEnrollmentDomain: string,
+   *   samlAclUrl: string
+   * }}
+   */
+  /* #export */ let AuthParams;
 
   // TODO(rogerta): should use gaia URL from GaiaUrls::gaia_url() instead
   // of hardcoding the prod URL here.  As is, this does not work with staging
@@ -32,20 +68,20 @@
   /**
    * The source URL parameter for the constrained signin flow.
    */
-  const CONSTRAINED_FLOW_SOURCE = 'chrome';
+  /* #export */ const CONSTRAINED_FLOW_SOURCE = 'chrome';
 
   /**
    * Enum for the authorization mode, must match AuthMode defined in
    * chrome/browser/ui/webui/inline_login_ui.cc.
    * @enum {number}
    */
-  const AuthMode = {DEFAULT: 0, OFFLINE: 1, DESKTOP: 2};
+  /* #export */ const AuthMode = {DEFAULT: 0, OFFLINE: 1, DESKTOP: 2};
 
   /**
    * Enum for the authorization type.
    * @enum {number}
    */
-  const AuthFlow = {DEFAULT: 0, SAML: 1};
+  /* #export */ const AuthFlow = {DEFAULT: 0, SAML: 1};
 
   /**
    * Supported Authenticator params.
@@ -215,7 +251,7 @@
   /**
    * Initializes the authenticator component.
    */
-  class Authenticator extends cr.EventTarget {
+  /* #export */ class Authenticator extends cr.EventTarget {
     /**
      * @param {!WebView|string} webview The webview element or its ID to host
      *     IdP web pages.
@@ -230,6 +266,10 @@
       this.chooseWhatToSync_ = false;
       this.skipForNow_ = false;
       this.authFlow = AuthFlow.DEFAULT;
+      /** @type {AuthMode} */
+      this.authMode = AuthMode.DEFAULT;
+      this.dontResizeNonEmbeddedPages = false;
+
       this.authDomain = '';
       /**
        * @type {!cr.login.SamlHandler|undefined}
@@ -243,7 +283,12 @@
       this.trusted_ = true;
       this.readyFired_ = false;
       this.authCompletedFired_ = false;
-      this.webview_ = typeof webview == 'string' ? $(webview) : webview;
+      /**
+       * @private {WebView|undefined}
+       */
+      this.webview_ = typeof webview == 'string' ?
+          /** @type {WebView} */ ($(webview)) :
+          webview;
       assert(this.webview_);
       this.enableGaiaActionButtons_ = false;
       this.webviewEventManager_ = WebviewEventManager.create();
@@ -260,7 +305,7 @@
        * Callback allowing to request whether the specified user which
        * authenticates via SAML is a user without a password (neither a manually
        * entered one nor one provided via Credentials Passing API).
-       * @type {function(string, string, function(boolean))} Arguments are the
+       * @type {?function(string, string, function(boolean))} Arguments are the
        * e-mail, the GAIA ID, and the response callback.
        */
       this.getIsSamlUserPasswordlessCallback = null;
@@ -273,6 +318,8 @@
        * @private
        */
       this.isSamlUserPasswordless_ = null;
+      /** @private {boolean} */
+      this.isConstrainedWindow_ = false;
       this.samlAclUrl_ = null;
 
       window.addEventListener(
@@ -398,7 +445,8 @@
 
     /**
      * Re-binds to another webview.
-     * @param {Object} webview the new webview to be used by this Authenticator.
+     * @param {WebView} webview the new webview to be used by this
+     *     Authenticator.
      * @private
      */
     rebindWebview_(webview) {
@@ -455,14 +503,14 @@
 
         webivewParent.replaceChild(newWebview, this.webview_);
 
-        this.rebindWebview_(newWebview);
+        this.rebindWebview_(/** @type {WebView} */ (newWebview));
       }
     }
 
     /**
      * Loads the authenticator component with the given parameters.
      * @param {AuthMode} authMode Authorization mode.
-     * @param {Object} data Parameters for the authorization flow.
+     * @param {AuthParams} data Parameters for the authorization flow.
      */
     load(authMode, data) {
       this.authMode = authMode;
@@ -576,7 +624,7 @@
       }
 
       if (data.isFirstUser) {
-        url = appendParam(url, 'is_first_user', true);
+        url = appendParam(url, 'is_first_user', 'true');
 
         if (data.lsbReleaseBoard) {
           url = appendParam(url, 'chromeos_board', data.lsbReleaseBoard);
@@ -615,7 +663,7 @@
         url = appendParam(url, 'ignoreCrOSIdpSetting', 'true');
       }
       if (data.enableGaiaActionButtons) {
-        url = appendParam(url, 'use_native_navigation', 1);
+        url = appendParam(url, 'use_native_navigation', '1');
       }
       return url;
     }
@@ -684,10 +732,9 @@
 
     /**
      * Invoked when the sign-in page takes focus.
-     * @param {object} e The focus event being triggered.
      * @private
      */
-    onFocus_(e) {
+    onFocus_() {
       if (this.authMode == AuthMode.DESKTOP &&
           document.activeElement == document.body) {
         this.webview_.focus();
@@ -696,7 +743,7 @@
 
     /**
      * Invoked when the history state is changed.
-     * @param {object} e The popstate event being triggered.
+     * @param {!Event} e The popstate event being triggered.
      * @private
      */
     onPopState_(e) {
@@ -710,7 +757,7 @@
      * Invoked when headers are received in the main frame of the webview. It
      * 1) reads the authenticated user info from a signin header,
      * 2) signals the start of a saml flow upon receiving a saml header.
-     * @return {!Object} Modified request headers.
+     * @param {OnHeadersReceivedDetails} details
      * @private
      */
     onHeadersReceived_(details) {
@@ -745,6 +792,7 @@
         } else if (headerName == LOCATION_HEADER) {
           // If the "choose what to sync" checkbox was clicked, then the
           // continue URL will contain a source=3 field.
+          assert(header.value);
           const location = decodeURIComponent(header.value);
           this.chooseWhatToSync_ = !!location.match(/(\?|&)source=3($|&)/);
         }
@@ -753,7 +801,7 @@
 
     /**
      * Returns true if given HTML5 message is received from the webview element.
-     * @param {object} e Payload of the received HTML5 message.
+     * @param {Object} e Payload of the received HTML5 message.
      */
     isGaiaMessage(e) {
       if (!this.isWebviewEvent_(e)) {
@@ -775,7 +823,7 @@
 
     /**
      * Invoked when an HTML5 message is received from the webview element.
-     * @param {object} e Payload of the received HTML5 message.
+     * @param {Object} e Payload of the received HTML5 message.
      * @private
      */
     onMessageFromWebview_(e) {
@@ -796,7 +844,7 @@
 
     /**
      * Invoked to send a HTML5 message to the webview element.
-     * @param {object} e Payload of the HTML5 message.
+     * @param {Object} payload Payload of the HTML5 message.
      */
     sendMessageToWebview(payload) {
       const currentUrl = this.webview_.src;
@@ -1196,6 +1244,7 @@
     }
   }
 
+  // #cr_define_end
   /**
    * The current auth flow of the hosted auth page.
    * @type {AuthFlow}
diff --git a/chrome/browser/resources/gaia_auth_host/channel.js b/chrome/browser/resources/gaia_auth_host/channel.js
index 5cffb5e..50fc5377 100644
--- a/chrome/browser/resources/gaia_auth_host/channel.js
+++ b/chrome/browser/resources/gaia_auth_host/channel.js
@@ -4,8 +4,9 @@
 
 /**
  * Channel to the background script.
+ * @constructor
  */
-function Channel() {
+/* #export */ function Channel() {
   this.messageCallbacks_ = {};
   this.internalRequestCallbacks_ = {};
 }
diff --git a/chrome/browser/resources/gaia_auth_host/gaia_auth_host_resources.grd b/chrome/browser/resources/gaia_auth_host/gaia_auth_host_resources.grd
new file mode 100644
index 0000000..52a9c5a
--- /dev/null
+++ b/chrome/browser/resources/gaia_auth_host/gaia_auth_host_resources.grd
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+  <outputs>
+    <output filename="grit/gaia_auth_host_resources.h" type="rc_header">
+      <emit emit_type='prepend'></emit>
+    </output>
+    <output filename="grit/gaia_auth_host_resources_map.cc"
+            type="resource_file_map_source" />
+    <output filename="grit/gaia_auth_host_resources_map.h"
+            type="resource_map_header" />
+    <output filename="gaia_auth_host_resources.pak" type="data_package" />
+  </outputs>
+  <release seq="1">
+    <includes>
+      <include name="IDR_GAIA_AUTH_AUTHENTICATOR_M_JS"
+               file="${root_gen_dir}/chrome/browser/resources/gaia_auth_host/authenticator.m.js"
+               use_base_dir="false"
+               type ="BINDATA"
+               compress="gzip" />
+      <include name="IDR_GAIA_AUTH_SAML_HANDLER_M_JS"
+               file="${root_gen_dir}/chrome/browser/resources/gaia_auth_host/saml_handler.m.js"
+               use_base_dir="false"
+               type ="BINDATA"
+               compress="gzip" />
+      <include name="IDR_GAIA_AUTH_CHANNEL_M_JS"
+               file="${root_gen_dir}/chrome/browser/resources/gaia_auth_host/channel.m.js"
+               use_base_dir="false"
+               type ="BINDATA"
+               compress="gzip" />
+      <include name="IDR_GAIA_AUTH_POST_MESSAGE_CHANNEL_M_JS"
+               file="${root_gen_dir}/chrome/browser/resources/gaia_auth_host/post_message_channel.m.js"
+               use_base_dir="false"
+               type ="BINDATA"
+               compress="gzip" />
+      <include name="IDR_GAIA_AUTH_WEBVIEW_EVENT_MANAGER_M_JS"
+               file="${root_gen_dir}/chrome/browser/resources/gaia_auth_host/webview_event_manager.m.js"
+               use_base_dir="false"
+               type ="BINDATA"
+               compress="gzip" />
+      <include name="IDR_GAIA_AUTH_SAML_PASSWORD_ATTRIBUTES_M_JS"
+               file="${root_gen_dir}/chrome/browser/resources/gaia_auth_host/saml_password_attributes.m.js"
+               use_base_dir="false"
+               type ="BINDATA"
+               compress="gzip" />
+      <include name="IDR_GAIA_AUTH_SAML_TIMESTAMPS_M_JS"
+               file="${root_gen_dir}/chrome/browser/resources/gaia_auth_host/saml_timestamps.m.js"
+               use_base_dir="false"
+               type ="BINDATA"
+               compress="gzip" />
+    </includes>
+  </release>
+</grit>
diff --git a/chrome/browser/resources/gaia_auth_host/post_message_channel.js b/chrome/browser/resources/gaia_auth_host/post_message_channel.js
index 49deaa2..0fab561d 100644
--- a/chrome/browser/resources/gaia_auth_host/post_message_channel.js
+++ b/chrome/browser/resources/gaia_auth_host/post_message_channel.js
@@ -11,7 +11,11 @@
 
 // <include src="channel.js">
 
-const PostMessageChannel = (function() {
+// clang-format off
+// #import {Channel} from './channel.m.js';
+// clang-format on
+
+/* #export */ const PostMessageChannel = (function() {
   /**
    * Allowed origins of the hosting page.
    * @type {Array<string>}
@@ -37,6 +41,7 @@
 
   /**
    * A simple event target.
+   * @constructor
    */
   function EventTarget() {
     this.listeners_ = [];
@@ -136,7 +141,8 @@
     createPort(channelId, channelName, opt_targetWindow, opt_targetOrigin) {
       const port = new PostMessagePort(channelId, channelName);
       if (opt_targetWindow) {
-        port.setTarget(opt_targetWindow, opt_targetOrigin);
+        port.setTarget(
+            opt_targetWindow, /** @type {string} */ (opt_targetOrigin));
       }
       this.channels_[channelId] = port;
       return port;
@@ -169,13 +175,13 @@
     /**
      * Creates a connecting port to the daemon and request connection.
      * @param {string} name
-     * @return {PostMessagePort}
+     * @return {?PostMessagePort}
      */
     connectToDaemon(name) {
       if (this.isDaemon) {
         console.error(
             'Error: Connecting from the daemon page is not supported.');
-        return;
+        return null;
       }
 
       const port = this.createPort(this.createChannelId_(), name);
@@ -270,6 +276,7 @@
   /**
    * A HTML5 postMessage based port that provides the same port interface
    * as the messaging API port.
+   * @constructor
    * @param {number} channelId
    * @param {string} name
    */
@@ -362,7 +369,6 @@
   return PostMessageChannel;
 })();
 
-/** @override */
 Channel.create = function() {
   return new PostMessageChannel();
 };
diff --git a/chrome/browser/resources/gaia_auth_host/saml_handler.js b/chrome/browser/resources/gaia_auth_host/saml_handler.js
index 5d30eac..ee280a5 100644
--- a/chrome/browser/resources/gaia_auth_host/saml_handler.js
+++ b/chrome/browser/resources/gaia_auth_host/saml_handler.js
@@ -6,12 +6,20 @@
 // <include src="webview_event_manager.js">
 // <include src="saml_password_attributes.js">
 
+// clang-format off
+// #import {Channel} from './channel.m.js';
+// #import {PostMessageChannel} from './post_message_channel.m.js';
+// #import {WebviewEventManager} from './webview_event_manager.m.js';
+// #import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js'
+// #import {PasswordAttributes, readPasswordAttributes} from './saml_password_attributes.m.js';
+// clang-format on
+
 /**
  * @fileoverview Saml support for webview based auth.
  */
 
 cr.define('cr.login', function() {
-  'use strict';
+  /* #ignore */ 'use strict';
 
   /**
    * The lowest version of the credentials passing API supported.
@@ -54,6 +62,47 @@
   `;
 
   /**
+   * @typedef {{
+   *   method: string,
+   *   requestedVersion: number,
+   *   keyType: string,
+   *   token: string,
+   *   passwordBytes: string
+   * }}
+   */
+  let ApiCallMessageCall;
+
+  /**
+   * @typedef {{
+   *   name: string,
+   *   call: ApiCallMessageCall
+   * }}
+   */
+  let ApiCallMessage;
+
+  /**
+   * Details about the request.
+   * @typedef {{
+   *   method: string,
+   *   requestBody: Object,
+   *   url: string
+   * }}
+   * @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/onBeforeRequest#details
+   */
+  /* #export */ let OnBeforeRequestDetails;
+
+  /**
+   * Details of the request.
+   * @typedef {{
+   *   responseHeaders: Array<HttpHeader>,
+   *   statusCode: number,
+   *   url: string,
+   * }}
+   * @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/onHeadersReceived#details
+   */
+  /* #export */ let OnHeadersReceivedDetails;
+
+  /**
    * Creates a new URL by striping all query parameters.
    * @param {string} url The original URL.
    * @return {string} The new URL with all query parameters stripped.
@@ -77,9 +126,9 @@
    * A handler to provide saml support for the given webview that hosts the
    * auth IdP pages.
    */
-  class SamlHandler extends cr.EventTarget {
+  /* #export */ class SamlHandler extends cr.EventTarget {
     /**
-     * @param {webview} webview
+     * @param {!WebView} webview
      * @param {boolean} startsOnSamlPage - whether initial URL is already SAML
      *                  page
      * */
@@ -108,25 +157,26 @@
 
       /**
        * The webview that serves IdP pages.
-       * @type {webview}
+       * @private {!WebView}
        */
       this.webview_ = webview;
 
       /**
        * Whether a Saml page is in the webview from the start.
+       * @private {boolean}
        */
       this.startsOnSamlPage_ = startsOnSamlPage;
 
       /**
        * Whether a Saml IdP page is display in the webview.
-       * @type {boolean}
+       * @private {boolean}
        */
       this.isSamlPage_ = this.startsOnSamlPage_;
 
       /**
        * Pending Saml IdP page flag that is set when a SAML_HEADER is received
        * and is copied to |isSamlPage_| in loadcommit.
-       * @type {boolean}
+       * @private {boolean}
        */
       this.pendingIsSamlPage_ = this.startsOnSamlPage_;
 
@@ -134,7 +184,7 @@
        * The last aborted top level url. It is recorded in loadabort event and
        * used to skip injection into Chrome's error page in the following
        * loadcommit event.
-       * @type {string}
+       * @private {?string}
        */
       this.abortedTopLevelUrl_ = null;
 
@@ -146,38 +196,38 @@
 
       /**
        * Scraped password stored in an id to password field value map.
-       * @type {Object<string, string>}
-       * @private
+       * @private {!Object<string, string>}
        */
       this.passwordStore_ = {};
 
       /**
        * Whether Saml API is initialized.
-       * @type {boolean}
+       * @private {boolean}
        */
       this.apiInitialized_ = false;
 
       /**
        * Saml API version to use.
-       * @type {number}
+       * @private {number}
        */
       this.apiVersion_ = 0;
 
       /**
        * Saml API tokens received.
-       * @type {Object}
+       * @private {!Object}
        */
       this.apiTokenStore_ = {};
 
       /**
        * Saml API confirmation token. Set by last 'confirm' call.
+       * @private {?string}
        */
       this.confirmToken_ = null;
 
       /**
        * Saml API password bytes set by last 'add' call. Needed to not break
        * existing behavior.
-       * @type {string}
+       * @private {?string}
        */
       this.lastApiPasswordBytes_ = null;
 
@@ -197,29 +247,26 @@
 
       /**
        * Current stage of device attestation flow.
-       * @type {DeviceAttestationStage}
-       * @private
+       * @private {!SamlHandler.DeviceAttestationStage}
        */
       this.deviceAttestationStage_ = SamlHandler.DeviceAttestationStage.NONE;
 
       /**
        * Challenge from IdP to perform device attestation.
-       * @type {?string}
-       * @private
+       * @private {?string}
        */
       this.verifiedAccessChallenge_ = null;
 
       /**
        * Response for a device attestation challenge.
-       * @type {?string}
-       * @private
+       * @private {?string}
        */
       this.verifiedAccessChallengeResponse_ = null;
 
       /**
        * The password-attributes that were extracted from the SAMLResponse, if
        * any. (Doesn't contain the password itself).
-       * @type {PasswordAttributes}
+       * @private {!PasswordAttributes}
        */
       this.passwordAttributes_ =
           samlPasswordAttributes.PasswordAttributes.EMPTY;
@@ -289,7 +336,7 @@
 
     /**
      * Returns the Saml API password bytes.
-     * @return {string}
+     * @return {?string}
      */
     get apiPasswordBytes() {
       if (this.confirmToken_ != null &&
@@ -350,7 +397,7 @@
 
     /**
      * Gets the password attributes extracted from SAML Response.
-     * @return {PasswordAttributes}
+     * @return {Object}
      */
     get passwordAttributes() {
       return this.passwordAttributes_;
@@ -483,18 +530,24 @@
      * Handler for webRequest.onBeforeRequest that looks for the Base64
      * encoded SAMLResponse in the POST-ed formdata sent from the SAML page.
      * Non-blocking.
-     * @param {Object} details The web-request details.
+     * @param {OnBeforeRequestDetails} details The web-request details.
      */
     onMainFrameWebRequest(details) {
-      if (!this.extractSamlPasswordAttributes) return;
-      if (!this.isSamlPage_ || details.method != 'POST') return;
+      if (!this.extractSamlPasswordAttributes) {
+        return;
+      }
+      if (!this.isSamlPage_ || details.method != 'POST') {
+        return;
+      }
 
       const formData = details.requestBody.formData;
       let samlResponse = (formData && formData.SAMLResponse);
       if (!samlResponse) {
         samlResponse = new URL(details.url).searchParams.get('SAMLResponse');
       }
-      if (!samlResponse) return;
+      if (!samlResponse) {
+        return;
+      }
 
       try {
         // atob means asciiToBinary, which actually means base64Decode:
@@ -705,7 +758,7 @@
     /**
      * Handlers for channel messages.
      * @param {Channel} channel A channel to send back response.
-     * @param {Object} msg Received message.
+     * @param {ApiCallMessage} msg Received message.
      * @private
      */
     onAPICall_(channel, msg) {
@@ -736,7 +789,7 @@
 
         this.dispatchEvent(new CustomEvent('apiPasswordAdded'));
       } else if (call.method == 'confirm') {
-        if (!call.token in this.apiTokenStore_) {
+        if (!(call.token in this.apiTokenStore_)) {
           console.error('SamlHandler.onAPICall_: token mismatch');
         } else {
           this.confirmToken_ = call.token;
@@ -777,5 +830,6 @@
     }
   }
 
+  // #cr_define_end
   return {SamlHandler: SamlHandler};
 });
diff --git a/chrome/browser/resources/gaia_auth_host/saml_password_attributes.js b/chrome/browser/resources/gaia_auth_host/saml_password_attributes.js
index f697475..4fecfd13 100644
--- a/chrome/browser/resources/gaia_auth_host/saml_password_attributes.js
+++ b/chrome/browser/resources/gaia_auth_host/saml_password_attributes.js
@@ -4,6 +4,10 @@
 
 // <include src="saml_timestamps.js">
 
+// clang-format off;
+// #import {decodeTimestamp} from './saml_timestamps.m.js';
+// clang-format on
+
 /**
  * @fileoverview A utility for extracting password information from SAML
  * authorization response. This requires that the SAML IDP administrator
@@ -11,7 +15,7 @@
  */
 
 cr.define('samlPasswordAttributes', function() {
-  'use strict';
+  /* #ignore */ 'use strict';
 
   /** @const @private {number} The shortest XML string that could be useful. */
   const MIN_SANE_XML_LENGTH = 100;
@@ -63,7 +67,7 @@
    * could be extracted, formatted as strings. Some or all of the strings can
    * be empty if some or all of the attributes could not be extracted.
    */
-  function readPasswordAttributes(xmlStr) {
+  /* #export */ function readPasswordAttributes(xmlStr) {
     // Don't throw any exception that could cause login to fail - extracting
     // these attributes can fail, but login should not be interrupted.
     try {
@@ -135,7 +139,7 @@
    * saml_password_attributes.cc must also be changed.
    * @export @final
    */
-  class PasswordAttributes {
+  /* #export */ class PasswordAttributes {
     constructor(modifiedTime, expirationTime, passwordChangeUrl) {
       /** @type {string} Password last-modified timestamp. */
       this.modifiedTime = modifiedTime;
@@ -153,7 +157,7 @@
   /** An immutable and empty PasswordAttributes struct. */
   PasswordAttributes.EMPTY = new PasswordAttributes('', '', '');
 
-  // Public functions:
+  // #cr_define_end
   return {
     readPasswordAttributes: readPasswordAttributes,
     PasswordAttributes: PasswordAttributes,
diff --git a/chrome/browser/resources/gaia_auth_host/saml_timestamps.js b/chrome/browser/resources/gaia_auth_host/saml_timestamps.js
index e9e5d4a..a15a7101 100644
--- a/chrome/browser/resources/gaia_auth_host/saml_timestamps.js
+++ b/chrome/browser/resources/gaia_auth_host/saml_timestamps.js
@@ -44,14 +44,14 @@
    * @param {string} str A timestamp formatted as a string.
    * @return {?Date} A valid decoded timestamp, or null.
    */
-  function decodeTimestamp(str) {
+  /* #export */ function decodeTimestamp(str) {
     str = str.trim();
     if (str.length == 0 || str.length > MAX_SANE_LENGTH) {
       return null;
     }
 
     if (INTEGER_PATTERN.test(str)) {
-      return decodeIntegerTimestamp(parseInt(str));
+      return decodeIntegerTimestamp(parseInt(str, 10));
     } else if (ISO_8601_PATTERN.test(str)) {
       return decodeIso8601(str);
     }
@@ -134,6 +134,7 @@
     return isNaN(date) ? null : date;
   }
 
+  // #cr_define_end
   // Public functions:
   return {decodeTimestamp: decodeTimestamp};
 });
diff --git a/chrome/browser/resources/gaia_auth_host/webview_event_manager.js b/chrome/browser/resources/gaia_auth_host/webview_event_manager.js
index 6e1cb26b..0aa53b3 100644
--- a/chrome/browser/resources/gaia_auth_host/webview_event_manager.js
+++ b/chrome/browser/resources/gaia_auth_host/webview_event_manager.js
@@ -11,8 +11,9 @@
 
 /**
  * Creates a new WebviewEventManager.
+ * @constructor
  */
-function WebviewEventManager() {
+/* #export */ function WebviewEventManager() {
   this.unbindWebviewCleanupFunctions_ = [];
 }
 
@@ -20,10 +21,9 @@
   /**
    * Adds a EventListener to |eventTarget| and adds a clean-up function so we
    * can remove the listener in unbindFromWebview.
-   * @param {Object} webview the object to add the listener to
+   * @param {Object} eventTarget the object to add the listener to
    * @param {string} type the event type
    * @param {Function} listener the event listener
-   * @private
    */
   addEventListener(eventTarget, type, listener) {
     eventTarget.addEventListener(type, listener);
@@ -34,10 +34,12 @@
   /**
    * Adds a listener to |webRequestEvent| and adds a clean-up function so we can
    * remove the listener in unbindFromWebview.
-   * @param {Object} webRequestEvent the object to add the listener to
-   * @param {string} type the event type
+   * @param {Object} webRequestEvent the object to add the listener to.
    * @param {Function} listener the event listener
-   * @private
+   * @param {RequestFilter} filter the object describing filters to apply to
+   *     webRequest events.
+   * @param {?Object} extraInfoSpec the object to pass additional event-specific
+   *     instructions.
    */
   addWebRequestEventListener(webRequestEvent, listener, filter, extraInfoSpec) {
     webRequestEvent.addListener(listener, filter, extraInfoSpec);
@@ -47,7 +49,6 @@
 
   /**
    * Unbinds this Authenticator from the currently bound webview.
-   * @private
    */
   removeAllListeners() {
     for (let i = 0; i < this.unbindWebviewCleanupFunctions_.length; i++) {
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.html b/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.html
index a2ac5fd..e7b6332 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.html
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.html
@@ -137,9 +137,9 @@
       </cr-button>
     </div>
 
-    <div id="outer" class="layout vertical nowrap">
+    <div id="outer" class="layout vertical nowrap" role="list">
       <template is="dom-repeat" id="account-list" items="[[accounts_]]">
-        <div class="settings-box account-list-item">
+        <div class="settings-box account-list-item" role="listitem">
 
           <div class="profile-icon"
               style="background-image: [[getIconImageSet_(item.pic)]]">
@@ -152,7 +152,8 @@
             <div class="flex text-elide">
               <!-- If account is signed in, display the full name -->
               <template is="dom-if" if="[[item.isSignedIn]]">
-                <span>[[item.fullName]]</span>
+                <span id="fullName-[[index]]"
+                    aria-hidden="true">[[item.fullName]]</span>
               </template>
               <!-- Else, display a re-authentication message -->
               <template is="dom-if" if="[[!item.isSignedIn]]">
@@ -161,26 +162,31 @@
                 </span>
               </template>
 
-              <div class="secondary">[[item.email]]</div>
+              <div class="secondary" id="email-[[index]]"
+                  aria-hidden="true">[[item.email]]</div>
             </div>
           </div>
 
           <template is="dom-if"
               if="[[shouldShowReauthenticationButton_(item)]]">
             <cr-button title="[[getAccountManagerSignedOutTitle_(item)]]"
-                class="reauth-button" on-click="onReauthenticationTap_">
+                class="reauth-button" on-click="onReauthenticationTap_"
+                aria-labelledby$="fullName-[[index]] email-[[index]]">
               [[getAccountManagerSignedOutLabel_(item.unmigrated)]]
             </cr-button>
           </template>
 
           <!-- If this is the Device Account, display the management status -->
           <template is="dom-if" if="[[item.isDeviceAccount]]">
-            <cr-tooltip-icon icon-class="cr:info-outline"
+            <cr-tooltip-icon id="primaryAccountTooltip" aria-hidden="true"
+                icon-class="cr:info-outline"
                 class="tooltip-primary-account"
                 tooltip-text="$i18n{accountManagerPrimaryAccountTooltip}"
                 icon-aria-label="$i18n{accountManagerPrimaryAccountTooltip}">
             </cr-tooltip-icon>
-            <span class="management-status">
+            <span class="management-status"
+                aria-labelledby$="fullName-[[index]] email-[[index]]"
+                aria-describedby="primaryAccountTooltip">
               [[getManagementLabel_(item)]]
             </span>
           </template>
@@ -188,6 +194,8 @@
           <template is="dom-if" if="[[!item.isDeviceAccount]]">
             <cr-icon-button class="icon-more-vert"
                 title="[[getMoreActionsTitle_(item)]]"
+                aria-label="[[getMoreActionsTitle_(item)]]"
+                aria-describedby$="fullName-[[index]]"
                 on-click="onAccountActionsMenuButtonTap_">
             </cr-icon-button>
           </template>
diff --git a/chrome/browser/resources/settings/people_page/people_page.js b/chrome/browser/resources/settings/people_page/people_page.js
index ce1c77d..1f8410c1 100644
--- a/chrome/browser/resources/settings/people_page/people_page.js
+++ b/chrome/browser/resources/settings/people_page/people_page.js
@@ -360,7 +360,7 @@
       return false;
     }
     // <if expr="chromeos">
-    if (!loadTimeData.getBoolean('splitSettingsSyncEnabled')) {
+    if (!loadTimeData.getBoolean('splitSyncConsent')) {
       return false;
     }
     // </if>
diff --git a/chrome/browser/resources/settings/people_page/sync_page.js b/chrome/browser/resources/settings/people_page/sync_page.js
index b088cf7..30fdf3e 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.js
+++ b/chrome/browser/resources/settings/people_page/sync_page.js
@@ -709,7 +709,7 @@
    */
   shouldShowSyncAccountControl_() {
     // <if expr="chromeos">
-    if (!loadTimeData.getBoolean('splitSettingsSyncEnabled')) {
+    if (!loadTimeData.getBoolean('splitSyncConsent')) {
       return false;
     }
     // </if>
diff --git a/chrome/browser/resources/settings/safety_check_page/safety_check_page.html b/chrome/browser/resources/settings/safety_check_page/safety_check_page.html
index 9ded6ed..6cdbbdc 100644
--- a/chrome/browser/resources/settings/safety_check_page/safety_check_page.html
+++ b/chrome/browser/resources/settings/safety_check_page/safety_check_page.html
@@ -49,7 +49,7 @@
       <template is="dom-if" if="[[shouldShowParentButton_(parentStatus_)]]"
           restamp>
         <div class="separator"></div>
-        <cr-button id="safetyCheckParentButton"
+        <cr-button id="safetyCheckParentButton" class="action-button"
             on-click="onRunSafetyCheckClick_" no-search>
           $i18n{safetyCheckParentButton}
         </cr-button>
@@ -79,7 +79,7 @@
         <template is="dom-if"
             if="[[shouldShowUpdatesButton_(updatesStatus_)]]" restamp>
           <div class="separator"></div>
-          <cr-button id="safetyCheckUpdatesButton"
+          <cr-button id="safetyCheckUpdatesButton" class="action-button"
               on-click="onSafetyCheckUpdatesButtonClicked_" no-search>
             $i18n{aboutRelaunch}
           </cr-button>
@@ -97,7 +97,7 @@
             class$="[[getPasswordsIconClass_(passwordsStatus_)]]">
         </iron-icon>
         <div class="start settings-box-text">
-          <div>$i18n{safetyCheckPasswordsPrimaryLabel}</div>
+          <div>$i18n{passwords}</div>
           <div class="secondary" no-search>
             [[getPasswordsSubLabelText_(passwordsStatus_)]]
           </div>
@@ -106,12 +106,39 @@
             if="[[shouldShowPasswordsButton_(passwordsStatus_)]]" restamp>
           <div class="separator"></div>
           <cr-button id="safetyCheckPasswordsButton"
+              class$="[[getPasswordsButtonClass_(passwordsStatus_)]]"
               on-click="onPasswordsButtonClick_" no-search>
             [[getPasswordsButtonText_(passwordsStatus_)]]
           </cr-button>
         </template>
       </div>
       <div class="settings-box two-line">
+        <iron-icon icon="[[getSafeBrowsingIcon_(safeBrowsingStatus_)]]"
+            src="[[getSafeBrowsingIconSrc_(safeBrowsingStatus_)]]"
+            class$="[[getSafeBrowsingIconClass_(safeBrowsingStatus_)]]">
+        </iron-icon>
+        <div class="start settings-box-text">
+          <div>$i18n{safeBrowsingSectionLabel}</div>
+          <div class="secondary" no-search>
+            [[getSafeBrowsingSubLabelText_(safeBrowsingStatus_)]]
+          </div>
+        </div>
+        <template is="dom-if"
+            if="[[shouldShowSafeBrowsingButton_(safeBrowsingStatus_)]]" restamp>
+          <div class="separator"></div>
+          <cr-button id="safetyCheckSafeBrowsingButton" class="action-button"
+              on-click="onSafeBrowsingButtonClick_" no-search>
+            $i18n{safetyCheckSafeBrowsingButton}
+          </cr-button>
+        </template>
+        <template is="dom-if"
+            if="[[shouldShowSafeBrowsingManagedIcon_(safeBrowsingStatus_)]]"
+            restamp>
+          <iron-icon id="safetyCheckSafeBrowsingManagedIcon" icon="cr20:domain">
+          </iron-icon>
+        </template>
+      </div>
+      <div class="settings-box two-line">
         <iron-icon icon="[[getExtensionsIcon_(extensionsStatus_)]]"
             src="[[getExtensionsIconSrc_(extensionsStatus_)]]"
             class$="[[getExtensionsIconClass_(extensionsStatus_)]]">
@@ -126,6 +153,7 @@
             if="[[shouldShowExtensionsButton_(extensionsStatus_)]]" restamp>
           <div class="separator"></div>
           <cr-button id="safetyCheckExtensionsButton"
+              class$="[[getExtensionsButtonClass_(extensionsStatus_)]]"
               on-click="onSafetyCheckExtensionsButtonClicked_" no-search>
             $i18n{safetyCheckExtensionsButton}
           </cr-button>
diff --git a/chrome/browser/resources/settings/safety_check_page/safety_check_page.js b/chrome/browser/resources/settings/safety_check_page/safety_check_page.js
index b1f0828d..57b35c2c 100644
--- a/chrome/browser/resources/settings/safety_check_page/safety_check_page.js
+++ b/chrome/browser/resources/settings/safety_check_page/safety_check_page.js
@@ -503,6 +503,19 @@
     }
   },
 
+  /**
+   * @private
+   * @return {string}
+   */
+  getPasswordsButtonClass_: function() {
+    switch (this.passwordsStatus_) {
+      case settings.SafetyCheckPasswordsStatus.COMPROMISED:
+        return 'action-button';
+      default:
+        return '';
+    }
+  },
+
   /** @private */
   onPasswordsButtonClick_: function() {
     switch (this.passwordsStatus_) {
@@ -521,6 +534,105 @@
    * @private
    * @return {boolean}
    */
+  shouldShowSafeBrowsingButton_: function() {
+    switch (this.safeBrowsingStatus_) {
+      case settings.SafetyCheckSafeBrowsingStatus.DISABLED:
+        return true;
+      default:
+        return false;
+    }
+  },
+
+  /**
+   * @private
+   * @return {boolean}
+   */
+  shouldShowSafeBrowsingManagedIcon_: function() {
+    switch (this.safeBrowsingStatus_) {
+      case settings.SafetyCheckSafeBrowsingStatus.DISABLED_BY_ADMIN:
+      case settings.SafetyCheckSafeBrowsingStatus.DISABLED_BY_EXTENSION:
+        return true;
+      default:
+        return false;
+    }
+  },
+
+  /**
+   * @private
+   * @return {string}
+   */
+  getSafeBrowsingSubLabelText_: function() {
+    switch (this.safeBrowsingStatus_) {
+      case settings.SafetyCheckSafeBrowsingStatus.CHECKING:
+        return '';
+      case settings.SafetyCheckSafeBrowsingStatus.ENABLED:
+        return this.i18n('safetyCheckSafeBrowsingSubLabelEnabled');
+      case settings.SafetyCheckSafeBrowsingStatus.DISABLED:
+        return this.i18n('safetyCheckSafeBrowsingSubLabelDisabled');
+      case settings.SafetyCheckSafeBrowsingStatus.DISABLED_BY_ADMIN:
+        return this.i18n('safetyCheckSafeBrowsingSubLabelDisabledByAdmin');
+      case settings.SafetyCheckSafeBrowsingStatus.DISABLED_BY_EXTENSION:
+        return this.i18n('safetyCheckSafeBrowsingSubLabelDisabledByExtension');
+      default:
+        assertNotReached();
+    }
+  },
+
+  /**
+   * @private
+   * @return {?string}
+   */
+  getSafeBrowsingIcon_: function() {
+    switch (this.safeBrowsingStatus_) {
+      case settings.SafetyCheckSafeBrowsingStatus.CHECKING:
+        return null;
+      case settings.SafetyCheckSafeBrowsingStatus.ENABLED:
+        return 'cr:check';
+      case settings.SafetyCheckSafeBrowsingStatus.DISABLED:
+      case settings.SafetyCheckSafeBrowsingStatus.DISABLED_BY_ADMIN:
+      case settings.SafetyCheckSafeBrowsingStatus.DISABLED_BY_EXTENSION:
+        return 'cr:info';
+      default:
+        assertNotReached();
+    }
+  },
+
+  /**
+   * @private
+   * @return {?string}
+   */
+  getSafeBrowsingIconSrc_: function() {
+    switch (this.safeBrowsingStatus_) {
+      case settings.SafetyCheckSafeBrowsingStatus.CHECKING:
+        return 'chrome://resources/images/throbber_small.svg';
+      default:
+        return null;
+    }
+  },
+
+  /**
+   * @private
+   * @return {string}
+   */
+  getSafeBrowsingIconClass_: function() {
+    switch (this.safeBrowsingStatus_) {
+      case settings.SafetyCheckSafeBrowsingStatus.CHECKING:
+      case settings.SafetyCheckSafeBrowsingStatus.ENABLED:
+        return 'icon-blue';
+      default:
+        return '';
+    }
+  },
+
+  /** @private */
+  onSafeBrowsingButtonClick_: function() {
+    // TODO(crbug.com/1010001): Implement once behavior has been agreed on.
+  },
+
+  /**
+   * @private
+   * @return {boolean}
+   */
   shouldShowExtensionsButton_: function() {
     switch (this.extensionsStatus_) {
       case settings.SafetyCheckExtensionsStatus.BAD_EXTENSIONS_ON:
@@ -636,5 +748,18 @@
         assertNotReached();
     }
   },
+
+  /**
+   * @private
+   * @return {string}
+   */
+  getExtensionsButtonClass_: function() {
+    switch (this.extensionsStatus_) {
+      case settings.SafetyCheckExtensionsStatus.BAD_EXTENSIONS_ON:
+        return 'action-button';
+      default:
+        return '';
+    }
+  },
 });
 })();
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/OWNERS b/chrome/browser/safe_browsing/cloud_content_scanning/OWNERS
index 0046b2a..351867e 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/OWNERS
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/OWNERS
@@ -1,7 +1,12 @@
 per-file deep_scanning_dialog_delegate*=rogerta@chromium.org
+per-file deep_scanning_dialog_delegate*=domfc@chromium.org
 per-file fake_deep_scanning_dialog_delegate*=rogerta@chromium.org
+per-file fake_deep_scanning_dialog_delegate*=domfc@chromium.org
 per-file deep_scanning_dialog_views*=rogerta@chromium.org
+per-file deep_scanning_dialog_views*=domfc@chromium.org
 per-file deep_scanning_browsertest_base*=rogerta@chromium.org
+per-file deep_scanning_browsertest_base*=domfc@chromium.org
 per-file deep_scanning_utils*=rogerta@chromium.org
+per-file deep_scanning_utils*=domfc@chromium.org
 
 drubery@chromium.org
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java
index 4c6702a..d26f155d 100644
--- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java
+++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java
@@ -9,6 +9,7 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.support.test.filters.SmallTest;
 import android.support.v4.content.FileProvider;
@@ -26,6 +27,7 @@
 import org.chromium.base.task.AsyncTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeSwitches;
@@ -201,6 +203,7 @@
 
     @Test
     @SmallTest
+    @DisableIf.Build(sdk_is_less_than = Build.VERSION_CODES.LOLLIPOP, message = "crbug.com/1056059")
     public void testSaveBitmap() throws IOException {
         String fileName = "chrome-test-bitmap";
         ShareImageFileUtils.OnImageSaveListener listener =
diff --git a/chrome/browser/sync/profile_sync_service_factory.cc b/chrome/browser/sync/profile_sync_service_factory.cc
index dca006d..b250bbb 100644
--- a/chrome/browser/sync/profile_sync_service_factory.cc
+++ b/chrome/browser/sync/profile_sync_service_factory.cc
@@ -260,7 +260,7 @@
     // those two cases. Bug 88109.
     bool is_auto_start = browser_defaults::kSyncAutoStarts;
 #if defined(OS_CHROMEOS)
-    if (chromeos::features::IsSplitSettingsSyncEnabled())
+    if (chromeos::features::IsSplitSyncConsentEnabled())
       is_auto_start = false;
 #endif
     init_params.start_behavior = is_auto_start
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 0310e13..7e421b1 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1365,7 +1365,6 @@
       "webui/settings/reset_settings_handler.h",
       "webui/settings/safety_check_handler.cc",
       "webui/settings/safety_check_handler.h",
-      "webui/settings/safety_check_handler_observer.h",
       "webui/settings/search_engines_handler.cc",
       "webui/settings/search_engines_handler.h",
       "webui/settings/settings_clear_browsing_data_handler.cc",
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller.h b/chrome/browser/ui/autofill/autofill_popup_controller.h
index 6357f91..e6c9b3be 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller.h
+++ b/chrome/browser/ui/autofill/autofill_popup_controller.h
@@ -35,6 +35,9 @@
   // Returns the number of lines of data that there are.
   virtual int GetLineCount() const = 0;
 
+  // Returns the full set of autofill suggestions, if applicable.
+  virtual const std::vector<autofill::Suggestion> GetSuggestions() = 0;
+
   // Returns the suggestion or pre-elided string at the given row index.
   virtual const autofill::Suggestion& GetSuggestionAt(int row) const = 0;
   virtual const base::string16& GetElidedValueAt(int row) const = 0;
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
index 9384626..9896064 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
@@ -79,22 +79,20 @@
     base::i18n::TextDirection text_direction)
     : controller_common_(element_bounds, text_direction, container_view),
       web_contents_(web_contents),
-      layout_model_(this, delegate->GetPopupType() == PopupType::kCreditCards),
+      layout_model_(delegate->GetPopupType() == PopupType::kCreditCards),
       delegate_(delegate) {
   ClearState();
   delegate->RegisterDeletionCallback(base::BindOnce(
       &AutofillPopupControllerImpl::HideViewAndDie, GetWeakPtr()));
 }
 
-AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
+AutofillPopupControllerImpl::~AutofillPopupControllerImpl() = default;
 
 void AutofillPopupControllerImpl::Show(
     const std::vector<Suggestion>& suggestions,
     bool autoselect_first_suggestion,
     PopupType popup_type) {
   SetValues(suggestions);
-  DCHECK_EQ(suggestions_.size(), elided_values_.size());
-  DCHECK_EQ(suggestions_.size(), elided_labels_.size());
 
   bool just_created = false;
   if (!view_) {
@@ -110,20 +108,6 @@
     just_created = true;
   }
 
-#if !defined(OS_ANDROID)
-  // Android displays the long text with ellipsis using the view attributes.
-
-  layout_model_.UpdatePopupBounds();
-
-  // Elide the name and label strings so that the popup fits in the available
-  // space.
-  for (int i = 0; i < GetLineCount(); ++i) {
-    bool has_label = !suggestions_[i].label.empty();
-    ElideValueAndLabelForRow(
-        i, layout_model_.GetAvailableWidthForRow(i, has_label));
-  }
-#endif
-
   if (just_created) {
 #if defined(OS_ANDROID)
     ManualFillingController::GetOrCreate(web_contents_)
@@ -149,17 +133,13 @@
       ->RegisterKeyPressHandler(
           base::Bind(&AutofillPopupControllerImpl::HandleKeyPressEvent,
                      base::Unretained(this)));
+  pinned_until_update_ = false;
   delegate_->OnPopupShown();
-
-  DCHECK_EQ(suggestions_.size(), elided_values_.size());
-  DCHECK_EQ(suggestions_.size(), elided_labels_.size());
 }
 
 void AutofillPopupControllerImpl::UpdateDataListValues(
     const std::vector<base::string16>& values,
     const std::vector<base::string16>& labels) {
-  DCHECK_EQ(suggestions_.size(), elided_values_.size());
-  DCHECK_EQ(suggestions_.size(), elided_labels_.size());
 
   selected_line_.reset();
   // Remove all the old data list values, which should always be at the top of
@@ -167,8 +147,6 @@
   while (!suggestions_.empty() &&
          suggestions_[0].frontend_id == POPUP_ITEM_ID_DATALIST_ENTRY) {
     suggestions_.erase(suggestions_.begin());
-    elided_values_.erase(elided_values_.begin());
-    elided_labels_.erase(elided_labels_.begin());
   }
 
   // If there are no new data list values, exit (clearing the separator if there
@@ -177,8 +155,6 @@
     if (!suggestions_.empty() &&
         suggestions_[0].frontend_id == POPUP_ITEM_ID_SEPARATOR) {
       suggestions_.erase(suggestions_.begin());
-      elided_values_.erase(elided_values_.begin());
-      elided_labels_.erase(elided_labels_.begin());
     }
 
      // The popup contents have changed, so either update the bounds or hide it.
@@ -195,29 +171,17 @@
       suggestions_[0].frontend_id != POPUP_ITEM_ID_SEPARATOR) {
     suggestions_.insert(suggestions_.begin(), Suggestion());
     suggestions_[0].frontend_id = POPUP_ITEM_ID_SEPARATOR;
-    elided_values_.insert(elided_values_.begin(), base::string16());
-    elided_labels_.insert(elided_labels_.begin(), base::string16());
   }
 
   // Prepend the parameters to the suggestions we already have.
   suggestions_.insert(suggestions_.begin(), values.size(), Suggestion());
-  elided_values_.insert(elided_values_.begin(), values.size(),
-                        base::string16());
-  elided_labels_.insert(elided_labels_.begin(), values.size(),
-                        base::string16());
   for (size_t i = 0; i < values.size(); i++) {
     suggestions_[i].value = values[i];
     suggestions_[i].label = labels[i];
     suggestions_[i].frontend_id = POPUP_ITEM_ID_DATALIST_ENTRY;
-
-    // TODO(brettw) it looks like these should be elided.
-    elided_values_[i] = values[i];
-    elided_labels_[i] = labels[i];
   }
 
   OnSuggestionsChanged();
-  DCHECK_EQ(suggestions_.size(), elided_values_.size());
-  DCHECK_EQ(suggestions_.size(), elided_labels_.size());
 }
 
 void AutofillPopupControllerImpl::PinViewUntilUpdate() {
@@ -231,9 +195,10 @@
 
 void AutofillPopupControllerImpl::Hide(PopupHidingReason reason) {
   // If the reason for hiding is only stale data or a user interacting with
-  // native Chrome UI (kFocusChanged), the popup might be kept open.
+  // native Chrome UI (kFocusChanged/kEndEditing), the popup might be kept open.
   if (pinned_until_update_ && (reason == PopupHidingReason::kStaleData ||
-                               reason == PopupHidingReason::kFocusChanged)) {
+                               reason == PopupHidingReason::kFocusChanged ||
+                               reason == PopupHidingReason::kEndEditing)) {
     return;  // Don't close the popup while waiting for an update.
   }
   if (delegate_) {
@@ -292,13 +257,7 @@
 }
 
 void AutofillPopupControllerImpl::OnSuggestionsChanged() {
-#if !defined(OS_ANDROID)
-  // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
-  // the popup could end up jumping from above the element to below it.
-  // It is unclear if it is better to keep the popup where it was, or if it
-  // should try and move to its desired position.
-  layout_model_.UpdatePopupBounds();
-#else
+#if defined(OS_ANDROID)
   // Assume that suggestions are (still) available. If this is wrong, the method
   // |HideViewAndDie| will be called soon after and will hide all suggestions.
   ManualFillingController::GetOrCreate(web_contents_)
@@ -349,18 +308,6 @@
   return suggestions_;
 }
 
-#if !defined(OS_ANDROID)
-int AutofillPopupControllerImpl::GetElidedValueWidthForRow(int row) {
-  return gfx::GetStringWidth(GetElidedValueAt(row),
-                             layout_model_.GetValueFontListForRow(row));
-}
-
-int AutofillPopupControllerImpl::GetElidedLabelWidthForRow(int row) {
-  return gfx::GetStringWidth(GetElidedLabelAt(row),
-                             layout_model_.GetLabelFontListForRow(row));
-}
-#endif
-
 int AutofillPopupControllerImpl::GetLineCount() const {
   return suggestions_.size();
 }
@@ -371,12 +318,12 @@
 
 const base::string16& AutofillPopupControllerImpl::GetElidedValueAt(
     int row) const {
-  return elided_values_[row];
+  return suggestions_[row].value;
 }
 
 const base::string16& AutofillPopupControllerImpl::GetElidedLabelAt(
     int row) const {
-  return elided_labels_[row];
+  return suggestions_[row].label;
 }
 
 bool AutofillPopupControllerImpl::GetRemovalConfirmationText(
@@ -396,8 +343,6 @@
 
   // Remove the deleted element.
   suggestions_.erase(suggestions_.begin() + list_index);
-  elided_values_.erase(elided_values_.begin() + list_index);
-  elided_labels_.erase(elided_labels_.begin() + list_index);
 
   selected_line_.reset();
 
@@ -501,46 +446,12 @@
 void AutofillPopupControllerImpl::SetValues(
     const std::vector<Suggestion>& suggestions) {
   suggestions_ = suggestions;
-  elided_values_.resize(suggestions.size());
-  elided_labels_.resize(suggestions.size());
-  for (size_t i = 0; i < suggestions.size(); i++) {
-    elided_values_[i] = suggestions[i].value;
-    elided_labels_[i] = suggestions[i].label;
-  }
 }
 
 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
 }
 
-#if !defined(OS_ANDROID)
-void AutofillPopupControllerImpl::ElideValueAndLabelForRow(
-    int row,
-    int available_width) {
-  int value_width = gfx::GetStringWidth(
-      suggestions_[row].value, layout_model_.GetValueFontListForRow(row));
-  int label_width = gfx::GetStringWidth(
-      suggestions_[row].label, layout_model_.GetLabelFontListForRow(row));
-  int total_text_length = value_width + label_width;
-
-  // The line can have no strings if it represents a UI element, such as
-  // a separator line.
-  if (total_text_length == 0)
-    return;
-
-  // Each field receives space in proportion to its length.
-  int value_size = available_width * value_width / total_text_length;
-  elided_values_[row] = gfx::ElideText(
-      suggestions_[row].value, layout_model_.GetValueFontListForRow(row),
-      value_size, gfx::ELIDE_TAIL);
-
-  int label_size = available_width * label_width / total_text_length;
-  elided_labels_[row] = gfx::ElideText(
-      suggestions_[row].label, layout_model_.GetLabelFontListForRow(row),
-      label_size, gfx::ELIDE_TAIL);
-}
-#endif
-
 bool AutofillPopupControllerImpl::AcceptSelectedLine() {
   if (!selected_line_)
     return false;
@@ -558,8 +469,6 @@
   // Don't clear view_, because otherwise the popup will have to get regenerated
   // and this will cause flickering.
   suggestions_.clear();
-  elided_values_.clear();
-  elided_labels_.clear();
 
   selected_line_.reset();
 }
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.h b/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
index 2f331f9f..3f9fb34 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
@@ -96,10 +96,6 @@
   void SetElementBounds(const gfx::RectF& bounds);
   bool IsRTL() const override;
   const std::vector<Suggestion> GetSuggestions() override;
-#if !defined(OS_ANDROID)
-  int GetElidedValueWidthForRow(int row) override;
-  int GetElidedLabelWidthForRow(int row) override;
-#endif
 
   // AutofillPopupController implementation.
   void OnSuggestionsChanged() override;
@@ -151,14 +147,6 @@
   virtual ui::AXPlatformNode* GetRootAXPlatformNodeForWebContents();
 
  private:
-#if !defined(OS_ANDROID)
-  FRIEND_TEST_ALL_PREFIXES(AutofillPopupControllerUnitTest, ElideText);
-  // Helper method which elides the value and label for the suggestion at |row|
-  // given the |available_width|. Puts the results in |elided_values_| and
-  // |elided_labels_|.
-  void ElideValueAndLabelForRow(int row, int available_width);
-#endif
-
   // The user has accepted the currently selected line. Returns whether there
   // was a selection to accept.
   bool AcceptSelectedLine();
@@ -187,11 +175,6 @@
   // The current Autofill query values.
   std::vector<Suggestion> suggestions_;
 
-  // Elided values and labels corresponding to the suggestions_ vector to
-  // ensure that it fits on the screen.
-  std::vector<base::string16> elided_values_;
-  std::vector<base::string16> elided_labels_;
-
   // The line that is currently selected by the user, null indicates that no
   // line is currently selected.
   base::Optional<int> selected_line_;
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc b/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
index de052a0..4a4b237 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
@@ -701,55 +701,15 @@
   EXPECT_CALL(*autofill_popup_view(), Hide).Times(0);
   autofill_popup_controller_->PinViewUntilUpdate();
 
-  // Hide() will not work for stale data.
+  // Hide() will not work for stale data or when focusing native UI.
   autofill_popup_controller_->DoHide(PopupHidingReason::kStaleData);
+  autofill_popup_controller_->DoHide(PopupHidingReason::kEndEditing);
 
   // Check the expectations now since TearDown will perform a successful hide.
   Mock::VerifyAndClearExpectations(delegate());
   Mock::VerifyAndClearExpectations(autofill_popup_view());
 }
 
-#if !defined(OS_ANDROID)
-TEST_F(AutofillPopupControllerUnitTest, ElideText) {
-  std::vector<Suggestion> suggestions;
-  suggestions.push_back(
-      Suggestion("Text that will need to be trimmed",
-      "Label that will be trimmed", "genericCC", 0));
-  suggestions.push_back(
-      Suggestion("untrimmed", "Untrimmed", "genericCC", 0));
-
-  autofill_popup_controller_->SetValues(suggestions);
-
-  // Ensure the popup will be too small to display all of the first row.
-  int popup_max_width =
-      gfx::GetStringWidth(
-          suggestions[0].value,
-          autofill_popup_controller_->layout_model().GetValueFontListForRow(
-              0)) +
-      gfx::GetStringWidth(
-          suggestions[0].label,
-          autofill_popup_controller_->layout_model().GetLabelFontListForRow(
-              0)) -
-      25;
-
-  autofill_popup_controller_->ElideValueAndLabelForRow(0, popup_max_width);
-
-  // The first element was long so it should have been trimmed.
-  EXPECT_NE(autofill_popup_controller_->GetSuggestionAt(0).value,
-            autofill_popup_controller_->GetElidedValueAt(0));
-  EXPECT_NE(autofill_popup_controller_->GetSuggestionAt(0).label,
-            autofill_popup_controller_->GetElidedLabelAt(0));
-
-  autofill_popup_controller_->ElideValueAndLabelForRow(1, popup_max_width);
-
-  // The second element was shorter so it should be unchanged.
-  EXPECT_EQ(autofill_popup_controller_->GetSuggestionAt(1).value,
-            autofill_popup_controller_->GetElidedValueAt(1));
-  EXPECT_EQ(autofill_popup_controller_->GetSuggestionAt(1).label,
-            autofill_popup_controller_->GetElidedLabelAt(1));
-}
-#endif
-
 #if !defined(OS_CHROMEOS)
 TEST_F(AutofillPopupControllerAccessibilityUnitTest, FireControlsChangedEvent) {
   StrictMock<MockAxPlatformNodeDelegate> mock_ax_platform_node_delegate;
diff --git a/chrome/browser/ui/autofill/autofill_popup_layout_model.cc b/chrome/browser/ui/autofill/autofill_popup_layout_model.cc
index ea6c8a59..f7a6748 100644
--- a/chrome/browser/ui/autofill/autofill_popup_layout_model.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_layout_model.cc
@@ -5,6 +5,9 @@
 #include "chrome/browser/ui/autofill/autofill_popup_layout_model.h"
 
 #include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
 
 #include "base/macros.h"
 #include "base/stl_util.h"
@@ -16,17 +19,12 @@
 #include "chrome/browser/ui/autofill/popup_constants.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
 #include "components/autofill/core/browser/ui/popup_item_ids.h"
-#include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_util.h"
 #include "components/grit/components_scaled_resources.h"
 #include "components/strings/grit/components_strings.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/resource/resource_bundle.h"
-#include "ui/gfx/color_palette.h"
-#include "ui/gfx/color_utils.h"
-#include "ui/gfx/font_list.h"
-#include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/paint_vector_icon.h"
 
@@ -39,16 +37,7 @@
 
 namespace {
 
-// The vertical height of each row in pixels.
-const size_t kRowHeight = 24;
-
-// The vertical height of a separator in pixels.
-const size_t kSeparatorHeight = 1;
-
 #if !defined(OS_ANDROID)
-// Size difference between the normal font and the smaller font, in pixels.
-const int kSmallerFontSizeDelta = -1;
-
 // Default sice for icons in the autofill popup.
 constexpr int kIconSize = 16;
 #endif
@@ -91,174 +80,33 @@
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 };
 
-int GetRowHeightFromId(int identifier) {
-  if (identifier == POPUP_ITEM_ID_SEPARATOR)
-    return kSeparatorHeight;
-
-  return kRowHeight;
-}
-
 }  // namespace
 
-AutofillPopupLayoutModel::AutofillPopupLayoutModel(
-    AutofillPopupViewDelegate* delegate,
-    bool is_credit_card_popup)
-    : delegate_(delegate), is_credit_card_popup_(is_credit_card_popup) {
-#if !defined(OS_ANDROID)
-  smaller_font_list_ =
-      normal_font_list_.DeriveWithSizeDelta(kSmallerFontSizeDelta);
-  bold_font_list_ = normal_font_list_.DeriveWithWeight(gfx::Font::Weight::BOLD);
-  view_common_ = std::make_unique<PopupViewCommon>();
-#endif
-}
+AutofillPopupLayoutModel::AutofillPopupLayoutModel(bool is_credit_card_popup)
+    : is_credit_card_popup_(is_credit_card_popup) {}
 
-AutofillPopupLayoutModel::~AutofillPopupLayoutModel() {}
+AutofillPopupLayoutModel::~AutofillPopupLayoutModel() = default;
 
 #if !defined(OS_ANDROID)
-int AutofillPopupLayoutModel::GetDesiredPopupHeight() const {
-  std::vector<autofill::Suggestion> suggestions = delegate_->GetSuggestions();
-  int popup_height = 2 * kPopupBorderThickness;
+// static
+gfx::ImageSkia AutofillPopupLayoutModel::GetIconImage(
+    const autofill::Suggestion& suggestion) {
+  if (!suggestion.custom_icon.IsEmpty())
+    return suggestion.custom_icon.AsImageSkia();
 
-  for (size_t i = 0; i < suggestions.size(); ++i) {
-    popup_height += GetRowHeightFromId(suggestions[i].frontend_id);
-  }
-
-  return popup_height;
+  return GetIconImageByName(suggestion.icon);
 }
 
-int AutofillPopupLayoutModel::GetDesiredPopupWidth() const {
-  std::vector<autofill::Suggestion> suggestions = delegate_->GetSuggestions();
-
-  int popup_width = RoundedElementBounds().width();
-
-  for (size_t i = 0; i < suggestions.size(); ++i) {
-    int label_size = delegate_->GetElidedLabelWidthForRow(i);
-    int row_size = delegate_->GetElidedValueWidthForRow(i) + label_size +
-                   RowWidthWithoutText(i, /* has_subtext= */ label_size > 0);
-
-    popup_width = std::max(popup_width, row_size);
-  }
-
-  return popup_width;
-}
-
-int AutofillPopupLayoutModel::RowWidthWithoutText(int row,
-                                                  bool has_subtext) const {
-  std::vector<autofill::Suggestion> suggestions = delegate_->GetSuggestions();
-  int row_size = 2 * (kEndPadding + kPopupBorderThickness);
-  if (has_subtext)
-    row_size += kNamePadding;
-
-  // Add the Autofill icon size, if required.
-  const std::string& icon = suggestions[row].icon;
-  if (!icon.empty()) {
-    row_size += GetIconImage(row).width() + kIconPadding;
-  }
-  return row_size;
-}
-
-int AutofillPopupLayoutModel::GetAvailableWidthForRow(int row,
-                                                      bool has_subtext) const {
-  return popup_bounds_.width() - RowWidthWithoutText(row, has_subtext);
-}
-
-void AutofillPopupLayoutModel::UpdatePopupBounds() {
-  int popup_width = GetDesiredPopupWidth();
-  int popup_height = GetDesiredPopupHeight();
-
-  popup_bounds_ = view_common_->CalculatePopupBounds(
-      popup_width, popup_height, RoundedElementBounds(),
-      delegate_->container_view(), delegate_->IsRTL());
-}
-
-const gfx::FontList& AutofillPopupLayoutModel::GetValueFontListForRow(
-    size_t index) const {
-  std::vector<autofill::Suggestion> suggestions = delegate_->GetSuggestions();
-
-  // Autofill values have positive |frontend_id|.
-  if (suggestions[index].frontend_id > 0)
-    return bold_font_list_;
-
-  // All other message types are defined here.
-  PopupItemId id = static_cast<PopupItemId>(suggestions[index].frontend_id);
-  switch (id) {
-    case POPUP_ITEM_ID_INSECURE_CONTEXT_PAYMENT_DISABLED_MESSAGE:
-    case POPUP_ITEM_ID_CLEAR_FORM:
-    case POPUP_ITEM_ID_CREDIT_CARD_SIGNIN_PROMO:
-    case POPUP_ITEM_ID_AUTOFILL_OPTIONS:
-    case POPUP_ITEM_ID_CREATE_HINT:
-    case POPUP_ITEM_ID_SCAN_CREDIT_CARD:
-    case POPUP_ITEM_ID_SEPARATOR:
-    case POPUP_ITEM_ID_LOADING_SPINNER:
-    case POPUP_ITEM_ID_TITLE:
-    case POPUP_ITEM_ID_PASSWORD_ENTRY:
-    case POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY:
-    case POPUP_ITEM_ID_HIDE_AUTOFILL_SUGGESTIONS:
-    case POPUP_ITEM_ID_GENERATE_PASSWORD_ENTRY:
-    case POPUP_ITEM_ID_SHOW_ACCOUNT_CARDS:
-    case POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN:
-    case POPUP_ITEM_ID_USE_VIRTUAL_CARD:
-    case POPUP_ITEM_ID_ONE_TIME_CODE:
-      return normal_font_list_;
-    case POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY:
-    case POPUP_ITEM_ID_DATALIST_ENTRY:
-    case POPUP_ITEM_ID_USERNAME_ENTRY:
-      return bold_font_list_;
-  }
-  NOTREACHED();
-  return normal_font_list_;
-}
-
-const gfx::FontList& AutofillPopupLayoutModel::GetLabelFontListForRow(
-    size_t index) const {
-  return smaller_font_list_;
-}
-
-gfx::ImageSkia AutofillPopupLayoutModel::GetIconImage(size_t index) const {
-  std::vector<autofill::Suggestion> suggestions = delegate_->GetSuggestions();
-  if (!suggestions[index].custom_icon.IsEmpty())
-    return suggestions[index].custom_icon.AsImageSkia();
-
-  return GetIconImageByName(suggestions[index].icon);
-}
-
+// static
 gfx::ImageSkia AutofillPopupLayoutModel::GetStoreIndicatorIconImage(
-    size_t index) const {
-  return GetIconImageByName(
-      delegate_->GetSuggestions()[index].store_indicator_icon);
+    const autofill::Suggestion& suggestion) {
+  return GetIconImageByName(suggestion.store_indicator_icon);
 }
 #endif  // !defined(OS_ANDROID)
 
-int AutofillPopupLayoutModel::LineFromY(int y) const {
-  std::vector<autofill::Suggestion> suggestions = delegate_->GetSuggestions();
-  int current_height = kPopupBorderThickness;
-
-  for (size_t i = 0; i < suggestions.size(); ++i) {
-    current_height += GetRowHeightFromId(suggestions[i].frontend_id);
-
-    if (y <= current_height)
-      return i;
-  }
-
-  // The y value goes beyond the popup so stop the selection at the last line.
-  return suggestions.size() - 1;
-}
-
-gfx::Rect AutofillPopupLayoutModel::GetRowBounds(size_t index) const {
-  std::vector<autofill::Suggestion> suggestions = delegate_->GetSuggestions();
-
-  int top = kPopupBorderThickness;
-  for (size_t i = 0; i < index; ++i) {
-    top += GetRowHeightFromId(suggestions[i].frontend_id);
-  }
-
-  return gfx::Rect(kPopupBorderThickness, top,
-                   popup_bounds_.width() - 2 * kPopupBorderThickness,
-                   GetRowHeightFromId(suggestions[index].frontend_id));
-}
-
+// static
 int AutofillPopupLayoutModel::GetIconResourceID(
-    const std::string& resource_name) const {
+    const std::string& resource_name) {
 #if !BUILDFLAG(GOOGLE_CHROME_BRANDING)
   if (resource_name == "googlePay" || resource_name == "googlePayDark") {
     return 0;
@@ -280,13 +128,10 @@
   view_common_ = std::move(view_common);
 }
 
-gfx::Rect AutofillPopupLayoutModel::RoundedElementBounds() const {
-  return gfx::ToEnclosingRect(delegate_->element_bounds());
-}
-
 #if !defined(OS_ANDROID)
+// static
 gfx::ImageSkia AutofillPopupLayoutModel::GetIconImageByName(
-    const std::string& icon_str) const {
+    const std::string& icon_str) {
   if (icon_str.empty())
     return gfx::ImageSkia();
 
diff --git a/chrome/browser/ui/autofill/autofill_popup_layout_model.h b/chrome/browser/ui/autofill/autofill_popup_layout_model.h
index 02fe284..ef4c03e7 100644
--- a/chrome/browser/ui/autofill/autofill_popup_layout_model.h
+++ b/chrome/browser/ui/autofill/autofill_popup_layout_model.h
@@ -7,10 +7,11 @@
 
 #include <stddef.h>
 
+#include <memory>
 #include <string>
 
-#include "chrome/browser/ui/autofill/autofill_popup_view_delegate.h"
 #include "chrome/browser/ui/autofill/popup_view_common.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
 #include "ui/gfx/font_list.h"
 #include "ui/gfx/geometry/rect.h"
 
@@ -24,100 +25,36 @@
 // TODO(mathp): investigate moving ownership of this class to the view.
 class AutofillPopupLayoutModel {
  public:
-  AutofillPopupLayoutModel(AutofillPopupViewDelegate* delegate,
-                           bool is_credit_card_popup);
+  explicit AutofillPopupLayoutModel(bool is_credit_card_popup);
 
   ~AutofillPopupLayoutModel();
 
-  // The minimum amount of padding between the Autofill name and subtext,
-  // in dip.
-  static const int kNamePadding = 15;
-
-  // The amount of padding around icons in dip.
-  static const int kIconPadding = 5;
-
-  // The amount of horizontal padding around icons in dip for the case, when
-  // icon is located on the left side.
-  static const int kPaddingAfterLeadingIcon = 8;
-
-  // The amount of padding at the end of the popup in dip.
-  static const int kEndPadding = 8;
-
 #if !defined(OS_ANDROID)
-  // Calculates the desired height of the popup based on its contents.
-  int GetDesiredPopupHeight() const;
+  // Returns the icon image of the given |suggestion|.
+  static gfx::ImageSkia GetIconImage(const autofill::Suggestion& suggestion);
 
-  // Calculates the desired width of the popup based on its contents.
-  int GetDesiredPopupWidth() const;
-
-  // Calculate the width of the row, excluding all the text. This provides
-  // the size of the row that won't be reducible (since all the text can be
-  // elided if there isn't enough space). |with_label| indicates whether a label
-  // is expected to be present.
-  int RowWidthWithoutText(int row, bool with_label) const;
-
-  // Get the available space for the total text width. |with_label| indicates
-  // whether a label is expected to be present.
-  int GetAvailableWidthForRow(int row, bool with_label) const;
-
-  // Calculates and sets the bounds of the popup, including placing it properly
-  // to prevent it from going off the screen.
-  void UpdatePopupBounds();
-
-  // The same font can vary based on the type of data it is showing at the row
-  // |index|.
-  const gfx::FontList& GetValueFontListForRow(size_t index) const;
-  const gfx::FontList& GetLabelFontListForRow(size_t index) const;
-
-  // Returns the icon image of the item at |index| in the popup.
-  gfx::ImageSkia GetIconImage(size_t index) const;
-
-  // Returns the store indicator icon image of the item at |index| in the popup.
-  gfx::ImageSkia GetStoreIndicatorIconImage(size_t index) const;
+  // Returns the store indicator icon image of the given |suggestion|.
+  static gfx::ImageSkia GetStoreIndicatorIconImage(
+      const autofill::Suggestion& suggestion);
 #endif
 
-  // Convert a y-coordinate to the closest line.
-  int LineFromY(int y) const;
-
-  const gfx::Rect popup_bounds() const { return popup_bounds_; }
-
-  // Returns the bounds of the item at |index| in the popup, relative to
-  // the top left of the popup.
-  gfx::Rect GetRowBounds(size_t index) const;
+  bool is_credit_card_popup() const { return is_credit_card_popup_; }
 
   // Gets the resource value for the given resource, returning 0 if the
   // resource isn't recognized.
-  int GetIconResourceID(const std::string& resource_name) const;
-
-  bool is_credit_card_popup() const { return is_credit_card_popup_; }
+  static int GetIconResourceID(const std::string& resource_name);
 
   // Allows the provision of another implementation of view_common, for use in
   // unit tests where using the real thing could cause crashes.
   void SetUpForTesting(std::unique_ptr<PopupViewCommon> view_common);
 
  private:
-  // Returns the enclosing rectangle for the element_bounds.
-  gfx::Rect RoundedElementBounds() const;
-
 #if !defined(OS_ANDROID)
-  gfx::ImageSkia GetIconImageByName(const std::string& icon_str) const;
-
-  // The fonts for the popup text.
-  // Normal font (readable size, non bold).
-  gfx::FontList normal_font_list_;
-  // Slightly smaller than the normal font.
-  gfx::FontList smaller_font_list_;
-  // Bold version of the normal font.
-  gfx::FontList bold_font_list_;
+  static gfx::ImageSkia GetIconImageByName(const std::string& icon_str);
 #endif
 
-  // The bounds of the Autofill popup.
-  gfx::Rect popup_bounds_;
-
   std::unique_ptr<PopupViewCommon> view_common_;
 
-  AutofillPopupViewDelegate* delegate_;  // Weak reference.
-
   const bool is_credit_card_popup_;
 
   DISALLOW_COPY_AND_ASSIGN(AutofillPopupLayoutModel);
diff --git a/chrome/browser/ui/autofill/autofill_popup_layout_model_unittest.cc b/chrome/browser/ui/autofill/autofill_popup_layout_model_unittest.cc
index 35b1a58..1c44dd9a 100644
--- a/chrome/browser/ui/autofill/autofill_popup_layout_model_unittest.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_layout_model_unittest.cc
@@ -9,101 +9,25 @@
 #include <memory>
 #include <vector>
 
-#include "chrome/browser/ui/autofill/autofill_popup_view.h"
-#include "chrome/browser/ui/autofill/autofill_popup_view_delegate.h"
-#include "chrome/browser/ui/autofill/popup_constants.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
-#include "components/autofill/core/browser/ui/popup_item_ids.h"
-#include "components/autofill/core/browser/ui/suggestion.h"
-#include "components/grit/components_scaled_resources.h"
-#include "content/public/browser/web_contents.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/base/resource/resource_bundle.h"
-#include "ui/gfx/geometry/point.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/geometry/rect_f.h"
-#include "ui/gfx/image/image_skia.h"
-#include "ui/gfx/native_widget_types.h"
 
 namespace autofill {
 
 namespace {
 
-class TestAutofillPopupViewDelegate : public AutofillPopupViewDelegate {
- public:
-  explicit TestAutofillPopupViewDelegate(content::WebContents* web_contents)
-      : element_bounds_(0.0, 0.0, 100.0, 100.0),
-        container_view_(web_contents->GetNativeView()) {}
-
-  void Hide(PopupHidingReason reason) override {}
-  void ViewDestroyed() override {}
-  void SelectionCleared() override {}
-  gfx::NativeView container_view() const override { return container_view_; }
-  const gfx::RectF& element_bounds() const override { return element_bounds_; }
-  bool IsRTL() const override { return false; }
-
-  const std::vector<autofill::Suggestion> GetSuggestions() override {
-    // Give elements 1 and 3 subtexts and elements 2 and 3 icons, to ensure
-    // all combinations of subtexts and icons.
-    std::vector<Suggestion> suggestions;
-    suggestions.push_back(Suggestion("", "", "", 0));
-    suggestions.push_back(Suggestion("", "x", "", 0));
-    suggestions.push_back(Suggestion("", "", "americanExpressCC", 0));
-    suggestions.push_back(Suggestion("", "x", "genericCC", 0));
-    return suggestions;
-  }
-#if !defined(OS_ANDROID)
-  int GetElidedValueWidthForRow(int row) override { return 0; }
-  int GetElidedLabelWidthForRow(int row) override { return 0; }
-#endif
-
- private:
-  gfx::RectF element_bounds_;
-  gfx::NativeView container_view_;
-};
-
 class AutofillPopupLayoutModelTest : public ChromeRenderViewHostTestHarness {
  public:
   void SetUp() override {
     ChromeRenderViewHostTestHarness::SetUp();
-
-    delegate_ = std::make_unique<TestAutofillPopupViewDelegate>(web_contents());
-    layout_model_ = std::make_unique<AutofillPopupLayoutModel>(
-        delegate_.get(), false /* is_credit_card_field */);
+    layout_model_ = std::make_unique<AutofillPopupLayoutModel>(false);
   }
 
   AutofillPopupLayoutModel* layout_model() { return layout_model_.get(); }
 
  private:
-  std::unique_ptr<TestAutofillPopupViewDelegate> delegate_;
   std::unique_ptr<AutofillPopupLayoutModel> layout_model_;
 };
 
 }  // namespace
 
-#if !defined(OS_ANDROID)
-TEST_F(AutofillPopupLayoutModelTest, RowWidthWithoutText) {
-  int base_size =
-      AutofillPopupLayoutModel::kEndPadding * 2 + kPopupBorderThickness * 2;
-  int subtext_increase = AutofillPopupLayoutModel::kNamePadding;
-
-  // Refer to GetSuggestions() in TestAutofillPopupViewDelegate.
-  EXPECT_EQ(base_size,
-            layout_model()->RowWidthWithoutText(0, /* has_substext= */ false));
-  EXPECT_EQ(base_size + subtext_increase,
-            layout_model()->RowWidthWithoutText(1, /* has_substext= */ true));
-  EXPECT_EQ(base_size + AutofillPopupLayoutModel::kIconPadding +
-                ui::ResourceBundle::GetSharedInstance()
-                    .GetImageNamed(IDR_AUTOFILL_CC_AMEX)
-                    .Width(),
-            layout_model()->RowWidthWithoutText(2, /* has_substext= */ false));
-  EXPECT_EQ(base_size + subtext_increase +
-                AutofillPopupLayoutModel::kIconPadding +
-                ui::ResourceBundle::GetSharedInstance()
-                    .GetImageNamed(IDR_AUTOFILL_CC_GENERIC)
-                    .Width(),
-            layout_model()->RowWidthWithoutText(3, /* has_substext= */ true));
-}
-#endif
-
 }  // namespace autofill
diff --git a/chrome/browser/ui/autofill/autofill_popup_view_delegate.h b/chrome/browser/ui/autofill/autofill_popup_view_delegate.h
index f03d35a4..1f6e2b5 100644
--- a/chrome/browser/ui/autofill/autofill_popup_view_delegate.h
+++ b/chrome/browser/ui/autofill/autofill_popup_view_delegate.h
@@ -20,8 +20,6 @@
 
 namespace autofill {
 
-struct Suggestion;
-
 // Base class for Controllers of Autofill-style popups. This interface is
 // used by the relevant views to communicate with the controller.
 class AutofillPopupViewDelegate {
@@ -47,15 +45,6 @@
   // If the current popup should be displayed in RTL mode.
   virtual bool IsRTL() const = 0;
 
-  // Returns the full set of autofill suggestions, if applicable.
-  virtual const std::vector<autofill::Suggestion> GetSuggestions() = 0;
-
-#if !defined(OS_ANDROID)
-  // Returns elided values and labels for the given |row|.
-  virtual int GetElidedValueWidthForRow(int row) = 0;
-  virtual int GetElidedLabelWidthForRow(int row) = 0;
-#endif
-
  protected:
   virtual ~AutofillPopupViewDelegate() {}
 };
diff --git a/chrome/browser/ui/cocoa/touchbar/credit_card_autofill_touch_bar_controller_unittest.mm b/chrome/browser/ui/cocoa/touchbar/credit_card_autofill_touch_bar_controller_unittest.mm
index b16b417a..829aa37 100644
--- a/chrome/browser/ui/cocoa/touchbar/credit_card_autofill_touch_bar_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/touchbar/credit_card_autofill_touch_bar_controller_unittest.mm
@@ -30,8 +30,7 @@
  public:
   MockAutofillPopupController() {
     gfx::FontList::SetDefaultFontDescription("Arial, Times New Roman, 15px");
-    layout_model_ =
-        std::make_unique<autofill::AutofillPopupLayoutModel>(this, false);
+    layout_model_ = std::make_unique<autofill::AutofillPopupLayoutModel>(false);
     suggestions_.push_back(
         autofill::Suggestion("bufflehead", "canvasback", "goldeneye", 1));
     suggestions_.push_back(
@@ -88,7 +87,7 @@
 
   void SetIsCreditCardField(bool is_credit_card_field) {
     layout_model_.reset(
-        new autofill::AutofillPopupLayoutModel(this, is_credit_card_field));
+        new autofill::AutofillPopupLayoutModel(is_credit_card_field));
   }
 
   void set_line_count(int line_count) {
diff --git a/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc b/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc
index a7da3621..64a3e3e 100644
--- a/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc
+++ b/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc
@@ -13,6 +13,7 @@
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/browsing_data/browsing_data_helper.h"
+#include "chrome/browser/password_manager/account_storage/account_password_store_factory.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #include "chrome/browser/password_manager/password_store_factory.h"
 #include "chrome/browser/signin/signin_ui_util.h"
@@ -56,7 +57,7 @@
 
 namespace {
 
-password_manager::PasswordStore* GetPasswordStore(
+password_manager::PasswordStore* GetProfilePasswordStore(
     content::WebContents* web_contents) {
   return PasswordStoreFactory::GetForProfile(
              Profile::FromBrowserContext(web_contents->GetBrowserContext()),
@@ -64,6 +65,14 @@
       .get();
 }
 
+password_manager::PasswordStore* GetAccountPasswordStore(
+    content::WebContents* web_contents) {
+  return AccountPasswordStoreFactory::GetForProfile(
+             Profile::FromBrowserContext(web_contents->GetBrowserContext()),
+             ServiceAccessType::EXPLICIT_ACCESS)
+      .get();
+}
+
 std::vector<std::unique_ptr<autofill::PasswordForm>> CopyFormVector(
     const std::vector<std::unique_ptr<autofill::PasswordForm>>& forms) {
   std::vector<std::unique_ptr<autofill::PasswordForm>> result(forms.size());
@@ -90,13 +99,17 @@
       are_passwords_revealed_when_next_bubble_is_opened_(false) {
   passwords_data_.set_client(
       ChromePasswordManagerClient::FromWebContents(web_contents));
-  password_manager::PasswordStore* password_store =
-      GetPasswordStore(web_contents);
-  if (password_store)
-    password_store->AddObserver(this);
+  password_manager::PasswordStore* profile_password_store =
+      GetProfilePasswordStore(web_contents);
+  if (profile_password_store)
+    profile_password_store->AddObserver(this);
+  password_manager::PasswordStore* account_password_store =
+      GetAccountPasswordStore(web_contents);
+  if (account_password_store)
+    account_password_store->AddObserver(this);
 }
 
-ManagePasswordsUIController::~ManagePasswordsUIController() {}
+ManagePasswordsUIController::~ManagePasswordsUIController() = default;
 
 void ManagePasswordsUIController::OnPasswordSubmitted(
     std::unique_ptr<PasswordFormManagerForUI> form_manager) {
@@ -623,10 +636,14 @@
 }
 
 void ManagePasswordsUIController::WebContentsDestroyed() {
-  password_manager::PasswordStore* password_store =
-      GetPasswordStore(web_contents());
-  if (password_store)
-    password_store->RemoveObserver(this);
+  password_manager::PasswordStore* profile_password_store =
+      GetProfilePasswordStore(web_contents());
+  if (profile_password_store)
+    profile_password_store->RemoveObserver(this);
+  password_manager::PasswordStore* account_password_store =
+      GetAccountPasswordStore(web_contents());
+  if (account_password_store)
+    account_password_store->RemoveObserver(this);
   HidePasswordBubble();
 }
 
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
index 41b16fd..b388a1c 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
@@ -319,21 +319,6 @@
   return base::i18n::IsRTL();
 }
 
-const std::vector<autofill::Suggestion>
-PasswordGenerationPopupControllerImpl::GetSuggestions() {
-  return std::vector<autofill::Suggestion>();
-}
-
-#if !defined(OS_ANDROID)
-int PasswordGenerationPopupControllerImpl::GetElidedValueWidthForRow(int row) {
-  return 0;
-}
-
-int PasswordGenerationPopupControllerImpl::GetElidedLabelWidthForRow(int row) {
-  return 0;
-}
-#endif
-
 PasswordGenerationPopupController::GenerationUIState
 PasswordGenerationPopupControllerImpl::state() const {
   return state_;
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
index 75ff28b..c77f3f77 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
@@ -40,7 +40,6 @@
 
 namespace autofill {
 struct FormData;
-struct Suggestion;
 namespace password_generation {
 struct PasswordGenerationUIData;
 }  // namespace password_generation
@@ -137,11 +136,6 @@
   gfx::NativeView container_view() const override;
   const gfx::RectF& element_bounds() const override;
   bool IsRTL() const override;
-  const std::vector<autofill::Suggestion> GetSuggestions() override;
-#if !defined(OS_ANDROID)
-  int GetElidedValueWidthForRow(int row) override;
-  int GetElidedLabelWidthForRow(int row) override;
-#endif
 
   GenerationUIState state() const override;
   bool password_selected() const override;
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view_browsertest.cc b/chrome/browser/ui/views/autofill/autofill_popup_base_view_browsertest.cc
index 8ae813c..7ece012 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_base_view_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view_browsertest.cc
@@ -41,11 +41,6 @@
   MOCK_CONST_METHOD0(container_view, gfx::NativeView());
   MOCK_CONST_METHOD0(element_bounds, gfx::RectF&());
   MOCK_CONST_METHOD0(IsRTL, bool());
-  MOCK_METHOD0(GetSuggestions, const std::vector<autofill::Suggestion>());
-#if !defined(OS_ANDROID)
-  MOCK_METHOD1(GetElidedValueWidthForRow, int(int));
-  MOCK_METHOD1(GetElidedLabelWidthForRow, int(int));
-#endif
 };
 
 }  // namespace
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
index d86cc08e..6b70805 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
@@ -447,8 +447,10 @@
   layout_manager->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
+  std::vector<autofill::Suggestion> suggestions = controller->GetSuggestions();
+
   const gfx::ImageSkia icon =
-      controller->layout_model().GetIconImage(line_number());
+      controller->layout_model().GetIconImage(suggestions[line_number()]);
 
   if (!icon.isNull()) {
     AddIcon(icon);
@@ -489,7 +491,8 @@
 
   AddChildView(std::move(all_labels));
   const gfx::ImageSkia store_indicator_icon =
-      controller->layout_model().GetStoreIndicatorIconImage(line_number());
+      controller->layout_model().GetStoreIndicatorIconImage(
+          suggestions[line_number()]);
   if (!store_indicator_icon.isNull()) {
     AddSpacerWithSize(GetHorizontalMargin(),
                       /*resize=*/true, layout_manager);
@@ -719,8 +722,8 @@
   layout_manager->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStretch);
 
-  const gfx::ImageSkia icon =
-      controller->layout_model().GetIconImage(line_number());
+  const gfx::ImageSkia icon = controller->layout_model().GetIconImage(
+      controller->GetSuggestions()[line_number()]);
 
   // A FooterView shows an icon, if any, on the trailing (right in LTR) side,
   // but the Show Account Cards context is an anomaly. Its icon is on the
diff --git a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc
index 7660ca0..abe19d4 100644
--- a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc
+++ b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc
@@ -1278,7 +1278,9 @@
   CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyShown);
 }
 
-IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, TestPermissionBubble) {
+// Disabled due to flakiness: https://crbug.com/1033651
+IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest,
+                       DISABLED_TestPermissionBubble) {
   ToggleTabletMode();
   ASSERT_TRUE(GetTabletModeEnabled());
   EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
diff --git a/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.cc
index 1a2a0cd..aa87c7a 100644
--- a/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.cc
@@ -75,6 +75,11 @@
                             g_browser_process->GetApplicationLocale().c_str());
 }
 
+std::string EulaScreenHandler::GetAdditionalToSUrl() {
+  return base::StringPrintf(chrome::kAdditionalToSOnlineURLPath,
+                            g_browser_process->GetApplicationLocale().c_str());
+}
+
 void EulaScreenHandler::DeclareLocalizedValues(
     ::login::LocalizedValuesBuilder* builder) {
   builder->Add("eulaScreenAccessibleTitle", IDS_EULA_SCREEN_ACCESSIBLE_TITLE);
@@ -107,9 +112,11 @@
 
   // Online URL to use. May be overridden by tests.
   builder->Add("eulaOnlineUrl", GetEulaOnlineUrl());
+  builder->Add("eulaAdditionalToSOnlineUrl", GetAdditionalToSUrl());
 
   /* MD-OOBE */
   builder->Add("oobeEulaSectionTitle", IDS_OOBE_EULA_SECTION_TITLE);
+  builder->Add("oobeEulaAditionalTerms", IDS_OOBE_EULA_ADDITIONAL_TERMS);
   builder->Add("oobeEulaIframeLabel", IDS_OOBE_EULA_IFRAME_LABEL);
   builder->Add("oobeEulaAcceptAndContinueButtonText",
                IDS_OOBE_EULA_ACCEPT_AND_CONTINUE_BUTTON_TEXT);
diff --git a/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.h
index 848c494..bb724d1f 100644
--- a/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.h
@@ -74,6 +74,7 @@
   // Determines the online URL to use.
   std::string GetEulaOnlineUrl();
   static const char* eula_url_for_testing_;
+  std::string GetAdditionalToSUrl();
 
   void UpdateLocalizedValues(::login::SecureModuleUsed secure_module_used);
 
diff --git a/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.cc
index ca64a2c2..8b92640 100644
--- a/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.cc
@@ -110,7 +110,7 @@
                          IDS_LOGIN_SYNC_CONSENT_SCREEN_ACCEPT_AND_CONTINUE,
                          builder);
 
-  // SplitSettingsSync strings. The version of the dialog to show is chosen
+  // SplitSyncConsent strings. The version of the dialog to show is chosen
   // after the WebUI is loaded, so always supply both sets of strings.
   RememberLocalizedValue("osSyncConsentTitle", IDS_LOGIN_OS_SYNC_CONSENT_TITLE,
                          builder);
@@ -152,15 +152,15 @@
 
 void SyncConsentScreenHandler::GetAdditionalParameters(
     base::DictionaryValue* parameters) {
-  parameters->SetBoolean("splitSettingsSync",
-                         chromeos::features::IsSplitSettingsSyncEnabled());
+  parameters->SetBoolean("splitSyncConsent",
+                         chromeos::features::IsSplitSyncConsentEnabled());
   BaseScreenHandler::GetAdditionalParameters(parameters);
 }
 
 void SyncConsentScreenHandler::HandleContinueAndReview(
     const login::StringList& consent_description,
     const std::string& consent_confirmation) {
-  DCHECK(!chromeos::features::IsSplitSettingsSyncEnabled());
+  DCHECK(!chromeos::features::IsSplitSyncConsentEnabled());
   std::vector<int> consent_description_ids;
   int consent_confirmation_id;
   GetConsentIDs(known_string_ids_, consent_description, consent_confirmation,
@@ -179,7 +179,7 @@
 void SyncConsentScreenHandler::HandleContinueWithDefaults(
     const login::StringList& consent_description,
     const std::string& consent_confirmation) {
-  DCHECK(!chromeos::features::IsSplitSettingsSyncEnabled());
+  DCHECK(!chromeos::features::IsSplitSyncConsentEnabled());
   std::vector<int> consent_description_ids;
   int consent_confirmation_id;
   GetConsentIDs(known_string_ids_, consent_description, consent_confirmation,
@@ -199,7 +199,7 @@
     const login::StringList& consent_description,
     const std::string& consent_confirmation,
     bool enable_os_sync) {
-  DCHECK(chromeos::features::IsSplitSettingsSyncEnabled());
+  DCHECK(chromeos::features::IsSplitSyncConsentEnabled());
   std::vector<int> consent_description_ids;
   int consent_confirmation_id;
   GetConsentIDs(known_string_ids_, consent_description, consent_confirmation,
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc
index 60ec95a5..b26d934 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc
@@ -2002,7 +2002,7 @@
   html_source->AddBoolean("isAccountManagerEnabled",
                           chromeos::IsAccountManagerAvailable(profile));
 
-  if (chromeos::features::IsSplitSettingsSyncEnabled()) {
+  if (chromeos::features::IsSplitSyncConsentEnabled()) {
     static constexpr webui::LocalizedString kTurnOffStrings[] = {
         {"syncDisconnect", IDS_SETTINGS_PEOPLE_SYNC_TURN_OFF},
         {"syncDisconnectTitle",
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc
index e01c22d..ae0407a 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc
@@ -169,6 +169,8 @@
                                                    ::features::kAppManagement));
   html_source->AddBoolean("splitSettingsSyncEnabled",
                           chromeos::features::IsSplitSettingsSyncEnabled());
+  html_source->AddBoolean("splitSyncConsent",
+                          chromeos::features::IsSplitSyncConsentEnabled());
 
   html_source->AddBoolean(
       "isSupportedArcVersion",
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler.cc b/chrome/browser/ui/webui/settings/safety_check_handler.cc
index 6da98bb..3bde7f6 100644
--- a/chrome/browser/ui/webui/settings/safety_check_handler.cc
+++ b/chrome/browser/ui/webui/settings/safety_check_handler.cc
@@ -5,18 +5,19 @@
 #include "chrome/browser/ui/webui/settings/safety_check_handler.h"
 
 #include "base/bind.h"
+#include "chrome/browser/password_manager/bulk_leak_check_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/webui/settings/safety_check_handler_observer.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 
 namespace {
 
 // Constants for communication with JS.
-static constexpr char kStatusChanged[] = "safety-check-status-changed";
-static constexpr char kPerformSafetyCheck[] = "performSafetyCheck";
-static constexpr char kSafetyCheckComponent[] = "safetyCheckComponent";
-static constexpr char kNewState[] = "newState";
+constexpr char kStatusChanged[] = "safety-check-status-changed";
+constexpr char kPerformSafetyCheck[] = "performSafetyCheck";
+constexpr char kSafetyCheckComponent[] = "safetyCheckComponent";
+constexpr char kNewState[] = "newState";
+constexpr char kPasswordsCompromised[] = "passwordsCompromised";
 
 // Converts the VersionUpdater::Status to the UpdateStatus enum to be passed
 // to the safety check frontend. Note: if the VersionUpdater::Status gets
@@ -47,8 +48,7 @@
 }
 }  // namespace
 
-SafetyCheckHandler::SafetyCheckHandler()
-    : SafetyCheckHandler(nullptr, nullptr) {}
+SafetyCheckHandler::SafetyCheckHandler() = default;
 
 SafetyCheckHandler::~SafetyCheckHandler() = default;
 
@@ -59,12 +59,19 @@
   }
   CheckUpdates();
   CheckSafeBrowsing();
+  if (!leak_service_) {
+    leak_service_ = BulkLeakCheckServiceFactory::GetForProfile(
+        Profile::FromWebUI(web_ui()));
+  }
+  DCHECK(leak_service_);
+  CheckPasswords();
 }
 
 SafetyCheckHandler::SafetyCheckHandler(
     std::unique_ptr<VersionUpdater> version_updater,
-    SafetyCheckHandlerObserver* observer)
-    : version_updater_(std::move(version_updater)), observer_(observer) {}
+    password_manager::BulkLeakCheckService* leak_service)
+    : version_updater_(std::move(version_updater)),
+      leak_service_(leak_service) {}
 
 void SafetyCheckHandler::HandlePerformSafetyCheck(
     const base::ListValue* /*args*/) {
@@ -72,9 +79,6 @@
 }
 
 void SafetyCheckHandler::CheckUpdates() {
-  if (observer_) {
-    observer_->OnUpdateCheckStart();
-  }
   version_updater_->CheckForUpdate(
       base::Bind(&SafetyCheckHandler::OnUpdateCheckResult,
                  base::Unretained(this)),
@@ -82,9 +86,6 @@
 }
 
 void SafetyCheckHandler::CheckSafeBrowsing() {
-  if (observer_) {
-    observer_->OnSafeBrowsingCheckStart();
-  }
   PrefService* pref_service = Profile::FromWebUI(web_ui())->GetPrefs();
   const PrefService::Preference* pref =
       pref_service->FindPreference(prefs::kSafeBrowsingEnabled);
@@ -101,39 +102,100 @@
   OnSafeBrowsingCheckResult(status);
 }
 
-void SafetyCheckHandler::OnUpdateCheckResult(
-    VersionUpdater::Status status,
-    int /*progress*/,
-    bool /*rollback*/,
-    const std::string& /*version*/,
-    int64_t /*update_size*/,
-    const base::string16& /*message*/) {
+void SafetyCheckHandler::CheckPasswords() {
+  // Remove |this| as an existing observer for BulkLeakCheck if it is
+  // registered. This takes care of an edge case when safety check starts twice
+  // on the same page. Normally this should not happen, but if it does, the
+  // browser should not crash.
+  observed_leak_check_.RemoveAll();
+  observed_leak_check_.Add(leak_service_);
+  // TODO(crbug.com/1015841): Implement starting a leak check if one is not
+  // running already once the API for it becomes available (see
+  // crrev.com/c/2072742 and follow up CLs).
+}
+
+void SafetyCheckHandler::OnUpdateCheckResult(VersionUpdater::Status status,
+                                             int progress,
+                                             bool rollback,
+                                             const std::string& version,
+                                             int64_t update_size,
+                                             const base::string16& message) {
   UpdateStatus update_status = ConvertToUpdateStatus(status);
-  if (observer_) {
-    observer_->OnUpdateCheckResult(update_status);
-  }
-  auto event = std::make_unique<base::DictionaryValue>();
-  event->SetInteger(kSafetyCheckComponent,
-                    static_cast<int>(SafetyCheckComponent::kUpdates));
-  event->SetInteger(kNewState, static_cast<int>(update_status));
-  FireWebUIListener(kStatusChanged, *event);
+  base::DictionaryValue event;
+  event.SetIntKey(kSafetyCheckComponent,
+                  static_cast<int>(SafetyCheckComponent::kUpdates));
+  event.SetIntKey(kNewState, static_cast<int>(update_status));
+  FireWebUIListener(kStatusChanged, event);
 }
 
 void SafetyCheckHandler::OnSafeBrowsingCheckResult(
     SafetyCheckHandler::SafeBrowsingStatus status) {
-  if (observer_) {
-    observer_->OnSafeBrowsingCheckResult(status);
+  base::DictionaryValue event;
+  event.SetIntKey(kSafetyCheckComponent,
+                  static_cast<int>(SafetyCheckComponent::kSafeBrowsing));
+  event.SetIntKey(kNewState, static_cast<int>(status));
+  FireWebUIListener(kStatusChanged, event);
+}
+
+void SafetyCheckHandler::OnPasswordsCheckResult(PasswordsStatus status,
+                                                int num_compromised) {
+  base::DictionaryValue event;
+  event.SetIntKey(kSafetyCheckComponent,
+                  static_cast<int>(SafetyCheckComponent::kPasswords));
+  event.SetIntKey(kNewState, static_cast<int>(status));
+  if (status == PasswordsStatus::kCompromisedExist) {
+    event.SetIntKey(kPasswordsCompromised, num_compromised);
   }
-  auto event = std::make_unique<base::DictionaryValue>();
-  event->SetInteger(kSafetyCheckComponent,
-                    static_cast<int>(SafetyCheckComponent::kSafeBrowsing));
-  event->SetInteger(kNewState, static_cast<int>(status));
-  FireWebUIListener(kStatusChanged, *event);
+  FireWebUIListener(kStatusChanged, event);
+}
+
+void SafetyCheckHandler::OnStateChanged(
+    password_manager::BulkLeakCheckService::State state,
+    size_t pending_credentials) {
+  using password_manager::BulkLeakCheckService;
+  switch (state) {
+    case BulkLeakCheckService::State::kIdle:
+      // TODO(crbug.com/1015841): Implement retrieving the number
+      // of leaked passwords (if any) once PasswordsPrivateDelegate provides an
+      // API for that (see crrev.com/c/2072742).
+      break;
+    case BulkLeakCheckService::State::kRunning:
+      OnPasswordsCheckResult(PasswordsStatus::kChecking, 0);
+      return;
+    case BulkLeakCheckService::State::kSignedOut:
+      OnPasswordsCheckResult(PasswordsStatus::kSignedOut, 0);
+      break;
+    case BulkLeakCheckService::State::kNetworkError:
+      OnPasswordsCheckResult(PasswordsStatus::kOffline, 0);
+      break;
+    case BulkLeakCheckService::State::kTokenRequestFailure:
+    case BulkLeakCheckService::State::kHashingFailure:
+    case BulkLeakCheckService::State::kServiceError:
+      OnPasswordsCheckResult(PasswordsStatus::kError, 0);
+      break;
+  }
+  // TODO(crbug.com/1015841): implement detecting the following states if it is
+  // possible: kNoPasswords, kQuotaLimit, and kTooManyPasswords.
+
+  // Stop observing the leak service in all terminal states.
+  observed_leak_check_.Remove(leak_service_);
+}
+
+void SafetyCheckHandler::OnLeakFound(
+    const password_manager::LeakCheckCredential& credential) {
+  // Do nothing because we only want to know the total number of compromised
+  // credentials at the end of the bulk leak check.
 }
 
 void SafetyCheckHandler::OnJavascriptAllowed() {}
 
-void SafetyCheckHandler::OnJavascriptDisallowed() {}
+void SafetyCheckHandler::OnJavascriptDisallowed() {
+  // Remove |this| as an observer for BulkLeakCheck. This takes care of an edge
+  // case when the page is reloaded while the password check is in progress and
+  // another safety check is started. Otherwise |observed_leak_check_|
+  // automatically calls RemoveAll() on destruction.
+  observed_leak_check_.RemoveAll();
+}
 
 void SafetyCheckHandler::RegisterMessages() {
   web_ui()->RegisterMessageCallback(
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler.h b/chrome/browser/ui/webui/settings/safety_check_handler.h
index 7721fd5..4a20d23 100644
--- a/chrome/browser/ui/webui/settings/safety_check_handler.h
+++ b/chrome/browser/ui/webui/settings/safety_check_handler.h
@@ -13,15 +13,17 @@
 #include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
 #include "chrome/browser/ui/webui/help/version_updater.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
-
-class SafetyCheckHandlerObserver;
+#include "components/password_manager/core/browser/bulk_leak_check_service.h"
 
 // Settings page UI handler that checks four areas of browser safety:
 // browser updates, password leaks, malicious extensions, and unwanted
 // software.
-class SafetyCheckHandler : public settings::SettingsPageUIHandler {
+class SafetyCheckHandler
+    : public settings::SettingsPageUIHandler,
+      public password_manager::BulkLeakCheckService::Observer {
  public:
   // Used in communication with the frontend to indicate which component the
   // communicated status update applies to.
@@ -31,6 +33,9 @@
     kSafeBrowsing,
     kExtensions,
   };
+  // The following enums represent the state of each component of the safety
+  // check and should be kept in sync with the JS frontend
+  // (safety_check_browser_proxy.js).
   enum class UpdateStatus {
     kChecking,
     kUpdated,
@@ -47,6 +52,17 @@
     kDisabledByAdmin,
     kDisabledByExtension,
   };
+  enum class PasswordsStatus {
+    kChecking,
+    kSafe,
+    kCompromisedExist,
+    kOffline,
+    kNoPasswords,
+    kSignedOut,
+    kQuotaLimit,
+    kTooManyPasswords,
+    kError,
+  };
 
   SafetyCheckHandler();
   ~SafetyCheckHandler() override;
@@ -58,14 +74,14 @@
 
  protected:
   SafetyCheckHandler(std::unique_ptr<VersionUpdater> version_updater,
-                     SafetyCheckHandlerObserver* observer);
+                     password_manager::BulkLeakCheckService* leak_service);
 
  private:
   // Handles triggering the safety check from the frontend (by user pressing a
   // button).
   void HandlePerformSafetyCheck(const base::ListValue* args);
 
-  // Triggers an update check and invokes the provided callback once results
+  // Triggers an update check and invokes OnUpdateCheckResult once results
   // are available.
   void CheckUpdates();
 
@@ -73,6 +89,10 @@
   // OnSafeBrowsingCheckResult with results.
   void CheckSafeBrowsing();
 
+  // Triggers a bulk password leak check and invokes OnPasswordsCheckResult once
+  // results are available.
+  void CheckPasswords();
+
   // Callbacks that get triggered when each check completes.
   void OnUpdateCheckResult(VersionUpdater::Status status,
                            int progress,
@@ -83,6 +103,14 @@
 
   void OnSafeBrowsingCheckResult(SafeBrowsingStatus status);
 
+  void OnPasswordsCheckResult(PasswordsStatus status, int num_compromised);
+
+  // BulkLeakCheckService::Observer implementation.
+  void OnStateChanged(password_manager::BulkLeakCheckService::State state,
+                      size_t pending_credentials) override;
+  void OnLeakFound(
+      const password_manager::LeakCheckCredential& credential) override;
+
   // SettingsPageUIHandler implementation.
   void OnJavascriptAllowed() override;
   void OnJavascriptDisallowed() override;
@@ -91,7 +119,10 @@
   void RegisterMessages() override;
 
   std::unique_ptr<VersionUpdater> version_updater_;
-  SafetyCheckHandlerObserver* const observer_;
+  password_manager::BulkLeakCheckService* leak_service_ = nullptr;
+  ScopedObserver<password_manager::BulkLeakCheckService,
+                 password_manager::BulkLeakCheckService::Observer>
+      observed_leak_check_{this};
 };
 
 #endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_SAFETY_CHECK_HANDLER_H_
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler_observer.h b/chrome/browser/ui/webui/settings/safety_check_handler_observer.h
deleted file mode 100644
index b1fe68c..0000000
--- a/chrome/browser/ui/webui/settings/safety_check_handler_observer.h
+++ /dev/null
@@ -1,27 +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_WEBUI_SETTINGS_SAFETY_CHECK_HANDLER_OBSERVER_H_
-#define CHROME_BROWSER_UI_WEBUI_SETTINGS_SAFETY_CHECK_HANDLER_OBSERVER_H_
-
-#include "chrome/browser/ui/webui/help/version_updater.h"
-#include "chrome/browser/ui/webui/settings/safety_check_handler.h"
-
-// Observer for SafetyCheckHandler events. Currently only used for testing.
-class SafetyCheckHandlerObserver {
- public:
-  SafetyCheckHandlerObserver() = default;
-  SafetyCheckHandlerObserver(const SafetyCheckHandlerObserver&) = delete;
-  SafetyCheckHandlerObserver& operator=(const SafetyCheckHandlerObserver&) =
-      delete;
-  virtual ~SafetyCheckHandlerObserver() = default;
-
-  virtual void OnUpdateCheckStart() = 0;
-  virtual void OnUpdateCheckResult(SafetyCheckHandler::UpdateStatus status) = 0;
-  virtual void OnSafeBrowsingCheckStart() = 0;
-  virtual void OnSafeBrowsingCheckResult(
-      SafetyCheckHandler::SafeBrowsingStatus status) = 0;
-};
-
-#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_SAFETY_CHECK_HANDLER_OBSERVER_H_
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc b/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
index e1a9d18..c817164 100644
--- a/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
@@ -7,46 +7,25 @@
 #include "base/bind.h"
 #include "base/optional.h"
 #include "chrome/browser/ui/webui/help/test_version_updater.h"
-#include "chrome/browser/ui/webui/settings/safety_check_handler_observer.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "components/password_manager/core/browser/bulk_leak_check_service.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/test_web_ui.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-class TestSafetyCheckObserver : public SafetyCheckHandlerObserver {
- public:
-  void OnUpdateCheckStart() override { update_check_started_ = true; }
-
-  void OnUpdateCheckResult(SafetyCheckHandler::UpdateStatus status) override {
-    update_check_status_ = status;
-  }
-
-  void OnSafeBrowsingCheckStart() override {
-    safe_browsing_check_started_ = true;
-  }
-
-  void OnSafeBrowsingCheckResult(
-      SafetyCheckHandler::SafeBrowsingStatus status) override {
-    safe_browsing_check_status_ = status;
-  }
-
-  bool update_check_started_ = false;
-  base::Optional<SafetyCheckHandler::UpdateStatus> update_check_status_;
-  bool safe_browsing_check_started_ = false;
-  base::Optional<SafetyCheckHandler::SafeBrowsingStatus>
-      safe_browsing_check_status_;
-};
-
 class TestingSafetyCheckHandler : public SafetyCheckHandler {
  public:
   using SafetyCheckHandler::AllowJavascript;
+  using SafetyCheckHandler::DisallowJavascript;
   using SafetyCheckHandler::set_web_ui;
 
-  TestingSafetyCheckHandler(std::unique_ptr<VersionUpdater> version_updater,
-                            SafetyCheckHandlerObserver* observer)
-      : SafetyCheckHandler(std::move(version_updater), observer) {}
+  TestingSafetyCheckHandler(
+      std::unique_ptr<VersionUpdater> version_updater,
+      password_manager::BulkLeakCheckService* leak_service)
+      : SafetyCheckHandler(std::move(version_updater), leak_service) {}
 };
 
 class SafetyCheckHandlerTest : public ChromeRenderViewHostTestHarness {
@@ -59,7 +38,7 @@
 
  protected:
   TestVersionUpdater* version_updater_ = nullptr;
-  TestSafetyCheckObserver observer_;
+  std::unique_ptr<password_manager::BulkLeakCheckService> test_leak_service_;
   content::TestWebUI test_web_ui_;
   std::unique_ptr<TestingSafetyCheckHandler> safety_check_;
 };
@@ -71,10 +50,12 @@
   // SafetyCheckHandler, but a raw pointer is retained here to change its
   // state.
   auto version_updater = std::make_unique<TestVersionUpdater>();
+  test_leak_service_ = std::make_unique<password_manager::BulkLeakCheckService>(
+      nullptr, nullptr);
   version_updater_ = version_updater.get();
   test_web_ui_.set_web_contents(web_contents());
   safety_check_ = std::make_unique<TestingSafetyCheckHandler>(
-      std::move(version_updater), &observer_);
+      std::move(version_updater), test_leak_service_.get());
   test_web_ui_.ClearTrackedCalls();
   safety_check_->set_web_ui(&test_web_ui_);
   safety_check_->AllowJavascript();
@@ -108,18 +89,9 @@
   return false;
 }
 
-TEST_F(SafetyCheckHandlerTest, PerformSafetyCheck_AllChecksInvoked) {
-  safety_check_->PerformSafetyCheck();
-  EXPECT_TRUE(observer_.update_check_started_);
-  EXPECT_TRUE(observer_.safe_browsing_check_started_);
-}
-
 TEST_F(SafetyCheckHandlerTest, CheckUpdates_Updated) {
   version_updater_->SetReturnedStatus(VersionUpdater::Status::UPDATED);
   safety_check_->PerformSafetyCheck();
-  ASSERT_TRUE(observer_.update_check_status_.has_value());
-  EXPECT_EQ(SafetyCheckHandler::UpdateStatus::kUpdated,
-            observer_.update_check_status_);
   EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
       static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kUpdates),
       static_cast<int>(SafetyCheckHandler::UpdateStatus::kUpdated)));
@@ -129,9 +101,6 @@
   version_updater_->SetReturnedStatus(
       VersionUpdater::Status::DISABLED_BY_ADMIN);
   safety_check_->PerformSafetyCheck();
-  ASSERT_TRUE(observer_.update_check_status_.has_value());
-  EXPECT_EQ(SafetyCheckHandler::UpdateStatus::kDisabledByAdmin,
-            observer_.update_check_status_);
   EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
       static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kUpdates),
       static_cast<int>(SafetyCheckHandler::UpdateStatus::kDisabledByAdmin)));
@@ -142,9 +111,6 @@
       ->GetPrefs()
       ->SetBoolean(prefs::kSafeBrowsingEnabled, true);
   safety_check_->PerformSafetyCheck();
-  ASSERT_TRUE(observer_.safe_browsing_check_status_.has_value());
-  EXPECT_EQ(SafetyCheckHandler::SafeBrowsingStatus::kEnabled,
-            observer_.safe_browsing_check_status_);
   EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
       static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kSafeBrowsing),
       static_cast<int>(SafetyCheckHandler::SafeBrowsingStatus::kEnabled)));
@@ -155,9 +121,6 @@
       ->GetPrefs()
       ->SetBoolean(prefs::kSafeBrowsingEnabled, false);
   safety_check_->PerformSafetyCheck();
-  ASSERT_TRUE(observer_.safe_browsing_check_status_.has_value());
-  EXPECT_EQ(SafetyCheckHandler::SafeBrowsingStatus::kDisabled,
-            observer_.safe_browsing_check_status_);
   EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
       static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kSafeBrowsing),
       static_cast<int>(SafetyCheckHandler::SafeBrowsingStatus::kDisabled)));
@@ -170,9 +133,6 @@
       ->SetManagedPref(prefs::kSafeBrowsingEnabled,
                        std::make_unique<base::Value>(false));
   safety_check_->PerformSafetyCheck();
-  ASSERT_TRUE(observer_.safe_browsing_check_status_.has_value());
-  EXPECT_EQ(SafetyCheckHandler::SafeBrowsingStatus::kDisabledByAdmin,
-            observer_.safe_browsing_check_status_);
   EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
       static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kSafeBrowsing),
       static_cast<int>(
@@ -186,11 +146,73 @@
       ->SetExtensionPref(prefs::kSafeBrowsingEnabled,
                          std::make_unique<base::Value>(false));
   safety_check_->PerformSafetyCheck();
-  ASSERT_TRUE(observer_.safe_browsing_check_status_.has_value());
-  EXPECT_EQ(SafetyCheckHandler::SafeBrowsingStatus::kDisabledByExtension,
-            observer_.safe_browsing_check_status_);
   EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
       static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kSafeBrowsing),
       static_cast<int>(
           SafetyCheckHandler::SafeBrowsingStatus::kDisabledByExtension)));
 }
+
+TEST_F(SafetyCheckHandlerTest, CheckPasswords_ObserverRemovedAfterError) {
+  safety_check_->PerformSafetyCheck();
+  // First, a "running" change of state.
+  test_leak_service_->set_state_and_notify(
+      password_manager::BulkLeakCheckService::State::kRunning);
+  EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
+      static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kPasswords),
+      static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking)));
+  // Second, an "offline" state.
+  test_leak_service_->set_state_and_notify(
+      password_manager::BulkLeakCheckService::State::kNetworkError);
+  EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
+      static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kPasswords),
+      static_cast<int>(SafetyCheckHandler::PasswordsStatus::kOffline)));
+  // Another error, but since the previous state is terminal, the handler should
+  // no longer be observing the BulkLeakCheckService state.
+  test_leak_service_->set_state_and_notify(
+      password_manager::BulkLeakCheckService::State::kServiceError);
+  EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
+      static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kPasswords),
+      static_cast<int>(SafetyCheckHandler::PasswordsStatus::kOffline)));
+}
+
+TEST_F(SafetyCheckHandlerTest, CheckPasswords_InterruptedAndRefreshed) {
+  safety_check_->PerformSafetyCheck();
+  // Password check running.
+  test_leak_service_->set_state_and_notify(
+      password_manager::BulkLeakCheckService::State::kRunning);
+  EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
+      static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kPasswords),
+      static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking)));
+  // The check gets interrupted and the page is refreshed.
+  safety_check_->DisallowJavascript();
+  safety_check_->AllowJavascript();
+  // Another run of the safety check.
+  safety_check_->PerformSafetyCheck();
+  test_leak_service_->set_state_and_notify(
+      password_manager::BulkLeakCheckService::State::kRunning);
+  EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
+      static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kPasswords),
+      static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking)));
+  test_leak_service_->set_state_and_notify(
+      password_manager::BulkLeakCheckService::State::kNetworkError);
+  EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
+      static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kPasswords),
+      static_cast<int>(SafetyCheckHandler::PasswordsStatus::kOffline)));
+}
+
+TEST_F(SafetyCheckHandlerTest, CheckPasswords_StartedTwice) {
+  safety_check_->PerformSafetyCheck();
+  safety_check_->PerformSafetyCheck();
+  // First, a "running" change of state.
+  test_leak_service_->set_state_and_notify(
+      password_manager::BulkLeakCheckService::State::kRunning);
+  EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
+      static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kPasswords),
+      static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking)));
+  // Second, an "offline" state.
+  test_leak_service_->set_state_and_notify(
+      password_manager::BulkLeakCheckService::State::kNetworkError);
+  EXPECT_TRUE(HasSafetyCheckStatusChangedWithData(
+      static_cast<int>(SafetyCheckHandler::SafetyCheckComponent::kPasswords),
+      static_cast<int>(SafetyCheckHandler::PasswordsStatus::kOffline)));
+}
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 2f2a4490..13e2f496 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -883,15 +883,15 @@
                              Profile* profile) {
 #if defined(OS_CHROMEOS)
   bool is_dice_enabled = false;
-  bool is_split_settings_sync_enabled =
-      chromeos::features::IsSplitSettingsSyncEnabled();
+  bool is_split_sync_consent_enabled =
+      chromeos::features::IsSplitSyncConsentEnabled();
 #else
   bool is_dice_enabled =
       AccountConsistencyModeManager::IsDiceEnabledForProfile(profile);
-  bool is_split_settings_sync_enabled = false;
+  bool is_split_sync_consent_enabled = false;
 #endif
 
-  if (is_split_settings_sync_enabled || is_dice_enabled) {
+  if (is_split_sync_consent_enabled || is_dice_enabled) {
     static constexpr webui::LocalizedString kTurnOffStrings[] = {
         {"syncDisconnect", IDS_SETTINGS_PEOPLE_SYNC_TURN_OFF},
         {"syncDisconnectTitle",
@@ -1126,8 +1126,6 @@
        IDS_SETTINGS_SAFETY_CHECK_UPDATES_SUB_LABEL_FAILED_OFFLINE},
       {"safetyCheckUpdatesSubLabelFailed",
        IDS_SETTINGS_SAFETY_CHECK_UPDATES_SUB_LABEL_FAILED},
-      {"safetyCheckPasswordsPrimaryLabel",
-       IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_PRIMARY_LABEL},
       {"safetyCheckPasswordsSubLabelSafe",
        IDS_SAFETY_CHECK_PASSWORDS_SUB_LABEL_SAFE},
       {"safetyCheckPasswordsSubLabelCompromisedSingular",
@@ -1150,6 +1148,16 @@
        IDS_SAFETY_CHECK_PASSWORDS_BUTTON_COMPROMISED},
       {"safetyCheckPasswordsButtonError",
        IDS_SAFETY_CHECK_PASSWORDS_BUTTON_ERROR},
+      {"safetyCheckSafeBrowsingSubLabelEnabled",
+       IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_SUB_LABEL_ENABLED},
+      {"safetyCheckSafeBrowsingSubLabelDisabled",
+       IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_SUB_LABEL_DISABLED},
+      {"safetyCheckSafeBrowsingSubLabelDisabledByAdmin",
+       IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_SUB_LABEL_DISABLED_BY_ADMIN},
+      {"safetyCheckSafeBrowsingSubLabelDisabledByExtension",
+       IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_SUB_LABEL_DISABLED_BY_EXTENSION},
+      {"safetyCheckSafeBrowsingButton",
+       IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_BUTTON},
       {"safetyCheckExtensionsPrimaryLabel",
        IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_PRIMARY_LABEL},
       {"safetyCheckExtensionsSubLabelError",
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index da445d8..b6702e7 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -280,6 +280,8 @@
 #if defined(OS_CHROMEOS)
   html_source->AddBoolean("splitSettingsSyncEnabled",
                           chromeos::features::IsSplitSettingsSyncEnabled());
+  html_source->AddBoolean("splitSyncConsent",
+                          chromeos::features::IsSplitSyncConsentEnabled());
 
   html_source->AddBoolean(
       "userCannotManuallyEnterPassword",
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
index 5121fa7..4bddb4a2 100644
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -128,6 +128,7 @@
         "$root_gen_dir/chrome/component_extension_resources.pak",
         "$root_gen_dir/chrome/dev_ui_resources.pak",
         "$root_gen_dir/chrome/downloads_resources.pak",
+        "$root_gen_dir/chrome/gaia_auth_host_resources.pak",
         "$root_gen_dir/chrome/history_resources.pak",
         "$root_gen_dir/chrome/local_ntp_resources.pak",
         "$root_gen_dir/chrome/new_tab_page_resources.pak",
@@ -140,6 +141,7 @@
         "//chrome/browser/resources:component_extension_resources",
         "//chrome/browser/resources:dev_ui_paks",
         "//chrome/browser/resources:downloads_resources",
+        "//chrome/browser/resources:gaia_auth_host_resources",
         "//chrome/browser/resources:history_resources",
         "//chrome/browser/resources:local_ntp_resources",
         "//chrome/browser/resources:new_tab_page_resources",
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 6e32f3d..1640e0c 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -359,7 +359,10 @@
 const char kOemEulaURLPath[] = "oem";
 
 const char kOnlineEulaURLPath[] =
-    "https://www.google.com/intl/%s/chrome/eula_text.html";
+    "https://policies.google.com/terms/embedded?hl=%s";
+
+const char kAdditionalToSOnlineURLPath[] =
+    "https://www.google.com/intl/%s/chrome/terms/";
 
 const char kOsSettingsSearchHelpURL[] =
     "https://support.google.com/chromebook/?p=settings_search_help";
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index de5ef3c4..93ad5152 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -320,6 +320,8 @@
 
 extern const char kOnlineEulaURLPath[];
 
+extern const char kAdditionalToSOnlineURLPath[];
+
 // The URL for the "learn more" link for TPM firmware update.
 extern const char kTPMFirmwareUpdateLearnMoreURL[];
 
diff --git a/chrome/installer/util/install_service_work_item_unittest.cc b/chrome/installer/util/install_service_work_item_unittest.cc
index b0727a53..5021569e 100644
--- a/chrome/installer/util/install_service_work_item_unittest.cc
+++ b/chrome/installer/util/install_service_work_item_unittest.cc
@@ -154,7 +154,7 @@
   EXPECT_EQ(0UL,
             GetImpl(item.get())->GetCurrentServiceName().find(kServiceName));
 
-  EXPECT_EQ(ERROR_SUCCESS, key.DeleteKey(L""));
+  EXPECT_EQ(ERROR_SUCCESS, key.DeleteValue(kServiceName));
 }
 
 }  // namespace installer
diff --git a/chrome/renderer/autofill/autofill_renderer_browsertest.cc b/chrome/renderer/autofill/autofill_renderer_browsertest.cc
index d960f0d..f83951e 100644
--- a/chrome/renderer/autofill/autofill_renderer_browsertest.cc
+++ b/chrome/renderer/autofill/autofill_renderer_browsertest.cc
@@ -117,6 +117,8 @@
 
   void DidPreviewAutofillFormData() override {}
 
+  void DidEndTextFieldEditing() override {}
+
   void SetDataList(const std::vector<base::string16>& values,
                    const std::vector<base::string16>& labels) override {}
 
diff --git a/chrome/renderer/autofill/form_autocomplete_browsertest.cc b/chrome/renderer/autofill/form_autocomplete_browsertest.cc
index c90ffc5..96bea1d 100644
--- a/chrome/renderer/autofill/form_autocomplete_browsertest.cc
+++ b/chrome/renderer/autofill/form_autocomplete_browsertest.cc
@@ -105,6 +105,8 @@
 
   void DidPreviewAutofillFormData() override {}
 
+  void DidEndTextFieldEditing() override {}
+
   void SetDataList(const std::vector<base::string16>& values,
                    const std::vector<base::string16>& labels) override {}
 
diff --git a/chrome/renderer/sync_encryption_keys_extension.cc b/chrome/renderer/sync_encryption_keys_extension.cc
index edde01c3..a21eb18 100644
--- a/chrome/renderer/sync_encryption_keys_extension.cc
+++ b/chrome/renderer/sync_encryption_keys_extension.cc
@@ -157,9 +157,8 @@
   int last_key_version = 0;
   if (!args->GetNext(&last_key_version)) {
     DLOG(ERROR) << "No version provided";
-    // TODO(crbug.com/1032485): Be more strict here and issue an error if the
-    // version is not provided.
-    last_key_version = static_cast<int>(encryption_keys.size()) - 1;
+    args->ThrowError();
+    return;
   }
 
   auto global_callback =
diff --git a/chrome/test/data/webui/settings/people_page_test.js b/chrome/test/data/webui/settings/people_page_test.js
index da13be0b..ebcd807 100644
--- a/chrome/test/data/webui/settings/people_page_test.js
+++ b/chrome/test/data/webui/settings/people_page_test.js
@@ -646,10 +646,11 @@
       });
     });
 
-    suite('Chrome OS with SplitSettingsSync', function() {
+    suite('Chrome OS with SplitSyncConsent', function() {
       suiteSetup(function() {
         loadTimeData.overrideValues({
           splitSettingsSyncEnabled: true,
+          splitSyncConsent: true,
         });
       });
 
diff --git a/chrome/test/data/webui/settings/safety_check_page_test.js b/chrome/test/data/webui/settings/safety_check_page_test.js
index 28d85de..ee5fc75 100644
--- a/chrome/test/data/webui/settings/safety_check_page_test.js
+++ b/chrome/test/data/webui/settings/safety_check_page_test.js
@@ -163,6 +163,56 @@
     }
   });
 
+  test('safeBrowsingCheckingUiTest', function() {
+    cr.webUIListenerCallback('safety-check-status-changed', {
+      safetyCheckComponent: settings.SafetyCheckComponent.SAFE_BROWSING,
+      newState: settings.SafetyCheckSafeBrowsingStatus.CHECKING,
+    });
+    Polymer.dom.flush();
+    assertFalse(!!page.$$('#safetyCheckSafeBrowsingButton'));
+    assertFalse(!!page.$$('#safetyCheckSafeBrowsingManagedIcon'));
+  });
+
+  test('safeBrowsingCheckingUiTest', function() {
+    cr.webUIListenerCallback('safety-check-status-changed', {
+      safetyCheckComponent: settings.SafetyCheckComponent.SAFE_BROWSING,
+      newState: settings.SafetyCheckSafeBrowsingStatus.ENABLED,
+    });
+    Polymer.dom.flush();
+    assertFalse(!!page.$$('#safetyCheckSafeBrowsingButton'));
+    assertFalse(!!page.$$('#safetyCheckSafeBrowsingManagedIcon'));
+  });
+
+  test('safeBrowsingCheckingUiTest', function() {
+    cr.webUIListenerCallback('safety-check-status-changed', {
+      safetyCheckComponent: settings.SafetyCheckComponent.SAFE_BROWSING,
+      newState: settings.SafetyCheckSafeBrowsingStatus.DISABLED,
+    });
+    Polymer.dom.flush();
+    assertTrue(!!page.$$('#safetyCheckSafeBrowsingButton'));
+    assertFalse(!!page.$$('#safetyCheckSafeBrowsingManagedIcon'));
+  });
+
+  test('safeBrowsingCheckingUiTest', function() {
+    cr.webUIListenerCallback('safety-check-status-changed', {
+      safetyCheckComponent: settings.SafetyCheckComponent.SAFE_BROWSING,
+      newState: settings.SafetyCheckSafeBrowsingStatus.DISABLED_BY_ADMIN,
+    });
+    Polymer.dom.flush();
+    assertFalse(!!page.$$('#safetyCheckSafeBrowsingButton'));
+    assertTrue(!!page.$$('#safetyCheckSafeBrowsingManagedIcon'));
+  });
+
+  test('safeBrowsingCheckingUiTest', function() {
+    cr.webUIListenerCallback('safety-check-status-changed', {
+      safetyCheckComponent: settings.SafetyCheckComponent.SAFE_BROWSING,
+      newState: settings.SafetyCheckSafeBrowsingStatus.DISABLED_BY_EXTENSION,
+    });
+    Polymer.dom.flush();
+    assertFalse(!!page.$$('#safetyCheckSafeBrowsingButton'));
+    assertTrue(!!page.$$('#safetyCheckSafeBrowsingManagedIcon'));
+  });
+
   test('extensionsCheckingUiTest', function() {
     cr.webUIListenerCallback('safety-check-status-changed', {
       safetyCheckComponent: settings.SafetyCheckComponent.EXTENSIONS,
diff --git a/chromecast/browser/cast_web_contents.h b/chromecast/browser/cast_web_contents.h
index fc44ff5b..7376fa73 100644
--- a/chromecast/browser/cast_web_contents.h
+++ b/chromecast/browser/cast_web_contents.h
@@ -16,6 +16,7 @@
 #include "base/strings/string16.h"
 #include "base/strings/string_piece_forward.h"
 #include "chromecast/common/mojom/feature_manager.mojom.h"
+#include "content/public/common/media_playback_renderer_type.mojom.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/blink/public/common/messaging/web_message_port.h"
@@ -204,7 +205,8 @@
     // debugging interfaces.
     bool enabled_for_dev = false;
     // Chooses a media renderer for the WebContents.
-    bool use_cma_renderer = false;
+    content::mojom::RendererType renderer_type =
+        content::mojom::RendererType::DEFAULT_RENDERER;
     // Whether the WebContents is a root native window, or if it is embedded in
     // another WebContents (see Delegate::InnerContentsCreated()).
     bool is_root_window = false;
diff --git a/chromecast/browser/cast_web_contents_impl.cc b/chromecast/browser/cast_web_contents_impl.cc
index 21d3c2f..d7160be 100644
--- a/chromecast/browser/cast_web_contents_impl.cc
+++ b/chromecast/browser/cast_web_contents_impl.cc
@@ -137,7 +137,7 @@
       page_state_(PageState::IDLE),
       last_state_(PageState::IDLE),
       enabled_for_dev_(init_params.enabled_for_dev),
-      use_cma_renderer_(init_params.use_cma_renderer),
+      renderer_type_(init_params.renderer_type),
       handle_inner_contents_(init_params.handle_inner_contents),
       view_background_color_(init_params.background_color),
       remote_debugging_server_(
@@ -175,8 +175,9 @@
   }
 
   // TODO(yucliu): Change the flag name to kDisableCmaRenderer in a latter diff.
-  if (GetSwitchValueBoolean(switches::kDisableMojoRenderer, false)) {
-    use_cma_renderer_ = false;
+  if (GetSwitchValueBoolean(switches::kDisableMojoRenderer, false) &&
+      renderer_type_ == content::mojom::RendererType::MOJO_RENDERER) {
+    renderer_type_ = content::mojom::RendererType::DEFAULT_RENDERER;
   }
 
   // Provides QueryableDataHostCast if the new QueryableData bindings is not
@@ -482,7 +483,7 @@
       media_playback_options;
   render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
       &media_playback_options);
-  media_playback_options->SetUseCmaRenderer(use_cma_renderer_);
+  media_playback_options->SetRendererType(renderer_type_);
 
   // Send queryable values
   mojo::Remote<chromecast::shell::mojom::QueryableDataStore>
diff --git a/chromecast/browser/cast_web_contents_impl.h b/chromecast/browser/cast_web_contents_impl.h
index 53664ad..63b59c3 100644
--- a/chromecast/browser/cast_web_contents_impl.h
+++ b/chromecast/browser/cast_web_contents_impl.h
@@ -26,6 +26,7 @@
 #include "content/public/browser/render_process_host_observer.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/media_playback_renderer_type.mojom.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/blink/public/mojom/favicon/favicon_url.mojom-forward.h"
@@ -165,7 +166,7 @@
   PageState page_state_;
   PageState last_state_;
   const bool enabled_for_dev_;
-  bool use_cma_renderer_;
+  content::mojom::RendererType renderer_type_;
   const bool handle_inner_contents_;
   BackgroundColor view_background_color_;
   shell::RemoteDebuggingServer* const remote_debugging_server_;
diff --git a/chromecast/browser/test/cast_browser_test.cc b/chromecast/browser/test/cast_browser_test.cc
index 5f9300e4..6cbad71 100644
--- a/chromecast/browser/test/cast_browser_test.cc
+++ b/chromecast/browser/test/cast_browser_test.cc
@@ -18,6 +18,7 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/common/media_playback_renderer_type.mojom.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_navigation_observer.h"
 
@@ -60,7 +61,9 @@
   CastWebView::CreateParams params;
   params.delegate = weak_factory_.GetWeakPtr();
   params.web_contents_params.delegate = weak_factory_.GetWeakPtr();
-  params.web_contents_params.use_cma_renderer = true;
+  // MOJO_RENDERER is CMA renderer on Chromecast
+  params.web_contents_params.renderer_type =
+      content::mojom::RendererType::MOJO_RENDERER;
   params.web_contents_params.enabled_for_dev = true;
   params.window_params.delegate = weak_factory_.GetWeakPtr();
   cast_web_view_ =
diff --git a/chromecast/common/mojom/BUILD.gn b/chromecast/common/mojom/BUILD.gn
index dbce21a0..f2a4e10 100644
--- a/chromecast/common/mojom/BUILD.gn
+++ b/chromecast/common/mojom/BUILD.gn
@@ -20,5 +20,8 @@
     "service_connector.mojom",
   ]
 
-  public_deps = [ "//mojo/public/mojom/base" ]
+  public_deps = [
+    "//content/public/common:renderer_type",
+    "//mojo/public/mojom/base",
+  ]
 }
diff --git a/chromecast/common/mojom/media_playback_options.mojom b/chromecast/common/mojom/media_playback_options.mojom
index 3de812017..8f94e2d 100644
--- a/chromecast/common/mojom/media_playback_options.mojom
+++ b/chromecast/common/mojom/media_playback_options.mojom
@@ -4,17 +4,18 @@
 
 module chromecast.shell.mojom;
 
+import "content/public/common/media_playback_renderer_type.mojom";
+
 // Receives messages from browser process to control media playback options
 // (block loading, background playback) for a specific RenderFrame.
 // Implemented by a RenderFrameObserver.
 interface MediaPlaybackOptions {
+  // Set true to enable background suspend
   SetMediaLoadingBlocked(bool blocked);
 
+  // Set true to allow video playback to be played in the background
   SetBackgroundVideoPlaybackEnabled(bool enabled);
 
-  // Enable CMA (MojoRenderer) for media playback.
-  // Otherwise, media playback uses RendererImpl, which is the same as other
-  // platforms. For video codec supported by v4l2 (e.g. h264), it's still
-  // accelerated by hardware. Video will be rendererd on graphics plane.
-  SetUseCmaRenderer(bool enabled);
+  // Set the renderer type that will be used in WebMediaPlayerImpl
+  SetRendererType(content.mojom.RendererType type);
 };
diff --git a/chromecast/renderer/cast_media_playback_options.cc b/chromecast/renderer/cast_media_playback_options.cc
index 83b4c68..ca30ebd3 100644
--- a/chromecast/renderer/cast_media_playback_options.cc
+++ b/chromecast/renderer/cast_media_playback_options.cc
@@ -78,8 +78,9 @@
       renderer_media_playback_options_);
 }
 
-void CastMediaPlaybackOptions::SetUseCmaRenderer(bool enable) {
-  renderer_media_playback_options_.is_mojo_renderer_enabled = enable;
+void CastMediaPlaybackOptions::SetRendererType(
+    content::mojom::RendererType type) {
+  renderer_media_playback_options_.renderer_type = type;
   render_frame()->SetRenderFrameMediaPlaybackOptions(
       renderer_media_playback_options_);
 }
diff --git a/chromecast/renderer/cast_media_playback_options.h b/chromecast/renderer/cast_media_playback_options.h
index 1531494..9d26773 100644
--- a/chromecast/renderer/cast_media_playback_options.h
+++ b/chromecast/renderer/cast_media_playback_options.h
@@ -11,6 +11,7 @@
 #include "base/callback_forward.h"
 #include "base/sequence_checker.h"
 #include "chromecast/common/mojom/media_playback_options.mojom.h"
+#include "content/public/common/media_playback_renderer_type.mojom.h"
 #include "content/public/renderer/render_frame_media_playback_options.h"
 #include "content/public/renderer/render_frame_observer.h"
 #include "content/public/renderer/render_frame_observer_tracker.h"
@@ -49,7 +50,7 @@
   // MediaPlaybackOptions implementation
   void SetMediaLoadingBlocked(bool blocked) override;
   void SetBackgroundVideoPlaybackEnabled(bool enabled) override;
-  void SetUseCmaRenderer(bool enable) override;
+  void SetRendererType(content::mojom::RendererType type) override;
 
   void OnMediaPlaybackOptionsAssociatedReceiver(
       mojo::PendingAssociatedReceiver<
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 4397488..ebaa8cf0 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -288,6 +288,14 @@
 const base::Feature kSplitSettingsSync{"SplitSettingsSync",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Introduces a new OOBE dialog for the OS sync feature. Uses the same browser
+// sync consent dialog as Windows/Mac/Linux. Allows the user to fully opt-out of
+// browser sync, including marking the signin primary account as unconsented.
+// Requires SplitSettingsSync.
+// NOTE: Use IsSplitSyncConsentEnabled() to test the flag, see implementation.
+const base::Feature kSplitSyncConsent{"SplitSyncConsent",
+                                      base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables unified media view in Files app to browse recently-modified media
 // files from local local, Google Drive, and Android.
 const base::Feature kUnifiedMediaView{"UnifiedMediaView",
@@ -365,6 +373,12 @@
   return base::FeatureList::IsEnabled(kSplitSettingsSync);
 }
 
+bool IsSplitSyncConsentEnabled() {
+  // SplitSyncConsent requires SplitSettingsSync.
+  return base::FeatureList::IsEnabled(kSplitSettingsSync) &&
+         base::FeatureList::IsEnabled(kSplitSyncConsent);
+}
+
 bool ShouldShowPlayStoreInDemoMode() {
   return base::FeatureList::IsEnabled(kShowPlayInDemoMode);
 }
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 9e0ac01..5d8ff98 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -132,6 +132,9 @@
 extern const base::Feature kSmartDimModelV3;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kSplitSettingsSync;
+// Visible for testing. Use IsSplitSyncConsentEnabled() to check the flag.
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const base::Feature kSplitSyncConsent;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kUnifiedMediaView;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
@@ -163,6 +166,7 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsQuickAnswersEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsQuickAnswersRichUiEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsSplitSettingsSyncEnabled();
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsSplitSyncConsentEnabled();
 
 // TODO(michaelpg): Remove after M71 branch to re-enable Play Store by default.
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool ShouldShowPlayStoreInDemoMode();
diff --git a/components/autofill/content/browser/content_autofill_driver.cc b/components/autofill/content/browser/content_autofill_driver.cc
index 1f9831a..df274c3 100644
--- a/components/autofill/content/browser/content_autofill_driver.cc
+++ b/components/autofill/content/browser/content_autofill_driver.cc
@@ -285,6 +285,10 @@
   autofill_handler_->OnDidPreviewAutofillFormData();
 }
 
+void ContentAutofillDriver::DidEndTextFieldEditing() {
+  autofill_handler_->OnDidEndTextFieldEditing();
+}
+
 void ContentAutofillDriver::SetDataList(
     const std::vector<base::string16>& values,
     const std::vector<base::string16>& labels) {
diff --git a/components/autofill/content/browser/content_autofill_driver.h b/components/autofill/content/browser/content_autofill_driver.h
index d62b3c40..9597492 100644
--- a/components/autofill/content/browser/content_autofill_driver.h
+++ b/components/autofill/content/browser/content_autofill_driver.h
@@ -116,6 +116,7 @@
   void DidFillAutofillFormData(const FormData& form,
                                base::TimeTicks timestamp) override;
   void DidPreviewAutofillFormData() override;
+  void DidEndTextFieldEditing() override;
   void SetDataList(const std::vector<base::string16>& values,
                    const std::vector<base::string16>& labels) override;
   void SelectFieldOptionsDidChange(const FormData& form) override;
diff --git a/components/autofill/content/common/mojom/autofill_driver.mojom b/components/autofill/content/common/mojom/autofill_driver.mojom
index 23844f9..3aa538b 100644
--- a/components/autofill/content/common/mojom/autofill_driver.mojom
+++ b/components/autofill/content/common/mojom/autofill_driver.mojom
@@ -69,6 +69,9 @@
   // Sent when a form is previewed with Autofill suggestions.
   DidPreviewAutofillFormData();
 
+  // Sent when a text field is done editing.
+  DidEndTextFieldEditing();
+
   // Informs browser of data list values for the current field.
   SetDataList(array<mojo_base.mojom.String16> values,
               array<mojo_base.mojom.String16> labels);
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 3c6fa39..9f8a720 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -319,6 +319,7 @@
       password_generation_agent_->ShouldIgnoreBlur()) {
     return;
   }
+  GetAutofillDriver()->DidEndTextFieldEditing();
   password_autofill_agent_->DidEndTextFieldEditing();
   if (password_generation_agent_)
     password_generation_agent_->DidEndTextFieldEditing(element);
diff --git a/components/autofill/core/browser/autofill_external_delegate.cc b/components/autofill/core/browser/autofill_external_delegate.cc
index 82575f6..411034a 100644
--- a/components/autofill/core/browser/autofill_external_delegate.cc
+++ b/components/autofill/core/browser/autofill_external_delegate.cc
@@ -311,6 +311,10 @@
   return false;
 }
 
+void AutofillExternalDelegate::DidEndTextFieldEditing() {
+  manager_->client()->HideAutofillPopup(PopupHidingReason::kEndEditing);
+}
+
 void AutofillExternalDelegate::ClearPreviewedForm() {
   driver_->RendererShouldClearPreviewedForm();
 }
diff --git a/components/autofill/core/browser/autofill_external_delegate.h b/components/autofill/core/browser/autofill_external_delegate.h
index cf68744..b54e4e7 100644
--- a/components/autofill/core/browser/autofill_external_delegate.h
+++ b/components/autofill/core/browser/autofill_external_delegate.h
@@ -94,6 +94,10 @@
       const std::vector<base::string16>& data_list_values,
       const std::vector<base::string16>& data_list_labels);
 
+  // Inform the delegate that the text field editing has ended. This is
+  // used to help record the metrics of when a new popup is shown.
+  void DidEndTextFieldEditing();
+
   // Returns the delegate to its starting state by removing any page specific
   // values or settings.
   void Reset();
diff --git a/components/autofill/core/browser/autofill_external_delegate_unittest.cc b/components/autofill/core/browser/autofill_external_delegate_unittest.cc
index c5e4b79..7adcb7a5 100644
--- a/components/autofill/core/browser/autofill_external_delegate_unittest.cc
+++ b/components/autofill/core/browser/autofill_external_delegate_unittest.cc
@@ -572,6 +572,17 @@
                                           POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY);
 }
 
+// Test that the popup is hidden once we are done editing the autofill field.
+TEST_F(AutofillExternalDelegateUnitTest,
+       ExternalDelegateHidePopupAfterEditing) {
+  EXPECT_CALL(autofill_client_, ShowAutofillPopup);
+  test::GenerateTestAutofillPopup(external_delegate_.get());
+
+  EXPECT_CALL(autofill_client_,
+              HideAutofillPopup(autofill::PopupHidingReason::kEndEditing));
+  external_delegate_->DidEndTextFieldEditing();
+}
+
 // Test that the driver is directed to accept the data list after being notified
 // that the user accepted the data list suggestion.
 TEST_F(AutofillExternalDelegateUnitTest,
diff --git a/components/autofill/core/browser/autofill_handler.h b/components/autofill/core/browser/autofill_handler.h
index bc4c95f..32dc40af 100644
--- a/components/autofill/core/browser/autofill_handler.h
+++ b/components/autofill/core/browser/autofill_handler.h
@@ -102,6 +102,9 @@
   // Invoked when preview autofill value has been shown.
   virtual void OnDidPreviewAutofillFormData() = 0;
 
+  // Invoked when textfeild editing ended
+  virtual void OnDidEndTextFieldEditing() = 0;
+
   // Invoked when popup window should be hidden.
   virtual void OnHidePopup() = 0;
 
diff --git a/components/autofill/core/browser/autofill_handler_proxy.cc b/components/autofill/core/browser/autofill_handler_proxy.cc
index 5b2fe15..8a9fcaf4 100644
--- a/components/autofill/core/browser/autofill_handler_proxy.cc
+++ b/components/autofill/core/browser/autofill_handler_proxy.cc
@@ -86,6 +86,8 @@
 
 void AutofillHandlerProxy::OnDidPreviewAutofillFormData() {}
 
+void AutofillHandlerProxy::OnDidEndTextFieldEditing() {}
+
 void AutofillHandlerProxy::OnHidePopup() {}
 
 void AutofillHandlerProxy::OnSetDataList(
diff --git a/components/autofill/core/browser/autofill_handler_proxy.h b/components/autofill/core/browser/autofill_handler_proxy.h
index cf3b247..152292c 100644
--- a/components/autofill/core/browser/autofill_handler_proxy.h
+++ b/components/autofill/core/browser/autofill_handler_proxy.h
@@ -26,6 +26,7 @@
                                  const base::TimeTicks timestamp) override;
 
   void OnDidPreviewAutofillFormData() override;
+  void OnDidEndTextFieldEditing() override;
   void OnHidePopup() override;
   void OnSetDataList(const std::vector<base::string16>& values,
                      const std::vector<base::string16>& labels) override;
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index 4b23493..cc33f645 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -1416,6 +1416,10 @@
                      credit_card_field_, *credit_card, cvc);
 }
 
+void AutofillManager::OnDidEndTextFieldEditing() {
+  external_delegate_->DidEndTextFieldEditing();
+}
+
 bool AutofillManager::IsAutofillEnabled() const {
   return IsAutofillProfileEnabled() || IsAutofillCreditCardEnabled();
 }
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h
index 8690596..348bf3bf 100644
--- a/components/autofill/core/browser/autofill_manager.h
+++ b/components/autofill/core/browser/autofill_manager.h
@@ -226,6 +226,7 @@
   void OnDidFillAutofillFormData(const FormData& form,
                                  const base::TimeTicks timestamp) override;
   void OnDidPreviewAutofillFormData() override;
+  void OnDidEndTextFieldEditing() override;
   void OnHidePopup() override;
   void OnSetDataList(const std::vector<base::string16>& values,
                      const std::vector<base::string16>& labels) override;
diff --git a/components/autofill/core/browser/ui/popup_types.h b/components/autofill/core/browser/ui/popup_types.h
index 5942255..1656116 100644
--- a/components/autofill/core/browser/ui/popup_types.h
+++ b/components/autofill/core/browser/ui/popup_types.h
@@ -23,7 +23,8 @@
 enum class PopupHidingReason {
   kAcceptSuggestion,        // A suggestion was accepted.
   kAttachInterstitialPage,  // An interstitial page displaces the popup.
-  kFocusChanged,            // Focus removed from field.
+  kEndEditing,    // A field isn't edited anymore but remains focused for now.
+  kFocusChanged,  // Focus removed from field. Follows kEndEditing.
   kContentAreaMoved,  // Scrolling or zooming into the page displaces popup.
   kNavigation,        // A navigation on page or frame level.
   kNoSuggestions,     // The popup is or would become empty.
diff --git a/components/autofill_assistant/browser/web/web_controller_browsertest.cc b/components/autofill_assistant/browser/web/web_controller_browsertest.cc
index 8acebe3..131487c 100644
--- a/components/autofill_assistant/browser/web/web_controller_browsertest.cc
+++ b/components/autofill_assistant/browser/web/web_controller_browsertest.cc
@@ -1483,9 +1483,10 @@
   Selector document_element({"#full_height_section"});
   EXPECT_TRUE(GetElementPosition(document_element, &document_element_rect));
 
-  // This element must be after the full_height_section!
+  // The iFrame must be after the #full_height_section element to check that
+  // the resulting rect is global.
   RectF iframe_element_rect;
-  Selector iframe_element({"#iframe", "#touch_area"});
+  Selector iframe_element({"#iframe", "#touch_area_1"});
   EXPECT_TRUE(GetElementPosition(iframe_element, &iframe_element_rect));
 
   EXPECT_GT(iframe_element_rect.top, document_element_rect.bottom);
diff --git a/components/invalidation/impl/single_object_invalidation_set_unittest.cc b/components/invalidation/impl/single_object_invalidation_set_unittest.cc
index 7643b6c..f343101 100644
--- a/components/invalidation/impl/single_object_invalidation_set_unittest.cc
+++ b/components/invalidation/impl/single_object_invalidation_set_unittest.cc
@@ -74,39 +74,6 @@
   EXPECT_FALSE(list.StartsWithUnknownVersion());
 }
 
-TEST_F(SingleObjectInvalidationSetTest, SerializeEmpty) {
-  SingleObjectInvalidationSet list;
-
-  std::unique_ptr<base::ListValue> value = list.ToValue();
-  ASSERT_TRUE(value.get());
-  SingleObjectInvalidationSet deserialized;
-  deserialized.ResetFromValue(*value);
-  EXPECT_TRUE(list == deserialized);
-}
-
-TEST_F(SingleObjectInvalidationSetTest, SerializeOne) {
-  SingleObjectInvalidationSet list;
-  list.Insert(Invalidation::Init(kId, 1, "one"));
-
-  std::unique_ptr<base::ListValue> value = list.ToValue();
-  ASSERT_TRUE(value.get());
-  SingleObjectInvalidationSet deserialized;
-  deserialized.ResetFromValue(*value);
-  EXPECT_TRUE(list == deserialized);
-}
-
-TEST_F(SingleObjectInvalidationSetTest, SerializeMany) {
-  SingleObjectInvalidationSet list;
-  list.Insert(Invalidation::Init(kId, 1, "one"));
-  list.Insert(Invalidation::InitUnknownVersion(kId));
-
-  std::unique_ptr<base::ListValue> value = list.ToValue();
-  ASSERT_TRUE(value.get());
-  SingleObjectInvalidationSet deserialized;
-  deserialized.ResetFromValue(*value);
-  EXPECT_TRUE(list == deserialized);
-}
-
 }  // namespace
 
 }  // namespace syncer
diff --git a/components/invalidation/public/invalidation.cc b/components/invalidation/public/invalidation.cc
index e6ec0da..6ca9a9a 100644
--- a/components/invalidation/public/invalidation.cc
+++ b/components/invalidation/public/invalidation.cc
@@ -59,42 +59,6 @@
       dropped.id_, true, kInvalidVersion, std::string(), dropped.ack_handle_);
 }
 
-// static
-std::unique_ptr<Invalidation> Invalidation::InitFromValue(
-    const base::DictionaryValue& value) {
-  invalidation::ObjectId id;
-
-  const base::DictionaryValue* object_id_dict;
-  if (!value.GetDictionary(kObjectIdKey, &object_id_dict) ||
-      !ObjectIdFromValue(*object_id_dict, &id)) {
-    DLOG(WARNING) << "Failed to parse id";
-    return nullptr;
-  }
-  bool is_unknown_version;
-  if (!value.GetBoolean(kIsUnknownVersionKey, &is_unknown_version)) {
-    DLOG(WARNING) << "Failed to parse is_unknown_version flag";
-    return nullptr;
-  }
-  if (is_unknown_version) {
-    return base::WrapUnique(new Invalidation(
-        id, true, kInvalidVersion, std::string(), AckHandle::CreateUnique()));
-  }
-  int64_t version = 0;
-  std::string version_as_string;
-  if (!value.GetString(kVersionKey, &version_as_string)
-      || !base::StringToInt64(version_as_string, &version)) {
-    DLOG(WARNING) << "Failed to parse version";
-    return nullptr;
-  }
-  std::string payload;
-  if (!value.GetString(kPayloadKey, &payload)) {
-    DLOG(WARNING) << "Failed to parse payload";
-    return nullptr;
-  }
-  return base::WrapUnique(
-      new Invalidation(id, false, version, payload, AckHandle::CreateUnique()));
-}
-
 Invalidation::Invalidation(const Invalidation& other) = default;
 
 Invalidation::~Invalidation() = default;
diff --git a/components/invalidation/public/invalidation.h b/components/invalidation/public/invalidation.h
index d458e85..01ffb3f 100644
--- a/components/invalidation/public/invalidation.h
+++ b/components/invalidation/public/invalidation.h
@@ -42,8 +42,6 @@
   static Invalidation InitUnknownVersion(const invalidation::ObjectId& id);
   static Invalidation InitUnknownVersion(const Topic& topic);
   static Invalidation InitFromDroppedInvalidation(const Invalidation& dropped);
-  static std::unique_ptr<Invalidation> InitFromValue(
-      const base::DictionaryValue& value);
 
   Invalidation(const Invalidation& other);
   ~Invalidation();
diff --git a/components/invalidation/public/single_object_invalidation_set.cc b/components/invalidation/public/single_object_invalidation_set.cc
index 4986332..b3ea986 100644
--- a/components/invalidation/public/single_object_invalidation_set.cc
+++ b/components/invalidation/public/single_object_invalidation_set.cc
@@ -96,23 +96,4 @@
   return value;
 }
 
-bool SingleObjectInvalidationSet::ResetFromValue(
-    const base::ListValue& list) {
-  for (size_t i = 0; i < list.GetSize(); ++i) {
-    const base::DictionaryValue* dict;
-    if (!list.GetDictionary(i, &dict)) {
-      DLOG(WARNING) << "Could not find invalidation at index " << i;
-      return false;
-    }
-    std::unique_ptr<Invalidation> invalidation =
-        Invalidation::InitFromValue(*dict);
-    if (!invalidation) {
-      DLOG(WARNING) << "Failed to parse invalidation at index " << i;
-      return false;
-    }
-    invalidations_.insert(*invalidation);
-  }
-  return true;
-}
-
 }  // namespace syncer
diff --git a/components/invalidation/public/single_object_invalidation_set.h b/components/invalidation/public/single_object_invalidation_set.h
index 34d2369..a83ef5d 100644
--- a/components/invalidation/public/single_object_invalidation_set.h
+++ b/components/invalidation/public/single_object_invalidation_set.h
@@ -56,7 +56,6 @@
   const Invalidation& back() const;
 
   std::unique_ptr<base::ListValue> ToValue() const;
-  bool ResetFromValue(const base::ListValue& list);
 
  private:
   InvalidationsSet invalidations_;
diff --git a/components/password_manager/OWNERS b/components/password_manager/OWNERS
index 55e5a05..5b8680ef 100644
--- a/components/password_manager/OWNERS
+++ b/components/password_manager/OWNERS
@@ -1,6 +1,7 @@
 dvadym@chromium.org
 jdoerrie@chromium.org
 kolos@chromium.org
+mamir@chromium.org
 vasilii@chromium.org
 
 # COMPONENT: UI>Browser>Passwords
diff --git a/components/password_manager/core/browser/bulk_leak_check_service.h b/components/password_manager/core/browser/bulk_leak_check_service.h
index a7523f9..7007218 100644
--- a/components/password_manager/core/browser/bulk_leak_check_service.h
+++ b/components/password_manager/core/browser/bulk_leak_check_service.h
@@ -92,6 +92,11 @@
   void set_leak_factory(std::unique_ptr<LeakDetectionCheckFactory> factory) {
     leak_check_factory_ = std::move(factory);
   }
+
+  void set_state_and_notify(State state) {
+    state_ = state;
+    NotifyStateChanged();
+  }
 #endif  // defined(UNIT_TEST)
 
  private:
diff --git a/components/password_manager/core/browser/login_database.cc b/components/password_manager/core/browser/login_database.cc
index d3a8e4a..6e7d89f 100644
--- a/components/password_manager/core/browser/login_database.cc
+++ b/components/password_manager/core/browser/login_database.cc
@@ -1165,11 +1165,6 @@
   if (changes) {
     changes->clear();
   }
-  if (form.is_public_suffix_match) {
-    // TODO(dvadym): Discuss whether we should allow to remove PSL matched
-    // credentials.
-    return false;
-  }
 #if defined(OS_IOS)
   DeleteEncryptedPassword(form);
 #endif
diff --git a/components/password_manager/core/browser/login_database_unittest.cc b/components/password_manager/core/browser/login_database_unittest.cc
index 7af0924..ce98196f 100644
--- a/components/password_manager/core/browser/login_database_unittest.cc
+++ b/components/password_manager/core/browser/login_database_unittest.cc
@@ -513,14 +513,6 @@
   EXPECT_EQ(1U, result.size());
   EXPECT_EQ("https://foo.com/", result[0]->signon_realm);
   EXPECT_TRUE(result[0]->is_public_suffix_match);
-
-  // Try to remove PSL matched form
-  EXPECT_FALSE(db().RemoveLogin(*result[0], /*changes=*/nullptr));
-  result.clear();
-  // Ensure that the original form is still there
-  EXPECT_TRUE(db().GetLogins(PasswordStore::FormDigest(form), &result));
-  EXPECT_EQ(1U, result.size());
-  result.clear();
 }
 
 TEST_F(LoginDatabaseTest, TestFederatedMatching) {
diff --git a/components/password_manager/core/browser/multi_store_password_save_manager.cc b/components/password_manager/core/browser/multi_store_password_save_manager.cc
index 0c9d481..37fb430 100644
--- a/components/password_manager/core/browser/multi_store_password_save_manager.cc
+++ b/components/password_manager/core/browser/multi_store_password_save_manager.cc
@@ -135,9 +135,7 @@
 
 void MultiStorePasswordSaveManager::MoveCredentialsToAccountStore() {
   // TODO(crbug.com/1032992): There are other rare corner cases that should
-  // still be handled: 0. Moving PSL matched credentials doesn't work now
-  // because of
-  // https://cs.chromium.org/chromium/src/components/password_manager/core/browser/login_database.cc?l=1318&rcl=e32055d4843e9fc1fa920c5f1f83c1313607e28a
+  // still be handled:
   // 1. Credential exists only in the profile store but with an outdated
   // password.
   // 2. Credentials exist in both stores.
diff --git a/components/password_manager/core/browser/password_manager_util.cc b/components/password_manager/core/browser/password_manager_util.cc
index 401347e..9c37ccd 100644
--- a/components/password_manager/core/browser/password_manager_util.cc
+++ b/components/password_manager/core/browser/password_manager_util.cc
@@ -349,12 +349,14 @@
   std::sort(non_federated_same_scheme->begin(),
             non_federated_same_scheme->end(), IsBetterMatch);
 
-  std::set<base::string16> usernames;
+  std::set<std::pair<PasswordForm::Store, base::string16>> store_usernames;
   for (const auto* match : *non_federated_same_scheme) {
-    const base::string16& username = match->username_value;
-    // The first match for |username| in the sorted array is best match.
-    if (!base::Contains(usernames, username)) {
-      usernames.insert(username);
+    auto store_username =
+        std::make_pair(match->in_store, match->username_value);
+    // The first match for |store_username| in the sorted array is best
+    // match.
+    if (!base::Contains(store_usernames, store_username)) {
+      store_usernames.insert(store_username);
       best_matches->push_back(match);
     }
   }
diff --git a/components/password_manager/core/browser/password_manager_util_unittest.cc b/components/password_manager/core/browser/password_manager_util_unittest.cc
index 795dd2f..3928c582 100644
--- a/components/password_manager/core/browser/password_manager_util_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_util_unittest.cc
@@ -228,6 +228,59 @@
   }
 }
 
+TEST(PasswordManagerUtil, FindBestMatchesInProfileAndAccountStores) {
+  const base::string16 kUsername1 = base::ASCIIToUTF16("Username1");
+  const base::string16 kPassword1 = base::ASCIIToUTF16("Password1");
+  const base::string16 kUsername2 = base::ASCIIToUTF16("Username2");
+  const base::string16 kPassword2 = base::ASCIIToUTF16("Password2");
+
+  PasswordForm form;
+  form.is_public_suffix_match = false;
+  form.date_last_used = base::Time::Now();
+
+  // Add the same credentials in account and profile stores.
+  PasswordForm account_form1(form);
+  account_form1.username_value = kUsername1;
+  account_form1.password_value = kPassword1;
+  account_form1.in_store = PasswordForm::Store::kAccountStore;
+
+  PasswordForm profile_form1(account_form1);
+  profile_form1.in_store = PasswordForm::Store::kProfileStore;
+
+  // Add the credentials for the same username in account and profile stores but
+  // with different passwords.
+  PasswordForm account_form2(form);
+  account_form2.username_value = kUsername2;
+  account_form2.password_value = kPassword1;
+  account_form2.in_store = PasswordForm::Store::kAccountStore;
+
+  PasswordForm profile_form2(account_form2);
+  profile_form2.password_value = kPassword2;
+  profile_form2.in_store = PasswordForm::Store::kProfileStore;
+
+  std::vector<const PasswordForm*> matches;
+  matches.push_back(&account_form1);
+  matches.push_back(&profile_form1);
+  matches.push_back(&account_form2);
+  matches.push_back(&profile_form2);
+
+  std::vector<const PasswordForm*> best_matches;
+  const PasswordForm* preferred_match = nullptr;
+  std::vector<const PasswordForm*> same_scheme_matches;
+  FindBestMatches(matches, PasswordForm::Scheme::kHtml, &same_scheme_matches,
+                  &best_matches, &preferred_match);
+  // All 4 matches should be returned in best matches.
+  EXPECT_EQ(best_matches.size(), 4U);
+  EXPECT_NE(std::find(best_matches.begin(), best_matches.end(), &account_form1),
+            best_matches.end());
+  EXPECT_NE(std::find(best_matches.begin(), best_matches.end(), &account_form2),
+            best_matches.end());
+  EXPECT_NE(std::find(best_matches.begin(), best_matches.end(), &profile_form1),
+            best_matches.end());
+  EXPECT_NE(std::find(best_matches.begin(), best_matches.end(), &profile_form2),
+            best_matches.end());
+}
+
 TEST(PasswordManagerUtil, GetMatchForUpdating_MatchUsername) {
   autofill::PasswordForm stored = GetTestCredential();
   autofill::PasswordForm parsed = GetTestCredential();
diff --git a/components/policy/proto/device_management_backend.proto b/components/policy/proto/device_management_backend.proto
index 53e23c9..f9b66cd4 100644
--- a/components/policy/proto/device_management_backend.proto
+++ b/components/policy/proto/device_management_backend.proto
@@ -1550,6 +1550,12 @@
 
   // A list of plugins installed in the browser.
   repeated Plugin plugins = 7;
+
+  // The installed version of the browser if it differs from |browser_version|,
+  // or absent otherwise. When present, it indicates that an update (of a higher
+  // or lower version) has been installed and will be the active version
+  // following a browser restart.
+  optional string installed_browser_version = 8;
 }
 
 // Report Operating system related information.
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java
index 8d622db..17e21dd1 100644
--- a/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java
@@ -262,14 +262,6 @@
      * Asynchronous version of {@link #tryGetGoogleAccountNames()}.
      */
     @MainThread
-    public void tryGetGoogleAccountNames(final Callback<List<String>> callback) {
-        runAfterCacheIsPopulated(() -> callback.onResult(tryGetGoogleAccountNames()));
-    }
-
-    /**
-     * Asynchronous version of {@link #tryGetGoogleAccountNames()}.
-     */
-    @MainThread
     public void getGoogleAccountNames(
             final Callback<AccountManagerResult<List<String>>> callback) {
         runAfterCacheIsPopulated(() -> {
@@ -316,14 +308,6 @@
     }
 
     /**
-     * Asynchronous version of {@link #getGoogleAccounts()}.
-     */
-    @MainThread
-    public void getGoogleAccounts(Callback<AccountManagerResult<List<Account>>> callback) {
-        runAfterCacheIsPopulated(() -> callback.onResult(mFilteredAccounts.get()));
-    }
-
-    /**
      * Retrieves all Google accounts on the device.
      * Returns an empty array if an error occurs while getting account list.
      */
@@ -344,23 +328,6 @@
         runAfterCacheIsPopulated(() -> callback.onResult(tryGetGoogleAccounts()));
     }
 
-    /**
-     * Determine whether there are any Google accounts on the device.
-     * Returns false if an error occurs while getting account list.
-     */
-    @AnyThread
-    public boolean hasGoogleAccounts() {
-        return !tryGetGoogleAccounts().isEmpty();
-    }
-
-    /**
-     * Asynchronous version of {@link #hasGoogleAccounts()}.
-     */
-    @MainThread
-    public void hasGoogleAccounts(final Callback<Boolean> callback) {
-        runAfterCacheIsPopulated(() -> callback.onResult(hasGoogleAccounts()));
-    }
-
     private String canonicalizeName(String name) {
         String[] parts = AT_SYMBOL.split(name);
         if (parts.length != 2) return name;
@@ -391,14 +358,6 @@
     }
 
     /**
-     * Asynchronous version of {@link #getAccountFromName(String)}.
-     */
-    @MainThread
-    public void getAccountFromName(String accountName, final Callback<Account> callback) {
-        runAfterCacheIsPopulated(() -> callback.onResult(getAccountFromName(accountName)));
-    }
-
-    /**
      * Returns whether an account exists with the given name.
      * Returns false if an error occurs while getting account list.
      */
@@ -408,16 +367,6 @@
     }
 
     /**
-     * Asynchronous version of {@link #hasAccountForName(String)}.
-     */
-    // TODO(maxbogue): Remove once this function is used outside of tests.
-    @VisibleForTesting
-    @MainThread
-    public void hasAccountForName(String accountName, final Callback<Boolean> callback) {
-        runAfterCacheIsPopulated(() -> callback.onResult(hasAccountForName(accountName)));
-    }
-
-    /**
      * @return Whether or not there is an account authenticator for Google accounts.
      */
     @AnyThread
diff --git a/components/signin/public/identity_manager/identity_manager.cc b/components/signin/public/identity_manager/identity_manager.cc
index 82a17d1..47d0021 100644
--- a/components/signin/public/identity_manager/identity_manager.cc
+++ b/components/signin/public/identity_manager/identity_manager.cc
@@ -539,7 +539,6 @@
 
 void IdentityManager::GoogleSigninSucceeded(
     const CoreAccountInfo& account_info) {
-  UpdateUnconsentedPrimaryAccount();
   for (auto& observer : observer_list_) {
     observer.OnPrimaryAccountSet(account_info);
   }
@@ -562,6 +561,12 @@
 void IdentityManager::GoogleSignedOut(const CoreAccountInfo& account_info) {
   DCHECK(!HasPrimaryAccount());
   DCHECK(!account_info.IsEmpty());
+  // This is needed for the case where the user chooses to start syncing
+  // with an account that is different then the unconsented primary account
+  // (not the first in cookies) but then cancel. In that case, the tokens stay
+  // the same. In all the other cases, either the token will be revoked which
+  // will trigger an update for the unconsented primary account or the
+  // primary account stays the same but the sync consent is revoked.
   UpdateUnconsentedPrimaryAccount();
   for (auto& observer : observer_list_) {
     observer.OnPrimaryAccountCleared(account_info);
@@ -688,7 +693,6 @@
     const CoreAccountId primary_account_id = GetPrimaryAccountId();
     if (primary_account_id == info.account_id) {
       primary_account_manager_->UpdateAuthenticatedAccountInfo();
-      UpdateUnconsentedPrimaryAccount();
     }
   }
 
@@ -698,7 +702,6 @@
 }
 
 void IdentityManager::OnAccountRemoved(const AccountInfo& info) {
-  UpdateUnconsentedPrimaryAccount();
   for (auto& observer : observer_list_)
     observer.OnExtendedAccountInfoRemoved(info);
 }
diff --git a/components/sync/engine_impl/non_blocking_type_commit_contribution.cc b/components/sync/engine_impl/non_blocking_type_commit_contribution.cc
index bc96b1b5..ccf939b 100644
--- a/components/sync/engine_impl/non_blocking_type_commit_contribution.cc
+++ b/components/sync/engine_impl/non_blocking_type_commit_contribution.cc
@@ -274,6 +274,9 @@
       encrypted_password.mutable_password()
           ->mutable_unencrypted_metadata()
           ->set_url(password_data.signon_realm());
+      encrypted_password.mutable_password()
+          ->mutable_unencrypted_metadata()
+          ->set_blacklisted(password_data.blacklisted());
     }
 
     bool result = cryptographer_->Encrypt(
diff --git a/components/sync/engine_impl/non_blocking_type_commit_contribution_unittest.cc b/components/sync/engine_impl/non_blocking_type_commit_contribution_unittest.cc
index 7194529..e979ed4 100644
--- a/components/sync/engine_impl/non_blocking_type_commit_contribution_unittest.cc
+++ b/components/sync/engine_impl/non_blocking_type_commit_contribution_unittest.cc
@@ -198,6 +198,10 @@
   EXPECT_TRUE(entity.specifics().has_password());
   EXPECT_EQ(kSignonRealm,
             entity.specifics().password().unencrypted_metadata().url());
+  EXPECT_TRUE(
+      entity.specifics().password().unencrypted_metadata().has_blacklisted());
+  EXPECT_FALSE(
+      entity.specifics().password().unencrypted_metadata().blacklisted());
   EXPECT_FALSE(entity.specifics().password().encrypted().blob().empty());
   EXPECT_TRUE(entity.parent_id_string().empty());
   EXPECT_FALSE(entity.unique_position().has_custom_compressed_v1());
diff --git a/components/sync/protocol/password_specifics.proto b/components/sync/protocol/password_specifics.proto
index 8dbee6bc..a12821d 100644
--- a/components/sync/protocol/password_specifics.proto
+++ b/components/sync/protocol/password_specifics.proto
@@ -119,6 +119,10 @@
 // Contains the password specifics metadata which simplifies its lookup.
 message PasswordSpecificsMetadata {
   optional string url = 1;
+
+  // True, if user chose permanently not to save the credentials for the form.
+  // Introduced in M82.
+  optional bool blacklisted = 2;
 }
 
 // Properties of password sync objects.
diff --git a/components/test/data/autofill_assistant/html/autofill_assistant_target_website_iframe_one.html b/components/test/data/autofill_assistant/html/autofill_assistant_target_website_iframe_one.html
index 2e7605506..050ee52 100644
--- a/components/test/data/autofill_assistant/html/autofill_assistant_target_website_iframe_one.html
+++ b/components/test/data/autofill_assistant/html/autofill_assistant_target_website_iframe_one.html
@@ -30,8 +30,8 @@
         button.parentNode.removeChild(button);
       }
 
-      var removeTouchArea = function() {
-        var touch_area = document.getElementById("touch_area");
+      var removeTouchArea = function(id) {
+        var touch_area = document.getElementById(id);
         touch_area.parentNode.removeChild(touch_area);
       }
 
@@ -91,7 +91,20 @@
     </div>
 
     <div>
-      <p id="touch_area" ontouchend="removeTouchArea()">Touchable Area</p>
+      <p id="touch_area" ontouchend="removeTouchArea('touch_area')">
+        Touchable Area</p>
+      <br>
+    </div>
+
+    <div>
+      <p id="touch_area_1" ontouchend="removeTouchArea('touch_area_1')">
+        Touchable Area 1 (iFrame)</p>
+      <br>
+    </div>
+
+    <div>
+      <p id="touch_area_2" ontouchend="removeTouchArea('touch_area_2')">
+        Touchable Area 2 (iFrame)</p>
       <br>
     </div>
 
@@ -108,6 +121,18 @@
     </div>
     <div id="focus">Hidden Text</div>
 
+    <div>
+      <p id="touch_area_3" ontouchend="removeTouchArea('touch_area_3')">
+        Touchable Area 3 (iFrame)</p>
+      <br>
+    </div>
+
+    <div>
+      <p id="touch_area_4" ontouchend="removeTouchArea('touch_area_4')">
+        Touchable Area 4 (iFrame)</p>
+      <br>
+    </div>
+
     <iframe id="iframe" width="100%" height="500" src=
         "autofill_assistant_target_website_iframe_two.html"></iframe>
   </body>
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 5cd92d7..6cd9edd 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -196,6 +196,7 @@
     "//media",
     "//media/capture:capture_lib",
     "//media/mojo/services",
+    "//services/tracing/public/cpp:cpp",
     "//services/viz/privileged/mojom",
     "//skia:skcms",
     "//ui/display/types",
diff --git a/components/viz/service/display/DEPS b/components/viz/service/display/DEPS
index 23bfdee..96ec1f3e 100644
--- a/components/viz/service/display/DEPS
+++ b/components/viz/service/display/DEPS
@@ -25,6 +25,7 @@
   "+skia",
   "+third_party/khronos",
   "+third_party/skia",
+  "+third_party/perfetto/protos/perfetto/trace/track_event",
   "+ui/latency",
   "+ui/gfx/video_types.h",
   "+ui/gl/android/android_surface_control_compat.h",
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index ced4a1d..3e81e49 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -38,6 +38,7 @@
 #include "gpu/command_buffer/client/gles2_interface.h"
 #include "gpu/ipc/scheduler_sequence.h"
 #include "services/viz/public/mojom/compositing/compositor_frame_sink.mojom.h"
+#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.pbzero.h"
 #include "ui/gfx/buffer_types.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/overlay_transform_utils.h"
@@ -688,8 +689,9 @@
                                  swapped_trace_id_, "WaitForSwap");
     swapped_since_resize_ = true;
 
-    ui::LatencyInfo::TraceIntermediateFlowEvents(frame.metadata.latency_info,
-                                                 "Display::DrawAndSwap");
+    ui::LatencyInfo::TraceIntermediateFlowEvents(
+        frame.metadata.latency_info,
+        perfetto::protos::pbzero::ChromeLatencyInfo::STEP_DRAW_AND_SWAP);
 
     cc::benchmark_instrumentation::IssueDisplayRenderingStatsEvent();
     DirectRenderer::SwapFrameData swap_frame_data;
diff --git a/components/viz/service/display_embedder/skia_output_device_gl.cc b/components/viz/service/display_embedder/skia_output_device_gl.cc
index badf412..11a5eaa7 100644
--- a/components/viz/service/display_embedder/skia_output_device_gl.cc
+++ b/components/viz/service/display_embedder/skia_output_device_gl.cc
@@ -41,9 +41,8 @@
                        std::move(did_swap_buffer_complete_callback)),
       mailbox_manager_(mailbox_manager),
       context_state_(context_state),
-      gl_surface_(std::move(gl_surface)) {
-  // Only BufferQueue should support async swap.
-  DCHECK(!gl_surface_->SupportsAsyncSwap());
+      gl_surface_(std::move(gl_surface)),
+      supports_async_swap_(gl_surface_->SupportsAsyncSwap()) {
   capabilities_.output_surface_origin = gl_surface_->GetOrigin();
   capabilities_.supports_post_sub_buffer = gl_surface_->SupportsPostSubBuffer();
   if (feature_info->workarounds()
@@ -172,8 +171,15 @@
   gfx::Size surface_size =
       gfx::Size(sk_surface_->width(), sk_surface_->height());
 
-  FinishSwapBuffers(gl_surface_->SwapBuffers(std::move(feedback)), surface_size,
-                    std::move(latency_info));
+  if (supports_async_swap_) {
+    auto callback = base::BindOnce(&SkiaOutputDeviceGL::DoFinishSwapBuffers,
+                                   weak_ptr_factory_.GetWeakPtr(), surface_size,
+                                   std::move(latency_info));
+    gl_surface_->SwapBuffersAsync(std::move(callback), std::move(feedback));
+  } else {
+    FinishSwapBuffers(gl_surface_->SwapBuffers(std::move(feedback)),
+                      surface_size, std::move(latency_info));
+  }
 }
 
 void SkiaOutputDeviceGL::PostSubBuffer(
@@ -185,10 +191,20 @@
   gfx::Size surface_size =
       gfx::Size(sk_surface_->width(), sk_surface_->height());
 
-  FinishSwapBuffers(
-      gl_surface_->PostSubBuffer(rect.x(), rect.y(), rect.width(),
-                                 rect.height(), std::move(feedback)),
-      surface_size, std::move(latency_info));
+  if (supports_async_swap_) {
+    auto callback = base::BindOnce(&SkiaOutputDeviceGL::DoFinishSwapBuffers,
+                                   weak_ptr_factory_.GetWeakPtr(), surface_size,
+                                   std::move(latency_info));
+    gl_surface_->PostSubBufferAsync(rect.x(), rect.y(), rect.width(),
+                                    rect.height(), std::move(callback),
+                                    std::move(feedback));
+
+  } else {
+    FinishSwapBuffers(
+        gl_surface_->PostSubBuffer(rect.x(), rect.y(), rect.width(),
+                                   rect.height(), std::move(feedback)),
+        surface_size, std::move(latency_info));
+  }
 }
 
 void SkiaOutputDeviceGL::CommitOverlayPlanes(
@@ -199,8 +215,25 @@
   gfx::Size surface_size =
       gfx::Size(sk_surface_->width(), sk_surface_->height());
 
-  FinishSwapBuffers(gl_surface_->CommitOverlayPlanes(std::move(feedback)),
-                    surface_size, std::move(latency_info));
+  if (supports_async_swap_) {
+    auto callback = base::BindOnce(&SkiaOutputDeviceGL::DoFinishSwapBuffers,
+                                   weak_ptr_factory_.GetWeakPtr(), surface_size,
+                                   std::move(latency_info));
+    gl_surface_->CommitOverlayPlanesAsync(std::move(callback),
+                                          std::move(feedback));
+  } else {
+    FinishSwapBuffers(gl_surface_->CommitOverlayPlanes(std::move(feedback)),
+                      surface_size, std::move(latency_info));
+  }
+}
+
+void SkiaOutputDeviceGL::DoFinishSwapBuffers(
+    const gfx::Size& size,
+    std::vector<ui::LatencyInfo> latency_info,
+    gfx::SwapResult result,
+    std::unique_ptr<gfx::GpuFence> gpu_fence) {
+  DCHECK(!gpu_fence);
+  FinishSwapBuffers(result, size, latency_info);
 }
 
 void SkiaOutputDeviceGL::SetDrawRectangle(const gfx::Rect& draw_rectangle) {
diff --git a/components/viz/service/display_embedder/skia_output_device_gl.h b/components/viz/service/display_embedder/skia_output_device_gl.h
index 77dd37e6..c9fea03 100644
--- a/components/viz/service/display_embedder/skia_output_device_gl.h
+++ b/components/viz/service/display_embedder/skia_output_device_gl.h
@@ -20,6 +20,10 @@
 class GLSurface;
 }  // namespace gl
 
+namespace gfx {
+class GpuFence;
+}  // namespace gfx
+
 namespace gpu {
 class MailboxManager;
 class SharedContextState;
@@ -71,12 +75,20 @@
   void EndPaint(const GrBackendSemaphore& semaphore) override;
 
  private:
+  // Used as callback for SwapBuffersAsync and PostSubBufferAsync to finish
+  // operation
+  void DoFinishSwapBuffers(const gfx::Size& size,
+                           std::vector<ui::LatencyInfo> latency_info,
+                           gfx::SwapResult result,
+                           std::unique_ptr<gfx::GpuFence>);
+
   scoped_refptr<gl::GLImage> GetGLImageForMailbox(const gpu::Mailbox& mailbox);
 
   gpu::MailboxManager* const mailbox_manager_;
 
   gpu::SharedContextState* const context_state_;
   scoped_refptr<gl::GLSurface> gl_surface_;
+  const bool supports_async_swap_;
 
   sk_sp<SkSurface> sk_surface_;
 
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index db36659..799ad94 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -1787,12 +1787,12 @@
 
 void RenderFrameHostImpl::AccessibilityFatalError() {
   browser_accessibility_manager_.reset(nullptr);
-  if (accessibility_reset_token_)
+  if (accessibility_reset_token_ || !render_accessibility_)
     return;
 
   accessibility_reset_count_++;
   if (accessibility_reset_count_ >= kMaxAccessibilityResets) {
-    Send(new AccessibilityMsg_FatalError(routing_id_));
+    render_accessibility_->FatalError();
   } else {
     accessibility_reset_token_ = g_next_accessibility_reset_token++;
     Send(new AccessibilityMsg_Reset(routing_id_, accessibility_reset_token_));
@@ -6192,10 +6192,19 @@
   if (!IsRenderFrameCreated())
     return;
 
-  if (!render_accessibility_)
-    GetRemoteAssociatedInterfaces()->GetInterface(&render_accessibility_);
-
   ui::AXMode ax_mode = delegate_->GetAccessibilityMode();
+  if (!ax_mode.has_mode(ui::AXMode::kWebContents)) {
+    // Resetting the Remote signals the renderer to shutdown accessibility
+    // in the renderer.
+    render_accessibility_.reset();
+    return;
+  }
+
+  if (!render_accessibility_) {
+    // Render accessibility is not enabled yet, so bind the interface first.
+    GetRemoteAssociatedInterfaces()->GetInterface(&render_accessibility_);
+  }
+
   render_accessibility_->SetMode(ax_mode.mode());
 }
 
diff --git a/content/browser/renderer_host/compositor_impl_android.cc b/content/browser/renderer_host/compositor_impl_android.cc
index 932d94ac..e6fb9a8 100644
--- a/content/browser/renderer_host/compositor_impl_android.cc
+++ b/content/browser/renderer_host/compositor_impl_android.cc
@@ -521,11 +521,16 @@
           << host_->LayersAsString();
 }
 
+void CompositorImpl::BeginMainFrame(const viz::BeginFrameArgs& args) {
+  latest_frame_time_ = args.frame_time;
+}
+
 void CompositorImpl::UpdateLayerTreeHost() {
+  DCHECK(!latest_frame_time_.is_null());
   client_->UpdateLayerTreeHost();
   if (needs_animate_) {
     needs_animate_ = false;
-    root_window_->Animate(base::TimeTicks::Now());
+    root_window_->Animate(latest_frame_time_);
   }
 }
 
diff --git a/content/browser/renderer_host/compositor_impl_android.h b/content/browser/renderer_host/compositor_impl_android.h
index 16517997..c9d7b2a2 100644
--- a/content/browser/renderer_host/compositor_impl_android.h
+++ b/content/browser/renderer_host/compositor_impl_android.h
@@ -112,7 +112,7 @@
   void DidBeginMainFrame() override {}
   void WillUpdateLayers() override {}
   void DidUpdateLayers() override;
-  void BeginMainFrame(const viz::BeginFrameArgs& args) override {}
+  void BeginMainFrame(const viz::BeginFrameArgs& args) override;
   void OnDeferMainFrameUpdatesChanged(bool) override {}
   void OnDeferCommitsChanged(bool) override {}
   void BeginMainFrameNotExpectedSoon() override {}
@@ -268,6 +268,8 @@
 
   size_t num_of_consecutive_surface_failures_ = 0u;
 
+  base::TimeTicks latest_frame_time_;
+
   base::WeakPtrFactory<CompositorImpl> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(CompositorImpl);
diff --git a/content/browser/renderer_host/input/fling_controller.cc b/content/browser/renderer_host/input/fling_controller.cc
index 631c3b0..ff74cc7 100644
--- a/content/browser/renderer_host/input/fling_controller.cc
+++ b/content/browser/renderer_host/input/fling_controller.cc
@@ -186,7 +186,7 @@
   // will be received when the user puts their finger down for a potential
   // boost. FlingBooster will process the event stream after the current fling
   // is ended and decide whether or not to boost any subsequent FlingStart.
-  EndCurrentFling();
+  EndCurrentFling(gesture_event.event.TimeStamp());
 }
 
 void FlingController::ProgressFling(base::TimeTicks current_time) {
@@ -235,13 +235,13 @@
   if (!fling_is_active && current_fling_parameters_.source_device !=
                               blink::WebGestureDevice::kSyntheticAutoscroll) {
     fling_booster_.Reset();
-    EndCurrentFling();
+    EndCurrentFling(current_time);
     return;
   }
 
   if (std::abs(delta_to_scroll.x()) > kMinInertialScrollDelta ||
       std::abs(delta_to_scroll.y()) > kMinInertialScrollDelta) {
-    GenerateAndSendFlingProgressEvents(delta_to_scroll);
+    GenerateAndSendFlingProgressEvents(current_time, delta_to_scroll);
     last_progress_time_ = current_time;
   }
 
@@ -253,15 +253,16 @@
 void FlingController::StopFling() {
   fling_booster_.Reset();
   if (fling_curve_)
-    EndCurrentFling();
+    EndCurrentFling(clock_->NowTicks());
 }
 
 void FlingController::GenerateAndSendWheelEvents(
+    base::TimeTicks current_time,
     const gfx::Vector2dF& delta,
     blink::WebMouseWheelEvent::Phase phase) {
   MouseWheelEventWithLatencyInfo synthetic_wheel(
       WebInputEvent::kMouseWheel, current_fling_parameters_.modifiers,
-      clock_->NowTicks(), ui::LatencyInfo(ui::SourceEventType::WHEEL));
+      current_time, ui::LatencyInfo(ui::SourceEventType::WHEEL));
   synthetic_wheel.event.delta_units =
       ui::ScrollGranularity::kScrollByPrecisePixel;
   synthetic_wheel.event.delta_x = delta.x();
@@ -278,10 +279,11 @@
 }
 
 void FlingController::GenerateAndSendGestureScrollEvents(
+    base::TimeTicks current_time,
     WebInputEvent::Type type,
     const gfx::Vector2dF& delta /* = gfx::Vector2dF() */) {
   GestureEventWithLatencyInfo synthetic_gesture(
-      type, current_fling_parameters_.modifiers, clock_->NowTicks(),
+      type, current_fling_parameters_.modifiers, current_time,
       ui::LatencyInfo(ui::SourceEventType::INERTIAL));
   synthetic_gesture.event.SetPositionInWidget(current_fling_parameters_.point);
   synthetic_gesture.event.SetPositionInScreen(
@@ -306,19 +308,20 @@
 }
 
 void FlingController::GenerateAndSendFlingProgressEvents(
+    base::TimeTicks current_time,
     const gfx::Vector2dF& delta) {
   switch (current_fling_parameters_.source_device) {
     case blink::WebGestureDevice::kTouchpad: {
       blink::WebMouseWheelEvent::Phase phase =
           first_fling_update_sent() ? blink::WebMouseWheelEvent::kPhaseChanged
                                     : blink::WebMouseWheelEvent::kPhaseBegan;
-      GenerateAndSendWheelEvents(delta, phase);
+      GenerateAndSendWheelEvents(current_time, delta, phase);
       break;
     }
     case blink::WebGestureDevice::kTouchscreen:
     case blink::WebGestureDevice::kSyntheticAutoscroll:
-      GenerateAndSendGestureScrollEvents(WebInputEvent::kGestureScrollUpdate,
-                                         delta);
+      GenerateAndSendGestureScrollEvents(
+          current_time, WebInputEvent::kGestureScrollUpdate, delta);
       break;
     case blink::WebGestureDevice::kUninitialized:
     case blink::WebGestureDevice::kScrollbar:
@@ -329,15 +332,17 @@
   fling_booster_.ObserveProgressFling(current_fling_parameters_.velocity);
 }
 
-void FlingController::GenerateAndSendFlingEndEvents() {
+void FlingController::GenerateAndSendFlingEndEvents(
+    base::TimeTicks current_time) {
   switch (current_fling_parameters_.source_device) {
     case blink::WebGestureDevice::kTouchpad:
-      GenerateAndSendWheelEvents(gfx::Vector2d(),
+      GenerateAndSendWheelEvents(current_time, gfx::Vector2d(),
                                  blink::WebMouseWheelEvent::kPhaseEnded);
       break;
     case blink::WebGestureDevice::kTouchscreen:
     case blink::WebGestureDevice::kSyntheticAutoscroll:
-      GenerateAndSendGestureScrollEvents(WebInputEvent::kGestureScrollEnd);
+      GenerateAndSendGestureScrollEvents(current_time,
+                                         WebInputEvent::kGestureScrollEnd);
       break;
     case blink::WebGestureDevice::kUninitialized:
     case blink::WebGestureDevice::kScrollbar:
@@ -347,10 +352,10 @@
   }
 }
 
-void FlingController::EndCurrentFling() {
+void FlingController::EndCurrentFling(base::TimeTicks current_time) {
   last_progress_time_ = base::TimeTicks();
 
-  GenerateAndSendFlingEndEvents();
+  GenerateAndSendFlingEndEvents(current_time);
   current_fling_parameters_ = ActiveFlingParameters();
 
   if (fling_curve_) {
@@ -387,7 +392,7 @@
   if (velocity.IsZero() && fling_start_event.SourceDevice() !=
                                blink::WebGestureDevice::kSyntheticAutoscroll) {
     fling_booster_.Reset();
-    EndCurrentFling();
+    EndCurrentFling(fling_start_event.TimeStamp());
     return false;
   }
 
@@ -398,7 +403,7 @@
   // state of fling_booster_ and return false.
   if (root_widget_viewport_size.IsEmpty()) {
     fling_booster_.Reset();
-    EndCurrentFling();
+    EndCurrentFling(last_seen_scroll_update_);
     return false;
   }
 
diff --git a/content/browser/renderer_host/input/fling_controller.h b/content/browser/renderer_host/input/fling_controller.h
index c524d559..91e6308 100644
--- a/content/browser/renderer_host/input/fling_controller.h
+++ b/content/browser/renderer_host/input/fling_controller.h
@@ -123,12 +123,14 @@
   void ScheduleFlingProgress();
 
   // Used to generate synthetic wheel events from touchpad fling and send them.
-  void GenerateAndSendWheelEvents(const gfx::Vector2dF& delta,
+  void GenerateAndSendWheelEvents(base::TimeTicks current_time,
+                                  const gfx::Vector2dF& delta,
                                   blink::WebMouseWheelEvent::Phase phase);
 
   // Used to generate synthetic gesture scroll events from touchscreen fling and
   // send them.
   void GenerateAndSendGestureScrollEvents(
+      base::TimeTicks current_time,
       blink::WebInputEvent::Type type,
       const gfx::Vector2dF& delta = gfx::Vector2dF());
 
@@ -138,11 +140,12 @@
   // to progress flings with touchscreen and touchpad source respectively.
   // The reason for this difference is that during the touchpad fling we still
   // send wheel events to JS and generating GSU events directly is not enough.
-  void GenerateAndSendFlingProgressEvents(const gfx::Vector2dF& delta);
+  void GenerateAndSendFlingProgressEvents(base::TimeTicks current_time,
+                                          const gfx::Vector2dF& delta);
 
-  void GenerateAndSendFlingEndEvents();
+  void GenerateAndSendFlingEndEvents(base::TimeTicks current_time);
 
-  void EndCurrentFling();
+  void EndCurrentFling(base::TimeTicks current_time);
 
   // Used to update the fling_curve_ state based on the parameters of the fling
   // start event. Returns true if the fling curve was updated for a valid
diff --git a/content/browser/renderer_host/input/input_router_impl.cc b/content/browser/renderer_host/input/input_router_impl.cc
index 3606a3b..4687453 100644
--- a/content/browser/renderer_host/input/input_router_impl.cc
+++ b/content/browser/renderer_host/input/input_router_impl.cc
@@ -26,6 +26,7 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/input_event_ack_state.h"
 #include "ipc/ipc_sender.h"
+#include "services/tracing/public/cpp/perfetto/flow_event_utils.h"
 #include "ui/events/blink/blink_event_util.h"
 #include "ui/events/blink/blink_features.h"
 #include "ui/events/blink/web_input_event_traits.h"
@@ -40,6 +41,8 @@
 using blink::WebMouseEvent;
 using blink::WebMouseWheelEvent;
 using blink::WebTouchEvent;
+using perfetto::protos::pbzero::ChromeLatencyInfo;
+using perfetto::protos::pbzero::TrackEvent;
 using ui::WebInputEventTraits;
 
 namespace {
@@ -514,11 +517,20 @@
     mojom::WidgetInputHandler::DispatchEventCallback callback) {
   TRACE_EVENT1("input", "InputRouterImpl::FilterAndSendWebInputEvent", "type",
                WebInputEvent::GetName(input_event.GetType()));
-  TRACE_EVENT_WITH_FLOW2(
-      "input,benchmark,devtools.timeline", "LatencyInfo.Flow",
-      TRACE_ID_GLOBAL(latency_info.trace_id()),
-      TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "step",
-      "SendInputEventUI", "frameTreeNodeId", frame_tree_node_id_);
+  TRACE_EVENT("input,benchmark,devtools.timeline", "LatencyInfo.Flow",
+              [&latency_info, this](perfetto::EventContext ctx) {
+                ChromeLatencyInfo* info =
+                    ctx.event()->set_chrome_latency_info();
+                info->set_trace_id(latency_info.trace_id());
+                info->set_step(ChromeLatencyInfo::STEP_SEND_INPUT_EVENT_UI);
+                info->set_frame_tree_node_id(frame_tree_node_id_);
+
+                tracing::FillFlowEvent(
+                    ctx,
+                    perfetto::protos::pbzero::
+                        TrackEvent_LegacyEvent_FlowDirection_FLOW_INOUT,
+                    latency_info.trace_id());
+              });
 
   output_stream_validator_.Validate(input_event);
   InputEventAckState filtered_state =
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index dd6d4fb..1baaaea1 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -789,6 +789,13 @@
       ->SetInitialFocus(reverse);
 }
 
+void RenderViewHostImpl::AnimateDoubleTapZoom(const gfx::Point& point,
+                                              const gfx::Rect& rect) {
+  static_cast<RenderFrameHostImpl*>(GetMainFrame())
+      ->GetAssociatedLocalMainFrame()
+      ->AnimateDoubleTapZoom(point, rect);
+}
+
 void RenderViewHostImpl::RenderWidgetDidFirstVisuallyNonEmptyPaint() {
   did_first_visually_non_empty_paint_ = true;
   delegate_->DidFirstVisuallyNonEmptyPaint(this);
diff --git a/content/browser/renderer_host/render_view_host_impl.h b/content/browser/renderer_host/render_view_host_impl.h
index cb27619..5670a4e 100644
--- a/content/browser/renderer_host/render_view_host_impl.h
+++ b/content/browser/renderer_host/render_view_host_impl.h
@@ -166,6 +166,10 @@
   // https://crbug.com/763548.
   void DispatchRenderViewCreated();
 
+  // Tells the renderer process to request a page-scale animation based on the
+  // specified point/rect.
+  void AnimateDoubleTapZoom(const gfx::Point& point, const gfx::Rect& rect);
+
   // Tells the renderer process to run the page's unload handler.
   // A completion callback is invoked by the renderer when the handler
   // execution completes.
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 173e4e0..0358c81 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -3126,8 +3126,7 @@
   }
 
   auto* root_rvhi = RenderViewHostImpl::From(root_view->GetRenderWidgetHost());
-  root_rvhi->Send(new ViewMsg_AnimateDoubleTapZoom(
-      root_rvhi->GetRoutingID(), transformed_point, transformed_rect_to_zoom));
+  root_rvhi->AnimateDoubleTapZoom(transformed_point, transformed_rect_to_zoom);
 }
 
 void RenderWidgetHostImpl::OnZoomToFindInPageRectInMainFrame(
diff --git a/content/browser/service_worker/service_worker_provider_host.cc b/content/browser/service_worker/service_worker_provider_host.cc
index f65f8aa8..9d33e7e0b 100644
--- a/content/browser/service_worker/service_worker_provider_host.cc
+++ b/content/browser/service_worker/service_worker_provider_host.cc
@@ -89,12 +89,6 @@
   container_host_.reset();
 }
 
-ServiceWorkerVersion* ServiceWorkerProviderHost::running_hosted_version()
-    const {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-  return running_hosted_version_;
-}
-
 void ServiceWorkerProviderHost::CompleteStartWorkerPreparation(
     int process_id,
     mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker>
diff --git a/content/browser/service_worker/service_worker_provider_host.h b/content/browser/service_worker/service_worker_provider_host.h
index cd9866b..60e172d 100644
--- a/content/browser/service_worker/service_worker_provider_host.h
+++ b/content/browser/service_worker/service_worker_provider_host.h
@@ -57,10 +57,9 @@
 
   int provider_id() const { return provider_id_; }
   int worker_process_id() const { return worker_process_id_; }
-
-  // This is nullptr when the worker is still starting up (until
-  // CompleteStartWorkerPreparation() is called).
-  ServiceWorkerVersion* running_hosted_version() const;
+  ServiceWorkerVersion* running_hosted_version() const {
+    return running_hosted_version_;
+  }
 
   // Completes initialization of this provider host. It is called once a
   // renderer process has been found to host the worker.
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index 6be234e..bdfcffa 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -4494,11 +4494,17 @@
   EXPECT_EQ(nullptr, router->wheel_target_);
 }
 
+#if defined(OS_LINUX)
+#define MAYBE_MouseWheelEventPositionChange \
+  DISABLED_MouseWheelEventPositionChange
+#else
+#define MAYBE_MouseWheelEventPositionChange MouseWheelEventPositionChange
+#endif
 // Ensure that the positions of mouse wheel events sent to cross-process
 // subframes account for any change in the position of the subframe during the
 // scroll sequence.
 IN_PROC_BROWSER_TEST_F(SitePerProcessMouseWheelHitTestBrowserTest,
-                       MouseWheelEventPositionChange) {
+                       MAYBE_MouseWheelEventPositionChange) {
   GURL main_url(embedded_test_server()->GetURL(
       "/frame_tree/page_with_tall_positioned_frame.html"));
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
diff --git a/content/browser/tracing/background_tracing_active_scenario.cc b/content/browser/tracing/background_tracing_active_scenario.cc
index 07ae2d2..2828e2c9 100644
--- a/content/browser/tracing/background_tracing_active_scenario.cc
+++ b/content/browser/tracing/background_tracing_active_scenario.cc
@@ -21,7 +21,9 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/system/data_pipe_drainer.h"
 #include "services/tracing/public/cpp/perfetto/perfetto_config.h"
+#include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
 #include "services/tracing/public/cpp/perfetto/trace_event_data_source.h"
+#include "services/tracing/public/cpp/trace_startup.h"
 #include "services/tracing/public/cpp/tracing_features.h"
 
 using base::trace_event::TraceConfig;
@@ -65,6 +67,7 @@
                                base::OnceClosure on_failure) = 0;
   virtual void AbortScenario(
       const base::RepeatingClosure& on_abort_callback) = 0;
+  virtual bool did_setup_startup_tracing() const = 0;
 };
 
 class PerfettoTracingSession
@@ -81,8 +84,9 @@
     // TODO(crbug.com/941318): Re-enable startup tracing for Android once all
     // Perfetto-related deadlocks are resolved.
     if (!TracingControllerImpl::GetInstance()->IsTracing()) {
-      tracing::TraceEventDataSource::GetInstance()->SetupStartupTracing(
-          /*privacy_filtering_enabled=*/true);
+      did_setup_startup_tracing_ = tracing::SetupStartupTracingForProcess(
+          /*privacy_filtering_enabled=*/true,
+          /*enable_sampler_profiler=*/false);
     }
 #endif
 
@@ -125,6 +129,10 @@
     on_abort_callback.Run();
   }
 
+  bool did_setup_startup_tracing() const override {
+    return did_setup_startup_tracing_;
+  }
+
   // mojo::DataPipeDrainer::Client implementation:
   void OnDataAvailable(const void* data, size_t num_bytes) override {
     raw_data_->append(reinterpret_cast<const char*>(data), num_bytes);
@@ -180,6 +188,7 @@
   std::unique_ptr<std::string> raw_data_;
   bool has_finished_read_buffers_ = false;
   bool has_finished_receiving_data_ = false;
+  bool did_setup_startup_tracing_ = false;
 };
 
 class LegacyTracingSession
@@ -192,8 +201,9 @@
     // TODO(crbug.com/941318): Re-enable startup tracing for Android once all
     // Perfetto-related deadlocks are resolved.
     if (!TracingControllerImpl::GetInstance()->IsTracing()) {
-      tracing::TraceEventDataSource::GetInstance()->SetupStartupTracing(
-          /*privacy_filtering_enabled=*/false);
+      did_setup_startup_tracing_ = tracing::SetupStartupTracingForProcess(
+          /*privacy_filtering_enabled=*/false,
+          /*enable_sampler_profiler=*/false);
     }
 #endif
 
@@ -257,8 +267,13 @@
     }
   }
 
+  bool did_setup_startup_tracing() const override {
+    return did_setup_startup_tracing_;
+  }
+
  private:
   BackgroundTracingActiveScenario* const parent_scenario_;
+  bool did_setup_startup_tracing_ = false;
 };
 
 BackgroundTracingActiveScenario::BackgroundTracingActiveScenario(
@@ -350,20 +365,6 @@
   if (!chrome_config.event_filters().empty())
     modes |= base::trace_event::TraceLog::FILTERING_MODE;
 
-// TODO(crbug.com/941318): Re-enable startup tracing for Perfetto backend on
-// Android once all Perfetto-related deadlocks are resolved.
-#if !defined(OS_ANDROID)
-  TraceConfig chrome_config_for_trace_log(chrome_config);
-  // Perfetto backend configures buffer sizes when tracing is started in the
-  // service (see perfetto_config.cc). Zero them out here for TraceLog to avoid
-  // DCHECKs in TraceConfig::Merge.
-  chrome_config_for_trace_log.SetTraceBufferSizeInKb(0);
-  chrome_config_for_trace_log.SetTraceBufferSizeInEvents(0);
-
-  base::trace_event::TraceLog::GetInstance()->SetEnabled(
-      chrome_config_for_trace_log, modes);
-#endif  // !defined(OS_ANDROID)
-
   DCHECK(!tracing_session_);
   if (base::FeatureList::IsEnabled(features::kBackgroundTracingProtoOutput)) {
     tracing_session_ = std::make_unique<PerfettoTracingSession>(
@@ -373,6 +374,22 @@
         std::make_unique<LegacyTracingSession>(this, chrome_config);
   }
 
+// TODO(crbug.com/941318): Re-enable startup tracing for Perfetto backend on
+// Android once all Perfetto-related deadlocks are resolved.
+#if !defined(OS_ANDROID)
+  if (tracing_session_->did_setup_startup_tracing()) {
+    TraceConfig chrome_config_for_trace_log(chrome_config);
+    // Perfetto backend configures buffer sizes when tracing is started in the
+    // service (see perfetto_config.cc). Zero them out here for TraceLog to
+    // avoid DCHECKs in TraceConfig::Merge.
+    chrome_config_for_trace_log.SetTraceBufferSizeInKb(0);
+    chrome_config_for_trace_log.SetTraceBufferSizeInEvents(0);
+
+    base::trace_event::TraceLog::GetInstance()->SetEnabled(
+        chrome_config_for_trace_log, modes);
+  }
+#endif  // !defined(OS_ANDROID)
+
   SetState(State::kTracing);
   BackgroundTracingManagerImpl::RecordMetric(Metrics::RECORDING_ENABLED);
   return true;
diff --git a/content/browser/tracing/startup_tracing_browsertest.cc b/content/browser/tracing/startup_tracing_browsertest.cc
index b9931973..bb9d6d0 100644
--- a/content/browser/tracing/startup_tracing_browsertest.cc
+++ b/content/browser/tracing/startup_tracing_browsertest.cc
@@ -132,8 +132,9 @@
 // the SMB once the full tracing service starts up. This is to catch common
 // deadlocks.
 IN_PROC_BROWSER_TEST_F(StartupTracingInProcessTest, TestFilledStartupBuffer) {
-  tracing::TraceEventDataSource::GetInstance()->SetupStartupTracing(
-      /*privacy_filtering_enabled=*/false);
+  CHECK(tracing::SetupStartupTracingForProcess(
+      /*privacy_filtering_enabled=*/false,
+      /*enable_sampler_profiler=*/false));
 
   auto config = tracing::TraceStartupConfig::GetInstance()
                     ->GetDefaultBrowserStartupConfig();
diff --git a/content/browser/tracing/tracing_controller_browsertest.cc b/content/browser/tracing/tracing_controller_browsertest.cc
index a7e7451..f07ab96 100644
--- a/content/browser/tracing/tracing_controller_browsertest.cc
+++ b/content/browser/tracing/tracing_controller_browsertest.cc
@@ -349,6 +349,9 @@
   DISABLED_EnableAndStopTracingWithFilePath
 #define MAYBE_EnableAndStopTracingWithCompression \
   DISABLED_EnableAndStopTracingWithCompression
+#define MAYBE_EnableAndStopTracingWithEmptyFile \
+  DISABLED_EnableAndStopTracingWithEmptyFile
+#define MAYBE_DoubleStopTracing DISABLED_DoubleStopTracing
 #define MAYBE_ProcessesPresentInTrace DISABLED_ProcessesPresentInTrace
 #else
 #define MAYBE_EnableAndStopTracing EnableAndStopTracing
@@ -357,6 +360,9 @@
 #define MAYBE_EnableAndStopTracingWithFilePath EnableAndStopTracingWithFilePath
 #define MAYBE_EnableAndStopTracingWithCompression \
   EnableAndStopTracingWithCompression
+#define MAYBE_EnableAndStopTracingWithEmptyFile \
+  EnableAndStopTracingWithEmptyFile
+#define MAYBE_DoubleStopTracing DoubleStopTracing
 #define MAYBE_ProcessesPresentInTrace ProcessesPresentInTrace
 #endif
 
@@ -450,7 +456,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(TracingControllerTest,
-                       EnableAndStopTracingWithEmptyFile) {
+                       MAYBE_EnableAndStopTracingWithEmptyFile) {
   Navigate(shell());
 
   base::RunLoop run_loop;
@@ -468,7 +474,7 @@
   run_loop.Run();
 }
 
-IN_PROC_BROWSER_TEST_F(TracingControllerTest, DoubleStopTracing) {
+IN_PROC_BROWSER_TEST_F(TracingControllerTest, MAYBE_DoubleStopTracing) {
   Navigate(shell());
 
   base::RunLoop run_loop;
diff --git a/content/common/accessibility_messages.h b/content/common/accessibility_messages.h
index e793c54..dd70f7a 100644
--- a/content/common/accessibility_messages.h
+++ b/content/common/accessibility_messages.h
@@ -146,10 +146,6 @@
 IPC_MESSAGE_ROUTED1(AccessibilityMsg_Reset,
                     int /* reset token */)
 
-// Kill the renderer because we got a fatal error in the accessibility tree
-// and we've already reset too many times.
-IPC_MESSAGE_ROUTED0(AccessibilityMsg_FatalError)
-
 // Request a one-time snapshot of the accessibility tree without
 // enabling accessibility if it wasn't already enabled. The passed id
 // will be returned in the AccessibilityHostMsg_SnapshotResponse message.
diff --git a/content/common/render_accessibility.mojom b/content/common/render_accessibility.mojom
index 3a0fa75..8ff041e 100644
--- a/content/common/render_accessibility.mojom
+++ b/content/common/render_accessibility.mojom
@@ -18,4 +18,8 @@
   // contain at least the ui::AXMode::kWebContents value to enable accessibility
   // support for web contents. See ui/accessibility/ax_mode.h for valid values.
   SetMode(uint32 ax_mode);
+
+  // Kills the renderer. Sent when there is a fatal error in the accessibility
+  // tree and the maximum number of resets has been hit.
+  FatalError();
 };
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index 7aa8ff0..276bd5b 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -127,12 +127,6 @@
                     bool /* result */)
 #endif
 
-// Sent to the main-frame's view to request performing a page scale animation
-// based on the point/rect provided.
-IPC_MESSAGE_ROUTED2(ViewMsg_AnimateDoubleTapZoom,
-                    gfx::Point /* tap point */,
-                    gfx::Rect /* rect_to_zoom */)
-
 // Sent to the main-frame's view to request performing a zoom-to-find-in-page
 // based on the rect provided.
 IPC_MESSAGE_ROUTED1(ViewMsg_ZoomToFindInPageRect, gfx::Rect /*rect_to_zoom */)
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 801826b5..865c1481 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -905,16 +905,18 @@
   // Returns the WakeLockContext accociated with this WebContents.
   virtual device::mojom::WakeLockContext* GetWakeLockContext() = 0;
 
-  using ImageDownloadCallback = base::OnceCallback<void(
-      int id,
-      int http_status_code,  // Can be 0 e.g. for data: URLs.
-      const GURL& image_url,
-      const std::vector<SkBitmap>& bitmaps,
-      /* The sizes in pixel of the bitmaps before they were resized due to the
-         max bitmap size passed to DownloadImage(). Each entry in the bitmaps
-         vector corresponds to an entry in the sizes vector. If a bitmap was
-         resized, there should be a single returned bitmap. */
-      const std::vector<gfx::Size>& sizes)>;
+  // |http_status_code| can be 0 e.g. for data: URLs.
+  // |bitmaps| will be empty on download failure.
+  // |sizes| are the sizes in pixels of the bitmaps before they were resized due
+  // to the max bitmap size passed to DownloadImage(). Each entry in the bitmaps
+  // vector corresponds to an entry in the sizes vector. If a bitmap was
+  // resized, there should be a single returned bitmap.
+  using ImageDownloadCallback =
+      base::OnceCallback<void(int id,
+                              int http_status_code,
+                              const GURL& image_url,
+                              const std::vector<SkBitmap>& bitmaps,
+                              const std::vector<gfx::Size>& sizes)>;
 
   // Sends a request to download the given image |url| and returns the unique
   // id of the download request. When the download is finished, |callback| will
diff --git a/content/public/common/BUILD.gn b/content/public/common/BUILD.gn
index 4642d27..ba68284 100644
--- a/content/public/common/BUILD.gn
+++ b/content/public/common/BUILD.gn
@@ -200,6 +200,7 @@
 
   public_deps = [
     ":interfaces",
+    ":renderer_type",
     ":service_names",
     "//content/common",
     "//ipc",
@@ -327,6 +328,10 @@
   export_header = "content/common/content_export.h"
 }
 
+mojom("renderer_type") {
+  sources = [ "media_playback_renderer_type.mojom" ]
+}
+
 mojom("web_preferences_mojom") {
   sources = [ "web_preferences.mojom" ]
 
diff --git a/content/public/common/media_playback_renderer_type.mojom b/content/public/common/media_playback_renderer_type.mojom
new file mode 100644
index 0000000..43d00a93
--- /dev/null
+++ b/content/public/common/media_playback_renderer_type.mojom
@@ -0,0 +1,20 @@
+// 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.
+
+module content.mojom;
+
+enum RendererType {
+  // Media playback uses RendererImpl, which is the same as other
+  // platforms. For video codec supported by v4l2 (e.g. h264), it's still
+  // accelerated by hardware. Video will be rendererd on graphics plane.
+  DEFAULT_RENDERER = 0,
+
+  // Enables Mojo Renderer (MojoRenderer) for media playback.
+  // On Chromecast, CMA renderer is enabled here.
+  MOJO_RENDERER = 1,
+
+  // Enables RemotingRenderer for playing remoting media in
+  // blink::WebMediaPlayer.
+  REMOTING_RENDERER = 2,
+};
diff --git a/content/public/renderer/render_frame_media_playback_options.h b/content/public/renderer/render_frame_media_playback_options.h
index 32b53942..2135fde 100644
--- a/content/public/renderer/render_frame_media_playback_options.h
+++ b/content/public/renderer/render_frame_media_playback_options.h
@@ -6,6 +6,7 @@
 #define CONTENT_PUBLIC_RENDERER_RENDER_FRAME_MEDIA_PLAYBACK_OPTIONS_H_
 
 #include "build/build_config.h"
+#include "content/public/common/media_playback_renderer_type.mojom.h"
 
 namespace content {
 
@@ -29,8 +30,16 @@
   // Whether background video optimization is supported on current platform.
   bool is_background_video_track_optimization_supported = true;
 
-  // Whether MojoRenderer should be used for given |render_frame|.
-  bool is_mojo_renderer_enabled = true;
+  // Which renderer should be used in this media playback.
+  mojom::RendererType renderer_type = mojom::RendererType::DEFAULT_RENDERER;
+
+  bool is_mojo_renderer_enabled() const {
+    return renderer_type == mojom::RendererType::MOJO_RENDERER;
+  }
+
+  bool is_remoting_renderer_enabled() const {
+    return renderer_type == mojom::RendererType::REMOTING_RENDERER;
+  }
 };
 
 }  // namespace content
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index 1fe6796..86bfa4e 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -298,7 +298,6 @@
     IPC_MESSAGE_HANDLER(AccessibilityMsg_PerformAction, OnPerformAction)
     IPC_MESSAGE_HANDLER(AccessibilityMsg_EventBundle_ACK, OnEventsAck)
     IPC_MESSAGE_HANDLER(AccessibilityMsg_Reset, OnReset)
-    IPC_MESSAGE_HANDLER(AccessibilityMsg_FatalError, OnFatalError)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
   return handled;
@@ -865,10 +864,6 @@
   SendPendingAccessibilityEvents();
 }
 
-void RenderAccessibilityImpl::OnFatalError() {
-  CHECK(false) << "Invalid accessibility tree.";
-}
-
 void RenderAccessibilityImpl::OnHitTest(const gfx::Point& point,
                                         ax::mojom::Event event_to_fire,
                                         int action_request_id) {
diff --git a/content/renderer/accessibility/render_accessibility_impl.h b/content/renderer/accessibility/render_accessibility_impl.h
index ba986bcd..44e92760 100644
--- a/content/renderer/accessibility/render_accessibility_impl.h
+++ b/content/renderer/accessibility/render_accessibility_impl.h
@@ -160,7 +160,6 @@
   // Handlers for messages from the browser to the renderer.
   void OnPerformAction(const ui::AXActionData& data);
   void OnEventsAck(int ack_token);
-  void OnFatalError();
   void OnReset(int reset_token);
 
   void OnHitTest(const gfx::Point& point,
diff --git a/content/renderer/accessibility/render_accessibility_manager.cc b/content/renderer/accessibility/render_accessibility_manager.cc
index b3e5aa6..a32ef236 100644
--- a/content/renderer/accessibility/render_accessibility_manager.cc
+++ b/content/renderer/accessibility/render_accessibility_manager.cc
@@ -22,6 +22,12 @@
     mojo::PendingAssociatedReceiver<mojom::RenderAccessibility> receiver) {
   DCHECK(!receiver_.is_bound());
   receiver_.Bind(std::move(receiver));
+  receiver_.set_disconnect_handler(base::BindOnce(
+      [](RenderAccessibilityManager* impl) {
+        impl->receiver_.reset();
+        impl->SetMode(0);
+      },
+      base::Unretained(this)));
 }
 
 RenderAccessibilityImpl*
@@ -58,4 +64,8 @@
   render_frame_->NotifyAccessibilityModeChange(new_mode);
 }
 
+void RenderAccessibilityManager::FatalError() {
+  CHECK(false) << "Invalid accessibility tree.";
+}
+
 }  // namespace content
diff --git a/content/renderer/accessibility/render_accessibility_manager.h b/content/renderer/accessibility/render_accessibility_manager.h
index 786ffa6..ba07276 100644
--- a/content/renderer/accessibility/render_accessibility_manager.h
+++ b/content/renderer/accessibility/render_accessibility_manager.h
@@ -54,6 +54,7 @@
 
   // mojom::RenderAccessibility implementation.
   void SetMode(uint32_t ax_mode) override;
+  void FatalError() override;
 
  private:
   // The RenderFrameImpl that owns us.
diff --git a/content/renderer/input/render_widget_input_handler.cc b/content/renderer/input/render_widget_input_handler.cc
index dd0de3f..7db490c 100644
--- a/content/renderer/input/render_widget_input_handler.cc
+++ b/content/renderer/input/render_widget_input_handler.cc
@@ -25,6 +25,8 @@
 #include "content/renderer/render_frame_proxy.h"
 #include "content/renderer/render_thread_impl.h"
 #include "content/renderer/render_widget.h"
+#include "services/tracing/public/cpp/perfetto/flow_event_utils.h"
+#include "services/tracing/public/cpp/perfetto/macros.h"
 #include "third_party/blink/public/common/input/web_gesture_device.h"
 #include "third_party/blink/public/common/input/web_gesture_event.h"
 #include "third_party/blink/public/common/input/web_keyboard_event.h"
@@ -56,6 +58,8 @@
 using blink::WebPointerEvent;
 using blink::WebTouchEvent;
 using blink::WebTouchPoint;
+using perfetto::protos::pbzero::ChromeLatencyInfo;
+using perfetto::protos::pbzero::TrackEvent;
 using ui::DidOverscrollParams;
 
 namespace content {
@@ -351,10 +355,15 @@
   TRACE_EVENT1("renderer,benchmark,rail",
                "RenderWidgetInputHandler::OnHandleInputEvent", "event",
                WebInputEvent::GetName(input_event.GetType()));
-  TRACE_EVENT_WITH_FLOW1("input,benchmark", "LatencyInfo.Flow",
-                         TRACE_ID_GLOBAL(latency_info.trace_id()),
-                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
-                         "step", "HandleInputEventMain");
+  TRACE_EVENT("input,benchmark", "LatencyInfo.Flow",
+              [&latency_info](perfetto::EventContext ctx) {
+                ChromeLatencyInfo* info =
+                    ctx.event()->set_chrome_latency_info();
+                info->set_trace_id(latency_info.trace_id());
+                info->set_step(ChromeLatencyInfo::STEP_HANDLE_INPUT_EVENT_MAIN);
+                tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_INOUT,
+                                       latency_info.trace_id());
+              });
 
   // If we don't have a high res timer, these metrics won't be accurate enough
   // to be worth collecting. Note that this does introduce some sampling bias.
diff --git a/content/renderer/media/media_factory.cc b/content/renderer/media/media_factory.cc
index 6823532..4eddb30 100644
--- a/content/renderer/media/media_factory.cc
+++ b/content/renderer/media/media_factory.cc
@@ -349,7 +349,7 @@
   auto factory_selector = CreateRendererFactorySelector(
       media_log.get(), use_media_player_renderer,
       render_frame_->GetRenderFrameMediaPlaybackOptions()
-          .is_mojo_renderer_enabled,
+          .is_mojo_renderer_enabled(),
       GetDecoderFactory(),
       std::make_unique<media::RemotePlaybackClientWrapperImpl>(client),
       &media_observer);
@@ -432,6 +432,8 @@
               .is_background_video_playback_enabled,
           render_frame_->GetRenderFrameMediaPlaybackOptions()
               .is_background_video_track_optimization_supported,
+          render_frame_->GetRenderFrameMediaPlaybackOptions()
+              .is_remoting_renderer_enabled(),
           std::move(power_status_helper)));
 
   std::unique_ptr<media::VideoFrameCompositor> vfc =
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index 9294830..9693972 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -2352,6 +2352,26 @@
   ASSERT_TRUE(GetRenderAccessibilityManager()->GetRenderAccessibilityImpl());
 }
 
+TEST_F(RenderViewImplTest, AccessibilityModeOnClosingConnection) {
+  // Force the RenderAccessibilityManager to bind a pending receiver so that we
+  // can test what happens after closing the remote endpoint.
+  mojo::AssociatedRemote<mojom::RenderAccessibility> remote;
+  GetRenderAccessibilityManager()->BindReceiver(
+      remote.BindNewEndpointAndPassReceiver());
+
+  GetRenderAccessibilityManager()->SetMode(ui::kAXModeWebContentsOnly.mode());
+  ASSERT_TRUE(GetAccessibilityMode() == ui::kAXModeWebContentsOnly);
+  ASSERT_TRUE(GetRenderAccessibilityManager()->GetRenderAccessibilityImpl());
+
+  // Closing the remote endpoint of the mojo pipe gets accessibility disabled
+  // for the frame and the RenderAccessibility object deleted.
+  remote.reset();
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(GetRenderAccessibilityManager());
+  ASSERT_TRUE(GetAccessibilityMode().is_mode_off());
+  ASSERT_FALSE(GetRenderAccessibilityManager()->GetRenderAccessibilityImpl());
+}
+
 // Checks that when a navigation starts in the renderer, |navigation_start| is
 // recorded at an appropriate time and is passed in the corresponding message.
 TEST_F(RenderViewImplTest, RendererNavigationStartTransmittedToBrowser) {
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 8d235493..97d7026e 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -1174,8 +1174,6 @@
     IPC_MESSAGE_HANDLER(ViewMsg_MoveOrResizeStarted, OnMoveOrResizeStarted)
     IPC_MESSAGE_HANDLER(ViewMsg_EnablePreferredSizeChangedMode,
                         OnEnablePreferredSizeChangedMode)
-    IPC_MESSAGE_HANDLER(ViewMsg_AnimateDoubleTapZoom,
-                        OnAnimateDoubleTapZoomInMainFrame)
     IPC_MESSAGE_HANDLER(ViewMsg_ZoomToFindInPageRect, OnZoomToFindInPageRect)
     IPC_MESSAGE_HANDLER(ViewMsg_SetBackgroundOpaque, OnSetBackgroundOpaque)
 
@@ -1924,12 +1922,6 @@
   }
 }
 
-void RenderViewImpl::OnAnimateDoubleTapZoomInMainFrame(
-    const gfx::Point& point,
-    const blink::WebRect& bound) {
-  GetWebView()->AnimateDoubleTapZoom(point, bound);
-}
-
 void RenderViewImpl::OnZoomToFindInPageRect(
     const blink::WebRect& rect_to_zoom) {
   GetWebView()->ZoomToFindInPageRect(rect_to_zoom);
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 7ac12ec..ddf098a 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -405,8 +405,6 @@
   void OnDisableScrollbarsForSmallWindows(
       const gfx::Size& disable_scrollbars_size_limit);
   void OnEnablePreferredSizeChangedMode();
-  void OnAnimateDoubleTapZoomInMainFrame(const gfx::Point& point,
-                                         const blink::WebRect& rect_to_zoom);
   void OnZoomToFindInPageRect(const blink::WebRect& rect_to_zoom);
   void OnMoveOrResizeStarted();
   void OnExitFullscreen();
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
index 92c49392..450b90d4 100644
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -158,8 +158,6 @@
     "browser/web_test/fake_bluetooth_chooser.h",
     "browser/web_test/fake_bluetooth_chooser_factory.cc",
     "browser/web_test/fake_bluetooth_chooser_factory.h",
-    "browser/web_test/fake_bluetooth_delegate.cc",
-    "browser/web_test/fake_bluetooth_delegate.h",
     "browser/web_test/fake_bluetooth_scanning_prompt.cc",
     "browser/web_test/fake_bluetooth_scanning_prompt.h",
     "browser/web_test/leak_detector.cc",
diff --git a/content/shell/browser/web_test/blink_test_controller.cc b/content/shell/browser/web_test/blink_test_controller.cc
index c898b1d1..eed2d06 100644
--- a/content/shell/browser/web_test/blink_test_controller.cc
+++ b/content/shell/browser/web_test/blink_test_controller.cc
@@ -10,7 +10,6 @@
 #include <algorithm>
 #include <iostream>
 #include <memory>
-#include <queue>
 #include <set>
 #include <utility>
 #include <vector>
@@ -576,7 +575,6 @@
   WebTestContentBrowserClient::Get()->SetPopupBlockingEnabled(false);
   WebTestContentBrowserClient::Get()->ResetMockClipboardHost();
   WebTestContentBrowserClient::Get()->SetScreenOrientationChanged(false);
-  WebTestContentBrowserClient::Get()->ResetFakeBluetoothDelegate();
   navigation_history_dump_ = "";
   pixel_dump_.reset();
   actual_pixel_hash_ = "";
diff --git a/content/shell/browser/web_test/fake_bluetooth_delegate.cc b/content/shell/browser/web_test/fake_bluetooth_delegate.cc
deleted file mode 100644
index 4f02cca..0000000
--- a/content/shell/browser/web_test/fake_bluetooth_delegate.cc
+++ /dev/null
@@ -1,139 +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 "content/shell/browser/web_test/fake_bluetooth_delegate.h"
-
-#include "content/public/browser/web_contents.h"
-#include "device/bluetooth/bluetooth_device.h"
-#include "third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h"
-#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
-#include "url/origin.h"
-
-using blink::WebBluetoothDeviceId;
-using device::BluetoothDevice;
-using device::BluetoothUUID;
-
-namespace content {
-
-// public
-FakeBluetoothDelegate::FakeBluetoothDelegate() = default;
-FakeBluetoothDelegate::~FakeBluetoothDelegate() = default;
-
-WebBluetoothDeviceId FakeBluetoothDelegate::GetWebBluetoothDeviceId(
-    RenderFrameHost* frame,
-    const std::string& device_address) {
-  auto& device_address_to_id_map = GetAddressToIdMapForOrigin(frame);
-  auto it = device_address_to_id_map.find(device_address);
-  if (it != device_address_to_id_map.end())
-    return it->second;
-  return {};
-}
-
-std::string FakeBluetoothDelegate::GetDeviceAddress(
-    RenderFrameHost* frame,
-    const WebBluetoothDeviceId& device_id) {
-  auto& device_address_to_id_map = GetAddressToIdMapForOrigin(frame);
-  for (auto& entry : device_address_to_id_map) {
-    if (entry.second == device_id)
-      return entry.first;
-  }
-  return std::string();
-}
-
-blink::WebBluetoothDeviceId FakeBluetoothDelegate::AddScannedDevice(
-    RenderFrameHost* frame,
-    const std::string& device_address) {
-  return GetOrCreateDeviceIdForDeviceAddress(frame, device_address);
-}
-
-WebBluetoothDeviceId FakeBluetoothDelegate::GrantServiceAccessPermission(
-    RenderFrameHost* frame,
-    const BluetoothDevice* device,
-    const blink::mojom::WebBluetoothRequestDeviceOptions* options) {
-  WebBluetoothDeviceId device_id =
-      GetOrCreateDeviceIdForDeviceAddress(frame, device->GetAddress());
-  device_id_to_name_map_[device_id] =
-      device->GetName() ? *device->GetName() : std::string();
-  GrantUnionOfServicesForDevice(device_id, options);
-  return device_id;
-}
-
-bool FakeBluetoothDelegate::HasDevicePermission(
-    RenderFrameHost* frame,
-    const WebBluetoothDeviceId& device_id) {
-  return base::Contains(device_id_to_services_map_, device_id);
-}
-
-bool FakeBluetoothDelegate::IsAllowedToAccessService(
-    RenderFrameHost* frame,
-    const WebBluetoothDeviceId& device_id,
-    const BluetoothUUID& service) {
-  auto id_to_services_it = device_id_to_services_map_.find(device_id);
-  if (id_to_services_it == device_id_to_services_map_.end())
-    return false;
-
-  return base::Contains(id_to_services_it->second, service);
-}
-
-bool FakeBluetoothDelegate::IsAllowedToAccessAtLeastOneService(
-    RenderFrameHost* frame,
-    const WebBluetoothDeviceId& device_id) {
-  auto id_to_services_it = device_id_to_services_map_.find(device_id);
-  if (id_to_services_it == device_id_to_services_map_.end())
-    return false;
-
-  return !id_to_services_it->second.empty();
-}
-
-WebBluetoothDeviceId FakeBluetoothDelegate::GetOrCreateDeviceIdForDeviceAddress(
-    RenderFrameHost* frame,
-    const std::string& device_address) {
-  WebBluetoothDeviceId device_id;
-  auto& device_address_to_id_map = GetAddressToIdMapForOrigin(frame);
-  auto it = device_address_to_id_map.find(device_address);
-  if (it != device_address_to_id_map.end()) {
-    device_id = it->second;
-  } else {
-    device_id = WebBluetoothDeviceId::Create();
-    device_address_to_id_map[device_address] = device_id;
-  }
-  return device_id;
-}
-
-void FakeBluetoothDelegate::GrantUnionOfServicesForDevice(
-    const WebBluetoothDeviceId& device_id,
-    const blink::mojom::WebBluetoothRequestDeviceOptions* options) {
-  if (!options)
-    return;
-
-  // Create an entry for |device_id| in |device_id_to_services_map_| to indicate
-  // that the site can attempt to GATT connect even if |options| does not
-  // contain any services.
-  base::flat_set<BluetoothUUID>& granted_services =
-      device_id_to_services_map_[device_id];
-  if (options->filters) {
-    for (const blink::mojom::WebBluetoothLeScanFilterPtr& filter :
-         options->filters.value()) {
-      if (!filter->services)
-        continue;
-
-      for (const BluetoothUUID& uuid : filter->services.value())
-        granted_services.insert(uuid);
-    }
-  }
-
-  for (const BluetoothUUID& uuid : options->optional_services)
-    granted_services.insert(uuid);
-}
-
-FakeBluetoothDelegate::AddressToIdMap&
-FakeBluetoothDelegate::GetAddressToIdMapForOrigin(RenderFrameHost* frame) {
-  auto* web_contents = WebContents::FromRenderFrameHost(frame);
-  auto origin_pair =
-      std::make_pair(frame->GetLastCommittedOrigin(),
-                     web_contents->GetMainFrame()->GetLastCommittedOrigin());
-  return device_address_to_id_map_for_origin_[origin_pair];
-}
-
-}  // namespace content
diff --git a/content/shell/browser/web_test/fake_bluetooth_delegate.h b/content/shell/browser/web_test/fake_bluetooth_delegate.h
deleted file mode 100644
index 1b80b79f..0000000
--- a/content/shell/browser/web_test/fake_bluetooth_delegate.h
+++ /dev/null
@@ -1,103 +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 CONTENT_SHELL_BROWSER_WEB_TEST_FAKE_BLUETOOTH_DELEGATE_H_
-#define CONTENT_SHELL_BROWSER_WEB_TEST_FAKE_BLUETOOTH_DELEGATE_H_
-
-#include <map>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/containers/flat_set.h"
-#include "content/public/browser/bluetooth_delegate.h"
-#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom-forward.h"
-
-namespace blink {
-class WebBluetoothDeviceId;
-}  // namespace blink
-
-namespace device {
-class BluetoothDevice;
-class BluetoothUUID;
-}  // namespace device
-
-namespace url {
-class Origin;
-}  // namespace url
-
-namespace content {
-
-class RenderFrameHost;
-
-// Fakes Web Bluetooth permissions for web tests by emulating Chrome's
-// implementation.
-class FakeBluetoothDelegate : public BluetoothDelegate {
- public:
-  FakeBluetoothDelegate();
-  ~FakeBluetoothDelegate() override;
-
-  // Move-only class.
-  FakeBluetoothDelegate(const FakeBluetoothDelegate&) = delete;
-  FakeBluetoothDelegate& operator=(const FakeBluetoothDelegate&) = delete;
-
-  // BluetoothDelegate implementation:
-  blink::WebBluetoothDeviceId GetWebBluetoothDeviceId(
-      RenderFrameHost* frame,
-      const std::string& device_address) override;
-  std::string GetDeviceAddress(RenderFrameHost* frame,
-                               const blink::WebBluetoothDeviceId&) override;
-  blink::WebBluetoothDeviceId AddScannedDevice(
-      RenderFrameHost* frame,
-      const std::string& device_address) override;
-  blink::WebBluetoothDeviceId GrantServiceAccessPermission(
-      RenderFrameHost* frame,
-      const device::BluetoothDevice* device,
-      const blink::mojom::WebBluetoothRequestDeviceOptions* options) override;
-  bool HasDevicePermission(
-      RenderFrameHost* frame,
-      const blink::WebBluetoothDeviceId& device_id) override;
-  bool IsAllowedToAccessService(RenderFrameHost* frame,
-                                const blink::WebBluetoothDeviceId& device_id,
-                                const device::BluetoothUUID& service) override;
-  bool IsAllowedToAccessAtLeastOneService(
-      RenderFrameHost* frame,
-      const blink::WebBluetoothDeviceId& device_id) override;
-
- private:
-  using AddressToIdMap = std::map<std::string, blink::WebBluetoothDeviceId>;
-  using OriginPair = std::pair<url::Origin, url::Origin>;
-  using IdToServicesMap = std::map<blink::WebBluetoothDeviceId,
-                                   base::flat_set<device::BluetoothUUID>>;
-  using IdToNameMap = std::map<blink::WebBluetoothDeviceId, std::string>;
-
-  // Finds an existing WebBluetoothDeviceId for |device_address| for |frame| or
-  // creates a new ID for the Bluetooth device on the current frame.
-  blink::WebBluetoothDeviceId GetOrCreateDeviceIdForDeviceAddress(
-      RenderFrameHost* frame,
-      const std::string& device_address);
-
-  // Adds the union of |options->filters->services| and
-  // |options->optional_services| to the allowed services for |device_id|.
-  void GrantUnionOfServicesForDevice(
-      const blink::WebBluetoothDeviceId& device_id,
-      const blink::mojom::WebBluetoothRequestDeviceOptions* options);
-  AddressToIdMap& GetAddressToIdMapForOrigin(RenderFrameHost* frame);
-
-  // Maps origins to their own maps of device address to device ID.
-  // If a given origin and device address does not have an associated device ID,
-  // then the origin does not have permission to access the device.
-  std::map<OriginPair, AddressToIdMap> device_address_to_id_map_for_origin_;
-
-  // These map device IDs to their set of allowed services and device names.
-  // Since devices IDs are randomly generated, it is very unlikely that two
-  // unique devices will share the same ID. Therefore, these maps contain all of
-  // the service permissions and device names from all of the origins.
-  IdToServicesMap device_id_to_services_map_;
-  IdToNameMap device_id_to_name_map_;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_SHELL_BROWSER_WEB_TEST_FAKE_BLUETOOTH_DELEGATE_H_
diff --git a/content/shell/browser/web_test/web_test_content_browser_client.cc b/content/shell/browser/web_test/web_test_content_browser_client.cc
index 730790e..b899d61 100644
--- a/content/shell/browser/web_test/web_test_content_browser_client.cc
+++ b/content/shell/browser/web_test/web_test_content_browser_client.cc
@@ -30,7 +30,6 @@
 #include "content/shell/browser/web_test/blink_test_controller.h"
 #include "content/shell/browser/web_test/fake_bluetooth_chooser.h"
 #include "content/shell/browser/web_test/fake_bluetooth_chooser_factory.h"
-#include "content/shell/browser/web_test/fake_bluetooth_delegate.h"
 #include "content/shell/browser/web_test/mojo_web_test_helper.h"
 #include "content/shell/browser/web_test/web_test_bluetooth_fake_adapter_setter_impl.h"
 #include "content/shell/browser/web_test/web_test_browser_context.h"
@@ -356,16 +355,6 @@
       switches::kRunWebTests);
 }
 
-BluetoothDelegate* WebTestContentBrowserClient::GetBluetoothDelegate() {
-  if (!fake_bluetooth_delegate_)
-    fake_bluetooth_delegate_ = std::make_unique<FakeBluetoothDelegate>();
-  return fake_bluetooth_delegate_.get();
-}
-
-void WebTestContentBrowserClient::ResetFakeBluetoothDelegate() {
-  fake_bluetooth_delegate_.reset();
-}
-
 content::TtsControllerDelegate*
 WebTestContentBrowserClient::GetTtsControllerDelegate() {
   return WebTestTtsControllerDelegate::GetInstance();
diff --git a/content/shell/browser/web_test/web_test_content_browser_client.h b/content/shell/browser/web_test/web_test_content_browser_client.h
index aaf3b8fe..0992408 100644
--- a/content/shell/browser/web_test/web_test_content_browser_client.h
+++ b/content/shell/browser/web_test/web_test_content_browser_client.h
@@ -6,8 +6,6 @@
 #define CONTENT_SHELL_BROWSER_WEB_TEST_WEB_TEST_CONTENT_BROWSER_CLIENT_H_
 
 #include <memory>
-#include <string>
-#include <vector>
 
 #include "content/public/common/client_hints.mojom.h"
 #include "content/shell/browser/shell_content_browser_client.h"
@@ -22,7 +20,6 @@
 
 class FakeBluetoothChooser;
 class FakeBluetoothChooserFactory;
-class FakeBluetoothDelegate;
 class MockClipboardHost;
 class MockPlatformNotificationService;
 class WebTestBrowserContext;
@@ -42,7 +39,6 @@
 
   // Retrieves the last created FakeBluetoothChooser instance.
   std::unique_ptr<FakeBluetoothChooser> GetNextFakeBluetoothChooser();
-  void ResetFakeBluetoothDelegate();
 
   // ContentBrowserClient overrides.
   void RenderProcessWillLaunch(RenderProcessHost* host) override;
@@ -79,7 +75,7 @@
       service_manager::BinderMapWithContext<content::RenderFrameHost*>* map)
       override;
   bool CanAcceptUntrustedExchangesIfNeeded() override;
-  BluetoothDelegate* GetBluetoothDelegate() override;
+
   content::TtsControllerDelegate* GetTtsControllerDelegate() override;
   content::TtsPlatform* GetTtsPlatform() override;
   bool CanEnterFullscreenWithoutUserActivation() override;
@@ -116,7 +112,6 @@
 
   // Stores the FakeBluetoothChooserFactory that produces FakeBluetoothChoosers.
   std::unique_ptr<FakeBluetoothChooserFactory> fake_bluetooth_chooser_factory_;
-  std::unique_ptr<FakeBluetoothDelegate> fake_bluetooth_delegate_;
   std::unique_ptr<MockClipboardHost> mock_clipboard_host_;
 };
 
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index fe0ecaa..dd69d9f 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -127,7 +127,6 @@
 #import "ios/chrome/browser/ui/promos/signin_promo_view_controller.h"
 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
 #include "ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.h"
-#import "ios/chrome/browser/ui/tab_grid/tab_switcher.h"
 #import "ios/chrome/browser/ui/tab_grid/view_controller_swapping.h"
 #import "ios/chrome/browser/ui/ui_feature_flags.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
@@ -282,8 +281,6 @@
   // The object that drives the Chrome startup/shutdown logic.
   std::unique_ptr<IOSChromeMain> _chromeMain;
 
-  // TabSwitcher object -- the tab grid.
-  id<TabSwitcher> _tabSwitcher;
 
   // True if the current session began from a cold start. False if the app has
   // entered the background at least once since start up.
@@ -328,11 +325,6 @@
   BOOL _animationDisabled;
 }
 
-// Wrangler to handle BVC and tab model creation, access, and related logic.
-// Implements faetures exposed from this object through the
-// BrowserViewInformation protocol.
-@property(nonatomic, strong) BrowserViewWrangler* browserViewWrangler;
-
 // The ChromeBrowserState associated with the main (non-OTR) browsing mode.
 @property(nonatomic, assign) ChromeBrowserState* mainBrowserState;  // Weak.
 
@@ -386,8 +378,6 @@
 - (void)scheduleDeleteTempPasswordsDirectory;
 // Returns whether or not the app can launch in incognito mode.
 - (BOOL)canLaunchInIncognito;
-// Determines which UI should be shown on startup, and shows it.
-- (void)createInitialUI:(ApplicationMode)launchMode;
 // Initializes the first run UI and presents it to the user.
 - (void)showFirstRunUI;
 // Schedules presentation of the first eligible promo found, if any.
@@ -602,10 +592,10 @@
 
   // This is per-window code.
 
-  DCHECK(!self.browserViewWrangler);
+  DCHECK(!self.sceneController.browserViewWrangler);
   DCHECK(self.sceneController.appURLLoadingService);
 
-  self.browserViewWrangler = [[BrowserViewWrangler alloc]
+  self.sceneController.browserViewWrangler = [[BrowserViewWrangler alloc]
              initWithBrowserState:self.mainBrowserState
              webStateListObserver:self.sceneController
        applicationCommandEndpoint:self.sceneController
@@ -613,7 +603,7 @@
              appURLLoadingService:self.sceneController.appURLLoadingService];
 
   // Ensure the main tab model is created. This also creates the BVC.
-  [self.browserViewWrangler createMainBrowser];
+  [self.sceneController.browserViewWrangler createMainBrowser];
 
   // Only create the restoration helper if the browser state was backed up
   // successfully.
@@ -641,16 +631,17 @@
   if (postCrashLaunch || switchFromIncognito) {
     [self.sceneController clearIOSSpecificIncognitoData];
     if (switchFromIncognito)
-      [self.browserViewWrangler
+      [self.sceneController.browserViewWrangler
           switchGlobalStateToMode:ApplicationMode::NORMAL];
   }
   if (switchFromIncognito)
     startInIncognito = NO;
 
-  [self createInitialUI:(startInIncognito ? ApplicationMode::INCOGNITO
-                                          : ApplicationMode::NORMAL)];
+  [self.sceneController
+      createInitialUI:(startInIncognito ? ApplicationMode::INCOGNITO
+                                        : ApplicationMode::NORMAL)];
 
-  [self.browserViewWrangler updateDeviceSharingManager];
+  [self.sceneController.browserViewWrangler updateDeviceSharingManager];
 
   if (!self.startupParameters) {
     // The startup parameters may create new tabs or navigations. If the restore
@@ -662,6 +653,8 @@
 
   // End of per-window code.
 
+  CustomizeUIAppearance();
+
   [self scheduleStartupCleanupTasks];
   [MetricsMediator
       logLaunchMetricsWithStartupInformation:self
@@ -728,7 +721,7 @@
 #pragma mark - Property implementation.
 
 - (id<BrowserInterfaceProvider>)interfaceProvider {
-  return self.browserViewWrangler;
+  return self.sceneController.browserViewWrangler;
 }
 
 - (TabGridCoordinator*)mainCoordinator {
@@ -786,8 +779,8 @@
 
   // Invariant: The UI is stopped before the model is shutdown.
   DCHECK(!_mainCoordinator);
-  [self.browserViewWrangler shutdown];
-  self.browserViewWrangler = nil;
+  [self.sceneController.browserViewWrangler shutdown];
+  self.sceneController.browserViewWrangler = nil;
 
   // End of per-window code.
 
@@ -1178,71 +1171,6 @@
   return ![self.otrTabModel isEmpty];
 }
 
-- (void)createInitialUI:(ApplicationMode)launchMode {
-  DCHECK(self.mainBrowserState);
-
-  // In order to correctly set the mode switch icon, we need to know how many
-  // tabs are in the other tab model. That means loading both models.  They
-  // may already be loaded.
-  // TODO(crbug.com/546203): Find a way to handle this that's closer to the
-  // point where it is necessary.
-  TabModel* mainTabModel = self.mainTabModel;
-  TabModel* otrTabModel = self.otrTabModel;
-
-  // MainCoordinator shouldn't have been initialized yet.
-  DCHECK(!_mainCoordinator);
-
-  // Enables UI initializations to query the keyWindow's size.
-  [self.window makeKeyAndVisible];
-
-  CustomizeUIAppearance();
-
-  // Lazy init of mainCoordinator.
-  [self.mainCoordinator start];
-
-  _tabSwitcher = self.mainCoordinator.tabSwitcher;
-  // Call -restoreInternalState so that the grid shows the correct panel.
-  [_tabSwitcher restoreInternalStateWithMainBrowser:self.mainBrowser
-                                         otrBrowser:self.otrBrowser
-                                      activeBrowser:self.currentBrowser];
-
-  // Decide if the First Run UI needs to run.
-  BOOL firstRun = (FirstRun::IsChromeFirstRun() ||
-                   experimental_flags::AlwaysDisplayFirstRun()) &&
-                  !tests_hook::DisableFirstRun();
-
-  [self.browserViewWrangler switchGlobalStateToMode:launchMode];
-
-  TabModel* tabModel;
-  if (launchMode == ApplicationMode::INCOGNITO) {
-    tabModel = otrTabModel;
-    [self.sceneController
-        setCurrentInterfaceForMode:ApplicationMode::INCOGNITO];
-  } else {
-    tabModel = mainTabModel;
-    [self.sceneController setCurrentInterfaceForMode:ApplicationMode::NORMAL];
-  }
-  if (self.tabSwitcherIsActive) {
-    DCHECK(!self.dismissingTabSwitcher);
-    [self.sceneController
-        beginDismissingTabSwitcherWithCurrentModel:self.mainTabModel
-                                      focusOmnibox:NO];
-    [self.sceneController finishDismissingTabSwitcher];
-  }
-  if (firstRun ||
-      [self.sceneController shouldOpenNTPTabOnActivationOfTabModel:tabModel]) {
-    OpenNewTabCommand* command = [OpenNewTabCommand
-        commandWithIncognito:(self.currentBVC == self.otrBVC)];
-    command.userInitiated = NO;
-    [self.currentBVC.dispatcher openURLInNewTab:command];
-  }
-
-  if (firstRun) {
-    [self showFirstRunUI];
-    // Do not ever show the 'restore' infobar during first run.
-    self.restoreHelper = nil;
-  }
-}
 
 - (void)showFirstRunUI {
   // Register for notification when First Run is completed.
@@ -1517,11 +1445,6 @@
           }));
 }
 
-#pragma mark - MainControllerGuts
-
-- (id<TabSwitcher>)tabSwitcher {
-  return _tabSwitcher;
-}
 
 @end
 
@@ -1536,11 +1459,15 @@
 }
 
 - (DeviceSharingManager*)deviceSharingManager {
-  return [self.browserViewWrangler deviceSharingManager];
+  return [self.sceneController.browserViewWrangler deviceSharingManager];
 }
 
 - (void)setTabSwitcher:(id<TabSwitcher>)switcher {
-  _tabSwitcher = switcher;
+  [self.sceneController setTabSwitcher:switcher];
+}
+
+- (id<TabSwitcher>)tabSwitcher {
+  return self.sceneController.tabSwitcher;
 }
 
 - (void)setTabSwitcherActive:(BOOL)active {
diff --git a/ios/chrome/app/main_controller_guts.h b/ios/chrome/app/main_controller_guts.h
index 91c67f7..92dd1a03 100644
--- a/ios/chrome/app/main_controller_guts.h
+++ b/ios/chrome/app/main_controller_guts.h
@@ -14,11 +14,9 @@
 #import "ios/chrome/browser/crash_report/crash_restore_helper.h"
 
 @class BrowserViewController;
-@class BrowserViewWrangler;
 class ChromeBrowserState;
 @class TabGridCoordinator;
 @protocol BrowserInterfaceProvider;
-@protocol TabSwitcher;
 
 // TODO(crbug.com/1012697): Remove this protocol when SceneController is
 // operational. Move the private internals back into MainController, and pass
@@ -39,8 +37,6 @@
 // Keeps track of the restore state during startup.
 @property(nonatomic, strong) CrashRestoreHelper* restoreHelper;
 
-- (BrowserViewWrangler*)browserViewWrangler;
-- (id<TabSwitcher>)tabSwitcher;
 - (TabModel*)currentTabModel;
 - (ChromeBrowserState*)mainBrowserState;
 - (ChromeBrowserState*)currentBrowserState;
@@ -49,12 +45,15 @@
 - (BrowserViewController*)otrBVC;
 - (TabGridCoordinator*)mainCoordinator;
 - (id<BrowserInterfaceProvider>)interfaceProvider;
+- (UIWindow*)window;
 
 - (void)removeBrowsingDataForBrowserState:(ChromeBrowserState*)browserState
                                timePeriod:(browsing_data::TimePeriod)timePeriod
                                removeMask:(BrowsingDataRemoveMask)removeMask
                           completionBlock:(ProceduralBlock)completionBlock;
 
+- (void)showFirstRunUI;
+
 @end
 
 #endif  // IOS_CHROME_APP_MAIN_CONTROLLER_GUTS_H_
diff --git a/ios/chrome/app/main_controller_private.h b/ios/chrome/app/main_controller_private.h
index b8d95ef0..6b483862 100644
--- a/ios/chrome/app/main_controller_private.h
+++ b/ios/chrome/app/main_controller_private.h
@@ -30,11 +30,12 @@
 @interface MainController (TestingOnly)
 
 @property(nonatomic, readonly) DeviceSharingManager* deviceSharingManager;
-@property(nonatomic, retain) id<TabSwitcher> tabSwitcher;
 
 // Tab switcher state.
 @property(nonatomic, getter=isTabSwitcherActive) BOOL tabSwitcherActive;
 
+@property(nonatomic, strong) id<TabSwitcher> tabSwitcher;
+
 // Sets the internal startup state to indicate that the launch was triggered
 // by an external app opening the given URL.
 - (void)setStartupParametersWithURL:(const GURL&)launchURL;
diff --git a/ios/chrome/browser/passwords/update_password_infobar_controller.mm b/ios/chrome/browser/passwords/update_password_infobar_controller.mm
index 39d6dd58..8744ef55 100644
--- a/ios/chrome/browser/passwords/update_password_infobar_controller.mm
+++ b/ios/chrome/browser/passwords/update_password_infobar_controller.mm
@@ -48,8 +48,10 @@
 
 - (void)infobarLinkDidPress:(NSUInteger)tag {
   DCHECK(self.baseViewController);
+  // TODO(crbug.com/1058340): Provide a Browser for the coordinator.
   self.selectorCoordinator = [[SelectorCoordinator alloc]
-      initWithBaseViewController:self.baseViewController];
+      initWithBaseViewController:self.baseViewController
+                         browser:nullptr];
   self.selectorCoordinator.delegate = self;
   self.selectorCoordinator.options =
       [NSOrderedSet orderedSetWithArray:_delegate->GetAccounts()];
diff --git a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm
index e1f9406b..a6f1da90 100644
--- a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm
@@ -133,7 +133,6 @@
 
   [self addConfirmationButtonToView];
   [self embedUserConsentView];
-  [self addActivityIndicatorToView];
   [self addSkipSigninButtonToView];
 
   [self.view addSubview:self.gradientView];
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index fbe077cc..307bcc6 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -753,10 +753,10 @@
     // removal.
     if (!IsDownloadInfobarMessagesUIEnabled()) {
       _downloadManagerCoordinator = [[DownloadManagerCoordinator alloc]
-          initWithBaseViewController:_browserContainerViewController];
+          initWithBaseViewController:_browserContainerViewController
+                             browser:browser];
       _downloadManagerCoordinator.presenter =
           [[VerticalAnimationContainer alloc] init];
-      _downloadManagerCoordinator.dispatcher = self.dispatcher;
     }
 
     if (!base::FeatureList::IsEnabled(dialogs::kNonModalDialogs)) {
@@ -2171,7 +2171,6 @@
   if (!IsDownloadInfobarMessagesUIEnabled()) {
     // DownloadManagerCoordinator is already created.
     DCHECK(_downloadManagerCoordinator);
-    _downloadManagerCoordinator.webStateList = self.browser->GetWebStateList();
     _downloadManagerCoordinator.bottomMarginHeightAnchor =
         [NamedGuide guideWithName:kSecondaryToolbarGuide view:self.contentArea]
             .heightAnchor;
diff --git a/ios/chrome/browser/ui/download/download_manager_coordinator.h b/ios/chrome/browser/ui/download/download_manager_coordinator.h
index b4d389a9..6f5066b 100644
--- a/ios/chrome/browser/ui/download/download_manager_coordinator.h
+++ b/ios/chrome/browser/ui/download/download_manager_coordinator.h
@@ -12,15 +12,20 @@
 class DownloadTask;
 }  // namespace web
 
-@protocol BrowserCoordinatorCommands;
-@class CommandDispatcher;
 @protocol ContainedPresenter;
-class WebStateList;
 
 // Coordinates presentation of Download Manager UI.
 @interface DownloadManagerCoordinator
     : ChromeCoordinator<DownloadManagerTabHelperDelegate>
 
+// Unavailable, use -initWithBaseViewController:browser:.
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+    NS_UNAVAILABLE;
+// Unavailable, use -initWithBaseViewController:browser:.
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+                              browserState:(ChromeBrowserState*)browserState
+    NS_UNAVAILABLE;
+
 // Presents the receiver's view controller.
 @property(nonatomic) id<ContainedPresenter> presenter;
 
@@ -31,17 +36,12 @@
 // stop method is called.
 @property(nonatomic) web::DownloadTask* downloadTask;
 
-// Needed to observe web state closing. Set to null when stop method is called.
-@property(nonatomic) WebStateList* webStateList;
-
 // Controls the height of the bottom margin.
 @property(nonatomic) NSLayoutDimension* bottomMarginHeightAnchor;
 
 // Underlying UIViewController presented by this coordinator.
 @property(nonatomic, readonly) UIViewController* viewController;
 
-@property(nonatomic, weak) CommandDispatcher* dispatcher;
-
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_DOWNLOAD_DOWNLOAD_MANAGER_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/download/download_manager_coordinator.mm b/ios/chrome/browser/ui/download/download_manager_coordinator.mm
index 3ec4ef14..1aeca3bd 100644
--- a/ios/chrome/browser/ui/download/download_manager_coordinator.mm
+++ b/ios/chrome/browser/ui/download/download_manager_coordinator.mm
@@ -20,6 +20,7 @@
 #import "ios/chrome/browser/download/download_manager_tab_helper.h"
 #import "ios/chrome/browser/download/google_drive_app_util.h"
 #import "ios/chrome/browser/installation_notifier.h"
+#import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/store_kit/store_kit_coordinator.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
 #import "ios/chrome/browser/ui/commands/browser_coordinator_commands.h"
@@ -180,7 +181,6 @@
 @synthesize presenter = _presenter;
 @synthesize animatesPresentation = _animatesPresentation;
 @synthesize downloadTask = _downloadTask;
-@synthesize webStateList = _webStateList;
 @synthesize bottomMarginHeightAnchor = _bottomMarginHeightAnchor;
 
 - (void)dealloc {
@@ -190,6 +190,7 @@
 
 - (void)start {
   DCHECK(self.presenter);
+  DCHECK(self.browser);
 
   _viewController = [[DownloadManagerViewController alloc] init];
   _viewController.delegate = self;
@@ -201,6 +202,8 @@
   self.presenter.presentedViewController = _viewController;
   self.presenter.delegate = self;
 
+  self.browser->GetWebStateList()->AddObserver(&_unopenedDownloads);
+
   [self.presenter prepareForPresentation];
 
   [self.presenter presentAnimated:self.animatesPresentation];
@@ -217,7 +220,9 @@
                                           completion:nil];
   _confirmationDialog = nil;
   _downloadTask = nullptr;
-  self.webStateList = nullptr;
+
+  if (self.browser)
+    (self.browser->GetWebStateList())->RemoveObserver(&_unopenedDownloads);
 
   [_storeKitCoordinator stop];
   _storeKitCoordinator = nil;
@@ -225,19 +230,6 @@
   _installDriveAlertCoordinator = nil;
 }
 
-- (void)setWebStateList:(WebStateList*)webStateList {
-  if (_webStateList == webStateList)
-    return;
-
-  if (_webStateList)
-    _webStateList->RemoveObserver(&_unopenedDownloads);
-
-  _webStateList = webStateList;
-
-  if (_webStateList)
-    _webStateList->AddObserver(&_unopenedDownloads);
-}
-
 - (UIViewController*)viewController {
   return _viewController;
 }
@@ -368,8 +360,8 @@
   if (base::FeatureList::IsEnabled(web::features::kEnablePersistentDownloads)) {
     OpenDownloadsFolderActivity* customActivity =
         [[OpenDownloadsFolderActivity alloc] init];
-    customActivity.browserHandler =
-        HandlerForProtocol(self.dispatcher, BrowserCoordinatorCommands);
+    customActivity.browserHandler = HandlerForProtocol(
+        self.browser->GetCommandDispatcher(), BrowserCoordinatorCommands);
     activities = @[ customActivity ];
   }
   _openInController =
diff --git a/ios/chrome/browser/ui/download/download_manager_coordinator_unittest.mm b/ios/chrome/browser/ui/download/download_manager_coordinator_unittest.mm
index 8da8e2f6..412ec6b 100644
--- a/ios/chrome/browser/ui/download/download_manager_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/download/download_manager_coordinator_unittest.mm
@@ -19,6 +19,7 @@
 #include "ios/chrome/browser/download/download_manager_metric_names.h"
 #import "ios/chrome/browser/download/download_manager_tab_helper.h"
 #import "ios/chrome/browser/download/google_drive_app_util.h"
+#include "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/ui/download/download_manager_view_controller.h"
 #import "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
@@ -76,11 +77,13 @@
   DownloadManagerCoordinatorTest()
       : presenter_([[FakeContainedPresenter alloc] init]),
         base_view_controller_([[UIViewController alloc] init]),
+        browser_(std::make_unique<TestBrowser>()),
         document_interaction_controller_class_(
             OCMClassMock([UIDocumentInteractionController class])),
         tab_helper_(&web_state_),
         coordinator_([[DownloadManagerCoordinator alloc]
-            initWithBaseViewController:base_view_controller_]) {
+            initWithBaseViewController:base_view_controller_
+                               browser:browser_.get()]) {
     [scoped_key_window_.Get() setRootViewController:base_view_controller_];
     coordinator_.presenter = presenter_;
   }
@@ -99,6 +102,7 @@
   web::WebTaskEnvironment task_environment_;
   FakeContainedPresenter* presenter_;
   UIViewController* base_view_controller_;
+  std::unique_ptr<Browser> browser_;
   ScopedKeyWindow scoped_key_window_;
   web::TestWebState web_state_;
   id document_interaction_controller_class_;
@@ -504,12 +508,9 @@
   auto task = CreateTestTask();
   coordinator_.downloadTask = task.get();
   web::DownloadTask* task_ptr = task.get();
-  FakeWebStateListDelegate web_state_list_delegate;
-  WebStateList web_state_list(&web_state_list_delegate);
   auto web_state = std::make_unique<web::TestWebState>();
-  web_state_list.InsertWebState(
+  browser_->GetWebStateList()->InsertWebState(
       0, std::move(web_state), WebStateList::INSERT_NO_FLAGS, WebStateOpener());
-  coordinator_.webStateList = &web_state_list;
   [coordinator_ start];
 
   EXPECT_EQ(1U, base_view_controller_.childViewControllers.count);
@@ -532,7 +533,7 @@
       }));
 
   // Web States are closed without user action only during app termination.
-  web_state_list.CloseAllWebStates(WebStateList::CLOSE_NO_FLAGS);
+  browser_->GetWebStateList()->CloseAllWebStates(WebStateList::CLOSE_NO_FLAGS);
 
   // Download task is destroyed before the download is complete.
   task = nullptr;
@@ -548,7 +549,6 @@
       static_cast<base::HistogramBase::Sample>(DownloadFileResult::Other), 1);
   histogram_tester_.ExpectTotalCount("Download.IOSDownloadFileUIGoogleDrive",
                                      0);
-  coordinator_.webStateList = nullptr;
 }
 
 // Tests closing view controller while the download is in progress. Coordinator
diff --git a/ios/chrome/browser/ui/elements/selector_coordinator.h b/ios/chrome/browser/ui/elements/selector_coordinator.h
index 7bdadec7..4ad0c852 100644
--- a/ios/chrome/browser/ui/elements/selector_coordinator.h
+++ b/ios/chrome/browser/ui/elements/selector_coordinator.h
@@ -19,6 +19,16 @@
 // Coordinator for displaying UI to allow the user to pick among options.
 @interface SelectorCoordinator : ChromeCoordinator
 
+// Use -initWithBaseViewController:browser:
+- (nonnull instancetype)initWithBaseViewController:
+    (nonnull UIViewController*)viewController NS_UNAVAILABLE;
+
+// Use -initWithBaseViewController:browser:
+- (nonnull instancetype)
+    initWithBaseViewController:(nonnull UIViewController*)viewController
+                  browserState:(nonnull ChromeBrowserState*)browserState
+    NS_UNAVAILABLE;
+
 // Options to present to the user.
 @property(nonatomic, nullable, copy) NSOrderedSet<NSString*>* options;
 
diff --git a/ios/chrome/browser/ui/elements/selector_coordinator_unittest.mm b/ios/chrome/browser/ui/elements/selector_coordinator_unittest.mm
index cd554be9..429c53f3c 100644
--- a/ios/chrome/browser/ui/elements/selector_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/elements/selector_coordinator_unittest.mm
@@ -5,6 +5,8 @@
 #import "ios/chrome/browser/ui/elements/selector_coordinator.h"
 
 #import "base/test/ios/wait_util.h"
+#import "base/test/task_environment.h"
+#import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/ui/elements/selector_picker_view_controller.h"
 #import "ios/chrome/browser/ui/elements/selector_view_controller_delegate.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -28,10 +30,14 @@
 // Tests that invoking start on the coordinator presents the selector view, and
 // that invoking stop dismisses the view and invokes the delegate.
 TEST_F(SelectorCoordinatorTest, StartAndStop) {
+  base::test::TaskEnvironment task_environment_;
+
   UIWindow* keyWindow = [[UIApplication sharedApplication] keyWindow];
   UIViewController* rootViewController = keyWindow.rootViewController;
-  SelectorCoordinator* coordinator = [[SelectorCoordinator alloc]
-      initWithBaseViewController:rootViewController];
+  std::unique_ptr<Browser> browser_ = std::make_unique<TestBrowser>();
+  SelectorCoordinator* coordinator =
+      [[SelectorCoordinator alloc] initWithBaseViewController:rootViewController
+                                                      browser:browser_.get()];
 
   void (^testSteps)(void) = ^{
     [coordinator start];
@@ -51,10 +57,14 @@
 // Tests that calling the view controller delegate method invokes the
 // SelectorCoordinatorDelegate method and stops the coordinator.
 TEST_F(SelectorCoordinatorTest, Delegate) {
+  base::test::TaskEnvironment task_environment_;
+
   UIWindow* keyWindow = [[UIApplication sharedApplication] keyWindow];
   UIViewController* rootViewController = keyWindow.rootViewController;
-  SelectorCoordinator* coordinator = [[SelectorCoordinator alloc]
-      initWithBaseViewController:rootViewController];
+  std::unique_ptr<Browser> browser_ = std::make_unique<TestBrowser>();
+  SelectorCoordinator* coordinator =
+      [[SelectorCoordinator alloc] initWithBaseViewController:rootViewController
+                                                      browser:browser_.get()];
   id delegate =
       [OCMockObject mockForProtocol:@protocol(SelectorCoordinatorDelegate)];
   coordinator.delegate = delegate;
diff --git a/ios/chrome/browser/ui/main/BUILD.gn b/ios/chrome/browser/ui/main/BUILD.gn
index d7c6224..87539db8 100644
--- a/ios/chrome/browser/ui/main/BUILD.gn
+++ b/ios/chrome/browser/ui/main/BUILD.gn
@@ -7,8 +7,10 @@
 source_set("scene_guts") {
   sources = [ "scene_controller_guts.h" ]
   deps = [
+    "//ios/chrome/app:mode",
     "//ios/chrome/app/application_delegate:application_delegate_internal",
     "//ios/chrome/browser:utils",
+    "//ios/chrome/browser/ui/tab_grid",
     "//ios/chrome/browser/url_loading",
     "//ios/chrome/browser/web_state_list",
   ]
@@ -34,6 +36,7 @@
     "//components/signin/public/identity_manager",
     "//components/url_formatter",
     "//ios/chrome/app:app",
+    "//ios/chrome/app:tests_hook",
     "//ios/chrome/app/application_delegate:application_delegate_internal",
     "//ios/chrome/browser",
     "//ios/chrome/browser:chrome_url_constants",
@@ -43,11 +46,13 @@
     "//ios/chrome/browser/crash_report:crash_report_internal",
     "//ios/chrome/browser/crash_report/breadcrumbs",
     "//ios/chrome/browser/crash_report/breadcrumbs:feature_flags",
+    "//ios/chrome/browser/first_run",
     "//ios/chrome/browser/main",
     "//ios/chrome/browser/ntp:features",
     "//ios/chrome/browser/signin",
     "//ios/chrome/browser/snapshots",
     "//ios/chrome/browser/tabs:tabs",
+    "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/authentication",
     "//ios/chrome/browser/ui/browser_view",
     "//ios/chrome/browser/ui/commands:commands",
diff --git a/ios/chrome/browser/ui/main/scene_controller.mm b/ios/chrome/browser/ui/main/scene_controller.mm
index 9519759..0814a40d 100644
--- a/ios/chrome/browser/ui/main/scene_controller.mm
+++ b/ios/chrome/browser/ui/main/scene_controller.mm
@@ -17,6 +17,7 @@
 #import "ios/chrome/app/chrome_overlay_window.h"
 #import "ios/chrome/app/deferred_initialization_runner.h"
 #import "ios/chrome/app/main_controller_guts.h"
+#import "ios/chrome/app/tests_hook.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/browsing_data/browsing_data_remove_mask.h"
 #include "ios/chrome/browser/browsing_data/browsing_data_remover.h"
@@ -29,10 +30,12 @@
 #include "ios/chrome/browser/crash_report/breadcrumbs/features.h"
 #include "ios/chrome/browser/crash_report/breakpad_helper.h"
 #include "ios/chrome/browser/crash_report/crash_report_helper.h"
+#import "ios/chrome/browser/first_run/first_run.h"
 #include "ios/chrome/browser/main/browser.h"
 #include "ios/chrome/browser/ntp/features.h"
 #include "ios/chrome/browser/signin/identity_manager_factory.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
+#include "ios/chrome/browser/system_flags.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
 #import "ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller.h"
 #import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
@@ -47,6 +50,7 @@
 #import "ios/chrome/browser/ui/signin_interaction/signin_interaction_coordinator.h"
 #include "ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.h"
 #import "ios/chrome/browser/ui/toolbar/public/omnibox_focuser.h"
+#import "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/multi_window_support.h"
 #import "ios/chrome/browser/ui/util/top_view_controller.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
@@ -145,12 +149,17 @@
 @property(nonatomic, readwrite)
     NTPTabOpeningPostOpeningAction NTPActionAfterTabSwitcherDismissal;
 
+// TabSwitcher object -- the tab grid.
+@property(nonatomic, strong) id<TabSwitcher> tabSwitcher;
+
 @end
 
 @implementation SceneController
 @synthesize settingsNavigationController;  //< From AppNavigation protocol.
 @synthesize appURLLoadingService =
     _appURLLoadingService;  //< From SceneControllerGuts
+@synthesize browserViewWrangler =
+    _browserViewWrangler;  //< From SceneControllerGuts
 
 - (instancetype)initWithSceneState:(SceneState*)sceneState {
   self = [super init];
@@ -204,10 +213,76 @@
   }
 }
 
+#pragma mark - SceneControllerGuts
+- (void)initializeUI {
+  if (self.hasInitializedUI) {
+    return;
+  }
+
+  self.hasInitializedUI = YES;
+}
+
 #pragma mark - private
 
-- (void)initializeUI {
-  self.hasInitializedUI = YES;
+// Determines which UI should be shown on startup, and shows it.
+- (void)createInitialUI:(ApplicationMode)launchMode {
+  DCHECK(self.mainController.mainBrowserState);
+
+  // In order to correctly set the mode switch icon, we need to know how many
+  // tabs are in the other tab model. That means loading both models.  They
+  // may already be loaded.
+  // TODO(crbug.com/546203): Find a way to handle this that's closer to the
+  // point where it is necessary.
+  TabModel* mainTabModel = self.mainInterface.tabModel;
+  TabModel* otrTabModel = self.incognitoInterface.tabModel;
+
+  // Enables UI initializations to query the keyWindow's size.
+  [self.mainController.window makeKeyAndVisible];
+
+  // Lazy init of mainCoordinator.
+  [self.mainController.mainCoordinator start];
+
+  _tabSwitcher = self.mainController.mainCoordinator.tabSwitcher;
+  // Call -restoreInternalState so that the grid shows the correct panel.
+  [_tabSwitcher
+      restoreInternalStateWithMainBrowser:self.mainInterface.browser
+                               otrBrowser:self.incognitoInterface.browser
+                            activeBrowser:self.currentInterface.browser];
+
+  // Decide if the First Run UI needs to run.
+  BOOL firstRun = (FirstRun::IsChromeFirstRun() ||
+                   experimental_flags::AlwaysDisplayFirstRun()) &&
+                  !tests_hook::DisableFirstRun();
+
+  [self.browserViewWrangler switchGlobalStateToMode:launchMode];
+
+  TabModel* tabModel;
+  if (launchMode == ApplicationMode::INCOGNITO) {
+    tabModel = otrTabModel;
+    [self setCurrentInterfaceForMode:ApplicationMode::INCOGNITO];
+  } else {
+    tabModel = mainTabModel;
+    [self setCurrentInterfaceForMode:ApplicationMode::NORMAL];
+  }
+  if (self.mainController.tabSwitcherIsActive) {
+    DCHECK(!self.mainController.dismissingTabSwitcher);
+    [self beginDismissingTabSwitcherWithCurrentModel:self.mainInterface.tabModel
+                                        focusOmnibox:NO];
+    [self finishDismissingTabSwitcher];
+  }
+  if (firstRun || [self shouldOpenNTPTabOnActivationOfTabModel:tabModel]) {
+    OpenNewTabCommand* command =
+        [OpenNewTabCommand commandWithIncognito:(self.currentInterface.bvc ==
+                                                 self.incognitoInterface.bvc)];
+    command.userInitiated = NO;
+    [self.currentInterface.bvc.dispatcher openURLInNewTab:command];
+  }
+
+  if (firstRun) {
+    [self.mainController showFirstRunUI];
+    // Do not ever show the 'restore' infobar during first run.
+    self.mainController.restoreHelper = nil;
+  }
 }
 
 // This method completely destroys all of the UI. It should be called when the
@@ -279,7 +354,7 @@
         });
   }
   [self.mainController.mainCoordinator
-      prepareToShowTabSwitcher:self.mainController.tabSwitcher];
+      prepareToShowTabSwitcher:self.tabSwitcher];
 }
 
 - (void)displayTabSwitcher {
@@ -760,7 +835,7 @@
 #pragma mark - TabSwitching
 
 - (BOOL)openNewTabFromTabSwitcher {
-  if (!self.mainController.tabSwitcher)
+  if (!self.tabSwitcher)
     return NO;
 
   UrlLoadParams urlLoadParams =
@@ -769,10 +844,9 @@
 
   Browser* mainBrowser = self.mainInterface.browser;
   WebStateList* webStateList = mainBrowser->GetWebStateList();
-  [self.mainController.tabSwitcher
-      dismissWithNewTabAnimationToBrowser:mainBrowser
-                        withUrlLoadParams:urlLoadParams
-                                  atIndex:webStateList->count()];
+  [self.tabSwitcher dismissWithNewTabAnimationToBrowser:mainBrowser
+                                      withUrlLoadParams:urlLoadParams
+                                                atIndex:webStateList->count()];
   return YES;
 }
 
@@ -832,14 +906,13 @@
 
     // If the tabSwitcher is contained, check if the parent container is
     // presenting another view controller.
-    if ([[self.mainController.tabSwitcher viewController]
+    if ([[self.tabSwitcher viewController]
                 .parentViewController presentedViewController]) {
       return NO;
     }
 
     // Check if the tabSwitcher is directly presenting another view controller.
-    if ([self.mainController.tabSwitcher viewController]
-            .presentedViewController) {
+    if ([self.tabSwitcher viewController].presentedViewController) {
       return NO;
     }
 
@@ -1024,7 +1097,7 @@
       self.NTPActionAfterTabSwitcherDismissal =
           [self.mainController.startupParameters postOpeningAction];
       [self.mainController setStartupParameters:nil];
-      [self.mainController.tabSwitcher
+      [self.tabSwitcher
           dismissWithNewTabAnimationToBrowser:targetInterface.browser
                             withUrlLoadParams:urlLoadParams
                                       atIndex:tabIndex];
@@ -1315,18 +1388,17 @@
 }
 
 - (void)showTabSwitcher {
-  DCHECK(self.mainController.tabSwitcher);
+  DCHECK(self.tabSwitcher);
   // Tab switcher implementations may need to rebuild state before being
   // displayed.
-  [self.mainController.tabSwitcher
+  [self.tabSwitcher
       restoreInternalStateWithMainBrowser:self.mainInterface.browser
                                otrBrowser:self.incognitoInterface.browser
                             activeBrowser:self.currentInterface.browser];
   self.mainController.tabSwitcherIsActive = YES;
-  [self.mainController.tabSwitcher setDelegate:self];
+  [self.tabSwitcher setDelegate:self];
 
-  [self.mainController.mainCoordinator
-      showTabSwitcher:self.mainController.tabSwitcher];
+  [self.mainController.mainCoordinator showTabSwitcher:self.tabSwitcher];
 }
 
 // Destroys and rebuilds the incognito browser state.
@@ -1336,7 +1408,7 @@
 
   // Clear the Incognito Browser and notify the _tabSwitcher that its otrBrowser
   // will be destroyed.
-  [self.mainController.tabSwitcher setOtrBrowser:nil];
+  [self.tabSwitcher setOtrBrowser:nil];
 
   if (base::FeatureList::IsEnabled(kLogBreadcrumbs)) {
     BreadcrumbManagerBrowserAgent::FromBrowser(self.incognitoInterface.browser)
@@ -1347,7 +1419,7 @@
             self.incognitoInterface.browserState));
   }
 
-  [self.mainController.browserViewWrangler destroyAndRebuildIncognitoBrowser];
+  [self.browserViewWrangler destroyAndRebuildIncognitoBrowser];
 
   if (base::FeatureList::IsEnabled(kLogBreadcrumbs)) {
     breakpad::MonitorBreadcrumbManagerService(
@@ -1361,8 +1433,7 @@
 
   // Always set the new otr Browser for the tablet or grid switcher.
   // Notify the _tabSwitcher with the new Incognito Browser.
-  [self.mainController.tabSwitcher
-      setOtrBrowser:self.incognitoInterface.browser];
+  [self.tabSwitcher setOtrBrowser:self.incognitoInterface.browser];
 
   // This seems the best place to deem the destroying and rebuilding the
   // incognito browser state to be completed.
diff --git a/ios/chrome/browser/ui/main/scene_controller_guts.h b/ios/chrome/browser/ui/main/scene_controller_guts.h
index f444b52..0fcc176 100644
--- a/ios/chrome/browser/ui/main/scene_controller_guts.h
+++ b/ios/chrome/browser/ui/main/scene_controller_guts.h
@@ -9,20 +9,30 @@
 
 #include "ios/chrome/app/application_delegate/startup_information.h"
 #import "ios/chrome/app/application_delegate/tab_opening.h"
+#include "ios/chrome/app/application_mode.h"
 #import "ios/chrome/browser/procedural_block_types.h"
+#import "ios/chrome/browser/ui/tab_grid/tab_switcher.h"
 #import "ios/chrome/browser/url_loading/app_url_loading_service.h"
 #import "ios/chrome/browser/url_loading/url_loading_params.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
 
 class ChromeBrowserState;
+@class BrowserViewWrangler;
 @class TabModel;
 
 @protocol SceneControllerGuts <WebStateListObserving>
 
+// Wrangler to handle BVC and tab model creation, access, and related logic.
+// Implements faetures exposed from this object through the
+// BrowserViewInformation protocol.
+@property(nonatomic, strong) BrowserViewWrangler* browserViewWrangler;
+
 // The scene level component for url loading. Is passed down to
 // browser state level UrlLoadingService instances.
 @property(nonatomic, assign) AppUrlLoadingService* appURLLoadingService;
 
+- (void)createInitialUI:(ApplicationMode)launchMode;
+
 - (void)dismissModalDialogsWithCompletion:(ProceduralBlock)completion
                            dismissOmnibox:(BOOL)dismissOmnibox;
 
@@ -55,6 +65,10 @@
 // screen and showing the appropriate BVC.
 - (void)finishDismissingTabSwitcher;
 
+// Testing only.
+- (void)setTabSwitcher:(id<TabSwitcher>)switcher;
+- (id<TabSwitcher>)tabSwitcher;
+
 #pragma mark - AppNavigation helpers
 
 // Presents a SignedInAccountsViewController for |browserState| on the top view
diff --git a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
index 3ceed59..b215650d 100644
--- a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
+++ b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
@@ -7,6 +7,12 @@
 
 #import <UIKit/UIKit.h>
 
+// A11y Identifiers for testing.
+extern NSString* const kConfirmationAlertMoreInfoAccessibilityIdentifier;
+extern NSString* const kConfirmationAlertTitleAccessibilityIdentifier;
+extern NSString* const kConfirmationAlertSubtitleAccessibilityIdentifier;
+extern NSString* const kConfirmationAlertPrimaryActionAccessibilityIdentifier;
+
 @protocol ConfirmationAlertActionHandler;
 
 // A view controller useful to show modal alerts and confirmations. The main
diff --git a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
index c85c4de..2acd368 100644
--- a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
+++ b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
@@ -13,6 +13,15 @@
 #error "This file requires ARC support."
 #endif
 
+NSString* const kConfirmationAlertMoreInfoAccessibilityIdentifier =
+    @"kConfirmationAlertMoreInfoAccessibilityIdentifier";
+NSString* const kConfirmationAlertTitleAccessibilityIdentifier =
+    @"kConfirmationAlertTitleAccessibilityIdentifier";
+NSString* const kConfirmationAlertSubtitleAccessibilityIdentifier =
+    @"kConfirmationAlertSubtitleAccessibilityIdentifier";
+NSString* const kConfirmationAlertPrimaryActionAccessibilityIdentifier =
+    @"kConfirmationAlertPrimaryActionAccessibilityIdentifier";
+
 namespace {
 
 constexpr CGFloat kButtonVerticalInsets = 17;
@@ -251,6 +260,8 @@
                target:self
                action:@selector(didTapHelpButton)];
     [items addObject:helpButton];
+    helpButton.accessibilityIdentifier =
+        kConfirmationAlertMoreInfoAccessibilityIdentifier;
     // Set the help button as the left button item so it can be used as a
     // popover anchor.
     _helpButton = helpButton;
@@ -297,6 +308,8 @@
   title.textAlignment = NSTextAlignmentCenter;
   title.translatesAutoresizingMaskIntoConstraints = NO;
   title.adjustsFontForContentSizeCategory = YES;
+  title.accessibilityIdentifier =
+      kConfirmationAlertTitleAccessibilityIdentifier;
   return title;
 }
 
@@ -310,6 +323,8 @@
   subtitle.textAlignment = NSTextAlignmentCenter;
   subtitle.translatesAutoresizingMaskIntoConstraints = NO;
   subtitle.adjustsFontForContentSizeCategory = YES;
+  subtitle.accessibilityIdentifier =
+      kConfirmationAlertSubtitleAccessibilityIdentifier;
   return subtitle;
 }
 
@@ -354,6 +369,8 @@
   primaryActionButton.layer.cornerRadius = kPrimaryButtonCornerRadius;
   primaryActionButton.titleLabel.adjustsFontForContentSizeCategory = NO;
   primaryActionButton.translatesAutoresizingMaskIntoConstraints = NO;
+  primaryActionButton.accessibilityIdentifier =
+      kConfirmationAlertPrimaryActionAccessibilityIdentifier;
   return primaryActionButton;
 }
 
diff --git a/ios/chrome/credential_provider_extension/ui/resources/no_data_illustration.imageset/Contents.json b/ios/chrome/credential_provider_extension/ui/resources/empty_credentials_illustration.imageset/Contents.json
similarity index 100%
rename from ios/chrome/credential_provider_extension/ui/resources/no_data_illustration.imageset/Contents.json
rename to ios/chrome/credential_provider_extension/ui/resources/empty_credentials_illustration.imageset/Contents.json
diff --git a/ios/chrome/credential_provider_extension/ui/resources/no_data_illustration.imageset/illustration_dark.png b/ios/chrome/credential_provider_extension/ui/resources/empty_credentials_illustration.imageset/illustration_dark.png
similarity index 100%
rename from ios/chrome/credential_provider_extension/ui/resources/no_data_illustration.imageset/illustration_dark.png
rename to ios/chrome/credential_provider_extension/ui/resources/empty_credentials_illustration.imageset/illustration_dark.png
Binary files differ
diff --git a/ios/chrome/credential_provider_extension/ui/resources/no_data_illustration.imageset/illustration_light.png b/ios/chrome/credential_provider_extension/ui/resources/empty_credentials_illustration.imageset/illustration_light.png
similarity index 100%
rename from ios/chrome/credential_provider_extension/ui/resources/no_data_illustration.imageset/illustration_light.png
rename to ios/chrome/credential_provider_extension/ui/resources/empty_credentials_illustration.imageset/illustration_light.png
Binary files differ
diff --git a/ios/showcase/BUILD.gn b/ios/showcase/BUILD.gn
index 93ca944d..5649886 100644
--- a/ios/showcase/BUILD.gn
+++ b/ios/showcase/BUILD.gn
@@ -26,6 +26,7 @@
     "//ios/showcase/badges",
     "//ios/showcase/bubble",
     "//ios/showcase/content_suggestions",
+    "//ios/showcase/credential_provider",
     "//ios/showcase/infobars",
     "//ios/showcase/omnibox_popup",
     "//ios/showcase/recent_tabs",
@@ -73,6 +74,7 @@
     "//ios/showcase/bubble:eg2_tests",
     "//ios/showcase/content_suggestions:eg2_tests",
     "//ios/showcase/core:eg2_tests",
+    "//ios/showcase/credential_provider:eg2_tests",
     "//ios/showcase/infobars:eg2_tests",
     "//ios/showcase/text_badge_view:eg2_tests",
   ]
diff --git a/ios/showcase/core/showcase_model.mm b/ios/showcase/core/showcase_model.mm
index dd2e768..7da05e9 100644
--- a/ios/showcase/core/showcase_model.mm
+++ b/ios/showcase/core/showcase_model.mm
@@ -17,6 +17,21 @@
 + (NSArray<showcase::ModelRow*>*)model {
   return @[
     @{
+      showcase::kClassForDisplayKey : @"ConsentViewController",
+      showcase::kClassForInstantiationKey : @"ConsentViewController",
+      showcase::kUseCaseKey : @"Credential Provider Consent UI",
+    },
+    @{
+      showcase::kClassForDisplayKey : @"EmptyCredentialsViewController",
+      showcase::kClassForInstantiationKey : @"EmptyCredentialsViewController",
+      showcase::kUseCaseKey : @"Credential Provider Empty Credentials UI",
+    },
+    @{
+      showcase::kClassForDisplayKey : @"StaleCredentialsViewController",
+      showcase::kClassForInstantiationKey : @"StaleCredentialsViewController",
+      showcase::kUseCaseKey : @"Credential Provider Stale Credentials UI",
+    },
+    @{
       showcase::kClassForDisplayKey : @"ContentSuggestionsViewController",
       showcase::kClassForInstantiationKey : @"SCContentSuggestionsCoordinator",
       showcase::kUseCaseKey : @"Content Suggestions UI",
diff --git a/ios/showcase/credential_provider/BUILD.gn b/ios/showcase/credential_provider/BUILD.gn
new file mode 100644
index 0000000..1eac6607
--- /dev/null
+++ b/ios/showcase/credential_provider/BUILD.gn
@@ -0,0 +1,29 @@
+# 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.
+
+source_set("credential_provider") {
+  sources = []
+  deps = [ "//ios/chrome/credential_provider_extension/ui" ]
+  libs = [ "UIKit.framework" ]
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
+
+source_set("eg2_tests") {
+  defines = [ "CHROME_EARL_GREY_2" ]
+  configs += [
+    "//build/config/compiler:enable_arc",
+    "//build/config/ios:xctest_config",
+  ]
+  testonly = true
+  sources = [ "credential_provider_egtest.mm" ]
+  deps = [
+    "//base",
+    "//ios/chrome/common/ui/confirmation_alert",
+    "//ios/showcase/test:eg2_test",
+    "//ios/testing/earl_grey:eg_test_support+eg2",
+    "//ios/third_party/earl_grey2:test_lib",
+    "//ui/base",
+  ]
+  libs = [ "UIKit.framework" ]
+}
diff --git a/ios/showcase/credential_provider/OWNERS b/ios/showcase/credential_provider/OWNERS
new file mode 100644
index 0000000..d3e76b7
--- /dev/null
+++ b/ios/showcase/credential_provider/OWNERS
@@ -0,0 +1,4 @@
+javierrobles@chromium.org
+
+# TEAM: ios-directory-owners@chromium.org
+# OS: iOS
diff --git a/ios/showcase/credential_provider/credential_provider_egtest.mm b/ios/showcase/credential_provider/credential_provider_egtest.mm
new file mode 100644
index 0000000..215d8e93
--- /dev/null
+++ b/ios/showcase/credential_provider/credential_provider_egtest.mm
@@ -0,0 +1,90 @@
+// 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 "base/ios/ios_util.h"
+#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h"
+#import "ios/showcase/test/showcase_eg_utils.h"
+#import "ios/showcase/test/showcase_test_case.h"
+#import "ios/testing/earl_grey/earl_grey_test.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+id<GREYMatcher> TitleMatcher() {
+  return grey_accessibilityID(kConfirmationAlertTitleAccessibilityIdentifier);
+}
+
+id<GREYMatcher> SubtitleMatcher() {
+  return grey_accessibilityID(
+      kConfirmationAlertSubtitleAccessibilityIdentifier);
+}
+
+id<GREYMatcher> PrimaryActionButtonMatcher() {
+  return grey_accessibilityID(
+      kConfirmationAlertPrimaryActionAccessibilityIdentifier);
+}
+
+id<GREYMatcher> MoreInfoButtonMatcher() {
+  return grey_accessibilityID(
+      kConfirmationAlertMoreInfoAccessibilityIdentifier);
+}
+
+}  // namespace
+
+// Tests for the suggestions view controller.
+@interface SCCredentialProviderTestCase : ShowcaseTestCase
+@end
+
+@implementation SCCredentialProviderTestCase
+
+// Tests ConsentViewController.
+- (void)testConsentScreen {
+  showcase_utils::Open(@"ConsentViewController");
+  [[EarlGrey selectElementWithMatcher:TitleMatcher()]
+      assertWithMatcher:grey_interactable()];
+  [[EarlGrey selectElementWithMatcher:SubtitleMatcher()]
+      assertWithMatcher:grey_interactable()];
+  [[EarlGrey selectElementWithMatcher:PrimaryActionButtonMatcher()]
+      assertWithMatcher:grey_interactable()];
+  [[EarlGrey selectElementWithMatcher:MoreInfoButtonMatcher()]
+      assertWithMatcher:grey_interactable()];
+
+  showcase_utils::Close();
+}
+
+// Tests ConsentViewController.
+- (void)testEmptyCredentialsScreen {
+  showcase_utils::Open(@"EmptyCredentialsViewController");
+  [[EarlGrey selectElementWithMatcher:TitleMatcher()]
+      assertWithMatcher:grey_interactable()];
+  [[EarlGrey selectElementWithMatcher:SubtitleMatcher()]
+      assertWithMatcher:grey_interactable()];
+  [[EarlGrey selectElementWithMatcher:PrimaryActionButtonMatcher()]
+      assertWithMatcher:grey_nil()];
+  [[EarlGrey selectElementWithMatcher:MoreInfoButtonMatcher()]
+      assertWithMatcher:grey_nil()];
+
+  showcase_utils::Close();
+}
+
+// Tests ConsentViewController.
+- (void)testStaleCredentialsScreen {
+  showcase_utils::Open(@"StaleCredentialsViewController");
+  [[EarlGrey selectElementWithMatcher:TitleMatcher()]
+      assertWithMatcher:grey_interactable()];
+  [[EarlGrey selectElementWithMatcher:SubtitleMatcher()]
+      assertWithMatcher:grey_interactable()];
+  [[EarlGrey selectElementWithMatcher:PrimaryActionButtonMatcher()]
+      assertWithMatcher:grey_nil()];
+  [[EarlGrey selectElementWithMatcher:MoreInfoButtonMatcher()]
+      assertWithMatcher:grey_nil()];
+
+  showcase_utils::Close();
+}
+
+@end
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 510845c0..246a7994 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -315,6 +315,7 @@
           params->IsBackgroundVideoPlaybackEnabled()),
       is_background_video_track_optimization_supported_(
           params->IsBackgroundVideoTrackOptimizationSupported()),
+      is_remoting_renderer_enabled_(params->IsRemotingRendererEnabled()),
       reported_renderer_type_(RendererFactoryType::kDefault),
       simple_watch_timer_(
           base::BindRepeating(&WebMediaPlayerImpl::OnSimpleWatchTimerTick,
diff --git a/media/blink/webmediaplayer_impl.h b/media/blink/webmediaplayer_impl.h
index 20fb66ab..c0718c90 100644
--- a/media/blink/webmediaplayer_impl.h
+++ b/media/blink/webmediaplayer_impl.h
@@ -1018,6 +1018,15 @@
   // Whether background video optimization is supported on current platform.
   bool is_background_video_track_optimization_supported_ = true;
 
+  // Whether the media in this frame is a remoting media.
+  //
+  // Remoting media is a special media that has the media streams are delivered
+  // to the browser directly from somewhere without any URL request
+  // (http, file, ...)
+  // When setting to true, a remoting renderer will be created as the remoting
+  // target in the client.
+  bool is_remoting_renderer_enabled_ = false;
+
   base::CancelableOnceClosure have_enough_after_lazy_load_cb_;
 
   // State for simplified watch time reporting.
diff --git a/media/blink/webmediaplayer_impl_unittest.cc b/media/blink/webmediaplayer_impl_unittest.cc
index 7c4ce9ea..3d48e62 100644
--- a/media/blink/webmediaplayer_impl_unittest.cc
+++ b/media/blink/webmediaplayer_impl_unittest.cc
@@ -396,7 +396,7 @@
         viz::TestContextProvider::Create(),
         blink::WebMediaPlayer::SurfaceLayerMode::kAlways,
         is_background_suspend_enabled_, is_background_video_playback_enabled_,
-        true, nullptr);
+        true, false, nullptr);
 
     auto compositor = std::make_unique<NiceMock<MockVideoFrameCompositor>>(
         params->video_frame_compositor_task_runner());
diff --git a/media/blink/webmediaplayer_params.cc b/media/blink/webmediaplayer_params.cc
index 6cf17a6..41cd376 100644
--- a/media/blink/webmediaplayer_params.cc
+++ b/media/blink/webmediaplayer_params.cc
@@ -32,6 +32,7 @@
     bool is_background_suspend_enabled,
     bool is_background_video_playback_enabled,
     bool is_background_video_track_optimization_supported,
+    bool is_remoting_renderer_enabled,
     std::unique_ptr<PowerStatusHelper> power_status_helper)
     : defer_load_cb_(defer_load_cb),
       audio_renderer_sink_(audio_renderer_sink),
@@ -55,6 +56,7 @@
           is_background_video_playback_enabled),
       is_background_video_track_optimization_supported_(
           is_background_video_track_optimization_supported),
+      is_remoting_renderer_enabled_(is_remoting_renderer_enabled),
       power_status_helper_(std::move(power_status_helper)) {}
 
 WebMediaPlayerParams::~WebMediaPlayerParams() = default;
diff --git a/media/blink/webmediaplayer_params.h b/media/blink/webmediaplayer_params.h
index 28958ec..dd732c0 100644
--- a/media/blink/webmediaplayer_params.h
+++ b/media/blink/webmediaplayer_params.h
@@ -84,6 +84,7 @@
       bool is_background_suspend_enabled,
       bool is_background_video_play_enabled,
       bool is_background_video_track_optimization_supported,
+      bool is_remoting_renderer_enabled,
       std::unique_ptr<PowerStatusHelper> power_status_helper);
 
   ~WebMediaPlayerParams();
@@ -167,6 +168,10 @@
     return is_background_video_track_optimization_supported_;
   }
 
+  bool IsRemotingRendererEnabled() const {
+    return is_remoting_renderer_enabled_;
+  }
+
   std::unique_ptr<PowerStatusHelper> TakePowerStatusHelper() {
     return std::move(power_status_helper_);
   }
@@ -199,6 +204,8 @@
   bool is_background_video_playback_enabled_ = true;
   // Whether background video optimization is supported on current platform.
   bool is_background_video_track_optimization_supported_ = true;
+  // Whether the media in this frame is a remoting media.
+  bool is_remoting_renderer_enabled_ = false;
 
   std::unique_ptr<PowerStatusHelper> power_status_helper_;
 
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index 965b7ed..2c790804 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -10,6 +10,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/callback_helpers.h"
 #include "base/location.h"
 #include "base/macros.h"
@@ -655,12 +656,10 @@
   std::unique_ptr<SourceBufferState> source_state =
       std::make_unique<SourceBufferState>(
           std::move(stream_parser), std::move(frame_processor),
-          base::Bind(&ChunkDemuxer::CreateDemuxerStream, base::Unretained(this),
-                     id),
+          base::BindRepeating(&ChunkDemuxer::CreateDemuxerStream,
+                              base::Unretained(this), id),
           media_log_);
 
-  SourceBufferState::NewTextTrackCB new_text_track_cb;
-
   // TODO(wolenetz): Change these to DCHECKs or switch to returning
   // kReachedIdLimit once less verification in release build is needed. See
   // https://crbug.com/786975.
@@ -673,7 +672,7 @@
   source_state->Init(base::BindOnce(&ChunkDemuxer::OnSourceInitDone,
                                     base::Unretained(this), id),
                      ExpectedCodecs(content_type, codecs),
-                     encrypted_media_init_data_cb_, new_text_track_cb);
+                     encrypted_media_init_data_cb_, base::NullCallback());
 
   // TODO(wolenetz): Change to DCHECKs once less verification in release build
   // is needed. See https://crbug.com/786975.
diff --git a/media/filters/source_buffer_range.cc b/media/filters/source_buffer_range.cc
index 08a33267..92da7cc 100644
--- a/media/filters/source_buffer_range.cc
+++ b/media/filters/source_buffer_range.cc
@@ -17,15 +17,15 @@
     GapPolicy gap_policy,
     const BufferQueue& new_buffers,
     base::TimeDelta range_start_pts,
-    const InterbufferDistanceCB& interbuffer_distance_cb)
+    InterbufferDistanceCB interbuffer_distance_cb)
     : gap_policy_(gap_policy),
       next_buffer_index_(-1),
-      interbuffer_distance_cb_(interbuffer_distance_cb),
+      interbuffer_distance_cb_(std::move(interbuffer_distance_cb)),
       size_in_bytes_(0),
       range_start_pts_(range_start_pts),
       keyframe_map_index_base_(0) {
   DVLOG(3) << __func__;
-  DCHECK(interbuffer_distance_cb);
+  DCHECK(interbuffer_distance_cb_);
   CHECK(!new_buffers.empty());
   DCHECK(new_buffers.front()->is_key_frame());
   AppendBuffersToEnd(new_buffers, range_start_pts_);
diff --git a/media/filters/source_buffer_range.h b/media/filters/source_buffer_range.h
index ef44264..eb5b830 100644
--- a/media/filters/source_buffer_range.h
+++ b/media/filters/source_buffer_range.h
@@ -28,7 +28,7 @@
   // of which this range is a part. Used to estimate the duration of a buffer if
   // its duration is not known, and in GetFudgeRoom() for determining whether a
   // time or coded frame is close enough to be considered part of this range.
-  using InterbufferDistanceCB = base::Callback<base::TimeDelta()>;
+  using InterbufferDistanceCB = base::RepeatingCallback<base::TimeDelta()>;
 
   using BufferQueue = StreamParser::BufferQueue;
 
@@ -48,7 +48,7 @@
   SourceBufferRange(GapPolicy gap_policy,
                     const BufferQueue& new_buffers,
                     base::TimeDelta range_start_pts,
-                    const InterbufferDistanceCB& interbuffer_distance_cb);
+                    InterbufferDistanceCB interbuffer_distance_cb);
 
   ~SourceBufferRange();
 
diff --git a/media/filters/source_buffer_state.cc b/media/filters/source_buffer_state.cc
index 6541a2a..9ce7a59 100644
--- a/media/filters/source_buffer_state.cc
+++ b/media/filters/source_buffer_state.cc
@@ -129,13 +129,13 @@
 SourceBufferState::SourceBufferState(
     std::unique_ptr<StreamParser> stream_parser,
     std::unique_ptr<FrameProcessor> frame_processor,
-    const CreateDemuxerStreamCB& create_demuxer_stream_cb,
+    CreateDemuxerStreamCB create_demuxer_stream_cb,
     MediaLog* media_log)
-    : timestamp_offset_during_append_(NULL),
+    : timestamp_offset_during_append_(nullptr),
       parsing_media_segment_(false),
       stream_parser_(stream_parser.release()),
       frame_processor_(frame_processor.release()),
-      create_demuxer_stream_cb_(create_demuxer_stream_cb),
+      create_demuxer_stream_cb_(std::move(create_demuxer_stream_cb)),
       media_log_(media_log),
       state_(UNINITIALIZED) {
   DCHECK(create_demuxer_stream_cb_);
@@ -150,11 +150,11 @@
     StreamParser::InitCB init_cb,
     const std::string& expected_codecs,
     const StreamParser::EncryptedMediaInitDataCB& encrypted_media_init_data_cb,
-    const NewTextTrackCB& new_text_track_cb) {
+    NewTextTrackCB new_text_track_cb) {
   DCHECK_EQ(state_, UNINITIALIZED);
   init_cb_ = std::move(init_cb);
   encrypted_media_init_data_cb_ = encrypted_media_init_data_cb;
-  new_text_track_cb_ = new_text_track_cb;
+  new_text_track_cb_ = std::move(new_text_track_cb);
   state_ = PENDING_PARSER_CONFIG;
   InitializeParser(expected_codecs);
 }
@@ -224,7 +224,7 @@
         << " append_window_end=" << append_window_end.InSecondsF();
   }
 
-  timestamp_offset_during_append_ = NULL;
+  timestamp_offset_during_append_ = nullptr;
   append_in_progress_ = false;
   return result;
 }
@@ -239,7 +239,7 @@
   append_window_end_during_append_ = append_window_end;
 
   stream_parser_->Flush();
-  timestamp_offset_during_append_ = NULL;
+  timestamp_offset_during_append_ = nullptr;
 
   frame_processor_->Reset();
   parsing_media_segment_ = false;
diff --git a/media/filters/source_buffer_state.h b/media/filters/source_buffer_state.h
index 38d3d55..ae4c76ab 100644
--- a/media/filters/source_buffer_state.h
+++ b/media/filters/source_buffer_state.h
@@ -31,15 +31,15 @@
 class MEDIA_EXPORT SourceBufferState {
  public:
   // Callback signature used to create ChunkDemuxerStreams.
-  typedef base::Callback<ChunkDemuxerStream*(DemuxerStream::Type)>
-      CreateDemuxerStreamCB;
+  using CreateDemuxerStreamCB =
+      base::RepeatingCallback<ChunkDemuxerStream*(DemuxerStream::Type)>;
 
-  typedef base::Callback<void(ChunkDemuxerStream*, const TextTrackConfig&)>
-      NewTextTrackCB;
+  using NewTextTrackCB = base::RepeatingCallback<void(ChunkDemuxerStream*,
+                                                      const TextTrackConfig&)>;
 
   SourceBufferState(std::unique_ptr<StreamParser> stream_parser,
                     std::unique_ptr<FrameProcessor> frame_processor,
-                    const CreateDemuxerStreamCB& create_demuxer_stream_cb,
+                    CreateDemuxerStreamCB create_demuxer_stream_cb,
                     MediaLog* media_log);
 
   ~SourceBufferState();
@@ -48,7 +48,7 @@
             const std::string& expected_codecs,
             const StreamParser::EncryptedMediaInitDataCB&
                 encrypted_media_init_data_cb,
-            const NewTextTrackCB& new_text_track_cb);
+            NewTextTrackCB new_text_track_cb);
 
   // Reconfigures this source buffer to use |new_stream_parser|. Caller must
   // first ensure that ResetParserState() was done to flush any pending frames
@@ -239,7 +239,7 @@
   DemuxerStreamMap text_streams_;
 
   std::unique_ptr<FrameProcessor> frame_processor_;
-  CreateDemuxerStreamCB create_demuxer_stream_cb_;
+  const CreateDemuxerStreamCB create_demuxer_stream_cb_;
   MediaLog* media_log_;
 
   StreamParser::InitCB init_cb_;
diff --git a/media/filters/source_buffer_state_unittest.cc b/media/filters/source_buffer_state_unittest.cc
index 1b4cd4f..d5eb381 100644
--- a/media/filters/source_buffer_state_unittest.cc
+++ b/media/filters/source_buffer_state_unittest.cc
@@ -68,8 +68,8 @@
     mock_stream_parser_ = new testing::StrictMock<MockStreamParser>();
     return base::WrapUnique(new SourceBufferState(
         base::WrapUnique(mock_stream_parser_), std::move(frame_processor),
-        base::Bind(&SourceBufferStateTest::CreateDemuxerStream,
-                   base::Unretained(this)),
+        base::BindRepeating(&SourceBufferStateTest::CreateDemuxerStream,
+                            base::Unretained(this)),
         &media_log_));
   }
 
diff --git a/media/formats/mp2t/es_adapter_video.cc b/media/formats/mp2t/es_adapter_video.cc
index 70e7a59..15a3dd3 100644
--- a/media/formats/mp2t/es_adapter_video.cc
+++ b/media/formats/mp2t/es_adapter_video.cc
@@ -26,9 +26,9 @@
 static const size_t kHistorySize = 5;
 
 EsAdapterVideo::EsAdapterVideo(NewVideoConfigCB new_video_config_cb,
-                               const EmitBufferCB& emit_buffer_cb)
+                               EmitBufferCB emit_buffer_cb)
     : new_video_config_cb_(std::move(new_video_config_cb)),
-      emit_buffer_cb_(emit_buffer_cb),
+      emit_buffer_cb_(std::move(emit_buffer_cb)),
       has_valid_config_(false),
       has_valid_frame_(false),
       last_frame_duration_(
diff --git a/media/formats/mp2t/es_adapter_video.h b/media/formats/mp2t/es_adapter_video.h
index 5c9b69b..60193351 100644
--- a/media/formats/mp2t/es_adapter_video.h
+++ b/media/formats/mp2t/es_adapter_video.h
@@ -35,10 +35,11 @@
  public:
   using NewVideoConfigCB =
       base::RepeatingCallback<void(const VideoDecoderConfig&)>;
-  typedef base::Callback<void(scoped_refptr<StreamParserBuffer>)> EmitBufferCB;
+  using EmitBufferCB =
+      base::RepeatingCallback<void(scoped_refptr<StreamParserBuffer>)>;
 
   EsAdapterVideo(NewVideoConfigCB new_video_config_cb,
-                 const EmitBufferCB& emit_buffer_cb);
+                 EmitBufferCB emit_buffer_cb);
   ~EsAdapterVideo();
 
   // Force the emission of the pending video buffers.
diff --git a/media/formats/mp2t/es_parser.h b/media/formats/mp2t/es_parser.h
index 3bcc38e8..2cca8db 100644
--- a/media/formats/mp2t/es_parser.h
+++ b/media/formats/mp2t/es_parser.h
@@ -28,8 +28,9 @@
 
 class MEDIA_EXPORT EsParser {
  public:
-  using EmitBufferCB = base::Callback<void(scoped_refptr<StreamParserBuffer>)>;
-  using GetDecryptConfigCB = base::Callback<const DecryptConfig*()>;
+  using EmitBufferCB =
+      base::RepeatingCallback<void(scoped_refptr<StreamParserBuffer>)>;
+  using GetDecryptConfigCB = base::RepeatingCallback<const DecryptConfig*()>;
 
   EsParser();
   virtual ~EsParser();
diff --git a/media/formats/mp2t/es_parser_adts.cc b/media/formats/mp2t/es_parser_adts.cc
index 7d682c2c..231578f9 100644
--- a/media/formats/mp2t/es_parser_adts.cc
+++ b/media/formats/mp2t/es_parser_adts.cc
@@ -116,10 +116,10 @@
 }
 
 EsParserAdts::EsParserAdts(const NewAudioConfigCB& new_audio_config_cb,
-                           const EmitBufferCB& emit_buffer_cb,
+                           EmitBufferCB emit_buffer_cb,
                            bool sbr_in_mimetype)
     : new_audio_config_cb_(new_audio_config_cb),
-      emit_buffer_cb_(emit_buffer_cb),
+      emit_buffer_cb_(std::move(emit_buffer_cb)),
 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
       get_decrypt_config_cb_(),
       init_encryption_scheme_(EncryptionScheme::kUnencrypted),
@@ -129,13 +129,13 @@
 
 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
 EsParserAdts::EsParserAdts(const NewAudioConfigCB& new_audio_config_cb,
-                           const EmitBufferCB& emit_buffer_cb,
-                           const GetDecryptConfigCB& get_decrypt_config_cb,
+                           EmitBufferCB emit_buffer_cb,
+                           GetDecryptConfigCB get_decrypt_config_cb,
                            EncryptionScheme init_encryption_scheme,
                            bool sbr_in_mimetype)
     : new_audio_config_cb_(new_audio_config_cb),
-      emit_buffer_cb_(emit_buffer_cb),
-      get_decrypt_config_cb_(get_decrypt_config_cb),
+      emit_buffer_cb_(std::move(emit_buffer_cb)),
+      get_decrypt_config_cb_(std::move(get_decrypt_config_cb)),
       init_encryption_scheme_(init_encryption_scheme),
       sbr_in_mimetype_(sbr_in_mimetype) {}
 #endif
diff --git a/media/formats/mp2t/es_parser_adts.h b/media/formats/mp2t/es_parser_adts.h
index c004469..7997788 100644
--- a/media/formats/mp2t/es_parser_adts.h
+++ b/media/formats/mp2t/es_parser_adts.h
@@ -34,12 +34,12 @@
   typedef base::Callback<void(const AudioDecoderConfig&)> NewAudioConfigCB;
 
   EsParserAdts(const NewAudioConfigCB& new_audio_config_cb,
-               const EmitBufferCB& emit_buffer_cb,
+               EmitBufferCB emit_buffer_cb,
                bool sbr_in_mimetype);
 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
   EsParserAdts(const NewAudioConfigCB& new_audio_config_cb,
-               const EmitBufferCB& emit_buffer_cb,
-               const GetDecryptConfigCB& get_decrypt_config_cb,
+               EmitBufferCB emit_buffer_cb,
+               GetDecryptConfigCB get_decrypt_config_cb,
                EncryptionScheme init_encryption_scheme,
                bool sbr_in_mimetype);
 #endif
diff --git a/media/formats/mp2t/es_parser_adts_fuzzer.cc b/media/formats/mp2t/es_parser_adts_fuzzer.cc
index 756a2597..ccc2f328 100644
--- a/media/formats/mp2t/es_parser_adts_fuzzer.cc
+++ b/media/formats/mp2t/es_parser_adts_fuzzer.cc
@@ -14,7 +14,7 @@
 // Entry point for LibFuzzer.
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   media::mp2t::EsParserAdts es_parser(base::Bind(&NewAudioConfig),
-                                      base::Bind(&EmitBuffer), true);
+                                      base::BindRepeating(&EmitBuffer), true);
   if (!es_parser.Parse(data, size, media::kNoTimestamp,
                        media::kNoDecodeTimestamp())) {
     return 0;
diff --git a/media/formats/mp2t/es_parser_adts_unittest.cc b/media/formats/mp2t/es_parser_adts_unittest.cc
index 1303e59..1f906f59 100644
--- a/media/formats/mp2t/es_parser_adts_unittest.cc
+++ b/media/formats/mp2t/es_parser_adts_unittest.cc
@@ -40,7 +40,8 @@
                                bool sbr_in_mimetype) {
   EsParserAdts es_parser(
       base::Bind(&EsParserAdtsTest::NewAudioConfig, base::Unretained(this)),
-      base::Bind(&EsParserAdtsTest::EmitBuffer, base::Unretained(this)),
+      base::BindRepeating(&EsParserAdtsTest::EmitBuffer,
+                          base::Unretained(this)),
       sbr_in_mimetype);
   return ProcessPesPackets(&es_parser, pes_packets, false /* force_timing */);
 }
@@ -87,4 +88,3 @@
 }
 }  // namespace mp2t
 }  // namespace media
-
diff --git a/media/formats/mp2t/es_parser_h264.cc b/media/formats/mp2t/es_parser_h264.cc
index 66e458c..2093e21d 100644
--- a/media/formats/mp2t/es_parser_h264.cc
+++ b/media/formats/mp2t/es_parser_h264.cc
@@ -178,8 +178,8 @@
 const int kMinAUDSize = 4;
 
 EsParserH264::EsParserH264(NewVideoConfigCB new_video_config_cb,
-                           const EmitBufferCB& emit_buffer_cb)
-    : es_adapter_(std::move(new_video_config_cb), emit_buffer_cb),
+                           EmitBufferCB emit_buffer_cb)
+    : es_adapter_(std::move(new_video_config_cb), std::move(emit_buffer_cb)),
       h264_parser_(new H264Parser()),
       current_access_unit_pos_(0),
       next_access_unit_pos_(0)
@@ -193,10 +193,10 @@
 
 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
 EsParserH264::EsParserH264(NewVideoConfigCB new_video_config_cb,
-                           const EmitBufferCB& emit_buffer_cb,
+                           EmitBufferCB emit_buffer_cb,
                            EncryptionScheme init_encryption_scheme,
                            const GetDecryptConfigCB& get_decrypt_config_cb)
-    : es_adapter_(std::move(new_video_config_cb), emit_buffer_cb),
+    : es_adapter_(std::move(new_video_config_cb), std::move(emit_buffer_cb)),
       h264_parser_(new H264Parser()),
       current_access_unit_pos_(0),
       next_access_unit_pos_(0),
diff --git a/media/formats/mp2t/es_parser_h264.h b/media/formats/mp2t/es_parser_h264.h
index 785d659..74c5a08 100644
--- a/media/formats/mp2t/es_parser_h264.h
+++ b/media/formats/mp2t/es_parser_h264.h
@@ -44,10 +44,10 @@
       base::RepeatingCallback<void(const VideoDecoderConfig&)>;
 
   EsParserH264(NewVideoConfigCB new_video_config_cb,
-               const EmitBufferCB& emit_buffer_cb);
+               EmitBufferCB emit_buffer_cb);
 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
   EsParserH264(NewVideoConfigCB new_video_config_cb,
-               const EmitBufferCB& emit_buffer_cb,
+               EmitBufferCB emit_buffer_cb,
                EncryptionScheme init_encryption_scheme,
                const GetDecryptConfigCB& get_decrypt_config_cb);
 #endif
diff --git a/media/formats/mp2t/es_parser_h264_fuzzer.cc b/media/formats/mp2t/es_parser_h264_fuzzer.cc
index c4581435..69e2365 100644
--- a/media/formats/mp2t/es_parser_h264_fuzzer.cc
+++ b/media/formats/mp2t/es_parser_h264_fuzzer.cc
@@ -13,8 +13,8 @@
 
 // Entry point for LibFuzzer.
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  media::mp2t::EsParserH264 es_parser(base::Bind(&NewVideoConfig),
-                                      base::Bind(&EmitBuffer));
+  media::mp2t::EsParserH264 es_parser(base::BindRepeating(&NewVideoConfig),
+                                      base::BindRepeating(&EmitBuffer));
   if (!es_parser.Parse(data, size, media::kNoTimestamp,
                        media::kNoDecodeTimestamp())) {
     return 0;
diff --git a/media/formats/mp2t/es_parser_mpeg1audio.cc b/media/formats/mp2t/es_parser_mpeg1audio.cc
index efcd7a1..0a4cb15 100644
--- a/media/formats/mp2t/es_parser_mpeg1audio.cc
+++ b/media/formats/mp2t/es_parser_mpeg1audio.cc
@@ -38,11 +38,11 @@
 
 EsParserMpeg1Audio::EsParserMpeg1Audio(
     const NewAudioConfigCB& new_audio_config_cb,
-    const EmitBufferCB& emit_buffer_cb,
+    EmitBufferCB emit_buffer_cb,
     MediaLog* media_log)
     : media_log_(media_log),
       new_audio_config_cb_(new_audio_config_cb),
-      emit_buffer_cb_(emit_buffer_cb) {}
+      emit_buffer_cb_(std::move(emit_buffer_cb)) {}
 
 EsParserMpeg1Audio::~EsParserMpeg1Audio() {
 }
diff --git a/media/formats/mp2t/es_parser_mpeg1audio.h b/media/formats/mp2t/es_parser_mpeg1audio.h
index 56e5a8b..e1cad36 100644
--- a/media/formats/mp2t/es_parser_mpeg1audio.h
+++ b/media/formats/mp2t/es_parser_mpeg1audio.h
@@ -32,7 +32,7 @@
   typedef base::Callback<void(const AudioDecoderConfig&)> NewAudioConfigCB;
 
   EsParserMpeg1Audio(const NewAudioConfigCB& new_audio_config_cb,
-                     const EmitBufferCB& emit_buffer_cb,
+                     EmitBufferCB emit_buffer_cb,
                      MediaLog* media_log);
   ~EsParserMpeg1Audio() override;
 
diff --git a/media/formats/mp2t/es_parser_mpeg1audio_fuzzer.cc b/media/formats/mp2t/es_parser_mpeg1audio_fuzzer.cc
index 5f295820b6..b11413d 100644
--- a/media/formats/mp2t/es_parser_mpeg1audio_fuzzer.cc
+++ b/media/formats/mp2t/es_parser_mpeg1audio_fuzzer.cc
@@ -17,8 +17,9 @@
 // Entry point for LibFuzzer.
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   media::NullMediaLog media_log;
-  media::mp2t::EsParserMpeg1Audio es_parser(
-      base::Bind(&NewAudioConfig), base::Bind(&EmitBuffer), &media_log);
+  media::mp2t::EsParserMpeg1Audio es_parser(base::Bind(&NewAudioConfig),
+                                            base::BindRepeating(&EmitBuffer),
+                                            &media_log);
   if (es_parser.Parse(data, size, media::kNoTimestamp,
                       media::kNoDecodeTimestamp())) {
     es_parser.Flush();
diff --git a/media/formats/mp2t/es_parser_mpeg1audio_unittest.cc b/media/formats/mp2t/es_parser_mpeg1audio_unittest.cc
index d3e4dd8a..19f8dcb6 100644
--- a/media/formats/mp2t/es_parser_mpeg1audio_unittest.cc
+++ b/media/formats/mp2t/es_parser_mpeg1audio_unittest.cc
@@ -41,7 +41,8 @@
   EsParserMpeg1Audio es_parser(
       base::Bind(&EsParserMpeg1AudioTest::NewAudioConfig,
                  base::Unretained(this)),
-      base::Bind(&EsParserMpeg1AudioTest::EmitBuffer, base::Unretained(this)),
+      base::BindRepeating(&EsParserMpeg1AudioTest::EmitBuffer,
+                          base::Unretained(this)),
       &media_log_);
   return ProcessPesPackets(&es_parser, pes_packets, force_timing);
 }
diff --git a/media/formats/mp2t/mp2t_stream_parser.cc b/media/formats/mp2t/mp2t_stream_parser.cc
index 67fa5971..f7138c9 100644
--- a/media/formats/mp2t/mp2t_stream_parser.cc
+++ b/media/formats/mp2t/mp2t_stream_parser.cc
@@ -397,30 +397,31 @@
 std::unique_ptr<EsParser> Mp2tStreamParser::CreateH264Parser(int pes_pid) {
   auto on_video_config_changed = base::BindRepeating(
       &Mp2tStreamParser::OnVideoConfigChanged, base::Unretained(this), pes_pid);
-  auto on_emit_video_buffer = base::Bind(&Mp2tStreamParser::OnEmitVideoBuffer,
-                                         base::Unretained(this), pes_pid);
+  auto on_emit_video_buffer = base::BindRepeating(
+      &Mp2tStreamParser::OnEmitVideoBuffer, base::Unretained(this), pes_pid);
 
   return std::make_unique<EsParserH264>(std::move(on_video_config_changed),
-                                        on_emit_video_buffer);
+                                        std::move(on_emit_video_buffer));
 }
 
 std::unique_ptr<EsParser> Mp2tStreamParser::CreateAacParser(int pes_pid) {
   auto on_audio_config_changed = base::Bind(
       &Mp2tStreamParser::OnAudioConfigChanged, base::Unretained(this), pes_pid);
-  auto on_emit_audio_buffer = base::Bind(&Mp2tStreamParser::OnEmitAudioBuffer,
-                                         base::Unretained(this), pes_pid);
+  auto on_emit_audio_buffer = base::BindRepeating(
+      &Mp2tStreamParser::OnEmitAudioBuffer, base::Unretained(this), pes_pid);
   return std::make_unique<EsParserAdts>(on_audio_config_changed,
-                                        on_emit_audio_buffer, sbr_in_mimetype_);
+                                        std::move(on_emit_audio_buffer),
+                                        sbr_in_mimetype_);
 }
 
 std::unique_ptr<EsParser> Mp2tStreamParser::CreateMpeg1AudioParser(
     int pes_pid) {
   auto on_audio_config_changed = base::Bind(
       &Mp2tStreamParser::OnAudioConfigChanged, base::Unretained(this), pes_pid);
-  auto on_emit_audio_buffer = base::Bind(&Mp2tStreamParser::OnEmitAudioBuffer,
-                                         base::Unretained(this), pes_pid);
-  return std::make_unique<EsParserMpeg1Audio>(on_audio_config_changed,
-                                              on_emit_audio_buffer, media_log_);
+  auto on_emit_audio_buffer = base::BindRepeating(
+      &Mp2tStreamParser::OnEmitAudioBuffer, base::Unretained(this), pes_pid);
+  return std::make_unique<EsParserMpeg1Audio>(
+      on_audio_config_changed, std::move(on_emit_audio_buffer), media_log_);
 }
 
 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
@@ -437,15 +438,16 @@
     bool emit_clear_buffers) {
   auto on_video_config_changed = base::BindRepeating(
       &Mp2tStreamParser::OnVideoConfigChanged, base::Unretained(this), pes_pid);
-  auto on_emit_video_buffer = base::Bind(&Mp2tStreamParser::OnEmitVideoBuffer,
-                                         base::Unretained(this), pes_pid);
-  auto get_decrypt_config =
-      emit_clear_buffers ? EsParser::GetDecryptConfigCB()
-                         : base::Bind(&Mp2tStreamParser::GetDecryptConfig,
-                                      base::Unretained(this));
+  auto on_emit_video_buffer = base::BindRepeating(
+      &Mp2tStreamParser::OnEmitVideoBuffer, base::Unretained(this), pes_pid);
+  EsParserAdts::GetDecryptConfigCB get_decrypt_config;
+  if (!emit_clear_buffers) {
+    get_decrypt_config = base::BindRepeating(
+        &Mp2tStreamParser::GetDecryptConfig, base::Unretained(this));
+  }
   return std::make_unique<EsParserH264>(
       std::move(on_video_config_changed), on_emit_video_buffer,
-      initial_encryption_scheme_, get_decrypt_config);
+      initial_encryption_scheme_, std::move(get_decrypt_config));
 }
 
 std::unique_ptr<EsParser> Mp2tStreamParser::CreateEncryptedAacParser(
@@ -453,15 +455,17 @@
     bool emit_clear_buffers) {
   auto on_audio_config_changed = base::Bind(
       &Mp2tStreamParser::OnAudioConfigChanged, base::Unretained(this), pes_pid);
-  auto on_emit_audio_buffer = base::Bind(&Mp2tStreamParser::OnEmitAudioBuffer,
-                                         base::Unretained(this), pes_pid);
-  auto get_decrypt_config =
-      emit_clear_buffers ? EsParser::GetDecryptConfigCB()
-                         : base::Bind(&Mp2tStreamParser::GetDecryptConfig,
-                                      base::Unretained(this));
+  auto on_emit_audio_buffer = base::BindRepeating(
+      &Mp2tStreamParser::OnEmitAudioBuffer, base::Unretained(this), pes_pid);
+  EsParserAdts::GetDecryptConfigCB get_decrypt_config;
+  if (!emit_clear_buffers) {
+    get_decrypt_config = base::BindRepeating(
+        &Mp2tStreamParser::GetDecryptConfig, base::Unretained(this));
+  }
   return std::make_unique<EsParserAdts>(
-      on_audio_config_changed, on_emit_audio_buffer, get_decrypt_config,
-      initial_encryption_scheme_, sbr_in_mimetype_);
+      on_audio_config_changed, std::move(on_emit_audio_buffer),
+      std::move(get_decrypt_config), initial_encryption_scheme_,
+      sbr_in_mimetype_);
 }
 #endif
 
diff --git a/media/gpu/test/video_frame_file_writer.cc b/media/gpu/test/video_frame_file_writer.cc
index 1df470a343..c5df651 100644
--- a/media/gpu/test/video_frame_file_writer.cc
+++ b/media/gpu/test/video_frame_file_writer.cc
@@ -88,6 +88,13 @@
   if (num_frames_writes_requested_ >= output_limit_)
     return;
 
+  if (video_frame->visible_rect().IsEmpty()) {
+    // This occurs in bitstream buffer in webrtc scenario.
+    DLOG(WARNING) << "Skipping writing, frame_index=" << frame_index
+                  << " because visible_rect is empty";
+    return;
+  }
+
   num_frames_writes_requested_++;
 
   base::AutoLock auto_lock(frame_writer_lock_);
diff --git a/media/gpu/test/video_frame_validator.cc b/media/gpu/test/video_frame_validator.cc
index 0f44c78..5fd9ac2 100644
--- a/media/gpu/test/video_frame_validator.cc
+++ b/media/gpu/test/video_frame_validator.cc
@@ -146,6 +146,13 @@
     ASSERT_TRUE(model_frame);
   }
 
+  if (video_frame->visible_rect().IsEmpty()) {
+    // This occurs in bitstream buffer in webrtc scenario.
+    DLOG(WARNING) << "Skipping validation, frame_index=" << frame_index
+                  << " because visible_rect is empty";
+    return;
+  }
+
   base::AutoLock auto_lock(frame_validator_lock_);
   num_frames_validating_++;
 
diff --git a/media/gpu/test/video_test_helpers.cc b/media/gpu/test/video_test_helpers.cc
index 426a564..e1ca359 100644
--- a/media/gpu/test/video_test_helpers.cc
+++ b/media/gpu/test/video_test_helpers.cc
@@ -7,6 +7,7 @@
 #include <limits>
 
 #include "base/stl_util.h"
+#include "base/sys_byteorder.h"
 #include "media/base/video_decoder_config.h"
 #include "media/video/h264_parser.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -14,6 +15,20 @@
 namespace media {
 namespace test {
 
+// The structure for IVF frame header. IVF is a video file format.
+// The helpful description is in https://wiki.multimedia.cx/index.php/IVF.
+struct EncodedDataHelper::IVFHeader {
+  uint32_t frame_size;
+  uint64_t timestamp;
+};
+
+// The structure for IVF frame data and header.
+// The data to be read is |header.frame_size| bytes from |data|.
+struct EncodedDataHelper::IVFFrame {
+  const char* data;
+  IVFHeader header;
+};
+
 EncodedDataHelper::EncodedDataHelper(const std::vector<uint8_t>& stream,
                                      VideoCodecProfile profile)
     : data_(std::string(reinterpret_cast<const char*>(stream.data()),
@@ -94,39 +109,119 @@
 scoped_refptr<DecoderBuffer> EncodedDataHelper::GetNextFrame() {
   // Helpful description: http://wiki.multimedia.cx/index.php?title=IVF
   constexpr size_t kIVFHeaderSize = 32;
-  constexpr size_t kIVFFrameHeaderSize = 12;
-
-  size_t pos = next_pos_to_decode_;
-
   // Only IVF video files are supported. The first 4bytes of an IVF video file's
   // header should be "DKIF".
-  if (pos == 0) {
+  if (next_pos_to_decode_ == 0) {
     if ((data_.size() < kIVFHeaderSize) || strncmp(&data_[0], "DKIF", 4) != 0) {
       LOG(ERROR) << "Unexpected data encountered while parsing IVF header";
       return nullptr;
     }
-    pos = kIVFHeaderSize;  // Skip IVF header.
+    next_pos_to_decode_ = kIVFHeaderSize;  // Skip IVF header.
   }
 
+  // Group IVF data whose timestamps are the same. Spatial layers in a
+  // spatial-SVC stream may separately be stored in IVF data, where the
+  // timestamps of the IVF frame headers are the same. However, it is necessary
+  // for VD(A) to feed the spatial layers by a single DecoderBuffer. So this
+  // grouping is required.
+  std::vector<IVFFrame> ivf_frames;
+  while (!ReachEndOfStream()) {
+    auto frame_header = GetNextIVFFrameHeader();
+    if (!frame_header)
+      return nullptr;
+
+    // Timestamp is different from the current one. The next IVF data must be
+    // grouped in the next group.
+    if (!ivf_frames.empty() &&
+        frame_header->timestamp != ivf_frames[0].header.timestamp) {
+      break;
+    }
+
+    auto frame_data = ReadNextIVFFrame();
+    if (!frame_data)
+      return nullptr;
+
+    ivf_frames.push_back(*frame_data);
+  }
+
+  if (ivf_frames.empty()) {
+    LOG(ERROR) << "No IVF frame is available";
+    return nullptr;
+  }
+
+  // Standard stream case.
+  if (ivf_frames.size() == 1) {
+    return DecoderBuffer::CopyFrom(
+        reinterpret_cast<const uint8_t*>(ivf_frames[0].data),
+        ivf_frames[0].header.frame_size);
+  }
+
+  if (ivf_frames.size() > 3) {
+    LOG(ERROR) << "Number of IVF frames with same timestamps exceeds maximum of"
+               << "3: ivf_frames.size()=" << ivf_frames.size();
+    return nullptr;
+  }
+
+  std::string data;
+  std::vector<uint32_t> frame_sizes;
+  frame_sizes.reserve(ivf_frames.size());
+  for (const IVFFrame& ivf : ivf_frames) {
+    data.append(ivf.data, ivf.header.frame_size);
+    frame_sizes.push_back(ivf.header.frame_size);
+  }
+
+  // Copy frame_sizes information to DecoderBuffer's side data. Since side_data
+  // is uint8_t*, we need to copy as uint8_t from uint32_t. The copied data is
+  // recognized as uint32_t in VD(A).
+  const uint8_t* side_data =
+      reinterpret_cast<const uint8_t*>(frame_sizes.data());
+  size_t side_data_size =
+      frame_sizes.size() * sizeof(uint32_t) / sizeof(uint8_t);
+
+  return DecoderBuffer::CopyFrom(reinterpret_cast<const uint8_t*>(data.data()),
+                                 data.size(), side_data, side_data_size);
+}
+
+base::Optional<EncodedDataHelper::IVFHeader>
+EncodedDataHelper::GetNextIVFFrameHeader() const {
+  constexpr size_t kIVFFrameHeaderSize = 12;
+
+  const size_t pos = next_pos_to_decode_;
+
   // Read VP8/9 frame size from IVF header.
   if (pos + kIVFFrameHeaderSize > data_.size()) {
     LOG(ERROR) << "Unexpected data encountered while parsing IVF frame header";
-    return nullptr;
+    return base::nullopt;
   }
-  const uint32_t frame_size = *reinterpret_cast<uint32_t*>(&data_[pos]);
-  pos += kIVFFrameHeaderSize;  // Skip IVF frame header.
+
+  uint32_t frame_size = 0;
+  uint64_t timestamp = 0;
+  memcpy(&frame_size, &data_[pos], 4);
+  memcpy(&timestamp, &data_[pos + 4], 8);
+  return IVFHeader{frame_size, timestamp};
+}
+
+base::Optional<EncodedDataHelper::IVFFrame>
+EncodedDataHelper::ReadNextIVFFrame() {
+  constexpr size_t kIVFFrameHeaderSize = 12;
+  auto frame_header = GetNextIVFFrameHeader();
+  if (!frame_header)
+    return base::nullopt;
+
+  // Skip IVF frame header.
+  const size_t pos = next_pos_to_decode_ + kIVFFrameHeaderSize;
 
   // Make sure we are not reading out of bounds.
-  if (pos + frame_size > data_.size()) {
+  if (pos + frame_header->frame_size > data_.size()) {
     LOG(ERROR) << "Unexpected data encountered while parsing IVF frame header";
     next_pos_to_decode_ = data_.size();
-    return nullptr;
+    return base::nullopt;
   }
 
   // Update next_pos_to_decode_.
-  next_pos_to_decode_ = pos + frame_size;
-  return DecoderBuffer::CopyFrom(reinterpret_cast<const uint8_t*>(&data_[pos]),
-                                 frame_size);
+  next_pos_to_decode_ = pos + frame_header->frame_size;
+
+  return IVFFrame{&data_[pos], *frame_header};
 }
 
 // static
diff --git a/media/gpu/test/video_test_helpers.h b/media/gpu/test/video_test_helpers.h
index cffff22..5908510 100644
--- a/media/gpu/test/video_test_helpers.h
+++ b/media/gpu/test/video_test_helpers.h
@@ -87,10 +87,15 @@
   size_t num_skipped_fragments() { return num_skipped_fragments_; }
 
  private:
+  struct IVFHeader;
+  struct IVFFrame;
+
   // For h.264.
   scoped_refptr<DecoderBuffer> GetNextFragment();
   // For VP8/9.
   scoped_refptr<DecoderBuffer> GetNextFrame();
+  base::Optional<IVFHeader> GetNextIVFFrameHeader() const;
+  base::Optional<IVFFrame> ReadNextIVFFrame();
 
   // Helpers for GetBytesForNextFragment above.
   size_t GetBytesForNextNALU(size_t pos);
diff --git a/net/dns/mdns_cache.h b/net/dns/mdns_cache.h
index d1ea1c1..6e91a9c0 100644
--- a/net/dns/mdns_cache.h
+++ b/net/dns/mdns_cache.h
@@ -50,7 +50,8 @@
     std::string optional_;
   };
 
-  typedef base::Callback<void(const RecordParsed*)> RecordRemovedCallback;
+  typedef base::RepeatingCallback<void(const RecordParsed*)>
+      RecordRemovedCallback;
 
   enum UpdateType {
     RecordAdded,
diff --git a/net/dns/mdns_cache_unittest.cc b/net/dns/mdns_cache_unittest.cc
index 2e4ee8f..aa83e615 100644
--- a/net/dns/mdns_cache_unittest.cc
+++ b/net/dns/mdns_cache_unittest.cc
@@ -184,8 +184,10 @@
 
   EXPECT_CALL(record_removal_, OnRecordRemoved(record_to_be_deleted));
 
-  cache_.CleanupRecords(default_time_ + ttl2, base::Bind(
-      &RecordRemovalMock::OnRecordRemoved, base::Unretained(&record_removal_)));
+  cache_.CleanupRecords(
+      default_time_ + ttl2,
+      base::BindRepeating(&RecordRemovalMock::OnRecordRemoved,
+                          base::Unretained(&record_removal_)));
 
   // To make sure that we've indeed removed them from the map, check no funny
   // business happens once they're deleted for good.
diff --git a/net/dns/mdns_client_impl.cc b/net/dns/mdns_client_impl.cc
index 050d40c..65adb27 100644
--- a/net/dns/mdns_client_impl.cc
+++ b/net/dns/mdns_client_impl.cc
@@ -402,9 +402,9 @@
 }
 
 void MDnsClientImpl::Core::DoCleanup() {
-  cache_.CleanupRecords(clock_->Now(),
-                        base::Bind(&MDnsClientImpl::Core::OnRecordRemoved,
-                                   base::Unretained(this)));
+  cache_.CleanupRecords(
+      clock_->Now(), base::BindRepeating(&MDnsClientImpl::Core::OnRecordRemoved,
+                                         base::Unretained(this)));
 
   ScheduleCleanup(cache_.next_expiration());
 }
diff --git a/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.cc b/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.cc
index deb86879..406b430 100644
--- a/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.cc
+++ b/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.cc
@@ -63,10 +63,10 @@
   scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery());
   task_runner_->PostTaskAndReply(
       FROM_HERE,
-      base::Bind(&DhcpPacFileAdapterFetcher::DhcpQuery::GetPacURLForAdapter,
-                 dhcp_query.get(), adapter_name),
-      base::Bind(&DhcpPacFileAdapterFetcher::OnDhcpQueryDone, AsWeakPtr(),
-                 dhcp_query, traffic_annotation));
+      base::BindOnce(&DhcpPacFileAdapterFetcher::DhcpQuery::GetPacURLForAdapter,
+                     dhcp_query.get(), adapter_name),
+      base::BindOnce(&DhcpPacFileAdapterFetcher::OnDhcpQueryDone, AsWeakPtr(),
+                     dhcp_query, traffic_annotation));
 }
 
 void DhcpPacFileAdapterFetcher::Cancel() {
@@ -154,10 +154,11 @@
   } else {
     state_ = STATE_WAIT_URL;
     script_fetcher_ = ImplCreateScriptFetcher();
-    script_fetcher_->Fetch(pac_url_, &pac_script_,
-                           base::Bind(&DhcpPacFileAdapterFetcher::OnFetcherDone,
-                                      base::Unretained(this)),
-                           traffic_annotation);
+    script_fetcher_->Fetch(
+        pac_url_, &pac_script_,
+        base::BindOnce(&DhcpPacFileAdapterFetcher::OnFetcherDone,
+                       base::Unretained(this)),
+        traffic_annotation);
   }
 }
 
diff --git a/net/proxy_resolution/dhcp_pac_file_fetcher_win.cc b/net/proxy_resolution/dhcp_pac_file_fetcher_win.cc
index 8b75311..7990f189 100644
--- a/net/proxy_resolution/dhcp_pac_file_fetcher_win.cc
+++ b/net/proxy_resolution/dhcp_pac_file_fetcher_win.cc
@@ -299,10 +299,11 @@
 
   task_runner_->PostTaskAndReply(
       FROM_HERE,
-      base::Bind(&DhcpPacFileFetcherWin::AdapterQuery::GetCandidateAdapterNames,
-                 last_query_.get()),
-      base::Bind(&DhcpPacFileFetcherWin::OnGetCandidateAdapterNamesDone,
-                 AsWeakPtr(), last_query_, traffic_annotation));
+      base::BindOnce(
+          &DhcpPacFileFetcherWin::AdapterQuery::GetCandidateAdapterNames,
+          last_query_.get()),
+      base::BindOnce(&DhcpPacFileFetcherWin::OnGetCandidateAdapterNamesDone,
+                     AsWeakPtr(), last_query_, traffic_annotation));
 
   return ERR_IO_PENDING;
 }
@@ -380,8 +381,8 @@
         ImplCreateAdapterFetcher());
     size_t fetcher_index = fetchers_.size();
     fetcher->Fetch(adapter_name,
-                   base::Bind(&DhcpPacFileFetcherWin::OnFetcherDone,
-                              base::Unretained(this), fetcher_index),
+                   base::BindOnce(&DhcpPacFileFetcherWin::OnFetcherDone,
+                                  base::Unretained(this), fetcher_index),
                    traffic_annotation);
     fetchers_.push_back(std::move(fetcher));
   }
diff --git a/net/proxy_resolution/dhcp_pac_file_fetcher_win_unittest.cc b/net/proxy_resolution/dhcp_pac_file_fetcher_win_unittest.cc
index 9deabb4..1bd7736 100644
--- a/net/proxy_resolution/dhcp_pac_file_fetcher_win_unittest.cc
+++ b/net/proxy_resolution/dhcp_pac_file_fetcher_win_unittest.cc
@@ -60,7 +60,7 @@
   void RunTest() {
     int result = fetcher_->Fetch(
         &pac_text_,
-        base::Bind(&RealFetchTester::OnCompletion, base::Unretained(this)),
+        base::BindOnce(&RealFetchTester::OnCompletion, base::Unretained(this)),
         NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS);
     if (result != ERR_IO_PENDING)
       finished_ = true;
@@ -386,7 +386,7 @@
   void RunTest() {
     int result = fetcher_.Fetch(
         &pac_text_,
-        base::Bind(&FetcherClient::OnCompletion, base::Unretained(this)),
+        base::BindOnce(&FetcherClient::OnCompletion, base::Unretained(this)),
         NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS);
     ASSERT_THAT(result, IsError(ERR_IO_PENDING));
   }
@@ -394,7 +394,7 @@
   int RunTestThatMayFailSync() {
     int result = fetcher_.Fetch(
         &pac_text_,
-        base::Bind(&FetcherClient::OnCompletion, base::Unretained(this)),
+        base::BindOnce(&FetcherClient::OnCompletion, base::Unretained(this)),
         NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS);
     if (result != ERR_IO_PENDING)
       result_ = result;
diff --git a/remoting/host/input_monitor/BUILD.gn b/remoting/host/input_monitor/BUILD.gn
index 3fde2d1..c25d0c5 100644
--- a/remoting/host/input_monitor/BUILD.gn
+++ b/remoting/host/input_monitor/BUILD.gn
@@ -31,6 +31,7 @@
   deps = [
     "//remoting/proto",
     "//third_party/webrtc_overrides:webrtc_component",
+    "//ui/events",
   ]
 
   if (use_ozone) {
diff --git a/services/tracing/BUILD.gn b/services/tracing/BUILD.gn
index 101b8c61..8a4a066 100644
--- a/services/tracing/BUILD.gn
+++ b/services/tracing/BUILD.gn
@@ -47,7 +47,6 @@
   deps = [
     "//base",
     "//third_party/perfetto:libperfetto",
-    "//third_party/perfetto/src/protozero:protozero",
   ]
 }
 
diff --git a/services/tracing/perfetto/perfetto_service.cc b/services/tracing/perfetto/perfetto_service.cc
index 93222c1..de5e660b 100644
--- a/services/tracing/perfetto/perfetto_service.cc
+++ b/services/tracing/perfetto/perfetto_service.cc
@@ -12,6 +12,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/task/post_task.h"
+#include "mojo/public/cpp/bindings/message.h"
 #include "services/tracing/perfetto/consumer_host.h"
 #include "services/tracing/perfetto/producer_host.h"
 #include "services/tracing/public/cpp/perfetto/shared_memory.h"
@@ -62,7 +63,6 @@
   // Chromium uses scraping of the shared memory chunks to ensure that data
   // from threads without a MessageLoop doesn't get lost.
   service_->SetSMBScrapingEnabled(true);
-  DCHECK(service_);
 
   receivers_.set_disconnect_handler(base::BindRepeating(
       &PerfettoService::OnServiceDisconnect, base::Unretained(this)));
@@ -85,12 +85,25 @@
 
 void PerfettoService::ConnectToProducerHost(
     mojo::PendingRemote<mojom::ProducerClient> producer_client,
-    mojo::PendingReceiver<mojom::ProducerHost> producer_host_receiver) {
-  auto new_producer = std::make_unique<ProducerHost>();
+    mojo::PendingReceiver<mojom::ProducerHost> producer_host_receiver,
+    mojo::ScopedSharedBufferHandle shared_memory,
+    uint64_t shared_memory_buffer_page_size_bytes) {
+  if (!shared_memory.is_valid()) {
+    mojo::ReportBadMessage("Producer connection request without valid SMB");
+    return;
+  }
+
+  auto new_producer = std::make_unique<ProducerHost>(&perfetto_task_runner_);
   uint32_t producer_pid = receivers_.current_context();
-  new_producer->Initialize(std::move(producer_client), service_.get(),
-                           base::StrCat({mojom::kPerfettoProducerNamePrefix,
-                                         base::NumberToString(producer_pid)}));
+  DCHECK(shared_memory.is_valid());
+  if (!new_producer->Initialize(
+          std::move(producer_client), service_.get(),
+          base::StrCat({mojom::kPerfettoProducerNamePrefix,
+                        base::NumberToString(producer_pid)}),
+          std::move(shared_memory), shared_memory_buffer_page_size_bytes)) {
+    mojo::ReportBadMessage("Producer connection failed");
+    return;
+  }
 
   ++num_active_connections_[producer_pid];
   producer_receivers_.Add(std::move(new_producer),
diff --git a/services/tracing/perfetto/perfetto_service.h b/services/tracing/perfetto/perfetto_service.h
index 64e6183..a54618f 100644
--- a/services/tracing/perfetto/perfetto_service.h
+++ b/services/tracing/perfetto/perfetto_service.h
@@ -43,8 +43,9 @@
   // mojom::PerfettoService implementation.
   void ConnectToProducerHost(
       mojo::PendingRemote<mojom::ProducerClient> producer_client,
-      mojo::PendingReceiver<mojom::ProducerHost> producer_host_receiver)
-      override;
+      mojo::PendingReceiver<mojom::ProducerHost> producer_host_receiver,
+      mojo::ScopedSharedBufferHandle shared_memory,
+      uint64_t shared_memory_buffer_page_size_bytes) override;
 
   perfetto::TracingService* GetService() const;
 
@@ -74,6 +75,8 @@
     return active_service_pids_initialized_;
   }
 
+  PerfettoTaskRunner* perfetto_task_runner() { return &perfetto_task_runner_; }
+
  private:
   void BindOnSequence(mojo::PendingReceiver<mojom::PerfettoService> receiver);
   void CreateServiceOnSequence();
diff --git a/services/tracing/perfetto/producer_host.cc b/services/tracing/perfetto/producer_host.cc
index 1cdc0788..e2e77e3dd 100644
--- a/services/tracing/perfetto/producer_host.cc
+++ b/services/tracing/perfetto/producer_host.cc
@@ -12,6 +12,7 @@
 #include "services/tracing/perfetto/perfetto_service.h"
 #include "services/tracing/public/cpp/perfetto/producer_client.h"
 #include "services/tracing/public/cpp/perfetto/shared_memory.h"
+#include "services/tracing/public/cpp/perfetto/task_runner.h"
 #include "services/tracing/public/cpp/tracing_features.h"
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/commit_data_request.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/data_source_descriptor.h"
@@ -19,16 +20,8 @@
 
 namespace tracing {
 
-// TODO(oysteine): Find a good compromise between performance and
-// data granularity (mainly relevant to running with small buffer sizes
-// when we use background tracing) on Android.
-#if defined(OS_ANDROID)
-constexpr size_t kSMBPageSizeInBytes = 4 * 1024;
-#else
-constexpr size_t kSMBPageSizeInBytes = 32 * 1024;
-#endif
-
-ProducerHost::ProducerHost() = default;
+ProducerHost::ProducerHost(PerfettoTaskRunner* task_runner)
+    : task_runner_(task_runner) {}
 
 ProducerHost::~ProducerHost() {
   // Manually reset to prevent any callbacks from the ProducerEndpoint
@@ -36,44 +29,52 @@
   producer_endpoint_.reset();
 }
 
-void ProducerHost::Initialize(
+bool ProducerHost::Initialize(
     mojo::PendingRemote<mojom::ProducerClient> producer_client,
     perfetto::TracingService* service,
-    const std::string& name) {
+    const std::string& name,
+    mojo::ScopedSharedBufferHandle shared_memory,
+    uint64_t shared_memory_buffer_page_size_bytes) {
   DCHECK(service);
   DCHECK(!producer_endpoint_);
 
   producer_client_.Bind(std::move(producer_client));
 
-  // Attempt to parse the PID out of the producer name.
-  // If the Producer is in-process, we:
-  // * Signal Perfetto that it should create and manage its own
-  // SharedMemoryArbiter
-  //   when we call ConnectProducer.
-  // * Set the local ProducerClient instance to use this SMA instead of passing
-  //   an SMB handle over Mojo and letting it create its own.
-  // * After that point, any TraceWriter created by TraceEventDataSource will
-  //   use this in-process SMA, and hence be able to synchronously flush full
-  //   SMB chunks if we're running on the same sequence as the Perfetto service,
-  //   hence we avoid any deadlocking occurring from trace events emitted from
-  //   the Perfetto sequence filling up the SMB and stalling while waiting for
-  //   Perfetto to free the chunks.
-  if (!base::FeatureList::IsEnabled(
-          features::kPerfettoForceOutOfProcessProducer)) {
-    base::ProcessId pid;
-    if (PerfettoService::ParsePidFromProducerName(name, &pid)) {
-      is_in_process_ = (pid == base::Process::Current().Pid());
+  auto shm = std::make_unique<MojoSharedMemory>(std::move(shared_memory));
+  size_t shm_size = shm->size();
+  MojoSharedMemory* shm_raw = shm.get();
+
+  // TODO(oysteine): Figure out a uid once we need it.
+  producer_endpoint_ = service->ConnectProducer(
+      this, 0 /* uid */, name, shm_size, /*in_process=*/false,
+      perfetto::TracingService::ProducerSMBScrapingMode::kDefault,
+      shared_memory_buffer_page_size_bytes, std::move(shm));
+
+  // In some cases, the service may deny the producer connection (e.g. if too
+  // many producers are registered). The service will adopt the shared memory
+  // buffer provided by the ProducerClient as long as it is correctly sized.
+  if (!producer_endpoint_ || producer_endpoint_->shared_memory() != shm_raw) {
+    return false;
+  }
+
+  // When we are in-process, we don't use the in-process arbiter perfetto would
+  // provide (thus pass |in_process = false| to ConnectProducer), but rather
+  // bind the ProducerClient's arbiter to the service's endpoint and task runner
+  // directly. This allows us to use startup tracing via an unbound SMA, while
+  // avoiding some cross-sequence PostTasks when committing chunks (since we
+  // bypass mojo).
+  base::ProcessId pid;
+  if (PerfettoService::ParsePidFromProducerName(name, &pid)) {
+    bool in_process = (pid == base::Process::Current().Pid());
+    if (in_process) {
+      PerfettoTracedProcess::Get()
+          ->producer_client()
+          ->BindInProcessSharedMemoryArbiter(producer_endpoint_.get(),
+                                             task_runner_);
     }
   }
 
-  // TODO(oysteine): Figure out an uid once we need it.
-  // TODO(oysteine): Figure out a good buffer size.
-  producer_endpoint_ = service->ConnectProducer(
-      this, 0 /* uid */, name,
-      /*shared_memory_size_hint_bytes=*/4 * 1024 * 1024, is_in_process_,
-      perfetto::TracingService::ProducerSMBScrapingMode::kDefault,
-      /*shared_memory_page_size_hint_bytes=*/kSMBPageSizeInBytes);
-  DCHECK(producer_endpoint_);
+  return true;
 }
 
 void ProducerHost::OnConnect() {
@@ -85,24 +86,7 @@
 }
 
 void ProducerHost::OnTracingSetup() {
-  if (is_in_process_) {
-    DCHECK(producer_endpoint_->MaybeSharedMemoryArbiter());
-    PerfettoTracedProcess::Get()
-        ->producer_client()
-        ->set_in_process_shmem_arbiter(
-            producer_endpoint_->MaybeSharedMemoryArbiter());
-    return;
-  }
-
-  MojoSharedMemory* shared_memory =
-      static_cast<MojoSharedMemory*>(producer_endpoint_->shared_memory());
-  DCHECK(shared_memory);
-  DCHECK(producer_client_);
-  mojo::ScopedSharedBufferHandle shm = shared_memory->Clone();
-  DCHECK(shm.is_valid());
-
-  producer_client_->OnTracingStart(
-      std::move(shm), producer_endpoint_->shared_buffer_page_size_kb() * 1024);
+  producer_client_->OnTracingStart();
 }
 
 void ProducerHost::SetupDataSource(perfetto::DataSourceInstanceID,
diff --git a/services/tracing/perfetto/producer_host.h b/services/tracing/perfetto/producer_host.h
index 45a0468..b93aafa 100644
--- a/services/tracing/perfetto/producer_host.h
+++ b/services/tracing/perfetto/producer_host.h
@@ -20,6 +20,8 @@
 
 namespace tracing {
 
+class PerfettoTaskRunner;
+
 // This class is the service-side part of the Perfetto Producer pair
 // and is responsible for registering any available DataSources
 // with Perfetto (like ChromeTracing) in OnConnect(). It will forward
@@ -32,15 +34,18 @@
 class ProducerHost : public tracing::mojom::ProducerHost,
                      public perfetto::Producer {
  public:
-  ProducerHost();
+  explicit ProducerHost(PerfettoTaskRunner*);
   ~ProducerHost() override;
 
-  // Called by the ProducerService to register the
-  // Producer with Perfetto and connect to the
-  // corresponding remote ProducerClient.
-  void Initialize(mojo::PendingRemote<mojom::ProducerClient> producer_client,
+  // Called by the ProducerService to register the Producer with Perfetto,
+  // connect to the corresponding remote ProducerClient, and setup the provided
+  // shared memory buffer for tracing data exchange. Returns false if the
+  // service failed to connect the producer or adopt the provided SMB.
+  bool Initialize(mojo::PendingRemote<mojom::ProducerClient> producer_client,
                   perfetto::TracingService* service,
-                  const std::string& name);
+                  const std::string& name,
+                  mojo::ScopedSharedBufferHandle shared_memory,
+                  uint64_t shared_memory_buffer_page_size_bytes);
 
   // perfetto::Producer implementation.
   // Gets called by perfetto::TracingService to toggle specific data sources
@@ -87,7 +92,7 @@
 
  private:
   mojo::Remote<mojom::ProducerClient> producer_client_;
-  bool is_in_process_ = false;
+  PerfettoTaskRunner* task_runner_;
 
  protected:
   // Perfetto guarantees that no OnXX callbacks are invoked on |this|
diff --git a/services/tracing/perfetto/test_utils.cc b/services/tracing/perfetto/test_utils.cc
index ed0f85c..de63052 100644
--- a/services/tracing/perfetto/test_utils.cc
+++ b/services/tracing/perfetto/test_utils.cc
@@ -7,6 +7,7 @@
 
 #include "base/bind.h"
 #include "base/run_loop.h"
+#include "services/tracing/public/cpp/perfetto/shared_memory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/commit_data_request.h"
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_packet.h"
@@ -111,6 +112,9 @@
   client.reset(this);
   old_producer_ = PerfettoTracedProcess::Get()->SetProducerClientForTesting(
       std::move(client));
+
+  // Create SMB immediately since we never call ProducerClient's Connect().
+  CHECK(InitSharedMemoryIfNeeded());
 }
 
 MockProducerClient::~MockProducerClient() {
@@ -305,13 +309,18 @@
     PerfettoService* service,
     MockProducerClient* producer_client,
     base::OnceClosure datasource_registered_callback)
-    : producer_name_(producer_name),
+    : ProducerHost(service->perfetto_task_runner()),
+      producer_name_(producer_name),
       datasource_registered_callback_(
           std::move(datasource_registered_callback)) {
   mojo::PendingRemote<mojom::ProducerClient> client;
   mojo::PendingRemote<mojom::ProducerHost> host_remote;
   auto client_receiver = client.InitWithNewPipeAndPassReceiver();
-  Initialize(std::move(client), service->GetService(), producer_name_);
+  Initialize(std::move(client), service->GetService(), producer_name_,
+             static_cast<MojoSharedMemory*>(
+                 producer_client->shared_memory_for_testing())
+                 ->Clone(),
+             PerfettoProducer::kSMBPageSizeBytes);
   receiver_.Bind(host_remote.InitWithNewPipeAndPassReceiver());
   producer_client->BindClientAndHostPipesForTesting(std::move(client_receiver),
                                                     std::move(host_remote));
diff --git a/services/tracing/public/cpp/BUILD.gn b/services/tracing/public/cpp/BUILD.gn
index 4e333ca..f1e605d 100644
--- a/services/tracing/public/cpp/BUILD.gn
+++ b/services/tracing/public/cpp/BUILD.gn
@@ -56,6 +56,8 @@
       "base_agent.h",
       "perfetto/dummy_producer.cc",
       "perfetto/dummy_producer.h",
+      "perfetto/flow_event_utils.cc",
+      "perfetto/flow_event_utils.h",
       "perfetto/interning_index.h",
       "perfetto/java_heap_profiler/hprof_buffer_android.cc",
       "perfetto/java_heap_profiler/hprof_buffer_android.h",
diff --git a/services/tracing/public/cpp/perfetto/dummy_producer.cc b/services/tracing/public/cpp/perfetto/dummy_producer.cc
index 4669dda..0b42e82 100644
--- a/services/tracing/public/cpp/perfetto/dummy_producer.cc
+++ b/services/tracing/public/cpp/perfetto/dummy_producer.cc
@@ -27,6 +27,9 @@
     size_t num_data_sources) {}
 
 // PerfettoProducer implementation.
+bool DummyProducer::SetupStartupTracing() {
+  return false;
+}
 perfetto::SharedMemoryArbiter* DummyProducer::MaybeSharedMemoryArbiter() {
   return nullptr;
 }
diff --git a/services/tracing/public/cpp/perfetto/dummy_producer.h b/services/tracing/public/cpp/perfetto/dummy_producer.h
index b03cae5..2fff6a0 100644
--- a/services/tracing/public/cpp/perfetto/dummy_producer.h
+++ b/services/tracing/public/cpp/perfetto/dummy_producer.h
@@ -32,6 +32,7 @@
       size_t num_data_sources) override;
 
   // PerfettoProducer implementation.
+  bool SetupStartupTracing() override;
   perfetto::SharedMemoryArbiter* MaybeSharedMemoryArbiter() override;
   bool IsTracingActive() override;
   void NewDataSourceAdded(
diff --git a/services/tracing/public/cpp/perfetto/flow_event_utils.cc b/services/tracing/public/cpp/perfetto/flow_event_utils.cc
new file mode 100644
index 0000000..055019ca
--- /dev/null
+++ b/services/tracing/public/cpp/perfetto/flow_event_utils.cc
@@ -0,0 +1,27 @@
+// 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 "services/tracing/public/cpp/perfetto/flow_event_utils.h"
+
+namespace tracing {
+
+// Fill the information about flow event in EventContext.
+//
+// BEWARE: this function currently sets the TrackEvent's LegacyEvent field, and
+// thus should be used from within trace macros that do not set the LegacyEvent
+// field themselves. As it is, it is fine to call this method from the typed
+// TRACE_EVENT macro.
+//
+// TODO(b/TODO): Change to the new model flow events when finalized
+void FillFlowEvent(
+    const perfetto::EventContext& ctx,
+    perfetto::protos::pbzero::TrackEvent_LegacyEvent_FlowDirection direction,
+    uint64_t bind_id) {
+  perfetto::protos::pbzero::TrackEvent_LegacyEvent* legacy_event =
+      ctx.event()->set_legacy_event();
+  legacy_event->set_flow_direction(direction);
+  legacy_event->set_bind_id(bind_id);
+}
+
+}  // namespace tracing
\ No newline at end of file
diff --git a/services/tracing/public/cpp/perfetto/flow_event_utils.h b/services/tracing/public/cpp/perfetto/flow_event_utils.h
new file mode 100644
index 0000000..de5694d
--- /dev/null
+++ b/services/tracing/public/cpp/perfetto/flow_event_utils.h
@@ -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.
+
+#ifndef SERVICES_TRACING_PUBLIC_CPP_PERFETTO_FLOW_EVENT_UTILS_H_
+#define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_FLOW_EVENT_UTILS_H_
+
+#include "base/component_export.h"
+#include "services/tracing/public/cpp/perfetto/macros.h"
+#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.pbzero.h"
+#include "third_party/perfetto/protos/perfetto/trace/track_event/track_event.pbzero.h"
+
+namespace tracing {
+
+// Fill the information about flow event in EventContext.
+//
+// BEWARE: this function currently sets the TrackEvent's LegacyEvent field, and
+// thus should be used from within trace macros that do not set the LegacyEvent
+// field themselves. As it is, it is fine to call this method from the typed
+// TRACE_EVENT macro.
+//
+// TODO(b/147673438): Change to the new model flow events when finalized
+void COMPONENT_EXPORT(TRACING_CPP) FillFlowEvent(
+    const perfetto::EventContext&,
+    perfetto::protos::pbzero::TrackEvent_LegacyEvent_FlowDirection,
+    uint64_t bind_id);
+
+}  // namespace tracing
+
+#endif  // SERVICES_TRACING_PUBLIC_CPP_PERFETTO_FLOW_EVENT_UTILS_H_
diff --git a/services/tracing/public/cpp/perfetto/macros.h b/services/tracing/public/cpp/perfetto/macros.h
index 935fdbd..3d7b473 100644
--- a/services/tracing/public/cpp/perfetto/macros.h
+++ b/services/tracing/public/cpp/perfetto/macros.h
@@ -43,6 +43,8 @@
 //           auto* event = ctx.event();
 //           // Fill in some field in track_event.
 //       });
+//
+// When lambda is passed as an argument, it is executed synchronously.
 #define TRACE_EVENT_BEGIN(category, name, ...)                              \
   TRACING_INTERNAL_ADD_TRACE_EVENT(TRACE_EVENT_PHASE_BEGIN, category, name, \
                                    TRACE_EVENT_FLAG_NONE, ##__VA_ARGS__)
@@ -55,6 +57,9 @@
 
 // Begin a thread-scoped slice which gets automatically closed when going out
 // of scope.
+//
+// Similarly to TRACE_EVENT_BEGIN, when lambda is passed as an argument, it is
+// executed synchronously.
 #define TRACE_EVENT(category, name, ...) \
   TRACING_INTERNAL_SCOPED_ADD_TRACE_EVENT(category, name, ##__VA_ARGS__)
 
diff --git a/services/tracing/public/cpp/perfetto/perfetto_producer.cc b/services/tracing/public/cpp/perfetto/perfetto_producer.cc
index f7457d7b..1f26548 100644
--- a/services/tracing/public/cpp/perfetto/perfetto_producer.cc
+++ b/services/tracing/public/cpp/perfetto/perfetto_producer.cc
@@ -10,6 +10,12 @@
 
 namespace tracing {
 
+// static
+constexpr size_t PerfettoProducer::kSMBPageSizeBytes;
+
+// static
+constexpr size_t PerfettoProducer::kSMBSizeBytes;
+
 PerfettoProducer::PerfettoProducer(PerfettoTaskRunner* task_runner)
     : task_runner_(task_runner) {
   DCHECK(task_runner_);
@@ -17,12 +23,19 @@
 
 PerfettoProducer::~PerfettoProducer() = default;
 
-void PerfettoProducer::BindStartupTraceWriterRegistry(
-    std::unique_ptr<perfetto::StartupTraceWriterRegistry> registry,
-    perfetto::BufferID target_buffer) {
+std::unique_ptr<perfetto::TraceWriter>
+PerfettoProducer::CreateStartupTraceWriter(uint32_t startup_session_id) {
   DCHECK(MaybeSharedMemoryArbiter());
-  return MaybeSharedMemoryArbiter()->BindStartupTraceWriterRegistry(
-      std::move(registry), target_buffer);
+  return MaybeSharedMemoryArbiter()->CreateStartupTraceWriter(
+      startup_session_id);
+}
+
+void PerfettoProducer::BindStartupTargetBuffer(
+    uint32_t startup_session_id,
+    perfetto::BufferID startup_target_buffer) {
+  DCHECK(MaybeSharedMemoryArbiter());
+  MaybeSharedMemoryArbiter()->BindStartupTargetBuffer(startup_session_id,
+                                                      startup_target_buffer);
 }
 
 std::unique_ptr<perfetto::TraceWriter> PerfettoProducer::CreateTraceWriter(
diff --git a/services/tracing/public/cpp/perfetto/perfetto_producer.h b/services/tracing/public/cpp/perfetto/perfetto_producer.h
index cd5b9db..9997120 100644
--- a/services/tracing/public/cpp/perfetto/perfetto_producer.h
+++ b/services/tracing/public/cpp/perfetto/perfetto_producer.h
@@ -8,12 +8,13 @@
 #include <memory>
 
 #include "base/component_export.h"
+#include "build/build_config.h"
 #include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
+#include "third_party/perfetto/include/perfetto/ext/tracing/core/basic_types.h"
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/tracing_service.h"
 
 namespace perfetto {
 class SharedMemoryArbiter;
-class StartupTraceWriterRegistry;
 }  // namespace perfetto
 
 namespace tracing {
@@ -30,15 +31,19 @@
 
   virtual ~PerfettoProducer();
 
-  // Binds the registry and its trace writers to the ProducerClient's SMB, to
-  // write into the given target buffer. The ownership of |registry| is
-  // transferred to PerfettoProducer (and its SharedMemoryArbiter).
-  //
-  // Should only be called while a tracing session is active and a
-  // SharedMemoryArbiter exists.
-  void BindStartupTraceWriterRegistry(
-      std::unique_ptr<perfetto::StartupTraceWriterRegistry> registry,
-      perfetto::BufferID target_buffer);
+  // Setup the shared memory buffer for startup tracing. Returns false on
+  // failure.
+  virtual bool SetupStartupTracing() = 0;
+
+  // See SharedMemoryArbiter::CreateStartupTraceWriter.
+  std::unique_ptr<perfetto::TraceWriter> CreateStartupTraceWriter(
+      uint32_t startup_session_id);
+
+  // See SharedMemoryArbiter::BindStartupTargetBuffer. Should be called on the
+  // producer's task runner.
+  virtual void BindStartupTargetBuffer(
+      uint32_t startup_session_id,
+      perfetto::BufferID startup_target_buffer);
 
   // Used by the DataSource implementations to create TraceWriters
   // for writing their protobufs, and respond to flushes.
@@ -71,6 +76,20 @@
       std::unique_ptr<PerfettoProducer> perfetto_producer);
 
  protected:
+  friend class MockProducerHost;
+
+  // TODO(oysteine): Find a good compromise between performance and
+  // data granularity (mainly relevant to running with small buffer sizes
+  // when we use background tracing) on Android.
+#if defined(OS_ANDROID)
+  static constexpr size_t kSMBPageSizeBytes = 4 * 1024;
+#else
+  static constexpr size_t kSMBPageSizeBytes = 32 * 1024;
+#endif
+
+  // TODO(oysteine): Figure out a good buffer size.
+  static constexpr size_t kSMBSizeBytes = 4 * 1024 * 1024;
+
   PerfettoTaskRunner* task_runner();
 
  private:
diff --git a/services/tracing/public/cpp/perfetto/perfetto_traced_process.cc b/services/tracing/public/cpp/perfetto/perfetto_traced_process.cc
index ae29b913..d9aded1 100644
--- a/services/tracing/public/cpp/perfetto/perfetto_traced_process.cc
+++ b/services/tracing/public/cpp/perfetto/perfetto_traced_process.cc
@@ -62,7 +62,8 @@
     PerfettoProducer* producer,
     const perfetto::DataSourceConfig& data_source_config) {
   data_source_id_ = data_source_id;
-  DCHECK(!producer_) << name_;
+  // Producer may already be set if startup tracing in TraceEventDataSource.
+  DCHECK(!producer_ || producer_ == producer) << name_;
   producer_ = producer;
   StartTracing(producer_, data_source_config);
 }
diff --git a/services/tracing/public/cpp/perfetto/posix_system_producer.cc b/services/tracing/public/cpp/perfetto/posix_system_producer.cc
index b97c6d4a..1f041619 100644
--- a/services/tracing/public/cpp/perfetto/posix_system_producer.cc
+++ b/services/tracing/public/cpp/perfetto/posix_system_producer.cc
@@ -93,6 +93,12 @@
   DisconnectWithReply(base::OnceClosure());
 }
 
+bool PosixSystemProducer::SetupStartupTracing() {
+  // TODO(eseckler): Support startup tracing using an unbound SMA.
+  NOTIMPLEMENTED();
+  return false;
+}
+
 perfetto::SharedMemoryArbiter* PosixSystemProducer::MaybeSharedMemoryArbiter() {
   base::AutoLock lock(services_lock_);
   DCHECK(GetService());
diff --git a/services/tracing/public/cpp/perfetto/posix_system_producer.h b/services/tracing/public/cpp/perfetto/posix_system_producer.h
index 7aa6771..d814f6f 100644
--- a/services/tracing/public/cpp/perfetto/posix_system_producer.h
+++ b/services/tracing/public/cpp/perfetto/posix_system_producer.h
@@ -54,6 +54,7 @@
   void SetNewSocketForTesting(const char* socket);
 
   // PerfettoProducer implementation.
+  bool SetupStartupTracing() override;
   perfetto::SharedMemoryArbiter* MaybeSharedMemoryArbiter() override;
   void NewDataSourceAdded(
       const PerfettoTracedProcess::DataSourceBase* const data_source) override;
diff --git a/services/tracing/public/cpp/perfetto/producer_client.cc b/services/tracing/public/cpp/perfetto/producer_client.cc
index 50fa86f75..e2d4287 100644
--- a/services/tracing/public/cpp/perfetto/producer_client.cc
+++ b/services/tracing/public/cpp/perfetto/producer_client.cc
@@ -7,9 +7,11 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/debug/dump_without_crashing.h"
 #include "base/no_destructor.h"
 #include "base/process/process.h"
 #include "base/task/post_task.h"
+#include "build/build_config.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/tracing/public/cpp/perfetto/shared_memory.h"
 #include "services/tracing/public/cpp/perfetto/trace_event_data_source.h"
@@ -36,13 +38,26 @@
 
 void ProducerClient::Connect(
     mojo::PendingRemote<mojom::PerfettoService> perfetto_service) {
+  if (!InitSharedMemoryIfNeeded()) {
+    LOG(ERROR) << "Failed to setup tracing service connection for this process";
+    return;
+  }
+
+  mojo::ScopedSharedBufferHandle shm;
+  {
+    base::AutoLock lock(shared_memory_lock_);
+    shm = shared_memory_->Clone();
+  }
+  CHECK(shm.is_valid());
+
   mojo::PendingRemote<mojom::ProducerClient> client;
   auto client_receiver = client.InitWithNewPipeAndPassReceiver();
   mojo::PendingRemote<mojom::ProducerHost> producer_host_remote;
   mojo::Remote<mojom::PerfettoService>(std::move(perfetto_service))
       ->ConnectToProducerHost(
           std::move(client),
-          producer_host_remote.InitWithNewPipeAndPassReceiver());
+          producer_host_remote.InitWithNewPipeAndPassReceiver(), std::move(shm),
+          kSMBPageSizeBytes);
   task_runner()->GetOrCreateTaskRunner()->PostTask(
       FROM_HERE,
       base::BindOnce(&ProducerClient::BindClientAndHostPipesOnSequence,
@@ -50,9 +65,51 @@
                      std::move(producer_host_remote)));
 }
 
+void ProducerClient::BindInProcessSharedMemoryArbiter(
+    perfetto::TracingService::ProducerEndpoint* producer_endpoint,
+    PerfettoTaskRunner* task_runner) {
+  DCHECK(!in_process_arbiter_task_runner_);
+  in_process_arbiter_task_runner_ = task_runner;
+
+  perfetto::SharedMemoryArbiter* arbiter;
+  {
+    base::AutoLock lock(shared_memory_lock_);
+    // Shared memory should have been created before connecting to ProducerHost.
+    DCHECK(shared_memory_arbiter_);
+    // |shared_memory_arbiter_| is never destroyed, thus OK to call
+    // BindToProducerEndpoint() without holding the lock.
+    arbiter = shared_memory_arbiter_.get();
+  }
+  arbiter->BindToProducerEndpoint(producer_endpoint, task_runner);
+}
+
+bool ProducerClient::SetupStartupTracing() {
+  return InitSharedMemoryIfNeeded();
+}
+
+void ProducerClient::BindStartupTargetBuffer(
+    uint32_t startup_session_id,
+    perfetto::BufferID startup_target_buffer) {
+  // While we should be called on the ProducerClient's task runner, it's
+  // possible that the SMA lives on a different sequence (when in process).
+  if (in_process_arbiter_task_runner_ &&
+      !in_process_arbiter_task_runner_->RunsTasksOnCurrentThread()) {
+    // |this| is never destroyed, except in tests.
+    in_process_arbiter_task_runner_->GetOrCreateTaskRunner()->PostTask(
+        FROM_HERE, base::BindOnce(&ProducerClient::BindStartupTargetBuffer,
+                                  base::Unretained(this), startup_session_id,
+                                  startup_target_buffer));
+    return;
+  }
+  PerfettoProducer::BindStartupTargetBuffer(startup_session_id,
+                                            startup_target_buffer);
+}
+
 perfetto::SharedMemoryArbiter* ProducerClient::MaybeSharedMemoryArbiter() {
-  return in_process_arbiter_ ? in_process_arbiter_
-                             : shared_memory_arbiter_.get();
+  base::AutoLock lock(shared_memory_lock_);
+  // |shared_memory_arbiter_| is never destroyed, thus OK to return a
+  // reference here.
+  return shared_memory_arbiter_.get();
 }
 
 void ProducerClient::NewDataSourceAdded(
@@ -87,25 +144,22 @@
   return data_sources_tracing_ > 0;
 }
 
-void ProducerClient::OnTracingStart(
-    mojo::ScopedSharedBufferHandle shared_memory,
-    uint64_t shared_memory_buffer_page_size_bytes) {
-  // If we're using in-process mode, we don't need to set up our
-  // own SharedMemoryArbiter.
-  DCHECK(!in_process_arbiter_);
+void ProducerClient::OnTracingStart() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(producer_host_);
-  if (!shared_memory_) {
-    shared_memory_ =
-        std::make_unique<MojoSharedMemory>(std::move(shared_memory));
 
-    shared_memory_arbiter_ = perfetto::SharedMemoryArbiter::CreateInstance(
-        shared_memory_.get(), shared_memory_buffer_page_size_bytes, this,
-        PerfettoTracedProcess::GetTaskRunner());
-  } else {
-    // TODO(oysteine): This is assuming the SMB is the same, currently. Swapping
-    // out SharedMemoryBuffers would require more thread synchronization.
-    DCHECK_EQ(shared_memory_->shared_buffer()->value(), shared_memory->value());
+  // In-process ProducerClient's arbiter is bound via
+  // BindInProcessSharedMemoryArbiter() instead.
+  if (!in_process_arbiter_task_runner_) {
+    perfetto::SharedMemoryArbiter* arbiter;
+    {
+      base::AutoLock lock(shared_memory_lock_);
+      // |shared_memory_arbiter_| is never destroyed, thus OK to call
+      // BindToProducerEndpoint() without holding the lock.
+      arbiter = shared_memory_arbiter_.get();
+    }
+    DCHECK(arbiter);
+    arbiter->BindToProducerEndpoint(this, task_runner());
   }
 }
 
@@ -291,10 +345,37 @@
   DETACH_FROM_SEQUENCE(sequence_checker_);
 }
 
-perfetto::SharedMemory* ProducerClient::shared_memory_for_testing() const {
+perfetto::SharedMemory* ProducerClient::shared_memory_for_testing() {
+  base::AutoLock lock(shared_memory_lock_);
+  // |shared_memory_| is never destroyed except in tests, thus OK to return a
+  // reference here.
   return shared_memory_.get();
 }
 
+bool ProducerClient::InitSharedMemoryIfNeeded() {
+  base::AutoLock lock(shared_memory_lock_);
+  if (shared_memory_) {
+    return true;
+  }
+
+  // The shared memory buffer is always provided by the ProducerClient, but only
+  // created upon the first tracing request.
+  shared_memory_ = std::make_unique<MojoSharedMemory>(kSMBSizeBytes);
+
+  if (!shared_memory_->shared_buffer().is_valid()) {
+    // TODO(crbug/1057614): We see shared memory buffer creation fail on windows
+    // in the field. Investigate why this can happen.
+    base::debug::DumpWithoutCrashing();
+    LOG(ERROR) << "Failed to create tracing SMB";
+    shared_memory_.reset();
+    return false;
+  }
+
+  shared_memory_arbiter_ = perfetto::SharedMemoryArbiter::CreateUnboundInstance(
+      shared_memory_.get(), kSMBPageSizeBytes);
+  return true;
+}
+
 // The Mojo binding should run on the same sequence as the one we get
 // callbacks from Perfetto on, to avoid additional PostTasks.
 void ProducerClient::BindClientAndHostPipesOnSequence(
diff --git a/services/tracing/public/cpp/perfetto/producer_client.h b/services/tracing/public/cpp/perfetto/producer_client.h
index 0661d471..0b5dc8f 100644
--- a/services/tracing/public/cpp/perfetto/producer_client.h
+++ b/services/tracing/public/cpp/perfetto/producer_client.h
@@ -16,11 +16,13 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
+#include "base/synchronization/lock.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/tracing/public/cpp/perfetto/perfetto_producer.h"
 #include "services/tracing/public/cpp/perfetto/task_runner.h"
 #include "services/tracing/public/mojom/perfetto_service.mojom.h"
+#include "third_party/perfetto/include/perfetto/ext/tracing/core/tracing_service.h"
 
 namespace perfetto {
 class SharedMemoryArbiter;
@@ -43,13 +45,15 @@
   ~ProducerClient() override;
 
   void Connect(mojo::PendingRemote<mojom::PerfettoService> perfetto_service);
-
-  void set_in_process_shmem_arbiter(perfetto::SharedMemoryArbiter* arbiter) {
-    DCHECK(!in_process_arbiter_);
-    in_process_arbiter_ = arbiter;
-  }
+  void BindInProcessSharedMemoryArbiter(
+      perfetto::TracingService::ProducerEndpoint*,
+      PerfettoTaskRunner*);
 
   // PerfettoProducer implementation.
+  bool SetupStartupTracing() override;
+  void BindStartupTargetBuffer(
+      uint32_t startup_session_id,
+      perfetto::BufferID startup_target_buffer) override;
   perfetto::SharedMemoryArbiter* MaybeSharedMemoryArbiter() override;
   void NewDataSourceAdded(
       const PerfettoTracedProcess::DataSourceBase* const data_source) override;
@@ -58,8 +62,7 @@
   // mojom::ProducerClient implementation.
   // Called through Mojo by the ProducerHost on the service-side to control
   // tracing and toggle specific DataSources.
-  void OnTracingStart(mojo::ScopedSharedBufferHandle shared_memory,
-                      uint64_t shared_memory_buffer_page_size_bytes) override;
+  void OnTracingStart() override;
   void StartDataSource(uint64_t id,
                        const perfetto::DataSourceConfig& data_source_config,
                        StartDataSourceCallback callback) override;
@@ -102,7 +105,11 @@
       mojo::PendingReceiver<mojom::ProducerClient>,
       mojo::PendingRemote<mojom::ProducerHost>);
   void ResetSequenceForTesting();
-  perfetto::SharedMemory* shared_memory_for_testing() const;
+  perfetto::SharedMemory* shared_memory_for_testing();
+
+ protected:
+  // Protected for testing. Returns false if SMB creation failed.
+  bool InitSharedMemoryIfNeeded();
 
  private:
   friend class base::NoDestructor<ProducerClient>;
@@ -117,13 +124,20 @@
   uint32_t data_sources_tracing_ = 0;
   std::unique_ptr<mojo::Receiver<mojom::ProducerClient>> receiver_;
   mojo::Remote<mojom::ProducerHost> producer_host_;
-  std::unique_ptr<MojoSharedMemory> shared_memory_;
-  std::unique_ptr<perfetto::SharedMemoryArbiter> shared_memory_arbiter_;
-  perfetto::SharedMemoryArbiter* in_process_arbiter_ = nullptr;
+  PerfettoTaskRunner* in_process_arbiter_task_runner_ = nullptr;
   // First value is the flush ID, the second is the number of
   // replies we're still waiting for.
   std::pair<uint64_t, size_t> pending_replies_for_latest_flush_;
 
+  // Guards initialization of |shared_memory_| and |shared_memory_arbiter_|.
+  // TODO(eseckler): Consider accessing these without locks after setup was
+  // completed, since we never destroy or unset them.
+  base::Lock shared_memory_lock_;
+  std::unique_ptr<MojoSharedMemory> shared_memory_
+      GUARDED_BY(shared_memory_lock_);
+  std::unique_ptr<perfetto::SharedMemoryArbiter> shared_memory_arbiter_
+      GUARDED_BY(shared_memory_lock_);
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   // NOTE: Weak pointers must be invalidated before all other member variables.
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
index 89142e7..3cd3af6 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
@@ -45,8 +45,6 @@
 #include "services/tracing/public/cpp/trace_startup.h"
 #include "services/tracing/public/mojom/constants.mojom.h"
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/shared_memory_arbiter.h"
-#include "third_party/perfetto/include/perfetto/ext/tracing/core/startup_trace_writer.h"
-#include "third_party/perfetto/include/perfetto/ext/tracing/core/startup_trace_writer_registry.h"
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_writer.h"
 #include "third_party/perfetto/include/perfetto/tracing/track.h"
 #include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_metadata.pbzero.h"
@@ -422,11 +420,6 @@
 
 TraceEventDataSource* g_trace_event_data_source_for_testing = nullptr;
 
-// crbug.com/914092: This has to be large enough for DevTools to be able to
-// start up and telemetry to start tracing through it before the buffer is
-// exhausted.
-constexpr size_t kMaxStartupWriterBufferSize = 10 * 1024 * 1024;
-
 }  // namespace
 
 // static
@@ -535,24 +528,25 @@
   return is_enabled_;
 }
 
-void TraceEventDataSource::SetupStartupTracing(bool privacy_filtering_enabled) {
+void TraceEventDataSource::SetupStartupTracing(PerfettoProducer* producer,
+                                               bool privacy_filtering_enabled) {
   {
     AutoLockWithDeferredTaskPosting lock(lock_);
-    // Do not enable startup registry if trace log is being flushed. The
+    // Do not enable startup tracing if trace log is being flushed. The
     // previous tracing session has not ended yet.
     if (flushing_trace_log_) {
       return;
     }
     // No need to do anything if startup tracing has already been set,
     // or we know Perfetto has already been setup.
-    if (startup_writer_registry_ || producer_) {
+    if (IsStartupTracingActive() || producer_) {
       DCHECK(!privacy_filtering_enabled || privacy_filtering_enabled_);
       return;
     }
 
+    producer_ = producer;
     privacy_filtering_enabled_ = privacy_filtering_enabled;
-    startup_writer_registry_ =
-        std::make_unique<perfetto::StartupTraceWriterRegistry>();
+
     SetStartupTracingFlagsWhileLocked();
 
     DCHECK(!trace_writer_);
@@ -569,8 +563,9 @@
   CHECK(IsTracingInitialized());
   {
     base::AutoLock lock(lock_);
-    if (!startup_writer_registry_)
+    if (!IsStartupTracingActive()) {
       return;
+    }
   }
   startup_tracing_timer_.Start(
       FROM_HERE, startup_tracing_timeout_,
@@ -589,18 +584,22 @@
     return;
   }
   DCHECK_CALLED_ON_VALID_SEQUENCE(perfetto_sequence_checker_);
-  std::unique_ptr<perfetto::StartupTraceWriterRegistry> registry;
-  std::unique_ptr<perfetto::StartupTraceWriter> trace_writer;
+  std::unique_ptr<perfetto::TraceWriter> trace_writer;
   {
     AutoLockWithDeferredTaskPosting lock(lock_);
-    if (!startup_writer_registry_) {
+    if (!IsStartupTracingActive()) {
       return;
     }
-    // Set startup_writer_registry_ to null so that no further writers are
-    // created.
-    startup_writer_registry_.reset();
+
+    // Prevent recreation of ThreadLocalEventSinks after flush.
+    producer_ = nullptr;
+    target_buffer_ = 0;
     flushing_trace_log_ = true;
     trace_writer = std::move(trace_writer_);
+
+    // Increment the session id to make sure that any subsequent tracing session
+    // uses fresh trace writers.
+    IncrementSessionIdOrClearStartupFlagWhileLocked();
   }
   if (trace_writer) {
     ReturnTraceWriter(std::move(trace_writer));
@@ -612,7 +611,8 @@
                    /*use_worker_thread=*/false);
 }
 
-void TraceEventDataSource::IncrementSessionIdOrClearStartupFlagWhileLocked() {
+uint32_t
+TraceEventDataSource::IncrementSessionIdOrClearStartupFlagWhileLocked() {
   // Protected by |lock_| for CreateThreadLocalEventSink() and
   // SetStartupTracingFlagsWhileLocked().
   lock_.AssertAcquired();
@@ -628,6 +628,7 @@
     flags.session_id++;
   }
   session_flags_.store(flags, std::memory_order_relaxed);
+  return flags.session_id;
 }
 
 void TraceEventDataSource::SetStartupTracingFlagsWhileLocked() {
@@ -640,6 +641,11 @@
   session_flags_.store(flags, std::memory_order_relaxed);
 }
 
+bool TraceEventDataSource::IsStartupTracingActive() const {
+  SessionFlags flags = session_flags_.load(std::memory_order_relaxed);
+  return flags.is_startup_tracing;
+}
+
 void TraceEventDataSource::OnFlushFinished(
     const scoped_refptr<base::RefCountedString>&,
     bool has_more_events) {
@@ -656,11 +662,6 @@
     // task.
     task = std::move(flush_complete_task_);
     flushing_trace_log_ = false;
-
-    // Increment the session id to make sure that once tracing starts the events
-    // are added to a new trace writer that comes from perfetto producer,
-    // instead of holding on to the startup registry's writers.
-    IncrementSessionIdOrClearStartupFlagWhileLocked();
   }
   if (task) {
     std::move(task).Run();
@@ -690,37 +691,43 @@
     PerfettoProducer* producer,
     const perfetto::DataSourceConfig& data_source_config) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(perfetto_sequence_checker_);
-  std::unique_ptr<perfetto::StartupTraceWriterRegistry> unbound_writer_registry;
+  bool startup_tracing_active;
+  uint32_t session_id;
   {
     AutoLockWithDeferredTaskPosting lock(lock_);
+
     bool should_enable_filtering =
         data_source_config.chrome_config().privacy_filtering_enabled();
-    if (should_enable_filtering) {
-      CHECK(!startup_writer_registry_ || privacy_filtering_enabled_)
-          << "Unexpected StartTracing received when startup tracing is "
-             "running.";
-    }
-    privacy_filtering_enabled_ = should_enable_filtering;
 
+    startup_tracing_active = IsStartupTracingActive();
+    if (startup_tracing_active) {
+      CHECK(!should_enable_filtering || privacy_filtering_enabled_)
+          << "Startup tracing was active without privacy filtering when "
+             "service started tracing with privacy filtering.";
+      DCHECK_EQ(producer_, producer)
+          << "Startup tracing was taken over by a different PerfettoProducer";
+    }
+
+    privacy_filtering_enabled_ = should_enable_filtering;
     producer_ = producer;
     target_buffer_ = data_source_config.target_buffer();
-    // Reduce lock contention by binding the registry without holding the lock.
-    unbound_writer_registry = std::move(startup_writer_registry_);
-
-    IncrementSessionIdOrClearStartupFlagWhileLocked();
+    session_id = IncrementSessionIdOrClearStartupFlagWhileLocked();
 
     if (!trace_writer_) {
       trace_writer_ = CreateTraceWriterLocked();
     }
   }
 
-  if (unbound_writer_registry) {
-    // TODO(oysteine): Investigate why trace events emitted by something in
-    // BindStartupTraceWriterRegistry() causes deadlocks.
+  // SetupStartupTracing() will not setup a new startup session after we set
+  // |producer_| above, so accessing |startup_tracing_active| outside the lock
+  // is safe.
+  if (startup_tracing_active) {
+    // Binding startup buffers may cause tasks to be posted. Disable trace
+    // events to avoid deadlocks due to reentrancy into tracing code.
     AutoThreadLocalBoolean thread_is_in_trace_event(
         GetThreadIsInTraceEventTLS());
-    producer->BindStartupTraceWriterRegistry(
-        std::move(unbound_writer_registry), data_source_config.target_buffer());
+    producer->BindStartupTargetBuffer(session_id,
+                                      data_source_config.target_buffer());
   } else {
     RegisterWithTraceLog();
   }
@@ -778,7 +785,7 @@
     TraceLog::GetInstance()->SetDisabled();
   }
 
-  std::unique_ptr<perfetto::StartupTraceWriter> trace_writer;
+  std::unique_ptr<perfetto::TraceWriter> trace_writer;
   {
     AutoLockWithDeferredTaskPosting lock(lock_);
     if (flush_complete_task_) {
@@ -883,27 +890,24 @@
   EmitTrackDescriptor();
 }
 
-std::unique_ptr<perfetto::StartupTraceWriter>
+std::unique_ptr<perfetto::TraceWriter>
 TraceEventDataSource::CreateTraceWriterLocked() {
   lock_.AssertAcquired();
-  // |startup_writer_registry_| only exists during startup tracing before we
-  // connect to the service. |producer_| is reset when tracing is
-  // stopped.
-  std::unique_ptr<perfetto::StartupTraceWriter> trace_writer;
-  if (startup_writer_registry_) {
-    // Chromium uses BufferExhaustedPolicy::kDrop to avoid stalling trace
-    // writers when the chunks in the SMB are exhausted. Stalling could
-    // otherwise lead to deadlocks in chromium, because a stalled mojo IPC
-    // thread could prevent CommitRequest messages from reaching the perfetto
-    // service.
-    auto buffer_exhausted_policy = perfetto::BufferExhaustedPolicy::kDrop;
-    trace_writer = startup_writer_registry_->CreateUnboundTraceWriter(
-        buffer_exhausted_policy, kMaxStartupWriterBufferSize);
-  } else if (producer_) {
-    trace_writer = std::make_unique<perfetto::StartupTraceWriter>(
-        producer_->CreateTraceWriter(target_buffer_));
+
+  if (IsStartupTracingActive()) {
+    DCHECK(producer_);
+    uint32_t session_id =
+        session_flags_.load(std::memory_order_relaxed).session_id;
+    return producer_->CreateStartupTraceWriter(session_id);
   }
-  return trace_writer;
+
+  // |producer_| is reset when tracing is stopped, and trace writer creation can
+  // happen in parallel on any thread.
+  if (producer_) {
+    return producer_->CreateTraceWriter(target_buffer_);
+  }
+
+  return nullptr;
 }
 
 TrackEventThreadLocalEventSink*
@@ -1005,29 +1009,29 @@
 }
 
 void TraceEventDataSource::ReturnTraceWriter(
-    std::unique_ptr<perfetto::StartupTraceWriter> trace_writer) {
+    std::unique_ptr<perfetto::TraceWriter> trace_writer) {
   {
-    // Prevent concurrent binding of the registry.
+    // Prevent concurrent changes to |session_flags_|.
     AutoLockWithDeferredTaskPosting lock(lock_);
 
-    // If we don't have a task runner yet, we must be attempting to return a
-    // writer before the (very first) registry was bound. We cannot create the
-    // task runner safely in this case, because the thread pool may not have
-    // been brought up yet.
+    // If we don't have a task runner yet, we cannot create the task runner
+    // safely, because the thread pool may not have been brought up yet. This
+    // should only happen during startup tracing.
     if (!PerfettoTracedProcess::GetTaskRunner()->HasTaskRunner()) {
-      DCHECK(startup_writer_registry_);
-      // It's safe to call ReturnToRegistry on the current sequence, as it won't
-      // destroy the writer since the registry was not bound yet. Will keep
-      // |trace_writer| alive until the registry is bound later.
-      perfetto::StartupTraceWriter::ReturnToRegistry(std::move(trace_writer));
+      DCHECK(IsStartupTracingActive());
+      // It's safe to destroy the TraceWriter on the current sequence, as the
+      // destruction won't post tasks or make mojo calls, because the arbiter
+      // wasn't bound yet.
+      trace_writer.reset();
       return;
     }
   }
 
-  // Return the TraceWriter on the sequence that Perfetto runs on. Needed as the
-  // ThreadLocalEventSink gets deleted on thread shutdown and we can't safely
-  // call TaskRunnerHandle::Get() at that point (which can happen as the
-  // TraceWriter destructor might make a Mojo call and trigger it).
+  // Return the TraceWriter on the sequence that the PerfettoProducers run on.
+  // Needed as the TrackEventThreadLocalEventSink gets deleted on thread
+  // shutdown and we can't safely call TaskRunnerHandle::Get() at that point
+  // (which can happen as the TraceWriter destructor might issue a Mojo call
+  // synchronously, which can trigger a call to TaskRunnerHandle::Get()).
   auto* trace_writer_raw = trace_writer.release();
   ANNOTATE_LEAKING_OBJECT_PTR(trace_writer_raw);
   PerfettoTracedProcess::GetTaskRunner()->GetOrCreateTaskRunner()->PostTask(
@@ -1035,14 +1039,7 @@
       base::BindOnce(
           // Pass writer as raw pointer so that we leak it if task posting fails
           // (during shutdown).
-          [](perfetto::StartupTraceWriter* trace_writer) {
-            // May destroy |trace_writer|. If the writer is still unbound, the
-            // registry will keep it alive until it was bound and its buffered
-            // data was copied. This ensures that we don't lose data from
-            // threads that are shut down during startup.
-            perfetto::StartupTraceWriter::ReturnToRegistry(
-                base::WrapUnique<perfetto::StartupTraceWriter>(trace_writer));
-          },
+          [](perfetto::TraceWriter* trace_writer) { delete trace_writer; },
           trace_writer_raw));
 }
 
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source.h b/services/tracing/public/cpp/perfetto/trace_event_data_source.h
index c1beb51f..2b3a30532 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source.h
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source.h
@@ -34,8 +34,6 @@
 }  // namespace base
 
 namespace perfetto {
-class StartupTraceWriter;
-class StartupTraceWriterRegistry;
 class TraceWriter;
 class EventContext;
 }
@@ -158,7 +156,8 @@
   // Enables startup tracing. Trace data is locally buffered until connection to
   // the perfetto service is established. Expects a later call to StartTracing()
   // to bind to the perfetto service. Should only be called once.
-  void SetupStartupTracing(bool privacy_filtering_enabled);
+  void SetupStartupTracing(PerfettoProducer* producer,
+                           bool privacy_filtering_enabled);
 
   // Installs TraceLog overrides for tracing during Chrome startup.
   void RegisterStartupHooks();
@@ -179,8 +178,7 @@
   void ClearIncrementalState() override;
 
   // Deletes TraceWriter safely on behalf of a ThreadLocalEventSink.
-  void ReturnTraceWriter(
-      std::unique_ptr<perfetto::StartupTraceWriter> trace_writer);
+  void ReturnTraceWriter(std::unique_ptr<perfetto::TraceWriter> trace_writer);
 
   void set_startup_tracing_timeout_for_testing(base::TimeDelta timeout_us) {
     startup_tracing_timeout_ = timeout_us;
@@ -235,7 +233,7 @@
   void RegisterWithTraceLog();
   void UnregisterFromTraceLog();
 
-  std::unique_ptr<perfetto::StartupTraceWriter> CreateTraceWriterLocked();
+  std::unique_ptr<perfetto::TraceWriter> CreateTraceWriterLocked();
   TrackEventThreadLocalEventSink* CreateThreadLocalEventSink(
       bool thread_will_flush);
 
@@ -262,8 +260,9 @@
   void LogHistogram(base::HistogramBase* histogram);
   void EmitTrackDescriptor();
 
-  void IncrementSessionIdOrClearStartupFlagWhileLocked();
+  uint32_t IncrementSessionIdOrClearStartupFlagWhileLocked();
   void SetStartupTracingFlagsWhileLocked();
+  bool IsStartupTracingActive() const;
 
   bool disable_interning_ = false;
   base::OnceClosure stop_complete_callback_;
@@ -281,12 +280,7 @@
   // base::AutoLock to protect code paths which may post tasks.
   base::Lock lock_;  // Protects subsequent members.
   uint32_t target_buffer_ = 0;
-  // We own the registry during startup, but transfer its ownership to the
-  // PerfettoProducer once the perfetto service is available. Only set if
-  // SetupStartupTracing() is called.
-  std::unique_ptr<perfetto::StartupTraceWriterRegistry>
-      startup_writer_registry_;
-  std::unique_ptr<perfetto::StartupTraceWriter> trace_writer_;
+  std::unique_ptr<perfetto::TraceWriter> trace_writer_;
   base::OneShotTimer startup_tracing_timer_;
   bool is_enabled_ = false;
   bool flushing_trace_log_ = false;
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc b/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
index 73342e63..bd063ef2 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
@@ -1477,7 +1477,9 @@
   // Start startup tracing registry with no timeout. This would cause startup
   // tracing to abort and flush as soon the current thread can run tasks.
   data_source->set_startup_tracing_timeout_for_testing(base::TimeDelta());
-  data_source->SetupStartupTracing(true);
+  producer_client()->SetupStartupTracing();
+  data_source->SetupStartupTracing(producer_client(),
+                                   /*privacy_filtering_enabled=*/true);
   base::trace_event::TraceLog::GetInstance()->SetEnabled(
       base::trace_event::TraceConfig(),
       base::trace_event::TraceLog::RECORDING_MODE);
diff --git a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
index 0339d15a..b61f26d1 100644
--- a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
+++ b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
@@ -200,7 +200,7 @@
     : src_loc(std::move(src)) {}
 
 TrackEventThreadLocalEventSink::TrackEventThreadLocalEventSink(
-    std::unique_ptr<perfetto::StartupTraceWriter> trace_writer,
+    std::unique_ptr<perfetto::TraceWriter> trace_writer,
     uint32_t session_id,
     bool disable_interning,
     bool proto_writer_filtering_enabled)
diff --git a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
index 975421b..559ec4a 100644
--- a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
+++ b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
@@ -17,7 +17,6 @@
 #include "base/time/time.h"
 #include "base/trace_event/thread_instruction_count.h"
 #include "services/tracing/public/cpp/perfetto/interning_index.h"
-#include "third_party/perfetto/include/perfetto/ext/tracing/core/startup_trace_writer.h"
 #include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_writer.h"
 #include "third_party/perfetto/include/perfetto/protozero/message_handle.h"
 #include "third_party/perfetto/include/perfetto/tracing/event_context.h"
@@ -25,10 +24,6 @@
 #include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_thread_descriptor.pbzero.h"
 #include "third_party/perfetto/protos/perfetto/trace/track_event/track_event.pbzero.h"
 
-namespace perfetto {
-class StartupTraceWriter;
-}  // namespace perfetto
-
 namespace tracing {
 
 // ThreadLocalEventSink that emits TrackEvent protos.
@@ -61,7 +56,7 @@
       std::vector<std::tuple<IndexType, IndexData, InterningIndexEntry>>;
 
   TrackEventThreadLocalEventSink(
-      std::unique_ptr<perfetto::StartupTraceWriter> trace_writer,
+      std::unique_ptr<perfetto::TraceWriter> trace_writer,
       uint32_t session_id,
       bool disable_interning,
       bool proto_writer_filtering_enabled);
@@ -174,7 +169,7 @@
 
   const bool privacy_filtering_enabled_;
 
-  std::unique_ptr<perfetto::StartupTraceWriter> trace_writer_;
+  std::unique_ptr<perfetto::TraceWriter> trace_writer_;
   uint32_t session_id_;
   bool disable_interning_;
   uint32_t sink_id_;
diff --git a/services/tracing/public/cpp/trace_startup.cc b/services/tracing/public/cpp/trace_startup.cc
index a373fb66..1542ebe2 100644
--- a/services/tracing/public/cpp/trace_startup.cc
+++ b/services/tracing/public/cpp/trace_startup.cc
@@ -11,6 +11,7 @@
 #include "components/tracing/common/trace_startup_config.h"
 #include "components/tracing/common/trace_to_console.h"
 #include "components/tracing/common/tracing_switches.h"
+#include "services/tracing/public/cpp/perfetto/producer_client.h"
 #include "services/tracing/public/cpp/perfetto/system_producer.h"
 #include "services/tracing/public/cpp/perfetto/trace_event_data_source.h"
 #include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
@@ -21,8 +22,30 @@
 
 namespace tracing {
 namespace {
+
 using base::trace_event::TraceConfig;
 using base::trace_event::TraceLog;
+
+bool SetupStartupTracing(PerfettoProducer* producer,
+                         bool privacy_filtering_enabled,
+                         bool enable_sampler_profiler) {
+  // TODO(eseckler): This should really go through PerfettoTracedProcess
+  // somehow, so that the "correct" producer gets to take over the session.
+
+  if (!producer->SetupStartupTracing()) {
+    LOG(ERROR) << "Failed to setup startup tracing for this process";
+    return false;
+  }
+
+  TraceEventDataSource::GetInstance()->SetupStartupTracing(
+      producer, privacy_filtering_enabled);
+
+  if (enable_sampler_profiler)
+    TracingSamplerProfiler::SetupStartupTracing();
+
+  return true;
+}
+
 }  // namespace
 
 bool g_tracing_initialized_after_threadpool_and_featurelist = false;
@@ -40,8 +63,7 @@
   perfetto::internal::TrackRegistry::InitializeInstance();
 
   // TODO(oysteine): Support startup tracing to a perfetto protobuf trace. This
-  // should also enable TraceLog and call
-  // TraceEventDataSource::SetupStartupTracing().
+  // should also enable TraceLog and call SetupStartupTracing().
   if (command_line.HasSwitch(switches::kPerfettoOutputFile))
     return;
 
@@ -71,32 +93,41 @@
     trace_config.SetTraceBufferSizeInKb(0);
     trace_config.SetTraceBufferSizeInEvents(0);
 
-    if (trace_config.IsCategoryGroupEnabled(
-            TRACE_DISABLED_BY_DEFAULT("cpu_profiler"))) {
-      TracingSamplerProfiler::SetupStartupTracing();
+    PerfettoProducer* producer =
+        PerfettoTracedProcess::Get()->producer_client();
+    if (startup_config->GetSessionOwner() ==
+        TraceStartupConfig::SessionOwner::kSystemTracing) {
+      producer = PerfettoTracedProcess::Get()->system_producer();
     }
-    TraceEventDataSource::GetInstance()->SetupStartupTracing(
+
+    bool privacy_filtering_enabled =
         startup_config->GetSessionOwner() ==
             TraceStartupConfig::SessionOwner::kBackgroundTracing ||
-        command_line.HasSwitch(switches::kTraceStartupEnablePrivacyFiltering));
+        command_line.HasSwitch(switches::kTraceStartupEnablePrivacyFiltering);
+
+    bool enable_sampler_profiler = trace_config.IsCategoryGroupEnabled(
+        TRACE_DISABLED_BY_DEFAULT("cpu_profiler"));
+
+    if (!SetupStartupTracing(producer, privacy_filtering_enabled,
+                             enable_sampler_profiler)) {
+      startup_config->SetDisabled();
+      return;
+    }
 
     uint8_t modes = TraceLog::RECORDING_MODE;
     if (!trace_config.event_filters().empty())
       modes |= TraceLog::FILTERING_MODE;
     trace_log->SetEnabled(trace_config, modes);
-  } else if (command_line.HasSwitch(switches::kTraceToConsole)) {
-    // TODO(eseckler): Remove ability to trace to the console, perfetto doesn't
-    // support this and noone seems to use it.
-    TraceConfig trace_config = GetConfigForTraceToConsole();
-    LOG(ERROR) << "Start " << switches::kTraceToConsole
-               << " with CategoryFilter '"
-               << trace_config.ToCategoryFilterString() << "'.";
-    TraceEventDataSource::GetInstance()->SetupStartupTracing(
-        /*privacy_filtering_enabled=*/false);
-    trace_log->SetEnabled(trace_config, TraceLog::RECORDING_MODE);
   }
 }
 
+bool SetupStartupTracingForProcess(bool privacy_filtering_enabled,
+                                   bool enable_sampler_profiler) {
+  return SetupStartupTracing(PerfettoTracedProcess::Get()->producer_client(),
+                             privacy_filtering_enabled,
+                             enable_sampler_profiler);
+}
+
 void InitTracingPostThreadPoolStartAndFeatureList() {
   if (g_tracing_initialized_after_threadpool_and_featurelist)
     return;
diff --git a/services/tracing/public/cpp/trace_startup.h b/services/tracing/public/cpp/trace_startup.h
index 089ba9d..b50c42f 100644
--- a/services/tracing/public/cpp/trace_startup.h
+++ b/services/tracing/public/cpp/trace_startup.h
@@ -17,11 +17,27 @@
 // for this process.
 bool COMPONENT_EXPORT(TRACING_CPP) IsTracingInitialized();
 
-// TraceLog with config based on the command line flags. Also hooks up service
-// callbacks in TraceLog if necessary. The latter is required when the perfetto
-// tracing backend is used.
+// Hooks up hooks up service callbacks in TraceLog for the perfetto backend and,
+// if startup tracing command line flags are present, enables TraceLog with a
+// config based on the flags. In zygote children, this should only be called
+// after mojo is initialized, as the zygote's sandbox prevents creation of the
+// tracing SMB before that point.
+//
+// TODO(eseckler): Consider allocating the SMB in parent processes outside the
+// sandbox and supply it via the command line. Then, we can revert to call this
+// earlier and from fewer places again.
 void COMPONENT_EXPORT(TRACING_CPP) EnableStartupTracingIfNeeded();
 
+// Prepare ProducerClient and trace event and/or sampler profiler data sources
+// for startup tracing with chrome's tracing service. Returns false on failure.
+// Note that TraceLog still needs to be enabled separately.
+//
+// TODO(eseckler): Figure out what startup tracing APIs should look like with
+// the client lib.
+bool COMPONENT_EXPORT(TRACING_CPP)
+    SetupStartupTracingForProcess(bool privacy_filtering_enabled,
+                                  bool enable_sampler_profiler);
+
 // Initialize tracing components that require task runners. Will switch
 // IsTracingInitialized() to return true.
 void COMPONENT_EXPORT(TRACING_CPP)
diff --git a/services/tracing/public/cpp/tracing_features.cc b/services/tracing/public/cpp/tracing_features.cc
index fece9b5bc..0b3cce6c 100644
--- a/services/tracing/public/cpp/tracing_features.cc
+++ b/services/tracing/public/cpp/tracing_features.cc
@@ -30,10 +30,6 @@
 #endif
 };
 
-// Causes Perfetto to run in-process mode for in-process tracing producers.
-const base::Feature kPerfettoForceOutOfProcessProducer{
-    "PerfettoForceOutOfProcessProducer", base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Runs the tracing service as an in-process browser service.
 const base::Feature kTracingServiceInProcess {
   "TracingServiceInProcess",
diff --git a/services/tracing/public/cpp/tracing_features.h b/services/tracing/public/cpp/tracing_features.h
index 9bd1a12..d93d074 100644
--- a/services/tracing/public/cpp/tracing_features.h
+++ b/services/tracing/public/cpp/tracing_features.h
@@ -22,9 +22,6 @@
     kBackgroundTracingProtoOutput;
 
 extern const COMPONENT_EXPORT(TRACING_CPP) base::Feature
-    kPerfettoForceOutOfProcessProducer;
-
-extern const COMPONENT_EXPORT(TRACING_CPP) base::Feature
     kEnablePerfettoSystemTracing;
 
 }  // namespace features
diff --git a/services/tracing/public/mojom/perfetto_service.mojom b/services/tracing/public/mojom/perfetto_service.mojom
index e17f002..3a8e2e5 100644
--- a/services/tracing/public/mojom/perfetto_service.mojom
+++ b/services/tracing/public/mojom/perfetto_service.mojom
@@ -122,8 +122,8 @@
 // this interface and use the PerfettoService interface within the tracing
 // service to register itself.
 interface ProducerClient {
-  OnTracingStart(handle<shared_buffer> shared_memory,
-                  uint64 shared_memory_buffer_page_size_bytes);
+  // Called when tracing is first enabled, before any data sources are started.
+  OnTracingStart();
 
   // Called by Perfetto (via ProducerHost) to request a data source to start
   // logging.
@@ -138,10 +138,15 @@
 // This is implemented by the tracing service, and is essentially a singleton
 // factory for establishing bi-directional communication with the Perfetto
 // tracing system. A client that wishes to provide tracing data when requested,
-// should implement ProducerClient for callbacks and pass along.
+// should implement ProducerClient for callbacks and pass along, together with
+// the shared memory buffer that'll be used to pass the data.
 interface PerfettoService {
+  // Connect a new ProducerClient to a ProducerHost in the tracing service,
+  // providing the shared memory buffer that will be used to send trace data.
   ConnectToProducerHost(pending_remote<ProducerClient> producer_client,
-                        pending_receiver<ProducerHost> producer_host_receiver);
+                        pending_receiver<ProducerHost> producer_host_receiver,
+                        handle<shared_buffer> shared_memory,
+                        uint64 shared_memory_buffer_page_size_bytes);
 };
 
 // The policy for filling the trace buffer.
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 0c172c26..526d4e6c 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -294,26 +294,6 @@
             ]
         }
     ],
-    "AndroidInProductHelpQuietNotificationPrompts": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "params": {
-                        "availability": "any",
-                        "event_trigger": "name:tabgroups_dummy;comparator:any;window:1;storage:1",
-                        "event_used": "name:tabgroups_dummy;comparator:any;window:1;storage:1"
-                    },
-                    "enable_features": [
-                        "IPH_QuietNotificationPrompts"
-                    ]
-                }
-            ]
-        }
-    ],
     "AndroidInlineUpdateFlowStudy": [
         {
             "platforms": [
@@ -365,24 +345,6 @@
             ]
         }
     ],
-    "AndroidPasswordManagerOnboarding": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "EnabledWithSafetyStory",
-                    "params": {
-                        "story": "safety"
-                    },
-                    "enable_features": [
-                        "PasswordManagerOnboardingAndroid"
-                    ]
-                }
-            ]
-        }
-    ],
     "AndroidPictureInPictureAPI": [
         {
             "platforms": [
@@ -3959,21 +3921,6 @@
             ]
         }
     ],
-    "Precache": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "EnabledCGRUV20b",
-                    "params": {
-                        "config_url": "https://www.gstatic.com/chrome/wifiprefetch/precache_config_g20"
-                    }
-                }
-            ]
-        }
-    ],
     "PreconnectOnDidFinishNavigation": [
         {
             "platforms": [
@@ -4274,10 +4221,15 @@
                 {
                     "name": "EnableWithAdaptiveActivation",
                     "params": {
+                        "availability": "any",
                         "enable_adaptive_activation": "true",
-                        "enable_crowd_deny_triggering": "true"
+                        "enable_crowd_deny_triggering": "true",
+                        "event_trigger": "name:tabgroups_dummy;comparator:any;window:1;storage:1",
+                        "event_used": "name:tabgroups_dummy;comparator:any;window:1;storage:1",
+                        "session_rate": "any"
                     },
                     "enable_features": [
+                        "IPH_QuietNotificationPrompts",
                         "QuietNotificationPrompts"
                     ]
                 }
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index c5a8c2cd..d241937 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -117,14 +117,6 @@
   ]
 }
 
-java_group("com_android_support_asynclayoutinflater_java") {
-  deps = [
-    ":${target_name}_orig__unpack_aar",
-    ":androidx_asynclayoutinflater_asynclayoutinflater_java",
-  ]
-  input_jars_paths = [ "${target_out_dir}/com_android_support_asynclayoutinflater_java_orig/classes.jar" ]
-}
-
 java_group("com_android_support_cardview_v7_java") {
   deps = [
     ":${target_name}_orig__unpack_aar",
diff --git a/third_party/blink/common/bluetooth/web_bluetooth_device_id.cc b/third_party/blink/common/bluetooth/web_bluetooth_device_id.cc
index 7ba88c0..b3d9ba9 100644
--- a/third_party/blink/common/bluetooth/web_bluetooth_device_id.cc
+++ b/third_party/blink/common/bluetooth/web_bluetooth_device_id.cc
@@ -4,8 +4,6 @@
 
 #include "third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h"
 
-#include <utility>
-
 #include "base/base64.h"
 #include "base/strings/string_util.h"
 #include "crypto/random.h"
@@ -85,11 +83,6 @@
   return !(*this == device_id);
 }
 
-bool WebBluetoothDeviceId::operator<(
-    const WebBluetoothDeviceId& device_id) const {
-  return str() < device_id.str();
-}
-
 std::ostream& operator<<(std::ostream& out,
                          const WebBluetoothDeviceId& device_id) {
   return out << device_id.str();
diff --git a/third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h b/third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h
index 0177372..5d00935 100644
--- a/third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h
+++ b/third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h
@@ -41,7 +41,6 @@
 
   bool operator==(const WebBluetoothDeviceId& device_id) const;
   bool operator!=(const WebBluetoothDeviceId& device_id) const;
-  bool operator<(const WebBluetoothDeviceId& device_id) const;
 
  private:
   std::string device_id_;
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index fb2f7d2..2511123 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -2845,6 +2845,22 @@
       # Media features to emulate.
       optional array of MediaFeature features
 
+  # Emulates the given vision deficiency.
+  experimental command setEmulatedVisionDeficiency
+    parameters
+      # Vision deficiency to emulate.
+      enum type
+        none
+        achromatomaly
+        achromatopsia
+        blurredVision
+        deuteranomaly
+        deuteranopia
+        protanomaly
+        protanopia
+        tritanomaly
+        tritanopia
+
   # Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position
   # unavailable.
   command setGeolocationOverride
diff --git a/third_party/blink/public/mojom/frame/frame.mojom b/third_party/blink/public/mojom/frame/frame.mojom
index f3187d36..5542faa 100644
--- a/third_party/blink/public/mojom/frame/frame.mojom
+++ b/third_party/blink/public/mojom/frame/frame.mojom
@@ -602,6 +602,9 @@
 //
 // This interface will only be provided when the LocalFrame is a main frame.
 interface LocalMainFrame {
+  // Requests performing a page scale animation based on the point/rect provided.
+  AnimateDoubleTapZoom(gfx.mojom.Point point, gfx.mojom.Rect rect);
+
   // Scales the view without affecting layout by using the visual viewport.
   SetScaleFactor(float scale);
 
diff --git a/third_party/blink/public/web/web_view.h b/third_party/blink/public/web/web_view.h
index fb8b8c1e..dbe3cd3 100644
--- a/third_party/blink/public/web/web_view.h
+++ b/third_party/blink/public/web/web_view.h
@@ -278,9 +278,6 @@
   // WebView.
   virtual WebSize ContentsPreferredMinimumSize() = 0;
 
-  // Requests a page-scale animation based on the specified point/rect.
-  virtual void AnimateDoubleTapZoom(const gfx::Point&, const WebRect&) = 0;
-
   // Requests a page-scale animation based on the specified rect.
   virtual void ZoomToFindInPageRect(const WebRect&) = 0;
 
diff --git a/third_party/blink/renderer/core/css/BUILD.gn b/third_party/blink/renderer/core/css/BUILD.gn
index 8f8b6f4..b70a955 100644
--- a/third_party/blink/renderer/core/css/BUILD.gn
+++ b/third_party/blink/renderer/core/css/BUILD.gn
@@ -584,6 +584,8 @@
     "style_traversal_root.h",
     "tree_scope_style_sheet_collection.cc",
     "tree_scope_style_sheet_collection.h",
+    "vision_deficiency.cc",
+    "vision_deficiency.h",
     "zoom_adjusted_pixel_value.h",
   ]
 }
diff --git a/third_party/blink/renderer/core/css/css_axis_value.h b/third_party/blink/renderer/core/css/css_axis_value.h
index 910f55f..70017c2 100644
--- a/third_party/blink/renderer/core/css/css_axis_value.h
+++ b/third_party/blink/renderer/core/css/css_axis_value.h
@@ -25,7 +25,7 @@
 
   double Z() const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValueList::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_basic_shape_values.cc b/third_party/blink/renderer/core/css/css_basic_shape_values.cc
index 8c8aa51..2de790b7 100644
--- a/third_party/blink/renderer/core/css/css_basic_shape_values.cc
+++ b/third_party/blink/renderer/core/css/css_basic_shape_values.cc
@@ -143,7 +143,8 @@
          DataEquivalent(radius_, other.radius_);
 }
 
-void CSSBasicShapeCircleValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSBasicShapeCircleValue::TraceAfterDispatch(
+    blink::Visitor* visitor) const {
   visitor->Trace(center_x_);
   visitor->Trace(center_y_);
   visitor->Trace(radius_);
@@ -226,7 +227,8 @@
          DataEquivalent(radius_y_, other.radius_y_);
 }
 
-void CSSBasicShapeEllipseValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSBasicShapeEllipseValue::TraceAfterDispatch(
+    blink::Visitor* visitor) const {
   visitor->Trace(center_x_);
   visitor->Trace(center_y_);
   visitor->Trace(radius_x_);
@@ -287,7 +289,8 @@
   return CompareCSSValueVector(values_, other.values_);
 }
 
-void CSSBasicShapePolygonValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSBasicShapePolygonValue::TraceAfterDispatch(
+    blink::Visitor* visitor) const {
   visitor->Trace(values_);
   CSSValue::TraceAfterDispatch(visitor);
 }
@@ -435,7 +438,8 @@
          DataEquivalent(bottom_left_radius_, other.bottom_left_radius_);
 }
 
-void CSSBasicShapeInsetValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSBasicShapeInsetValue::TraceAfterDispatch(
+    blink::Visitor* visitor) const {
   visitor->Trace(top_);
   visitor->Trace(right_);
   visitor->Trace(bottom_);
diff --git a/third_party/blink/renderer/core/css/css_basic_shape_values.h b/third_party/blink/renderer/core/css/css_basic_shape_values.h
index 027d284..bfadef6 100644
--- a/third_party/blink/renderer/core/css/css_basic_shape_values.h
+++ b/third_party/blink/renderer/core/css/css_basic_shape_values.h
@@ -58,7 +58,7 @@
   void SetCenterY(CSSValue* center_y) { center_y_ = center_y; }
   void SetRadius(CSSValue* radius) { radius_ = radius; }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<CSSValue> center_x_;
@@ -84,7 +84,7 @@
   void SetRadiusX(CSSValue* radius_x) { radius_x_ = radius_x; }
   void SetRadiusY(CSSValue* radius_y) { radius_y_ = radius_y; }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<CSSValue> center_x_;
@@ -116,7 +116,7 @@
   String CustomCSSText() const;
   bool Equals(const CSSBasicShapePolygonValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   HeapVector<Member<CSSPrimitiveValue>> values_;
@@ -180,7 +180,7 @@
   String CustomCSSText() const;
   bool Equals(const CSSBasicShapeInsetValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<CSSPrimitiveValue> top_;
diff --git a/third_party/blink/renderer/core/css/css_border_image_slice_value.cc b/third_party/blink/renderer/core/css/css_border_image_slice_value.cc
index a762bde..d18e899 100644
--- a/third_party/blink/renderer/core/css/css_border_image_slice_value.cc
+++ b/third_party/blink/renderer/core/css/css_border_image_slice_value.cc
@@ -51,7 +51,8 @@
   return fill_ == other.fill_ && DataEquivalent(slices_, other.slices_);
 }
 
-void CSSBorderImageSliceValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSBorderImageSliceValue::TraceAfterDispatch(
+    blink::Visitor* visitor) const {
   visitor->Trace(slices_);
   CSSValue::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/css_border_image_slice_value.h b/third_party/blink/renderer/core/css/css_border_image_slice_value.h
index 27468d6..6438b0c 100644
--- a/third_party/blink/renderer/core/css/css_border_image_slice_value.h
+++ b/third_party/blink/renderer/core/css/css_border_image_slice_value.h
@@ -46,7 +46,7 @@
 
   bool Equals(const CSSBorderImageSliceValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   // These four values are used to make "cuts" in the border image. They can be
diff --git a/third_party/blink/renderer/core/css/css_color_value.h b/third_party/blink/renderer/core/css/css_color_value.h
index c752b9a4..2b4d16b 100644
--- a/third_party/blink/renderer/core/css/css_color_value.h
+++ b/third_party/blink/renderer/core/css/css_color_value.h
@@ -32,7 +32,7 @@
     return color_ == other.color_;
   }
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_content_distribution_value.h b/third_party/blink/renderer/core/css/css_content_distribution_value.h
index ce32858..6b96107 100644
--- a/third_party/blink/renderer/core/css/css_content_distribution_value.h
+++ b/third_party/blink/renderer/core/css/css_content_distribution_value.h
@@ -30,7 +30,7 @@
 
   bool Equals(const CSSContentDistributionValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_counter_value.cc b/third_party/blink/renderer/core/css/css_counter_value.cc
index af96186f..34f1f796 100644
--- a/third_party/blink/renderer/core/css/css_counter_value.cc
+++ b/third_party/blink/renderer/core/css/css_counter_value.cc
@@ -33,7 +33,7 @@
   return result.ToString();
 }
 
-void CSSCounterValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSCounterValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(identifier_);
   visitor->Trace(list_style_);
   visitor->Trace(separator_);
diff --git a/third_party/blink/renderer/core/css/css_counter_value.h b/third_party/blink/renderer/core/css/css_counter_value.h
index 6c5e9b68..004b5684 100644
--- a/third_party/blink/renderer/core/css/css_counter_value.h
+++ b/third_party/blink/renderer/core/css/css_counter_value.h
@@ -52,7 +52,7 @@
 
   String CustomCSSText() const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<CSSCustomIdentValue> identifier_;  // string
diff --git a/third_party/blink/renderer/core/css/css_crossfade_value.cc b/third_party/blink/renderer/core/css/css_crossfade_value.cc
index 3a8d2ea9..5051a54 100644
--- a/third_party/blink/renderer/core/css/css_crossfade_value.cc
+++ b/third_party/blink/renderer/core/css/css_crossfade_value.cc
@@ -309,7 +309,7 @@
          DataEquivalent(percentage_value_, other.percentage_value_);
 }
 
-void CSSCrossfadeValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSCrossfadeValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(from_value_);
   visitor->Trace(to_value_);
   visitor->Trace(percentage_value_);
diff --git a/third_party/blink/renderer/core/css/css_crossfade_value.h b/third_party/blink/renderer/core/css/css_crossfade_value.h
index c33d5ee..35bc6bb 100644
--- a/third_party/blink/renderer/core/css/css_crossfade_value.h
+++ b/third_party/blink/renderer/core/css/css_crossfade_value.h
@@ -71,7 +71,7 @@
   CSSCrossfadeValue* ComputedCSSValue(const ComputedStyle&,
                                       bool allow_visited_style);
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   void Dispose();
diff --git a/third_party/blink/renderer/core/css/css_cursor_image_value.cc b/third_party/blink/renderer/core/css/css_cursor_image_value.cc
index 226ba48c..913c7b6 100644
--- a/third_party/blink/renderer/core/css/css_cursor_image_value.cc
+++ b/third_party/blink/renderer/core/css/css_cursor_image_value.cc
@@ -57,7 +57,7 @@
          DataEquivalent(image_value_, other.image_value_);
 }
 
-void CSSCursorImageValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSCursorImageValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(image_value_);
   CSSValue::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/css_cursor_image_value.h b/third_party/blink/renderer/core/css/css_cursor_image_value.h
index 75107dcb..8fd4c47 100644
--- a/third_party/blink/renderer/core/css/css_cursor_image_value.h
+++ b/third_party/blink/renderer/core/css/css_cursor_image_value.h
@@ -43,7 +43,7 @@
 
   bool Equals(const CSSCursorImageValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<const CSSValue> image_value_;
diff --git a/third_party/blink/renderer/core/css/css_custom_ident_value.cc b/third_party/blink/renderer/core/css/css_custom_ident_value.cc
index 257cc01..b3927288 100644
--- a/third_party/blink/renderer/core/css/css_custom_ident_value.cc
+++ b/third_party/blink/renderer/core/css/css_custom_ident_value.cc
@@ -31,7 +31,7 @@
   return builder.ToString();
 }
 
-void CSSCustomIdentValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSCustomIdentValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   CSSValue::TraceAfterDispatch(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/css/css_custom_ident_value.h b/third_party/blink/renderer/core/css/css_custom_ident_value.h
index d60d893b..0179c94 100644
--- a/third_party/blink/renderer/core/css/css_custom_ident_value.h
+++ b/third_party/blink/renderer/core/css/css_custom_ident_value.h
@@ -36,7 +36,7 @@
                                : string_ == other.string_;
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   AtomicString string_;
diff --git a/third_party/blink/renderer/core/css/css_custom_property_declaration.cc b/third_party/blink/renderer/core/css/css_custom_property_declaration.cc
index f0e2f873..887cbed4 100644
--- a/third_party/blink/renderer/core/css/css_custom_property_declaration.cc
+++ b/third_party/blink/renderer/core/css/css_custom_property_declaration.cc
@@ -8,7 +8,8 @@
 
 namespace blink {
 
-void CSSCustomPropertyDeclaration::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSCustomPropertyDeclaration::TraceAfterDispatch(
+    blink::Visitor* visitor) const {
   CSSValue::TraceAfterDispatch(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/css/css_custom_property_declaration.h b/third_party/blink/renderer/core/css/css_custom_property_declaration.h
index 57cbd75..f7d09e70 100644
--- a/third_party/blink/renderer/core/css/css_custom_property_declaration.h
+++ b/third_party/blink/renderer/core/css/css_custom_property_declaration.h
@@ -50,7 +50,7 @@
     return this == &other;
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   const AtomicString name_;
diff --git a/third_party/blink/renderer/core/css/css_font_face_src_value.h b/third_party/blink/renderer/core/css/css_font_face_src_value.h
index bf1a1b9..b51006a 100644
--- a/third_party/blink/renderer/core/css/css_font_face_src_value.h
+++ b/third_party/blink/renderer/core/css/css_font_face_src_value.h
@@ -91,7 +91,7 @@
 
   bool Equals(const CSSFontFaceSrcValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     visitor->Trace(fetched_);
     CSSValue::TraceAfterDispatch(visitor);
   }
diff --git a/third_party/blink/renderer/core/css/css_font_family_value.cc b/third_party/blink/renderer/core/css/css_font_family_value.cc
index 99e4f96..e503a32 100644
--- a/third_party/blink/renderer/core/css/css_font_family_value.cc
+++ b/third_party/blink/renderer/core/css/css_font_family_value.cc
@@ -29,7 +29,7 @@
   return SerializeFontFamily(string_);
 }
 
-void CSSFontFamilyValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSFontFamilyValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   CSSValue::TraceAfterDispatch(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/css/css_font_family_value.h b/third_party/blink/renderer/core/css/css_font_family_value.h
index 197efeb0..0b2dfb0 100644
--- a/third_party/blink/renderer/core/css/css_font_family_value.h
+++ b/third_party/blink/renderer/core/css/css_font_family_value.h
@@ -25,7 +25,7 @@
     return string_ == other.string_;
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   friend class CSSValuePool;
diff --git a/third_party/blink/renderer/core/css/css_font_feature_value.h b/third_party/blink/renderer/core/css/css_font_feature_value.h
index aa7db9ed..3eb15cd 100644
--- a/third_party/blink/renderer/core/css/css_font_feature_value.h
+++ b/third_party/blink/renderer/core/css/css_font_feature_value.h
@@ -42,7 +42,7 @@
 
   bool Equals(const CSSFontFeatureValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_font_style_range_value.cc b/third_party/blink/renderer/core/css/css_font_style_range_value.cc
index 17e54db..336ac79 100644
--- a/third_party/blink/renderer/core/css/css_font_style_range_value.cc
+++ b/third_party/blink/renderer/core/css/css_font_style_range_value.cc
@@ -48,7 +48,7 @@
          *oblique_values_ == *other.oblique_values_;
 }
 
-void CSSFontStyleRangeValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSFontStyleRangeValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(font_style_value_);
   visitor->Trace(oblique_values_);
   CSSValue::TraceAfterDispatch(visitor);
diff --git a/third_party/blink/renderer/core/css/css_font_style_range_value.h b/third_party/blink/renderer/core/css/css_font_style_range_value.h
index 0427b30..a68c764 100644
--- a/third_party/blink/renderer/core/css/css_font_style_range_value.h
+++ b/third_party/blink/renderer/core/css/css_font_style_range_value.h
@@ -55,7 +55,7 @@
 
   bool Equals(const CSSFontStyleRangeValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<const CSSIdentifierValue> font_style_value_;
diff --git a/third_party/blink/renderer/core/css/css_font_variation_value.h b/third_party/blink/renderer/core/css/css_font_variation_value.h
index bce8e1dd..5488e51 100644
--- a/third_party/blink/renderer/core/css/css_font_variation_value.h
+++ b/third_party/blink/renderer/core/css/css_font_variation_value.h
@@ -22,7 +22,7 @@
 
   bool Equals(const CSSFontVariationValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_function_value.h b/third_party/blink/renderer/core/css/css_function_value.h
index 2ecd2e5..2ce074cb 100644
--- a/third_party/blink/renderer/core/css/css_function_value.h
+++ b/third_party/blink/renderer/core/css/css_function_value.h
@@ -23,7 +23,7 @@
   }
   CSSValueID FunctionType() const { return value_id_; }
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValueList::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_gradient_value.cc b/third_party/blink/renderer/core/css/css_gradient_value.cc
index fc11775..14af20c 100644
--- a/third_party/blink/renderer/core/css/css_gradient_value.cc
+++ b/third_party/blink/renderer/core/css/css_gradient_value.cc
@@ -775,7 +775,7 @@
   return stop_colors;
 }
 
-void CSSGradientValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSGradientValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(stops_);
   CSSImageGeneratorValue::TraceAfterDispatch(visitor);
 }
@@ -1051,7 +1051,7 @@
   return result;
 }
 
-void CSSLinearGradientValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSLinearGradientValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(first_x_);
   visitor->Trace(first_y_);
   visitor->Trace(second_x_);
@@ -1448,7 +1448,7 @@
   return result;
 }
 
-void CSSRadialGradientValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSRadialGradientValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(first_x_);
   visitor->Trace(first_y_);
   visitor->Trace(second_x_);
@@ -1528,7 +1528,7 @@
   return result;
 }
 
-void CSSConicGradientValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSConicGradientValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(x_);
   visitor->Trace(y_);
   visitor->Trace(from_angle_);
diff --git a/third_party/blink/renderer/core/css/css_gradient_value.h b/third_party/blink/renderer/core/css/css_gradient_value.h
index 9bb0b9f..54f88df9 100644
--- a/third_party/blink/renderer/core/css/css_gradient_value.h
+++ b/third_party/blink/renderer/core/css/css_gradient_value.h
@@ -119,7 +119,7 @@
 
   Vector<Color> GetStopColors(const Document&, const ComputedStyle&) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
   struct GradientDesc;
 
@@ -183,7 +183,7 @@
   CSSLinearGradientValue* ComputedCSSValue(const ComputedStyle&,
                                            bool allow_visited_style);
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   // Any of these may be null.
@@ -281,7 +281,7 @@
   CSSRadialGradientValue* ComputedCSSValue(const ComputedStyle&,
                                            bool allow_visited_style);
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   // Any of these may be null.
@@ -327,7 +327,7 @@
   CSSConicGradientValue* ComputedCSSValue(const ComputedStyle&,
                                           bool allow_visited_style);
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   // Any of these may be null.
diff --git a/third_party/blink/renderer/core/css/css_grid_auto_repeat_value.h b/third_party/blink/renderer/core/css/css_grid_auto_repeat_value.h
index 93f2f90..44dd00b 100644
--- a/third_party/blink/renderer/core/css/css_grid_auto_repeat_value.h
+++ b/third_party/blink/renderer/core/css/css_grid_auto_repeat_value.h
@@ -38,7 +38,7 @@
 
   CSSValueID AutoRepeatID() const { return auto_repeat_id_; }
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValueList::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_grid_integer_repeat_value.h b/third_party/blink/renderer/core/css/css_grid_integer_repeat_value.h
index 0e86202..e7b5c8b 100644
--- a/third_party/blink/renderer/core/css/css_grid_integer_repeat_value.h
+++ b/third_party/blink/renderer/core/css/css_grid_integer_repeat_value.h
@@ -33,7 +33,7 @@
 
   size_t Repetitions() const { return repetitions_; }
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValueList::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_grid_line_names_value.h b/third_party/blink/renderer/core/css/css_grid_line_names_value.h
index 8fa3bfb..2986ed8 100644
--- a/third_party/blink/renderer/core/css/css_grid_line_names_value.h
+++ b/third_party/blink/renderer/core/css/css_grid_line_names_value.h
@@ -43,7 +43,7 @@
 
   String CustomCSSText() const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValueList::TraceAfterDispatch(visitor);
   }
 };
diff --git a/third_party/blink/renderer/core/css/css_grid_template_areas_value.h b/third_party/blink/renderer/core/css/css_grid_template_areas_value.h
index a847793..69e7b35f 100644
--- a/third_party/blink/renderer/core/css/css_grid_template_areas_value.h
+++ b/third_party/blink/renderer/core/css/css_grid_template_areas_value.h
@@ -54,7 +54,7 @@
 
   bool Equals(const CSSGridTemplateAreasValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_identifier_value.cc b/third_party/blink/renderer/core/css/css_identifier_value.cc
index 92099bf..0498875 100644
--- a/third_party/blink/renderer/core/css/css_identifier_value.cc
+++ b/third_party/blink/renderer/core/css/css_identifier_value.cc
@@ -63,7 +63,7 @@
   }
 }
 
-void CSSIdentifierValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSIdentifierValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   CSSValue::TraceAfterDispatch(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/css/css_identifier_value.h b/third_party/blink/renderer/core/css/css_identifier_value.h
index 669b9a4..585a5ec 100644
--- a/third_party/blink/renderer/core/css/css_identifier_value.h
+++ b/third_party/blink/renderer/core/css/css_identifier_value.h
@@ -59,7 +59,7 @@
     return CssValueIDToPlatformEnum<T>(value_id_);
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   CSSValueID value_id_;
diff --git a/third_party/blink/renderer/core/css/css_image_generator_value.h b/third_party/blink/renderer/core/css/css_image_generator_value.h
index 9418049..2d6f9b3 100644
--- a/third_party/blink/renderer/core/css/css_image_generator_value.h
+++ b/third_party/blink/renderer/core/css/css_image_generator_value.h
@@ -108,7 +108,7 @@
   bool IsUsingCustomProperty(const AtomicString& custom_property_name,
                              const Document&) const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_image_set_value.cc b/third_party/blink/renderer/core/css/css_image_set_value.cc
index 7bbe52d..6a9f23a 100644
--- a/third_party/blink/renderer/core/css/css_image_set_value.cc
+++ b/third_party/blink/renderer/core/css/css_image_set_value.cc
@@ -174,7 +174,7 @@
   return true;
 }
 
-void CSSImageSetValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSImageSetValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(cached_image_);
   CSSValueList::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/css_image_set_value.h b/third_party/blink/renderer/core/css/css_image_set_value.h
index de879b89..846cebf 100644
--- a/third_party/blink/renderer/core/css/css_image_set_value.h
+++ b/third_party/blink/renderer/core/css/css_image_set_value.h
@@ -65,7 +65,7 @@
 
   bool HasFailedOrCanceledSubresources() const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  protected:
   ImageWithScale BestImageForScaleFactor(float scale_factor);
diff --git a/third_party/blink/renderer/core/css/css_image_value.cc b/third_party/blink/renderer/core/css/css_image_value.cc
index 0bce383..118e117 100644
--- a/third_party/blink/renderer/core/css/css_image_value.cc
+++ b/third_party/blink/renderer/core/css/css_image_value.cc
@@ -150,7 +150,7 @@
                        : false;
 }
 
-void CSSImageValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSImageValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(cached_image_);
   CSSValue::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/css_image_value.h b/third_party/blink/renderer/core/css/css_image_value.h
index 25bc88e5..39469ee 100644
--- a/third_party/blink/renderer/core/css/css_image_value.h
+++ b/third_party/blink/renderer/core/css/css_image_value.h
@@ -86,7 +86,7 @@
 
   void SetInitiator(const AtomicString& name) { initiator_name_ = name; }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
   void RestoreCachedResourceIfNeeded(const Document&) const;
 
  private:
diff --git a/third_party/blink/renderer/core/css/css_inherited_value.h b/third_party/blink/renderer/core/css/css_inherited_value.h
index e9d9352..b249c963 100644
--- a/third_party/blink/renderer/core/css/css_inherited_value.h
+++ b/third_party/blink/renderer/core/css/css_inherited_value.h
@@ -39,7 +39,7 @@
 
   bool Equals(const CSSInheritedValue&) const { return true; }
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_initial_value.h b/third_party/blink/renderer/core/css/css_initial_value.h
index ef3e4194..0e2aebd 100644
--- a/third_party/blink/renderer/core/css/css_initial_value.h
+++ b/third_party/blink/renderer/core/css/css_initial_value.h
@@ -38,7 +38,7 @@
 
   bool Equals(const CSSInitialValue&) const { return true; }
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_invalid_variable_value.h b/third_party/blink/renderer/core/css/css_invalid_variable_value.h
index 504244f..eb40dcb 100644
--- a/third_party/blink/renderer/core/css/css_invalid_variable_value.h
+++ b/third_party/blink/renderer/core/css/css_invalid_variable_value.h
@@ -23,7 +23,7 @@
 
   bool Equals(const CSSInvalidVariableValue&) const { return true; }
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.cc b/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.cc
index 7311cb6..6196eef 100644
--- a/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.cc
+++ b/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.cc
@@ -43,7 +43,8 @@
   return properties_->GetPropertyValue(shorthand_);
 }
 
-void CSSKeyframeShorthandValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSKeyframeShorthandValue::TraceAfterDispatch(
+    blink::Visitor* visitor) const {
   visitor->Trace(properties_);
   CSSValue::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.h b/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.h
index 43f849f4..d1ea4002 100644
--- a/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.h
+++ b/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.h
@@ -45,7 +45,7 @@
     return shorthand_ == other.shorthand_ && properties_ == other.properties_;
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   // The shorthand property that these longhands belonged to.
diff --git a/third_party/blink/renderer/core/css/css_keyframes_rule.cc b/third_party/blink/renderer/core/css/css_keyframes_rule.cc
index f476c80..734b890 100644
--- a/third_party/blink/renderer/core/css/css_keyframes_rule.cc
+++ b/third_party/blink/renderer/core/css/css_keyframes_rule.cc
@@ -74,7 +74,7 @@
   return -1;
 }
 
-void StyleRuleKeyframes::TraceAfterDispatch(blink::Visitor* visitor) {
+void StyleRuleKeyframes::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(keyframes_);
   StyleRuleBase::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/css_keyframes_rule.h b/third_party/blink/renderer/core/css/css_keyframes_rule.h
index 491bed2f..4313c2e 100644
--- a/third_party/blink/renderer/core/css/css_keyframes_rule.h
+++ b/third_party/blink/renderer/core/css/css_keyframes_rule.h
@@ -64,7 +64,7 @@
     return MakeGarbageCollected<StyleRuleKeyframes>(*this);
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
   void StyleChanged() { version_++; }
   unsigned Version() const { return version_; }
diff --git a/third_party/blink/renderer/core/css/css_layout_function_value.cc b/third_party/blink/renderer/core/css/css_layout_function_value.cc
index 3bc61f11..ada6eaf 100644
--- a/third_party/blink/renderer/core/css/css_layout_function_value.cc
+++ b/third_party/blink/renderer/core/css/css_layout_function_value.cc
@@ -32,7 +32,7 @@
   return GetName() == other.GetName() && IsInline() == other.IsInline();
 }
 
-void CSSLayoutFunctionValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSLayoutFunctionValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(name_);
   CSSValue::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/css_layout_function_value.h b/third_party/blink/renderer/core/css/css_layout_function_value.h
index 13f55aae..853110df 100644
--- a/third_party/blink/renderer/core/css/css_layout_function_value.h
+++ b/third_party/blink/renderer/core/css/css_layout_function_value.h
@@ -25,7 +25,7 @@
   bool IsInline() const { return is_inline_; }
 
   bool Equals(const CSSLayoutFunctionValue&) const;
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<CSSCustomIdentValue> name_;
diff --git a/third_party/blink/renderer/core/css/css_light_dark_color_pair.h b/third_party/blink/renderer/core/css/css_light_dark_color_pair.h
index 84737f5..994dc1b 100644
--- a/third_party/blink/renderer/core/css/css_light_dark_color_pair.h
+++ b/third_party/blink/renderer/core/css/css_light_dark_color_pair.h
@@ -14,7 +14,7 @@
   CSSLightDarkColorPair(const CSSValue* first, const CSSValue* second)
       : CSSValuePair(kLightDarkColorPairClass, first, second) {}
   String CustomCSSText() const;
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValuePair::TraceAfterDispatch(visitor);
   }
 };
diff --git a/third_party/blink/renderer/core/css/css_math_function_value.cc b/third_party/blink/renderer/core/css/css_math_function_value.cc
index 49986ea..7df858d 100644
--- a/third_party/blink/renderer/core/css/css_math_function_value.cc
+++ b/third_party/blink/renderer/core/css/css_math_function_value.cc
@@ -17,7 +17,7 @@
 };
 ASSERT_SIZE(CSSMathFunctionValue, SameSizeAsCSSMathFunctionValue);
 
-void CSSMathFunctionValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSMathFunctionValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(expression_);
   CSSPrimitiveValue::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/css_math_function_value.h b/third_party/blink/renderer/core/css/css_math_function_value.h
index b96b5c9..b098693a 100644
--- a/third_party/blink/renderer/core/css/css_math_function_value.h
+++ b/third_party/blink/renderer/core/css/css_math_function_value.h
@@ -90,7 +90,7 @@
 
   bool HasComparisons() const { return expression_->HasComparisons(); }
 
-  void TraceAfterDispatch(blink::Visitor* visitor);
+  void TraceAfterDispatch(blink::Visitor* visitor) const;
 
  private:
   bool IsNonNegative() const { return is_non_negative_math_function_; }
diff --git a/third_party/blink/renderer/core/css/css_numeric_literal_value.cc b/third_party/blink/renderer/core/css/css_numeric_literal_value.cc
index 368f079..41e34de 100644
--- a/third_party/blink/renderer/core/css/css_numeric_literal_value.cc
+++ b/third_party/blink/renderer/core/css/css_numeric_literal_value.cc
@@ -17,7 +17,7 @@
 };
 ASSERT_SIZE(CSSNumericLiteralValue, SameSizeAsCSSNumericLiteralValue);
 
-void CSSNumericLiteralValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSNumericLiteralValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   CSSPrimitiveValue::TraceAfterDispatch(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/css/css_numeric_literal_value.h b/third_party/blink/renderer/core/css/css_numeric_literal_value.h
index 64087343..d07cbea 100644
--- a/third_party/blink/renderer/core/css/css_numeric_literal_value.h
+++ b/third_party/blink/renderer/core/css/css_numeric_literal_value.h
@@ -63,7 +63,7 @@
   String CustomCSSText() const;
   bool Equals(const CSSNumericLiteralValue& other) const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor);
+  void TraceAfterDispatch(blink::Visitor* visitor) const;
 
  private:
   double num_;
diff --git a/third_party/blink/renderer/core/css/css_paint_value.cc b/third_party/blink/renderer/core/css/css_paint_value.cc
index 5747f01..0a997825 100644
--- a/third_party/blink/renderer/core/css/css_paint_value.cc
+++ b/third_party/blink/renderer/core/css/css_paint_value.cc
@@ -249,7 +249,7 @@
          CustomCSSText() == other.CustomCSSText();
 }
 
-void CSSPaintValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSPaintValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(name_);
   visitor->Trace(generators_);
   visitor->Trace(paint_image_generator_observer_);
diff --git a/third_party/blink/renderer/core/css/css_paint_value.h b/third_party/blink/renderer/core/css/css_paint_value.h
index a54d08c..06d1cced 100644
--- a/third_party/blink/renderer/core/css/css_paint_value.h
+++ b/third_party/blink/renderer/core/css/css_paint_value.h
@@ -71,7 +71,7 @@
   }
   unsigned NumberOfGeneratorsForTesting() const { return generators_.size(); }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   class Observer final : public CSSPaintImageGenerator::Observer {
diff --git a/third_party/blink/renderer/core/css/css_path_value.cc b/third_party/blink/renderer/core/css/css_path_value.cc
index 9e8982f..3c1ae0b 100644
--- a/third_party/blink/renderer/core/css/css_path_value.cc
+++ b/third_party/blink/renderer/core/css/css_path_value.cc
@@ -53,7 +53,7 @@
   return ByteStream() == other.ByteStream();
 }
 
-void CSSPathValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSPathValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   CSSValue::TraceAfterDispatch(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/css/css_path_value.h b/third_party/blink/renderer/core/css/css_path_value.h
index 6ff0f908..ee3d8128 100644
--- a/third_party/blink/renderer/core/css/css_path_value.h
+++ b/third_party/blink/renderer/core/css/css_path_value.h
@@ -33,7 +33,7 @@
 
   bool Equals(const CSSPathValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
   const SVGPathByteStream& ByteStream() const {
     return style_path_->ByteStream();
diff --git a/third_party/blink/renderer/core/css/css_pending_substitution_value.cc b/third_party/blink/renderer/core/css/css_pending_substitution_value.cc
index c2a141e..40bf775 100644
--- a/third_party/blink/renderer/core/css/css_pending_substitution_value.cc
+++ b/third_party/blink/renderer/core/css/css_pending_substitution_value.cc
@@ -7,7 +7,8 @@
 namespace blink {
 namespace cssvalue {
 
-void CSSPendingSubstitutionValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSPendingSubstitutionValue::TraceAfterDispatch(
+    blink::Visitor* visitor) const {
   CSSValue::TraceAfterDispatch(visitor);
   visitor->Trace(shorthand_value_);
 }
diff --git a/third_party/blink/renderer/core/css/css_pending_substitution_value.h b/third_party/blink/renderer/core/css/css_pending_substitution_value.h
index b5931b6..7af6162 100644
--- a/third_party/blink/renderer/core/css/css_pending_substitution_value.h
+++ b/third_party/blink/renderer/core/css/css_pending_substitution_value.h
@@ -32,7 +32,7 @@
   }
   String CustomCSSText() const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   CSSPropertyID shorthand_property_id_;
diff --git a/third_party/blink/renderer/core/css/css_primitive_value.cc b/third_party/blink/renderer/core/css/css_primitive_value.cc
index 62f9867..0c63781 100644
--- a/third_party/blink/renderer/core/css/css_primitive_value.cc
+++ b/third_party/blink/renderer/core/css/css_primitive_value.cc
@@ -553,7 +553,7 @@
   return To<CSSNumericLiteralValue>(this)->CustomCSSText();
 }
 
-void CSSPrimitiveValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSPrimitiveValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   CSSValue::TraceAfterDispatch(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/css/css_primitive_value.h b/third_party/blink/renderer/core/css/css_primitive_value.h
index b1eb9251..2cfe667 100644
--- a/third_party/blink/renderer/core/css/css_primitive_value.h
+++ b/third_party/blink/renderer/core/css/css_primitive_value.h
@@ -248,7 +248,7 @@
 
   String CustomCSSText() const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
   static UnitType CanonicalUnitTypeForCategory(UnitCategory);
   static double ConversionToCanonicalUnitsScaleFactor(UnitType);
diff --git a/third_party/blink/renderer/core/css/css_property_value_set.cc b/third_party/blink/renderer/core/css/css_property_value_set.cc
index fc32d31..e1488e5 100644
--- a/third_party/blink/renderer/core/css/css_property_value_set.cc
+++ b/third_party/blink/renderer/core/css/css_property_value_set.cc
@@ -169,7 +169,8 @@
 template CORE_EXPORT int ImmutableCSSPropertyValueSet::FindPropertyIndex(
     AtRuleDescriptorID) const;
 
-void ImmutableCSSPropertyValueSet::TraceAfterDispatch(blink::Visitor* visitor) {
+void ImmutableCSSPropertyValueSet::TraceAfterDispatch(
+    blink::Visitor* visitor) const {
   const Member<const CSSValue>* values = ValueArray();
   for (unsigned i = 0; i < array_size_; i++)
     visitor->Trace(values[i]);
@@ -637,7 +638,8 @@
 template CORE_EXPORT int MutableCSSPropertyValueSet::FindPropertyIndex(
     AtomicString) const;
 
-void MutableCSSPropertyValueSet::TraceAfterDispatch(blink::Visitor* visitor) {
+void MutableCSSPropertyValueSet::TraceAfterDispatch(
+    blink::Visitor* visitor) const {
   visitor->Trace(cssom_wrapper_);
   visitor->Trace(property_vector_);
   CSSPropertyValueSet::TraceAfterDispatch(visitor);
diff --git a/third_party/blink/renderer/core/css/css_property_value_set.h b/third_party/blink/renderer/core/css/css_property_value_set.h
index dcf8b45..44bcdab 100644
--- a/third_party/blink/renderer/core/css/css_property_value_set.h
+++ b/third_party/blink/renderer/core/css/css_property_value_set.h
@@ -139,7 +139,7 @@
   bool PropertyMatches(CSSPropertyID, const CSSValue&) const;
 
   void Trace(Visitor*);
-  void TraceAfterDispatch(blink::Visitor* visitor) {}
+  void TraceAfterDispatch(blink::Visitor* visitor) const {}
 
  protected:
   enum { kMaxArraySize = (1 << 28) - 1 };
@@ -195,7 +195,7 @@
   template <typename T>  // CSSPropertyID or AtomicString
   int FindPropertyIndex(T property) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 };
 
 inline const Member<const CSSValue>* ImmutableCSSPropertyValueSet::ValueArray()
@@ -283,7 +283,7 @@
   template <typename T>  // CSSPropertyID or AtomicString
   int FindPropertyIndex(T property) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   bool RemovePropertyAtIndex(int, String* return_text);
diff --git a/third_party/blink/renderer/core/css/css_quad_value.cc b/third_party/blink/renderer/core/css/css_quad_value.cc
index 21a7c60..5ecec7e 100644
--- a/third_party/blink/renderer/core/css/css_quad_value.cc
+++ b/third_party/blink/renderer/core/css/css_quad_value.cc
@@ -37,7 +37,7 @@
   return result.ToString();
 }
 
-void CSSQuadValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSQuadValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(top_);
   visitor->Trace(right_);
   visitor->Trace(bottom_);
diff --git a/third_party/blink/renderer/core/css/css_quad_value.h b/third_party/blink/renderer/core/css/css_quad_value.h
index 7f02d75..711b74b 100644
--- a/third_party/blink/renderer/core/css/css_quad_value.h
+++ b/third_party/blink/renderer/core/css/css_quad_value.h
@@ -68,7 +68,7 @@
            DataEquivalent(bottom_, other.bottom_);
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   TypeForSerialization serialization_type_;
diff --git a/third_party/blink/renderer/core/css/css_ray_value.cc b/third_party/blink/renderer/core/css/css_ray_value.cc
index d54da15..65fac43 100644
--- a/third_party/blink/renderer/core/css/css_ray_value.cc
+++ b/third_party/blink/renderer/core/css/css_ray_value.cc
@@ -36,7 +36,7 @@
          DataEquivalent(contain_, other.contain_);
 }
 
-void CSSRayValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSRayValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(angle_);
   visitor->Trace(size_);
   visitor->Trace(contain_);
diff --git a/third_party/blink/renderer/core/css/css_ray_value.h b/third_party/blink/renderer/core/css/css_ray_value.h
index dd642cd77..ea27a1d 100644
--- a/third_party/blink/renderer/core/css/css_ray_value.h
+++ b/third_party/blink/renderer/core/css/css_ray_value.h
@@ -29,7 +29,7 @@
 
   bool Equals(const CSSRayValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<const CSSPrimitiveValue> angle_;
diff --git a/third_party/blink/renderer/core/css/css_reflect_value.cc b/third_party/blink/renderer/core/css/css_reflect_value.cc
index 7974b236..4d92d42 100644
--- a/third_party/blink/renderer/core/css/css_reflect_value.cc
+++ b/third_party/blink/renderer/core/css/css_reflect_value.cc
@@ -44,7 +44,7 @@
          DataEquivalent(mask_, other.mask_);
 }
 
-void CSSReflectValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSReflectValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(direction_);
   visitor->Trace(offset_);
   visitor->Trace(mask_);
diff --git a/third_party/blink/renderer/core/css/css_reflect_value.h b/third_party/blink/renderer/core/css/css_reflect_value.h
index 50e206b..1b6e5749 100644
--- a/third_party/blink/renderer/core/css/css_reflect_value.h
+++ b/third_party/blink/renderer/core/css/css_reflect_value.h
@@ -55,7 +55,7 @@
 
   bool Equals(const CSSReflectValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<CSSIdentifierValue> direction_;
diff --git a/third_party/blink/renderer/core/css/css_shadow_value.cc b/third_party/blink/renderer/core/css/css_shadow_value.cc
index 49972ca..5ac6c6b4 100644
--- a/third_party/blink/renderer/core/css/css_shadow_value.cc
+++ b/third_party/blink/renderer/core/css/css_shadow_value.cc
@@ -77,7 +77,7 @@
          DataEquivalent(style, other.style);
 }
 
-void CSSShadowValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSShadowValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(x);
   visitor->Trace(y);
   visitor->Trace(blur);
diff --git a/third_party/blink/renderer/core/css/css_shadow_value.h b/third_party/blink/renderer/core/css/css_shadow_value.h
index 34223f84..9cd0d95 100644
--- a/third_party/blink/renderer/core/css/css_shadow_value.h
+++ b/third_party/blink/renderer/core/css/css_shadow_value.h
@@ -52,7 +52,7 @@
   Member<CSSIdentifierValue> style;
   Member<CSSValue> color;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/css_string_value.cc b/third_party/blink/renderer/core/css/css_string_value.cc
index b988113..3795a10 100644
--- a/third_party/blink/renderer/core/css/css_string_value.cc
+++ b/third_party/blink/renderer/core/css/css_string_value.cc
@@ -16,7 +16,7 @@
   return SerializeString(string_);
 }
 
-void CSSStringValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSStringValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   CSSValue::TraceAfterDispatch(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/css/css_string_value.h b/third_party/blink/renderer/core/css/css_string_value.h
index b2b71ee..2641f37 100644
--- a/third_party/blink/renderer/core/css/css_string_value.h
+++ b/third_party/blink/renderer/core/css/css_string_value.h
@@ -23,7 +23,7 @@
     return string_ == other.string_;
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   String string_;
diff --git a/third_party/blink/renderer/core/css/css_timing_function_value.h b/third_party/blink/renderer/core/css/css_timing_function_value.h
index 795abd9..26e09608 100644
--- a/third_party/blink/renderer/core/css/css_timing_function_value.h
+++ b/third_party/blink/renderer/core/css/css_timing_function_value.h
@@ -53,7 +53,7 @@
 
   bool Equals(const CSSCubicBezierTimingFunctionValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
@@ -81,7 +81,7 @@
 
   bool Equals(const CSSStepsTimingFunctionValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_unicode_range_value.h b/third_party/blink/renderer/core/css/css_unicode_range_value.h
index 506c080..4db1508 100644
--- a/third_party/blink/renderer/core/css/css_unicode_range_value.h
+++ b/third_party/blink/renderer/core/css/css_unicode_range_value.h
@@ -45,7 +45,7 @@
 
   bool Equals(const CSSUnicodeRangeValue&) const;
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/css_unset_value.h b/third_party/blink/renderer/core/css/css_unset_value.h
index 91f23e06..dfbf7a8 100644
--- a/third_party/blink/renderer/core/css/css_unset_value.h
+++ b/third_party/blink/renderer/core/css/css_unset_value.h
@@ -26,7 +26,7 @@
 
   bool Equals(const CSSUnsetValue&) const { return true; }
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValue::TraceAfterDispatch(visitor);
   }
 };
diff --git a/third_party/blink/renderer/core/css/css_uri_value.cc b/third_party/blink/renderer/core/css/css_uri_value.cc
index 242c7273..ce4e757 100644
--- a/third_party/blink/renderer/core/css/css_uri_value.cc
+++ b/third_party/blink/renderer/core/css/css_uri_value.cc
@@ -83,7 +83,7 @@
       AtomicString(KURL(base_url, relative_url_, charset).GetString()));
 }
 
-void CSSURIValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSURIValue::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(resource_);
   CSSValue::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/css_uri_value.h b/third_party/blink/renderer/core/css/css_uri_value.h
index 3999c76..909779e 100644
--- a/third_party/blink/renderer/core/css/css_uri_value.h
+++ b/third_party/blink/renderer/core/css/css_uri_value.h
@@ -43,7 +43,7 @@
   CSSURIValue* ValueWithURLMadeAbsolute(const KURL& base_url,
                                         const WTF::TextEncoding&) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   KURL AbsoluteUrl() const;
diff --git a/third_party/blink/renderer/core/css/css_value.h b/third_party/blink/renderer/core/css/css_value.h
index f37fe6e82..29eac1b6 100644
--- a/third_party/blink/renderer/core/css/css_value.h
+++ b/third_party/blink/renderer/core/css/css_value.h
@@ -180,7 +180,7 @@
   bool operator==(const CSSValue&) const;
 
   void FinalizeGarbageCollectedObject();
-  void TraceAfterDispatch(blink::Visitor* visitor) {}
+  void TraceAfterDispatch(blink::Visitor* visitor) const {}
   void Trace(Visitor*);
 
   // ~CSSValue should be public, because non-public ~CSSValue causes C2248
diff --git a/third_party/blink/renderer/core/css/css_value_list.cc b/third_party/blink/renderer/core/css/css_value_list.cc
index 1e02ae2..fb8bf147 100644
--- a/third_party/blink/renderer/core/css/css_value_list.cc
+++ b/third_party/blink/renderer/core/css/css_value_list.cc
@@ -138,7 +138,7 @@
     value->ReResolveUrl(document);
 }
 
-void CSSValueList::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSValueList::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(values_);
   CSSValue::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/css_value_list.h b/third_party/blink/renderer/core/css/css_value_list.h
index 6883efc..eb63aff 100644
--- a/third_party/blink/renderer/core/css/css_value_list.h
+++ b/third_party/blink/renderer/core/css/css_value_list.h
@@ -75,7 +75,7 @@
   bool MayContainUrl() const;
   void ReResolveUrl(const Document&) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   HeapVector<Member<const CSSValue>, 4> values_;
diff --git a/third_party/blink/renderer/core/css/css_value_pair.cc b/third_party/blink/renderer/core/css/css_value_pair.cc
index 3ca5de6b..820b646 100644
--- a/third_party/blink/renderer/core/css/css_value_pair.cc
+++ b/third_party/blink/renderer/core/css/css_value_pair.cc
@@ -6,7 +6,7 @@
 
 namespace blink {
 
-void CSSValuePair::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSValuePair::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(first_);
   visitor->Trace(second_);
   CSSValue::TraceAfterDispatch(visitor);
diff --git a/third_party/blink/renderer/core/css/css_value_pair.h b/third_party/blink/renderer/core/css/css_value_pair.h
index 2cd6b476..db0081bf 100644
--- a/third_party/blink/renderer/core/css/css_value_pair.h
+++ b/third_party/blink/renderer/core/css/css_value_pair.h
@@ -64,7 +64,7 @@
            DataEquivalent(second_, other.second_);
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  protected:
   CSSValuePair(ClassType class_type,
diff --git a/third_party/blink/renderer/core/css/css_variable_reference_value.cc b/third_party/blink/renderer/core/css/css_variable_reference_value.cc
index 859749f..699091f0 100644
--- a/third_party/blink/renderer/core/css/css_variable_reference_value.cc
+++ b/third_party/blink/renderer/core/css/css_variable_reference_value.cc
@@ -6,7 +6,8 @@
 
 namespace blink {
 
-void CSSVariableReferenceValue::TraceAfterDispatch(blink::Visitor* visitor) {
+void CSSVariableReferenceValue::TraceAfterDispatch(
+    blink::Visitor* visitor) const {
   CSSValue::TraceAfterDispatch(visitor);
   visitor->Trace(parser_context_);
 }
diff --git a/third_party/blink/renderer/core/css/css_variable_reference_value.h b/third_party/blink/renderer/core/css/css_variable_reference_value.h
index 7ec8b2d..d3dcf20 100644
--- a/third_party/blink/renderer/core/css/css_variable_reference_value.h
+++ b/third_party/blink/renderer/core/css/css_variable_reference_value.h
@@ -38,7 +38,7 @@
   }
   String CustomCSSText() const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   scoped_refptr<CSSVariableData> data_;
diff --git a/third_party/blink/renderer/core/css/media_query_evaluator.cc b/third_party/blink/renderer/core/css/media_query_evaluator.cc
index 275dc47..b6863df 100644
--- a/third_party/blink/renderer/core/css/media_query_evaluator.cc
+++ b/third_party/blink/renderer/core/css/media_query_evaluator.cc
@@ -158,6 +158,15 @@
   return result;
 }
 
+bool MediaQueryEvaluator::DidResultsChange(
+    const MediaQueryResultList& results) const {
+  for (auto& result : results) {
+    if (Eval(result.Expression()) != result.Result())
+      return true;
+  }
+  return false;
+}
+
 template <typename T>
 bool CompareValue(T a, T b, MediaFeaturePrefix op) {
   switch (op) {
diff --git a/third_party/blink/renderer/core/css/media_query_evaluator.h b/third_party/blink/renderer/core/css/media_query_evaluator.h
index 57d6a8a..2d9e016d 100644
--- a/third_party/blink/renderer/core/css/media_query_evaluator.h
+++ b/third_party/blink/renderer/core/css/media_query_evaluator.h
@@ -94,6 +94,10 @@
   // Evaluates media query subexpression, ie "and (media-feature: value)" part.
   bool Eval(const MediaQueryExp&) const;
 
+  // Returns true if any of the expressions in the results lists changed its
+  // evaluation.
+  bool DidResultsChange(const MediaQueryResultList& results) const;
+
   void Trace(Visitor*);
 
  private:
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index 7293a782..1605896 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -737,6 +737,8 @@
   viewport_style->SetOverflowX(EOverflow::kAuto);
   viewport_style->SetOverflowY(EOverflow::kAuto);
 
+  document.GetStyleEngine().ApplyVisionDeficiencyStyle(viewport_style);
+
   return viewport_style;
 }
 
diff --git a/third_party/blink/renderer/core/css/resolver/viewport_style_resolver.cc b/third_party/blink/renderer/core/css/resolver/viewport_style_resolver.cc
index 797836e0..e7a0dde 100644
--- a/third_party/blink/renderer/core/css/resolver/viewport_style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/viewport_style_resolver.cc
@@ -358,13 +358,9 @@
   if (has_viewport_units_)
     needs_update_ = kResolve;
 
-  auto& results = viewport_dependent_media_query_results_;
-  for (unsigned i = 0; i < results.size(); i++) {
-    if (initial_viewport_medium_->Eval(results[i].Expression()) !=
-        results[i].Result()) {
-      needs_update_ = kCollectRules;
-      break;
-    }
+  if (initial_viewport_medium_->DidResultsChange(
+          viewport_dependent_media_query_results_)) {
+    needs_update_ = kCollectRules;
   }
   if (needs_update_ == kNoUpdate)
     return;
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index fd300fb..5ab331c8 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -36,6 +36,7 @@
 #include "third_party/blink/renderer/core/css/css_font_selector.h"
 #include "third_party/blink/renderer/core/css/css_identifier_value.h"
 #include "third_party/blink/renderer/core/css/css_style_sheet.h"
+#include "third_party/blink/renderer/core/css/css_uri_value.h"
 #include "third_party/blink/renderer/core/css/css_value_list.h"
 #include "third_party/blink/renderer/core/css/document_style_environment_variables.h"
 #include "third_party/blink/renderer/core/css/document_style_sheet_collector.h"
@@ -78,7 +79,9 @@
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/core/style/filter_operations.h"
 #include "third_party/blink/renderer/core/style/style_initial_data.h"
+#include "third_party/blink/renderer/core/svg/svg_resource.h"
 #include "third_party/blink/renderer/core/svg/svg_style_element.h"
 #include "third_party/blink/renderer/platform/fonts/font_cache.h"
 #include "third_party/blink/renderer/platform/fonts/font_selector.h"
@@ -1626,6 +1629,42 @@
                               kInvalidateCurrentScope, has_rebuilt_font_cache);
 }
 
+void StyleEngine::LoadVisionDeficiencyFilter() {
+  VisionDeficiency old_vision_deficiency = vision_deficiency_;
+  vision_deficiency_ = GetDocument().GetPage()->GetVisionDeficiency();
+  if (vision_deficiency_ == old_vision_deficiency)
+    return;
+
+  if (vision_deficiency_ == VisionDeficiency::kNoVisionDeficiency) {
+    vision_deficiency_filter_ = nullptr;
+  } else {
+    AtomicString url = CreateVisionDeficiencyFilterUrl(vision_deficiency_);
+    cssvalue::CSSURIValue css_uri_value(url);
+    SVGResource* svg_resource = css_uri_value.EnsureResourceReference();
+    // Note: The fact that we're using data: URLs here is an
+    // implementation detail. Emulating vision deficiencies should still
+    // work even if the Document's Content-Security-Policy disallows
+    // data: URLs.
+    svg_resource->LoadWithoutCSP(GetDocument());
+    vision_deficiency_filter_ =
+        MakeGarbageCollected<ReferenceFilterOperation>(url, svg_resource);
+  }
+}
+
+void StyleEngine::VisionDeficiencyChanged() {
+  MarkViewportStyleDirty();
+}
+
+void StyleEngine::ApplyVisionDeficiencyStyle(
+    scoped_refptr<ComputedStyle> layout_view_style) {
+  LoadVisionDeficiencyFilter();
+  if (vision_deficiency_filter_) {
+    FilterOperations ops;
+    ops.Operations().push_back(vision_deficiency_filter_);
+    layout_view_style->SetFilter(ops);
+  }
+}
+
 const MediaQueryEvaluator& StyleEngine::EnsureMediaQueryEvaluator() {
   if (!media_query_evaluator_) {
     if (GetDocument().GetFrame()) {
@@ -1641,27 +1680,16 @@
 bool StyleEngine::MediaQueryAffectedByViewportChange() {
   DCHECK(IsMaster());
   DCHECK(global_rule_set_);
-  const MediaQueryEvaluator& evaluator = EnsureMediaQueryEvaluator();
-  const auto& results = global_rule_set_->GetRuleFeatureSet()
-                            .ViewportDependentMediaQueryResults();
-  for (unsigned i = 0; i < results.size(); ++i) {
-    if (evaluator.Eval(results[i].Expression()) != results[i].Result())
-      return true;
-  }
-  return false;
+  return EnsureMediaQueryEvaluator().DidResultsChange(
+      global_rule_set_->GetRuleFeatureSet()
+          .ViewportDependentMediaQueryResults());
 }
 
 bool StyleEngine::MediaQueryAffectedByDeviceChange() {
   DCHECK(IsMaster());
   DCHECK(global_rule_set_);
-  const MediaQueryEvaluator& evaluator = EnsureMediaQueryEvaluator();
-  const auto& results =
-      global_rule_set_->GetRuleFeatureSet().DeviceDependentMediaQueryResults();
-  for (unsigned i = 0; i < results.size(); ++i) {
-    if (evaluator.Eval(results[i].Expression()) != results[i].Result())
-      return true;
-  }
-  return false;
+  return EnsureMediaQueryEvaluator().DidResultsChange(
+      global_rule_set_->GetRuleFeatureSet().DeviceDependentMediaQueryResults());
 }
 
 bool StyleEngine::UpdateRemUnits(const ComputedStyle* old_root_style,
@@ -2146,6 +2174,7 @@
   visitor->Trace(active_tree_scopes_);
   visitor->Trace(tree_boundary_crossing_scopes_);
   visitor->Trace(resolver_);
+  visitor->Trace(vision_deficiency_filter_);
   visitor->Trace(viewport_resolver_);
   visitor->Trace(media_query_evaluator_);
   visitor->Trace(global_rule_set_);
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h
index e750feac..05a79dfd 100644
--- a/third_party/blink/renderer/core/css/style_engine.h
+++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -51,6 +51,7 @@
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/tree_ordered_list.h"
 #include "third_party/blink/renderer/core/html/track/text_track.h"
+#include "third_party/blink/renderer/core/style/filter_operations.h"
 #include "third_party/blink/renderer/platform/bindings/name_client.h"
 #include "third_party/blink/renderer/platform/fonts/font_selector_client.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
@@ -80,7 +81,7 @@
 
 // The StyleEngine class manages style-related state for the document. There is
 // a 1-1 relationship of Document to StyleEngine. The document calls the
-// StyleEngine when the the document is updated in a way that impacts styles.
+// StyleEngine when the document is updated in a way that impacts styles.
 class CORE_EXPORT StyleEngine final : public GarbageCollected<StyleEngine>,
                                       public FontSelectorClient,
                                       public NameClient {
@@ -324,6 +325,10 @@
   void ApplyUserRuleSetChanges(const ActiveStyleSheetVector& old_style_sheets,
                                const ActiveStyleSheetVector& new_style_sheets);
 
+  void VisionDeficiencyChanged();
+  void ApplyVisionDeficiencyStyle(
+      scoped_refptr<ComputedStyle> layout_view_style);
+
   void CollectMatchingUserRules(ElementRuleCollector&) const;
 
   void CustomPropertyRegistered();
@@ -386,6 +391,8 @@
   // FontSelectorClient implementation.
   void FontsNeedUpdate(FontSelector*) override;
 
+  void LoadVisionDeficiencyFilter();
+
  private:
   bool NeedsActiveStyleSheetUpdate() const {
     return all_tree_scopes_dirty_ || tree_scopes_removed_ ||
@@ -538,6 +545,9 @@
   bool viewport_style_dirty_ = false;
   bool fonts_need_update_ = false;
 
+  VisionDeficiency vision_deficiency_ = VisionDeficiency::kNoVisionDeficiency;
+  Member<ReferenceFilterOperation> vision_deficiency_filter_;
+
   Member<StyleResolver> resolver_;
   Member<ViewportStyleResolver> viewport_resolver_;
   Member<MediaQueryEvaluator> media_query_evaluator_;
diff --git a/third_party/blink/renderer/core/css/style_rule.cc b/third_party/blink/renderer/core/css/style_rule.cc
index 138731c..1220ed8 100644
--- a/third_party/blink/renderer/core/css/style_rule.cc
+++ b/third_party/blink/renderer/core/css/style_rule.cc
@@ -287,7 +287,7 @@
   return !lazy_property_parser_;
 }
 
-void StyleRule::TraceAfterDispatch(blink::Visitor* visitor) {
+void StyleRule::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(properties_);
   visitor->Trace(lazy_property_parser_);
   StyleRuleBase::TraceAfterDispatch(visitor);
@@ -312,7 +312,7 @@
   return *To<MutableCSSPropertyValueSet>(properties_.Get());
 }
 
-void StyleRulePage::TraceAfterDispatch(blink::Visitor* visitor) {
+void StyleRulePage::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(properties_);
   StyleRuleBase::TraceAfterDispatch(visitor);
 }
@@ -333,7 +333,7 @@
   return *To<MutableCSSPropertyValueSet>(properties_.Get());
 }
 
-void StyleRuleProperty::TraceAfterDispatch(blink::Visitor* visitor) {
+void StyleRuleProperty::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(properties_);
   StyleRuleBase::TraceAfterDispatch(visitor);
 }
@@ -351,7 +351,7 @@
   return *To<MutableCSSPropertyValueSet>(properties_.Get());
 }
 
-void StyleRuleFontFace::TraceAfterDispatch(blink::Visitor* visitor) {
+void StyleRuleFontFace::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(properties_);
   StyleRuleBase::TraceAfterDispatch(visitor);
 }
@@ -376,7 +376,7 @@
   child_rules_.EraseAt(index);
 }
 
-void StyleRuleGroup::TraceAfterDispatch(blink::Visitor* visitor) {
+void StyleRuleGroup::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(child_rules_);
   StyleRuleBase::TraceAfterDispatch(visitor);
 }
@@ -412,7 +412,7 @@
     : StyleRuleCondition(kSupports, condition_text, adopt_rules),
       condition_is_supported_(condition_is_supported) {}
 
-void StyleRuleMedia::TraceAfterDispatch(blink::Visitor* visitor) {
+void StyleRuleMedia::TraceAfterDispatch(blink::Visitor* visitor) const {
   StyleRuleCondition::TraceAfterDispatch(visitor);
 }
 
@@ -433,7 +433,7 @@
   return *To<MutableCSSPropertyValueSet>(properties_.Get());
 }
 
-void StyleRuleViewport::TraceAfterDispatch(blink::Visitor* visitor) {
+void StyleRuleViewport::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(properties_);
   StyleRuleBase::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/style_rule.h b/third_party/blink/renderer/core/css/style_rule.h
index ddf2b30d..4d1a1b01 100644
--- a/third_party/blink/renderer/core/css/style_rule.h
+++ b/third_party/blink/renderer/core/css/style_rule.h
@@ -74,7 +74,7 @@
   CSSRule* CreateCSSOMWrapper(CSSRule* parent_rule) const;
 
   void Trace(Visitor*);
-  void TraceAfterDispatch(blink::Visitor* visitor) {}
+  void TraceAfterDispatch(blink::Visitor* visitor) const {}
   void FinalizeGarbageCollectedObject();
 
   // ~StyleRuleBase should be public, because non-public ~StyleRuleBase
@@ -121,7 +121,7 @@
   bool PropertiesHaveFailedOrCanceledSubresources() const;
   bool ShouldConsiderForMatchingRules(bool include_empty_rules) const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   friend class CSSLazyParsingTest;
@@ -155,7 +155,7 @@
     return MakeGarbageCollected<StyleRuleFontFace>(*this);
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<CSSPropertyValueSet> properties_;  // Cannot be null.
@@ -179,7 +179,7 @@
     return MakeGarbageCollected<StyleRulePage>(*this);
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<CSSPropertyValueSet> properties_;  // Cannot be null.
@@ -200,7 +200,7 @@
     return MakeGarbageCollected<StyleRuleProperty>(*this);
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   String name_;
@@ -216,7 +216,7 @@
   void WrapperInsertRule(unsigned, StyleRuleBase*);
   void WrapperRemoveRule(unsigned);
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  protected:
   StyleRuleGroup(RuleType, HeapVector<Member<StyleRuleBase>>& adopt_rule);
@@ -230,7 +230,7 @@
  public:
   String ConditionText() const { return condition_text_; }
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     StyleRuleGroup::TraceAfterDispatch(visitor);
   }
 
@@ -255,7 +255,7 @@
     return MakeGarbageCollected<StyleRuleMedia>(*this);
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   scoped_refptr<MediaQuerySet> media_queries_;
@@ -273,7 +273,7 @@
     return MakeGarbageCollected<StyleRuleSupports>(*this);
   }
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     StyleRuleCondition::TraceAfterDispatch(visitor);
   }
 
@@ -294,7 +294,7 @@
     return MakeGarbageCollected<StyleRuleViewport>(*this);
   }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<CSSPropertyValueSet> properties_;  // Cannot be null
@@ -304,7 +304,7 @@
 class StyleRuleCharset : public StyleRuleBase {
  public:
   StyleRuleCharset() : StyleRuleBase(kCharset) {}
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     StyleRuleBase::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/style_rule_import.cc b/third_party/blink/renderer/core/css/style_rule_import.cc
index 900b29c..ed89757 100644
--- a/third_party/blink/renderer/core/css/style_rule_import.cc
+++ b/third_party/blink/renderer/core/css/style_rule_import.cc
@@ -54,7 +54,7 @@
   style_sheet_client_->Dispose();
 }
 
-void StyleRuleImport::TraceAfterDispatch(blink::Visitor* visitor) {
+void StyleRuleImport::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(style_sheet_client_);
   visitor->Trace(parent_style_sheet_);
   visitor->Trace(style_sheet_);
diff --git a/third_party/blink/renderer/core/css/style_rule_import.h b/third_party/blink/renderer/core/css/style_rule_import.h
index 8359418..db062f0 100644
--- a/third_party/blink/renderer/core/css/style_rule_import.h
+++ b/third_party/blink/renderer/core/css/style_rule_import.h
@@ -57,7 +57,7 @@
 
   void RequestStyleSheet();
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   // FIXME: inherit from ResourceClient directly to eliminate back
diff --git a/third_party/blink/renderer/core/css/style_rule_keyframe.cc b/third_party/blink/renderer/core/css/style_rule_keyframe.cc
index f9aa723..d688193 100644
--- a/third_party/blink/renderer/core/css/style_rule_keyframe.cc
+++ b/third_party/blink/renderer/core/css/style_rule_keyframe.cc
@@ -63,7 +63,7 @@
   return result.ToString();
 }
 
-void StyleRuleKeyframe::TraceAfterDispatch(blink::Visitor* visitor) {
+void StyleRuleKeyframe::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(properties_);
   StyleRuleBase::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/css/style_rule_keyframe.h b/third_party/blink/renderer/core/css/style_rule_keyframe.h
index cb8cac2e..37493bc 100644
--- a/third_party/blink/renderer/core/css/style_rule_keyframe.h
+++ b/third_party/blink/renderer/core/css/style_rule_keyframe.h
@@ -30,7 +30,7 @@
 
   String CssText() const;
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   Member<CSSPropertyValueSet> properties_;
diff --git a/third_party/blink/renderer/core/css/style_rule_namespace.h b/third_party/blink/renderer/core/css/style_rule_namespace.h
index 76331b5..b1875ca 100644
--- a/third_party/blink/renderer/core/css/style_rule_namespace.h
+++ b/third_party/blink/renderer/core/css/style_rule_namespace.h
@@ -24,7 +24,7 @@
   AtomicString Prefix() const { return prefix_; }
   AtomicString Uri() const { return uri_; }
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     StyleRuleBase::TraceAfterDispatch(visitor);
   }
 
diff --git a/third_party/blink/renderer/core/css/vision_deficiency.cc b/third_party/blink/renderer/core/css/vision_deficiency.cc
new file mode 100644
index 0000000..66e9d39
--- /dev/null
+++ b/third_party/blink/renderer/core/css/vision_deficiency.cc
@@ -0,0 +1,100 @@
+// 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/css/vision_deficiency.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+AtomicString CreateFilterDataUrl(AtomicString piece) {
+  // clang-format off
+  AtomicString url =
+      "data:image/svg+xml,"
+        "<svg xmlns=\"http://www.w3.org/2000/svg\">"
+          "<filter id=\"f\">" +
+            piece +
+          "</filter>"
+        "</svg>"
+      "#f";
+  // clang-format on
+  return url;
+}
+
+AtomicString CreateVisionDeficiencyFilterUrl(
+    VisionDeficiency vision_deficiency) {
+  switch (vision_deficiency) {
+    case VisionDeficiency::kAchromatomaly:
+      return CreateFilterDataUrl(
+          "<feColorMatrix values=\""
+          "0.618 0.320 0.062 0.000 0.000 "
+          "0.163 0.775 0.062 0.000 0.000 "
+          "0.163 0.320 0.516 0.000 0.000 "
+          "0.000 0.000 0.000 1.000 0.000 "
+          "\"/>");
+    case VisionDeficiency::kAchromatopsia:
+      return CreateFilterDataUrl(
+          "<feColorMatrix values=\""
+          "0.299 0.587 0.114 0.000 0.000 "
+          "0.299 0.587 0.114 0.000 0.000 "
+          "0.299 0.587 0.114 0.000 0.000 "
+          "0.000 0.000 0.000 1.000 0.000 "
+          "\"/>");
+    case VisionDeficiency::kBlurredVision:
+      return CreateFilterDataUrl(
+          "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"2\"/>");
+    case VisionDeficiency::kDeuteranomaly:
+      return CreateFilterDataUrl(
+          "<feColorMatrix values=\""
+          "0.800 0.200 0.000 0.000 0.000 "
+          "0.258 0.742 0.000 0.000 0.000 "
+          "0.000 0.142 0.858 0.000 0.000 "
+          "0.000 0.000 0.000 1.000 0.000 "
+          "\"/>");
+    case VisionDeficiency::kDeuteranopia:
+      return CreateFilterDataUrl(
+          "<feColorMatrix values=\""
+          "0.625 0.375 0.000 0.000 0.000 "
+          "0.700 0.300 0.000 0.000 0.000 "
+          "0.000 0.300 0.700 0.000 0.000 "
+          "0.000 0.000 0.000 1.000 0.000 "
+          "\"/>");
+    case VisionDeficiency::kProtanomaly:
+      return CreateFilterDataUrl(
+          "<feColorMatrix values=\""
+          "0.817 0.183 0.000 0.000 0.000 "
+          "0.333 0.667 0.000 0.000 0.000 "
+          "0.000 0.125 0.875 0.000 0.000 "
+          "0.000 0.000 0.000 1.000 0.000 "
+          "\"/>");
+    case VisionDeficiency::kProtanopia:
+      return CreateFilterDataUrl(
+          "<feColorMatrix values=\""
+          "0.567 0.433 0.000 0.000 0.000 "
+          "0.558 0.442 0.000 0.000 0.000 "
+          "0.000 0.242 0.758 0.000 0.000 "
+          "0.000 0.000 0.000 1.000 0.000 "
+          "\"/>");
+    case VisionDeficiency::kTritanomaly:
+      return CreateFilterDataUrl(
+          "<feColorMatrix values=\""
+          "0.967 0.033 0.000 0.000 0.000 "
+          "0.000 0.733 0.267 0.000 0.000 "
+          "0.000 0.183 0.817 0.000 0.000 "
+          "0.000 0.000 0.000 1.000 0.000 "
+          "\"/>");
+    case VisionDeficiency::kTritanopia:
+      return CreateFilterDataUrl(
+          "<feColorMatrix values=\""
+          "0.950 0.050 0.000 0.000 0.000 "
+          "0.000 0.433 0.567 0.000 0.000 "
+          "0.000 0.475 0.525 0.000 0.000 "
+          "0.000 0.000 0.000 1.000 0.000 "
+          "\"/>");
+    case VisionDeficiency::kNoVisionDeficiency:
+      NOTREACHED();
+      return "";
+  }
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/vision_deficiency.h b/third_party/blink/renderer/core/css/vision_deficiency.h
new file mode 100644
index 0000000..b91d422
--- /dev/null
+++ b/third_party/blink/renderer/core/css/vision_deficiency.h
@@ -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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_VISION_DEFICIENCY_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_VISION_DEFICIENCY_H_
+
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+enum class VisionDeficiency {
+  kNoVisionDeficiency,
+  kAchromatomaly,
+  kAchromatopsia,
+  kBlurredVision,
+  kDeuteranomaly,
+  kDeuteranopia,
+  kProtanomaly,
+  kProtanopia,
+  kTritanomaly,
+  kTritanopia,
+};
+
+AtomicString CreateVisionDeficiencyFilterUrl(
+    VisionDeficiency vision_deficiency);
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_VISION_DEFICIENCY_H_
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 87cfca1a..fa3b053 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -8606,6 +8606,10 @@
   MediaQueryAffectingValueChanged();
 }
 
+void Document::VisionDeficiencyChanged() {
+  GetStyleEngine().VisionDeficiencyChanged();
+}
+
 void Document::UpdateForcedColors() {
   auto* web_theme_engine =
       RuntimeEnabledFeatures::ForcedColorsEnabled() && Platform::Current()
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index de5330e..e2ca926 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -44,6 +44,7 @@
 #include "third_party/blink/public/mojom/scroll/scrollbar_mode.mojom-blink.h"
 #include "third_party/blink/renderer/core/accessibility/axid.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/css/vision_deficiency.h"
 #include "third_party/blink/renderer/core/dom/container_node.h"
 #include "third_party/blink/renderer/core/dom/create_element_flags.h"
 #include "third_party/blink/renderer/core/dom/document_encoding_data.h"
@@ -1698,6 +1699,9 @@
 
   void ColorSchemeChanged();
 
+  // A new vision deficiency is being emulated through DevTools.
+  void VisionDeficiencyChanged();
+
   void ClearIsolatedWorldCSPForTesting(int32_t world_id);
 
   // A META element with name=color-scheme was added, removed, or modified.
diff --git a/third_party/blink/renderer/core/dom/element_data.cc b/third_party/blink/renderer/core/dom/element_data.cc
index cd728be..c64ec79 100644
--- a/third_party/blink/renderer/core/dom/element_data.cc
+++ b/third_party/blink/renderer/core/dom/element_data.cc
@@ -120,7 +120,7 @@
   }
 }
 
-void ElementData::TraceAfterDispatch(blink::Visitor* visitor) {
+void ElementData::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(inline_style_);
 }
 
@@ -185,7 +185,7 @@
       *this);
 }
 
-void UniqueElementData::TraceAfterDispatch(blink::Visitor* visitor) {
+void UniqueElementData::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(presentation_attribute_style_);
   ElementData::TraceAfterDispatch(visitor);
 }
diff --git a/third_party/blink/renderer/core/dom/element_data.h b/third_party/blink/renderer/core/dom/element_data.h
index 373bcebca..0164868f 100644
--- a/third_party/blink/renderer/core/dom/element_data.h
+++ b/third_party/blink/renderer/core/dom/element_data.h
@@ -80,7 +80,7 @@
 
   bool IsUnique() const { return is_unique_; }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
   void Trace(Visitor*);
 
  protected:
@@ -127,7 +127,7 @@
   explicit ShareableElementData(const UniqueElementData&);
   ~ShareableElementData();
 
-  void TraceAfterDispatch(blink::Visitor* visitor) {
+  void TraceAfterDispatch(blink::Visitor* visitor) const {
     ElementData::TraceAfterDispatch(visitor);
   }
 
@@ -162,7 +162,7 @@
   explicit UniqueElementData(const ShareableElementData&);
   explicit UniqueElementData(const UniqueElementData&);
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
   // FIXME: We might want to support sharing element data for elements with
   // presentation attribute style. Lots of table cells likely have the same
diff --git a/third_party/blink/renderer/core/dom/element_rare_data.cc b/third_party/blink/renderer/core/dom/element_rare_data.cc
index 7003e5c..0f6056b 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data.cc
+++ b/third_party/blink/renderer/core/dom/element_rare_data.cc
@@ -93,7 +93,7 @@
   return *element_internals_;
 }
 
-void ElementRareData::TraceAfterDispatch(blink::Visitor* visitor) {
+void ElementRareData::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(dataset_);
   visitor->Trace(class_list_);
   visitor->Trace(part_);
diff --git a/third_party/blink/renderer/core/dom/element_rare_data.h b/third_party/blink/renderer/core/dom/element_rare_data.h
index dd0b37e..e72d697 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data.h
+++ b/third_party/blink/renderer/core/dom/element_rare_data.h
@@ -207,7 +207,7 @@
   const AtomicString& GetNonce() const { return nonce_; }
   void SetNonce(const AtomicString& nonce) { nonce_ = nonce; }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
   ScrollOffset saved_layer_scroll_offset_;
diff --git a/third_party/blink/renderer/core/dom/node_rare_data.cc b/third_party/blink/renderer/core/dom/node_rare_data.cc
index f59977d..43337f3 100644
--- a/third_party/blink/renderer/core/dom/node_rare_data.cc
+++ b/third_party/blink/renderer/core/dom/node_rare_data.cc
@@ -108,7 +108,7 @@
   return *shared_empty_data;
 }
 
-void NodeRareData::TraceAfterDispatch(blink::Visitor* visitor) {
+void NodeRareData::TraceAfterDispatch(blink::Visitor* visitor) const {
   visitor->Trace(mutation_observer_data_);
   visitor->Trace(flat_tree_node_data_);
   visitor->Trace(node_layout_data_);
diff --git a/third_party/blink/renderer/core/dom/node_rare_data.h b/third_party/blink/renderer/core/dom/node_rare_data.h
index 56eb104..0c3b213 100644
--- a/third_party/blink/renderer/core/dom/node_rare_data.h
+++ b/third_party/blink/renderer/core/dom/node_rare_data.h
@@ -78,7 +78,7 @@
     DCHECK(!is_element_rare_data || is_rare_data);
   }
   void Trace(Visitor*);
-  void TraceAfterDispatch(blink::Visitor*) {}
+  void TraceAfterDispatch(blink::Visitor*) const {}
 
   enum {
     kConnectedFrameCountBits = 10,  // Must fit Page::maxNumberOfFrames.
@@ -118,7 +118,7 @@
   static NodeRenderingData& SharedEmptyData();
   bool IsSharedEmptyData() { return this == &SharedEmptyData(); }
 
-  void TraceAfterDispatch(Visitor* visitor) {
+  void TraceAfterDispatch(Visitor* visitor) const {
     NodeData::TraceAfterDispatch(visitor);
   }
 
@@ -193,7 +193,7 @@
   bool HasRestyleFlags() const { return restyle_flags_; }
   void ClearRestyleFlags() { restyle_flags_ = 0; }
 
-  void TraceAfterDispatch(blink::Visitor*);
+  void TraceAfterDispatch(blink::Visitor*) const;
   void FinalizeGarbageCollectedObject();
 
  protected:
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index e170d5d..9eaaf86 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -178,8 +178,6 @@
   void SetIgnoreViewportTagScaleLimits(bool) override;
   WebSize ContentsPreferredMinimumSize() override;
   void SetDisplayMode(blink::mojom::DisplayMode) override;
-  void AnimateDoubleTapZoom(const gfx::Point&,
-                            const WebRect& block_bounds) override;
   void ZoomToFindInPageRect(const WebRect&) override;
   void SetDeviceScaleFactor(float) override;
   void SetZoomFactorForDeviceScaleFactor(float) override;
@@ -216,6 +214,9 @@
   void PaintContent(cc::PaintCanvas*, const gfx::Rect&) override;
   void SetTextAutosizerPageInfo(const WebTextAutosizerPageInfo&) override;
 
+  // Requests a page-scale animation based on the specified point/rect.
+  void AnimateDoubleTapZoom(const gfx::Point&, const WebRect& block_bounds);
+
   float DefaultMinimumPageScaleFactor() const;
   float DefaultMaximumPageScaleFactor() const;
   float ClampPageScaleFactorToLimits(float) const;
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 90b7868e..aab8e14 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -2019,6 +2019,11 @@
   GetLocalFrameHostRemote().EvictFromBackForwardCache();
 }
 
+void LocalFrame::AnimateDoubleTapZoom(const gfx::Point& point,
+                                      const gfx::Rect& rect) {
+  GetPage()->GetChromeClient().AnimateDoubleTapZoom(point, rect);
+}
+
 void LocalFrame::SetScaleFactor(float scale_factor) {
   DCHECK(IsMainFrame());
 
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index cfe9c624..2ad182d 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -528,6 +528,8 @@
   void DidUpdateFramePolicy(const FramePolicy& frame_policy) final;
 
   // blink::mojom::LocalMainFrame overrides:
+  void AnimateDoubleTapZoom(const gfx::Point& point,
+                            const gfx::Rect& rect) override;
   void SetScaleFactor(float scale) override;
   void ClosePage(
       mojom::blink::LocalMainFrame::ClosePageCallback callback) override;
diff --git a/third_party/blink/renderer/core/frame/settings_delegate.h b/third_party/blink/renderer/core/frame/settings_delegate.h
index 1b0bdcf..8919c3d5 100644
--- a/third_party/blink/renderer/core/frame/settings_delegate.h
+++ b/third_party/blink/renderer/core/frame/settings_delegate.h
@@ -72,6 +72,7 @@
     kColorSchemeChange,
     kSpatialNavigationChange,
     kUniversalAccessChange,
+    kVisionDeficiencyChange,
   };
 
   virtual void SettingsChanged(ChangeType) = 0;
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.cc b/third_party/blink/renderer/core/html/forms/html_select_element.cc
index 1c58e29..e3c59ff 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.cc
@@ -804,11 +804,7 @@
     return;
   suggested_option_ = option;
 
-  select_type_->UpdateTextStyleAndContent();
-  if (GetLayoutObject())
-    ScrollToOption(option);
-  if (PopupIsVisible())
-    popup_->UpdateFromElement(PopupMenu::kBySelectionChange);
+  select_type_->DidSetSuggestedOption(option);
 }
 
 void HTMLSelectElement::ScrollToOption(HTMLOptionElement* option) {
@@ -1530,12 +1526,7 @@
 }
 
 void HTMLSelectElement::PopupDidHide() {
-  SetPopupIsVisible(false);
-  UnobserveTreeMutation();
-  if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) {
-    if (GetLayoutObject() && UsesMenuList())
-      cache->DidHideMenuListPopup(GetLayoutObject());
-  }
+  select_type_->PopupDidHide();
 }
 
 void HTMLSelectElement::SetIndexToSelectOnCancel(int list_index) {
@@ -1597,11 +1588,7 @@
 
 void HTMLSelectElement::DidRecalcStyle(const StyleRecalcChange change) {
   HTMLFormControlElementWithState::DidRecalcStyle(change);
-  if (change.ReattachLayoutTree())
-    return;
-  select_type_->UpdateTextStyle();
-  if (PopupIsVisible())
-    popup_->UpdateFromElement(PopupMenu::kByStyleChange);
+  select_type_->DidRecalcStyle(change);
 }
 
 void HTMLSelectElement::AttachLayoutTree(AttachContext& context) {
@@ -1623,11 +1610,7 @@
 
 void HTMLSelectElement::DetachLayoutTree(bool performing_reattach) {
   HTMLFormControlElementWithState::DetachLayoutTree(performing_reattach);
-  if (popup_)
-    popup_->DisconnectClient();
-  SetPopupIsVisible(false);
-  popup_ = nullptr;
-  UnobserveTreeMutation();
+  select_type_->DidDetachLayoutTree();
 }
 
 void HTMLSelectElement::ResetTypeAheadSessionForTesting() {
@@ -1716,6 +1699,7 @@
 }
 
 void HTMLSelectElement::ChangeRendering() {
+  select_type_->DidDetachLayoutTree();
   UpdateUsesMenuList();
   select_type_->WillBeDestroyed();
   select_type_ = SelectType::Create(*this);
diff --git a/third_party/blink/renderer/core/html/forms/select_type.cc b/third_party/blink/renderer/core/html/forms/select_type.cc
index e7542bde..9751219 100644
--- a/third_party/blink/renderer/core/html/forms/select_type.cc
+++ b/third_party/blink/renderer/core/html/forms/select_type.cc
@@ -70,6 +70,9 @@
                        HTMLSelectElement::SelectOptionFlags flags,
                        bool should_update_popup) override;
   void DidBlur() override;
+  void DidDetachLayoutTree() override;
+  void DidRecalcStyle(const StyleRecalcChange change) override;
+  void DidSetSuggestedOption(HTMLOptionElement* option) override;
 
   void UpdateTextStyle() override { UpdateTextStyleInternal(); }
   void UpdateTextStyleAndContent() override;
@@ -79,6 +82,7 @@
 
   void ShowPopup() override;
   void HidePopup() override;
+  void PopupDidHide() override;
 
  private:
   bool ShouldOpenPopupForKeyDownEvent(const KeyboardEvent& event);
@@ -290,6 +294,15 @@
     select_->popup_->Hide();
 }
 
+void MenuListSelectType::PopupDidHide() {
+  select_->SetPopupIsVisible(false);
+  select_->UnobserveTreeMutation();
+  if (AXObjectCache* cache = select_->GetDocument().ExistingAXObjectCache()) {
+    if (auto* layout_object = select_->GetLayoutObject())
+      cache->DidHideMenuListPopup(layout_object);
+  }
+}
+
 void MenuListSelectType::DidSelectOption(
     HTMLOptionElement* element,
     HTMLSelectElement::SelectOptionFlags flags,
@@ -340,6 +353,28 @@
   SelectType::DidBlur();
 }
 
+void MenuListSelectType::DidSetSuggestedOption(HTMLOptionElement*) {
+  UpdateTextStyleAndContent();
+  if (select_->PopupIsVisible())
+    select_->popup_->UpdateFromElement(PopupMenu::kBySelectionChange);
+}
+
+void MenuListSelectType::DidDetachLayoutTree() {
+  if (select_->popup_)
+    select_->popup_->DisconnectClient();
+  select_->SetPopupIsVisible(false);
+  select_->popup_ = nullptr;
+  select_->UnobserveTreeMutation();
+}
+
+void MenuListSelectType::DidRecalcStyle(const StyleRecalcChange change) {
+  if (change.ReattachLayoutTree())
+    return;
+  UpdateTextStyle();
+  if (select_->PopupIsVisible())
+    select_->popup_->UpdateFromElement(PopupMenu::kByStyleChange);
+}
+
 String MenuListSelectType::UpdateTextStyleInternal() {
   HTMLOptionElement* option = select_->OptionToBeShown();
   String text = g_empty_string;
@@ -431,6 +466,7 @@
  public:
   explicit ListBoxSelectType(HTMLSelectElement& select) : SelectType(select) {}
   bool DefaultEventHandler(const Event& event) override;
+  void DidSetSuggestedOption(HTMLOptionElement* option) override;
   void UpdateMultiSelectFocus() override;
   void SelectAll() override;
 
@@ -690,6 +726,11 @@
   return false;
 }
 
+void ListBoxSelectType::DidSetSuggestedOption(HTMLOptionElement* option) {
+  if (select_->GetLayoutObject())
+    select_->ScrollToOption(option);
+}
+
 void ListBoxSelectType::UpdateMultiSelectFocus() {
   if (!select_->is_multiple_)
     return;
@@ -772,6 +813,10 @@
   select_->last_on_change_selection_.clear();
 }
 
+void SelectType::DidDetachLayoutTree() {}
+
+void SelectType::DidRecalcStyle(const StyleRecalcChange) {}
+
 void SelectType::UpdateTextStyle() {}
 
 void SelectType::UpdateTextStyleAndContent() {}
@@ -795,6 +840,10 @@
   NOTREACHED();
 }
 
+void SelectType::PopupDidHide() {
+  NOTREACHED();
+}
+
 // Returns the 1st valid OPTION |skip| items from |list_index| in direction
 // |direction| if there is one.
 // Otherwise, it returns the valid OPTION closest to that boundary which is past
diff --git a/third_party/blink/renderer/core/html/forms/select_type.h b/third_party/blink/renderer/core/html/forms/select_type.h
index 84cb291..77239ab4 100644
--- a/third_party/blink/renderer/core/html/forms/select_type.h
+++ b/third_party/blink/renderer/core/html/forms/select_type.h
@@ -29,6 +29,9 @@
                                bool should_update_popup);
 
   virtual void DidBlur();
+  virtual void DidDetachLayoutTree();
+  virtual void DidRecalcStyle(const StyleRecalcChange change);
+  virtual void DidSetSuggestedOption(HTMLOptionElement* option) = 0;
 
   // Update style of text in the CSS box on style or selected OPTION change.
   virtual void UpdateTextStyle();
@@ -46,6 +49,7 @@
 
   virtual void ShowPopup();
   virtual void HidePopup();
+  virtual void PopupDidHide();
 
   enum SkipDirection { kSkipBackwards = -1, kSkipForwards = 1 };
   CORE_EXPORT HTMLOptionElement* NextSelectableOption(HTMLOptionElement*) const;
diff --git a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
index 3d94b51..70efd14 100644
--- a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
@@ -6,6 +6,7 @@
 
 #include "third_party/blink/public/common/input/web_touch_event.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/core/css/vision_deficiency.h"
 #include "third_party/blink/renderer/core/exported/web_view_impl.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
@@ -39,6 +40,8 @@
       max_touch_points_(&agent_state_, /*default_value=*/1),
       emulated_media_(&agent_state_, /*default_value=*/WTF::String()),
       emulated_media_features_(&agent_state_, /*default_value=*/WTF::String()),
+      emulated_vision_deficiency_(&agent_state_,
+                                  /*default_value=*/WTF::String()),
       navigator_platform_override_(&agent_state_,
                                    /*default_value=*/WTF::String()),
       user_agent_override_(&agent_state_, /*default_value=*/WTF::String()),
@@ -101,6 +104,8 @@
                             .build());
   }
   setEmulatedMedia(emulated_media_.Get(), std::move(features));
+  if (!emulated_vision_deficiency_.Get().IsNull())
+    setEmulatedVisionDeficiency(emulated_vision_deficiency_.Get());
   auto rgba = ParseRGBA(default_background_color_override_rgba_.Get());
   if (rgba)
     setDefaultBackgroundColorOverride(std::move(rgba));
@@ -166,6 +171,8 @@
   // (e.g. if we allowed two different front-ends with the same
   // settings to attach to the same page). TODO: support this use case.
   setEmulatedMedia(String(), {});
+  if (!emulated_vision_deficiency_.Get().IsNull())
+    setEmulatedVisionDeficiency(String("none"));
   setCPUThrottlingRate(1);
   setFocusEmulationEnabled(false);
   setDefaultBackgroundColorOverride(Maybe<protocol::DOM::RGBA>());
@@ -272,6 +279,43 @@
   return response;
 }
 
+Response InspectorEmulationAgent::setEmulatedVisionDeficiency(
+    const String& type) {
+  Response response = AssertPage();
+  if (!response.isSuccess())
+    return response;
+
+  VisionDeficiency vision_deficiency;
+  namespace TypeEnum =
+      protocol::Emulation::SetEmulatedVisionDeficiency::TypeEnum;
+  if (type == TypeEnum::None)
+    vision_deficiency = VisionDeficiency::kNoVisionDeficiency;
+  else if (type == TypeEnum::Achromatomaly)
+    vision_deficiency = VisionDeficiency::kAchromatomaly;
+  else if (type == TypeEnum::Achromatopsia)
+    vision_deficiency = VisionDeficiency::kAchromatopsia;
+  else if (type == TypeEnum::BlurredVision)
+    vision_deficiency = VisionDeficiency::kBlurredVision;
+  else if (type == TypeEnum::Deuteranomaly)
+    vision_deficiency = VisionDeficiency::kDeuteranomaly;
+  else if (type == TypeEnum::Deuteranopia)
+    vision_deficiency = VisionDeficiency::kDeuteranopia;
+  else if (type == TypeEnum::Protanomaly)
+    vision_deficiency = VisionDeficiency::kProtanomaly;
+  else if (type == TypeEnum::Protanopia)
+    vision_deficiency = VisionDeficiency::kProtanopia;
+  else if (type == TypeEnum::Tritanomaly)
+    vision_deficiency = VisionDeficiency::kTritanomaly;
+  else if (type == TypeEnum::Tritanopia)
+    vision_deficiency = VisionDeficiency::kTritanopia;
+  else
+    return Response::InvalidParams("Unknown vision deficiency type");
+
+  emulated_vision_deficiency_.Set(type);
+  GetWebViewImpl()->GetPage()->SetVisionDeficiency(vision_deficiency);
+  return response;
+}
+
 Response InspectorEmulationAgent::setCPUThrottlingRate(double rate) {
   Response response = AssertPage();
   if (!response.isSuccess())
diff --git a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h
index 4543033e..4c77396 100644
--- a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h
@@ -48,6 +48,7 @@
       protocol::Maybe<String> media,
       protocol::Maybe<protocol::Array<protocol::Emulation::MediaFeature>>
           features) override;
+  protocol::Response setEmulatedVisionDeficiency(const String&) override;
   protocol::Response setCPUThrottlingRate(double) override;
   protocol::Response setFocusEmulationEnabled(bool) override;
   protocol::Response setVirtualTimePolicy(
@@ -127,6 +128,7 @@
   InspectorAgentState::Integer max_touch_points_;
   InspectorAgentState::String emulated_media_;
   InspectorAgentState::StringMap emulated_media_features_;
+  InspectorAgentState::String emulated_vision_deficiency_;
   InspectorAgentState::String navigator_platform_override_;
   InspectorAgentState::String user_agent_override_;
   InspectorAgentState::String accept_language_override_;
diff --git a/third_party/blink/renderer/core/inspector/inspector_protocol_config.json b/third_party/blink/renderer/core/inspector/inspector_protocol_config.json
index bafa314..b6ba2f3 100644
--- a/third_party/blink/renderer/core/inspector/inspector_protocol_config.json
+++ b/third_party/blink/renderer/core/inspector/inspector_protocol_config.json
@@ -84,7 +84,7 @@
             {
                 "domain": "Emulation",
                 "include": ["forceViewport", "resetViewport", "resetPageScaleFactor", "setPageScaleFactor", "setScriptExecutionDisabled", "setTouchEmulationEnabled",
-                            "setEmulatedMedia", "setCPUThrottlingRate", "setVirtualTimePolicy", "setTimezoneOverride", "setNavigatorOverrides", "setDefaultBackgroundColorOverride", "setDeviceMetricsOverride", "clearDeviceMetricsOverride",
+                            "setEmulatedMedia", "setEmulatedVisionDeficiency", "setCPUThrottlingRate", "setVirtualTimePolicy", "setTimezoneOverride", "setNavigatorOverrides", "setDefaultBackgroundColorOverride", "setDeviceMetricsOverride", "clearDeviceMetricsOverride",
                             "setUserAgentOverride", "setScrollbarsHidden", "setDocumentCookieDisabled", "setFocusEmulationEnabled", "setLocaleOverride"],
                 "include_events": ["virtualTimeBudgetExpired", "virtualTimeAdvanced", "virtualTimePaused"]
             },
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_inline.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_inline.cc
index 09e1acd..13d5b39 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_inline.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_inline.cc
@@ -146,18 +146,14 @@
                                LayoutObject* before_child) {
   LayoutInline::AddChild(child, before_child);
   SVGResourcesCache::ClientWasAddedToTree(*child);
-
-  if (LayoutSVGText* text_layout_object =
-          LayoutSVGText::LocateLayoutSVGTextAncestor(this))
-    text_layout_object->SubtreeChildWasAdded();
+  LayoutSVGText::NotifySubtreeStructureChanged(
+      this, layout_invalidation_reason::kChildChanged);
 }
 
 void LayoutSVGInline::RemoveChild(LayoutObject* child) {
   SVGResourcesCache::ClientWillBeRemovedFromTree(*child);
-
-  if (LayoutSVGText* text_layout_object =
-          LayoutSVGText::LocateLayoutSVGTextAncestor(this))
-    text_layout_object->SubtreeChildWillBeRemoved();
+  LayoutSVGText::NotifySubtreeStructureChanged(
+      this, layout_invalidation_reason::kChildChanged);
   LayoutInline::RemoveChild(child);
 }
 
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.cc
index d0c70ef1..a5930c7aa 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.cc
@@ -59,9 +59,8 @@
 void LayoutSVGInlineText::TextDidChange() {
   SetTextInternal(NormalizeWhitespace(GetText().Impl()));
   LayoutText::TextDidChange();
-  if (LayoutSVGText* text_layout_object =
-          LayoutSVGText::LocateLayoutSVGTextAncestor(this))
-    text_layout_object->SubtreeTextDidChange();
+  LayoutSVGText::NotifySubtreeStructureChanged(
+      this, layout_invalidation_reason::kTextChanged);
 }
 
 void LayoutSVGInlineText::StyleDidChange(StyleDifference diff,
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.cc
index a5909730..7530f56 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.cc
@@ -25,8 +25,6 @@
 
 #include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
 #include "third_party/blink/renderer/core/svg/svg_filter_element.h"
-#include "third_party/blink/renderer/core/svg/svg_filter_primitive_standard_attributes.h"
-#include "third_party/blink/renderer/core/svg/svg_resource.h"
 #include "third_party/blink/renderer/platform/graphics/filters/filter_effect.h"
 
 namespace blink {
@@ -44,45 +42,20 @@
 }
 
 LayoutSVGResourceFilter::LayoutSVGResourceFilter(SVGFilterElement* node)
-    : LayoutSVGResourceContainer(node),
-      filter_(MakeGarbageCollected<FilterMap>()) {}
+    : LayoutSVGResourceContainer(node) {}
 
 LayoutSVGResourceFilter::~LayoutSVGResourceFilter() = default;
 
-void LayoutSVGResourceFilter::DisposeFilterMap() {
-  for (auto& entry : *filter_)
-    entry.value->Dispose();
-  filter_->clear();
-}
-
-void LayoutSVGResourceFilter::WillBeDestroyed() {
-  DisposeFilterMap();
-  LayoutSVGResourceContainer::WillBeDestroyed();
-}
-
 bool LayoutSVGResourceFilter::IsChildAllowed(LayoutObject* child,
                                              const ComputedStyle&) const {
   return child->IsSVGResourceFilterPrimitive();
 }
 
 void LayoutSVGResourceFilter::RemoveAllClientsFromCache() {
-  // LayoutSVGResourceFilter::removeClientFromCache will be called for
-  // all clients through markAllClientsForInvalidation so no explicit
-  // display item invalidation is needed here.
-  DisposeFilterMap();
   MarkAllClientsForInvalidation(SVGResourceClient::kLayoutInvalidation |
                                 SVGResourceClient::kBoundariesInvalidation);
 }
 
-bool LayoutSVGResourceFilter::RemoveClientFromCache(SVGResourceClient& client) {
-  auto entry = filter_->find(&client);
-  if (entry == filter_->end())
-    return false;
-  entry->value->Dispose();
-  filter_->erase(entry);
-  return true;
-}
-
 FloatRect LayoutSVGResourceFilter::ResourceBoundingBox(
     const FloatRect& reference_box) const {
   const auto* filter_element = To<SVGFilterElement>(GetElement());
@@ -104,32 +77,6 @@
       ->EnumValue();
 }
 
-void LayoutSVGResourceFilter::PrimitiveAttributeChanged(
-    SVGFilterPrimitiveStandardAttributes& primitive,
-    const QualifiedName& attribute) {
-  for (auto& filter : *filter_) {
-    FilterData* filter_data = filter.value.Get();
-    if (filter_data->state_ != FilterData::kReadyToPaint)
-      continue;
-
-    SVGFilterGraphNodeMap* node_map = filter_data->node_map.Get();
-    FilterEffect* effect = node_map->EffectForElement(primitive);
-    if (!effect)
-      continue;
-    // Since all effects shares the same attribute value, all
-    // or none of them will be changed.
-    if (!primitive.SetFilterEffectAttribute(effect, attribute))
-      return;
-    node_map->InvalidateDependentEffects(effect);
-  }
-  if (auto* resource =
-          To<SVGFilterElement>(GetElement())->AssociatedResource()) {
-    resource->NotifyContentChanged(
-        SVGResourceClient::kPaintInvalidation |
-        SVGResourceClient::kSkipAncestorInvalidation);
-  }
-}
-
 LayoutSVGResourceFilter* GetFilterResourceForSVG(const ComputedStyle& style) {
   if (!style.HasFilter())
     return nullptr;
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.h b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.h
index 667f803..67df89df 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.h
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.h
@@ -32,7 +32,6 @@
 class FilterEffect;
 class SVGFilterElement;
 class SVGFilterGraphNodeMap;
-class SVGFilterPrimitiveStandardAttributes;
 
 class FilterData final : public GarbageCollected<FilterData> {
  public:
@@ -77,35 +76,14 @@
   }
 
   void RemoveAllClientsFromCache() override;
-  bool RemoveClientFromCache(SVGResourceClient&) override;
 
   FloatRect ResourceBoundingBox(const FloatRect& reference_box) const;
 
   SVGUnitTypes::SVGUnitType FilterUnits() const;
   SVGUnitTypes::SVGUnitType PrimitiveUnits() const;
 
-  void PrimitiveAttributeChanged(SVGFilterPrimitiveStandardAttributes&,
-                                 const QualifiedName&);
-
   static const LayoutSVGResourceType kResourceType = kFilterResourceType;
   LayoutSVGResourceType ResourceType() const override { return kResourceType; }
-
-  FilterData* GetFilterDataForClient(const SVGResourceClient* client) {
-    return filter_->at(const_cast<SVGResourceClient*>(client));
-  }
-  void SetFilterDataForClient(const SVGResourceClient* client,
-                              FilterData* filter_data) {
-    filter_->Set(const_cast<SVGResourceClient*>(client), filter_data);
-  }
-
- protected:
-  void WillBeDestroyed() override;
-
- private:
-  void DisposeFilterMap();
-
-  using FilterMap = HeapHashMap<Member<SVGResourceClient>, Member<FilterData>>;
-  Persistent<FilterMap> filter_;
 };
 
 // Get the LayoutSVGResourceFilter from the 'filter' property iff the 'filter'
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc
index a0539f0..adb9ac9 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc
@@ -111,15 +111,8 @@
   }
 }
 
-void LayoutSVGText::InvalidatePositioningValues(
+void LayoutSVGText::SubtreeStructureChanged(
     LayoutInvalidationReasonForTracing reason) {
-  descendant_text_nodes_.clear();
-  SetNeedsPositioningValuesUpdate();
-  // TODO(fs): Restore the passing of |reason| here.
-  LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(*this);
-}
-
-void LayoutSVGText::SubtreeChildWasAdded() {
   if (BeingDestroyed() || !EverHadLayout()) {
     DCHECK(descendant_text_nodes_.IsEmpty());
     return;
@@ -128,37 +121,20 @@
     return;
 
   // The positioning elements cache depends on the size of each text
-  // layoutObject in the subtree. If this changes, clear the cache. It will be
+  // LayoutObject in the subtree. If this changes, clear the cache. It will be
   // rebuilt on the next layout.
-  InvalidatePositioningValues(layout_invalidation_reason::kChildChanged);
+  descendant_text_nodes_.clear();
+  SetNeedsPositioningValuesUpdate();
   SetNeedsTextMetricsUpdate();
+  // TODO(fs): Restore the passing of |reason| here.
+  LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(*this);
 }
 
-void LayoutSVGText::SubtreeChildWillBeRemoved() {
-  if (BeingDestroyed() || !EverHadLayout()) {
-    DCHECK(descendant_text_nodes_.IsEmpty());
-    return;
-  }
-
-  // The positioning elements cache depends on the size of each text
-  // layoutObject in the subtree. If this changes, clear the cache. It will be
-  // rebuilt on the next layout.
-  InvalidatePositioningValues(layout_invalidation_reason::kChildChanged);
-  SetNeedsTextMetricsUpdate();
-}
-
-void LayoutSVGText::SubtreeTextDidChange() {
-  DCHECK(!BeingDestroyed());
-  if (!EverHadLayout()) {
-    DCHECK(descendant_text_nodes_.IsEmpty());
-    return;
-  }
-
-  // The positioning elements cache depends on the size of each text object in
-  // the subtree. If this changes, clear the cache and mark it for rebuilding
-  // in the next layout.
-  InvalidatePositioningValues(layout_invalidation_reason::kTextChanged);
-  SetNeedsTextMetricsUpdate();
+void LayoutSVGText::NotifySubtreeStructureChanged(
+    LayoutObject* object,
+    LayoutInvalidationReasonForTracing reason) {
+  if (LayoutSVGText* layout_text = LocateLayoutSVGTextAncestor(object))
+    layout_text->SubtreeStructureChanged(reason);
 }
 
 static inline void UpdateFontAndMetrics(LayoutSVGText& text_root) {
@@ -429,12 +405,12 @@
   LayoutSVGBlock::AddChild(child, before_child);
 
   SVGResourcesCache::ClientWasAddedToTree(*child);
-  SubtreeChildWasAdded();
+  SubtreeStructureChanged(layout_invalidation_reason::kChildChanged);
 }
 
 void LayoutSVGText::RemoveChild(LayoutObject* child) {
   SVGResourcesCache::ClientWillBeRemovedFromTree(*child);
-  SubtreeChildWillBeRemoved();
+  SubtreeStructureChanged(layout_invalidation_reason::kChildChanged);
 
   LayoutSVGBlock::RemoveChild(child);
 }
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_text.h b/third_party/blink/renderer/core/layout/svg/layout_svg_text.h
index 2360fe04..c5ebeb9 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_text.h
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_text.h
@@ -53,15 +53,14 @@
   static LayoutSVGText* LocateLayoutSVGTextAncestor(LayoutObject*);
   static const LayoutSVGText* LocateLayoutSVGTextAncestor(const LayoutObject*);
 
+  static void NotifySubtreeStructureChanged(LayoutObject*,
+                                            LayoutInvalidationReasonForTracing);
+
   bool NeedsReordering() const { return needs_reordering_; }
   const Vector<LayoutSVGInlineText*>& DescendantTextNodes() const {
     return descendant_text_nodes_;
   }
 
-  void SubtreeChildWasAdded();
-  void SubtreeChildWillBeRemoved();
-  void SubtreeTextDidChange();
-
   void RecalcVisualOverflow() override;
 
   const char* GetName() const override { return "LayoutSVGText"; }
@@ -94,7 +93,7 @@
 
   RootInlineBox* CreateRootInlineBox() override;
 
-  void InvalidatePositioningValues(LayoutInvalidationReasonForTracing);
+  void SubtreeStructureChanged(LayoutInvalidationReasonForTracing);
 
   bool needs_reordering_ : 1;
   bool needs_positioning_values_update_ : 1;
diff --git a/third_party/blink/renderer/core/layout/svg/svg_resources.cc b/third_party/blink/renderer/core/layout/svg/svg_resources.cc
index b64f055..703d828 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_resources.cc
+++ b/third_party/blink/renderer/core/layout/svg/svg_resources.cc
@@ -31,6 +31,8 @@
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/core/style/reference_clip_path_operation.h"
 #include "third_party/blink/renderer/core/style/style_svg_resource.h"
+#include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
+#include "third_party/blink/renderer/core/svg/svg_filter_primitive_standard_attributes.h"
 #include "third_party/blink/renderer/core/svg/svg_pattern_element.h"
 #include "third_party/blink/renderer/core/svg/svg_resource.h"
 #include "third_party/blink/renderer/core/svg/svg_tree_scope_resources.h"
@@ -256,8 +258,8 @@
     return 0;
   InvalidationModeMask invalidation_flags =
       SVGResourceClient::kBoundariesInvalidation;
-  if (LayoutSVGResourceFilter* filter = clipper_filter_masker_data_->filter) {
-    if (filter->RemoveClientFromCache(client))
+  if (clipper_filter_masker_data_->filter) {
+    if (client.ClearFilterData())
       invalidation_flags |= SVGResourceClient::kPaintInvalidation;
   }
   return invalidation_flags;
@@ -669,9 +671,7 @@
   LayoutSVGResourceContainer::MarkClientForInvalidation(*layout_object,
                                                         invalidation_mask);
 
-  // Special case for filter invalidation.
-  if (invalidation_mask & SVGResourceClient::kSkipAncestorInvalidation)
-    return;
+  ClearFilterData();
 
   bool needs_layout =
       invalidation_mask & SVGResourceClient::kLayoutInvalidation;
@@ -680,8 +680,10 @@
 }
 
 void SVGElementResourceClient::ResourceElementChanged() {
-  if (LayoutObject* layout_object = element_->GetLayoutObject())
+  if (LayoutObject* layout_object = element_->GetLayoutObject()) {
+    ClearFilterData();
     SVGResourcesCache::ResourceReferenceChanged(*layout_object);
+  }
 }
 
 void SVGElementResourceClient::ResourceDestroyed(
@@ -695,8 +697,38 @@
     resources->ResourceDestroyed(resource);
 }
 
+void SVGElementResourceClient::FilterPrimitiveChanged(
+    SVGFilterPrimitiveStandardAttributes& primitive,
+    const QualifiedName& attribute) {
+  if (filter_data_ && filter_data_->state_ == FilterData::kReadyToPaint) {
+    SVGFilterGraphNodeMap* node_map = filter_data_->node_map;
+    if (FilterEffect* effect = node_map->EffectForElement(primitive)) {
+      if (!primitive.SetFilterEffectAttribute(effect, attribute))
+        return;  // No change
+      node_map->InvalidateDependentEffects(effect);
+    }
+  }
+  LayoutObject* layout_object = element_->GetLayoutObject();
+  if (!layout_object)
+    return;
+  LayoutSVGResourceContainer::MarkClientForInvalidation(
+      *layout_object, SVGResourceClient::kPaintInvalidation);
+}
+
+void SVGElementResourceClient::SetFilterData(FilterData* filter_data) {
+  filter_data_ = filter_data;
+}
+
+bool SVGElementResourceClient::ClearFilterData() {
+  FilterData* filter_data = filter_data_.Release();
+  if (filter_data)
+    filter_data->Dispose();
+  return !!filter_data;
+}
+
 void SVGElementResourceClient::Trace(Visitor* visitor) {
   visitor->Trace(element_);
+  visitor->Trace(filter_data_);
   SVGResourceClient::Trace(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/layout/svg/svg_resources.h b/third_party/blink/renderer/core/layout/svg/svg_resources.h
index 740bbb9..a27e7f8 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_resources.h
+++ b/third_party/blink/renderer/core/layout/svg/svg_resources.h
@@ -32,6 +32,7 @@
 namespace blink {
 
 class ComputedStyle;
+class FilterData;
 class LayoutObject;
 class LayoutSVGResourceClipper;
 class LayoutSVGResourceFilter;
@@ -202,10 +203,18 @@
   void ResourceElementChanged() override;
   void ResourceDestroyed(LayoutSVGResourceContainer*) override;
 
+  void FilterPrimitiveChanged(SVGFilterPrimitiveStandardAttributes& primitive,
+                              const QualifiedName& attribute) override;
+
+  void SetFilterData(FilterData*);
+  bool ClearFilterData();
+  FilterData* GetFilterData() const { return filter_data_; }
+
   void Trace(Visitor*) override;
 
  private:
   Member<SVGElement> element_;
+  Member<FilterData> filter_data_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/chrome_client.h b/third_party/blink/renderer/core/page/chrome_client.h
index 8e520b6..4ae4efd8 100644
--- a/third_party/blink/renderer/core/page/chrome_client.h
+++ b/third_party/blink/renderer/core/page/chrome_client.h
@@ -355,6 +355,9 @@
   virtual void FullscreenElementChanged(Element* old_element,
                                         Element* new_element) {}
 
+  virtual void AnimateDoubleTapZoom(const gfx::Point& point,
+                                    const gfx::Rect& rect) {}
+
   virtual void ClearLayerSelection(LocalFrame*) {}
   virtual void UpdateLayerSelection(LocalFrame*, const cc::LayerSelection&) {}
 
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl.cc b/third_party/blink/renderer/core/page/chrome_client_impl.cc
index ea88d33e..3f256bb 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl.cc
+++ b/third_party/blink/renderer/core/page/chrome_client_impl.cc
@@ -821,6 +821,11 @@
   web_view_->FullscreenElementChanged(old_element, new_element);
 }
 
+void ChromeClientImpl::AnimateDoubleTapZoom(const gfx::Point& point,
+                                            const gfx::Rect& rect) {
+  web_view_->AnimateDoubleTapZoom(point, WebRect(rect));
+}
+
 void ChromeClientImpl::ClearLayerSelection(LocalFrame* frame) {
   WebFrameWidgetBase* widget =
       WebLocalFrameImpl::FromFrame(frame)->LocalRootFrameWidget();
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl.h b/third_party/blink/renderer/core/page/chrome_client_impl.h
index af5642b..494d1b88 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl.h
+++ b/third_party/blink/renderer/core/page/chrome_client_impl.h
@@ -183,6 +183,9 @@
   void FullscreenElementChanged(Element* old_element,
                                 Element* new_element) override;
 
+  void AnimateDoubleTapZoom(const gfx::Point& point,
+                            const gfx::Rect& rect) override;
+
   void ClearLayerSelection(LocalFrame*) override;
   void UpdateLayerSelection(LocalFrame*, const cc::LayerSelection&) override;
 
diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/renderer/core/page/page.cc
index 70905ed7..8ff83ef 100644
--- a/third_party/blink/renderer/core/page/page.cc
+++ b/third_party/blink/renderer/core/page/page.cc
@@ -30,6 +30,7 @@
 #include "third_party/blink/renderer/core/css/media_feature_overrides.h"
 #include "third_party/blink/renderer/core/css/style_change_reason.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
+#include "third_party/blink/renderer/core/css/vision_deficiency.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
 #include "third_party/blink/renderer/core/dom/visited_link_state.h"
 #include "third_party/blink/renderer/core/editing/drag_caret.h"
@@ -799,6 +800,11 @@
       }
       break;
     }
+    case SettingsDelegate::kVisionDeficiencyChange: {
+      if (auto* main_local_frame = DynamicTo<LocalFrame>(MainFrame()))
+        main_local_frame->GetDocument()->VisionDeficiencyChanged();
+      break;
+    }
   }
 }
 
@@ -1069,6 +1075,13 @@
   SettingsChanged(SettingsDelegate::kColorSchemeChange);
 }
 
+void Page::SetVisionDeficiency(VisionDeficiency new_vision_deficiency) {
+  if (new_vision_deficiency != vision_deficiency_) {
+    vision_deficiency_ = new_vision_deficiency;
+    SettingsChanged(SettingsDelegate::kVisionDeficiencyChange);
+  }
+}
+
 Page::PageClients::PageClients() : chrome_client(nullptr) {}
 
 template class CORE_TEMPLATE_EXPORT Supplement<Page>;
diff --git a/third_party/blink/renderer/core/page/page.h b/third_party/blink/renderer/core/page/page.h
index 7f5b14a..ee101e42 100644
--- a/third_party/blink/renderer/core/page/page.h
+++ b/third_party/blink/renderer/core/page/page.h
@@ -31,6 +31,7 @@
 #include "third_party/blink/public/platform/web_text_autosizer_page_info.h"
 #include "third_party/blink/public/web/web_window_features.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/css/vision_deficiency.h"
 #include "third_party/blink/renderer/core/frame/deprecation.h"
 #include "third_party/blink/renderer/core/frame/hosts_using_features.h"
 #include "third_party/blink/renderer/core/frame/settings_delegate.h"
@@ -342,6 +343,9 @@
   }
   void ClearMediaFeatureOverrides();
 
+  void SetVisionDeficiency(VisionDeficiency new_vision_deficiency);
+  VisionDeficiency GetVisionDeficiency() const { return vision_deficiency_; }
+
   WebScopedVirtualTimePauser& HistoryNavigationVirtualTimePauser() {
     return history_navigation_virtual_time_pauser_;
   }
@@ -454,9 +458,12 @@
 
   std::unique_ptr<PageScheduler> page_scheduler_;
 
-  // Overrides for various media features set from the devtools.
+  // Overrides for various media features, set from DevTools.
   std::unique_ptr<MediaFeatureOverrides> media_feature_overrides_;
 
+  // Emulated vision deficiency, set from DevTools.
+  VisionDeficiency vision_deficiency_ = VisionDeficiency::kNoVisionDeficiency;
+
   int32_t autoplay_flags_;
 
   // Accessed by frames to determine whether to expose the PortalHost object.
diff --git a/third_party/blink/renderer/core/paint/svg_filter_painter.cc b/third_party/blink/renderer/core/paint/svg_filter_painter.cc
index 5ee6960..0917fbf 100644
--- a/third_party/blink/renderer/core/paint/svg_filter_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_filter_painter.cc
@@ -87,7 +87,7 @@
   filter_.ClearInvalidationMask();
 
   SVGElementResourceClient* client = SVGResources::GetClient(object);
-  if (FilterData* filter_data = filter_.GetFilterDataForClient(client)) {
+  if (FilterData* filter_data = client->GetFilterData()) {
     // If the filterData already exists we do not need to record the content
     // to be filtered. This can occur if the content was previously recorded
     // or we are in a cycle.
@@ -117,7 +117,7 @@
   DCHECK_EQ(filter_data->state_, FilterData::kInitial);
 
   // TODO(pdr): Can this be moved out of painter?
-  filter_.SetFilterDataForClient(client, filter_data);
+  client->SetFilterData(filter_data);
   filter_data->state_ = FilterData::kRecordingContent;
   return recording_context.BeginContent();
 }
@@ -127,7 +127,7 @@
     const DisplayItemClient& display_item_client,
     SVGFilterRecordingContext& recording_context) {
   SVGElementResourceClient* client = SVGResources::GetClient(object);
-  FilterData* filter_data = filter_.GetFilterDataForClient(client);
+  FilterData* filter_data = client->GetFilterData();
   if (!filter_data) {
     // Our state was torn down while we were being painted (selection style for
     // <text> can have this effect), or it was never created (invalid filter.)
diff --git a/third_party/blink/renderer/core/svg/svg_element.cc b/third_party/blink/renderer/core/svg/svg_element.cc
index be5a681..c2b425c 100644
--- a/third_party/blink/renderer/core/svg/svg_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_element.cc
@@ -620,6 +620,12 @@
     return;
   }
 
+  // SVGElement and HTMLElement are handling "nonce" the same way.
+  if (params.name == html_names::kNonceAttr) {
+    if (params.new_value != g_empty_atom)
+      setNonce(params.new_value);
+  }
+
   const AtomicString& event_name =
       HTMLElement::EventNameForAttributeName(params.name);
   if (!event_name.IsNull()) {
diff --git a/third_party/blink/renderer/core/svg/svg_filter_element.cc b/third_party/blink/renderer/core/svg/svg_filter_element.cc
index 7e6bc33..510b134 100644
--- a/third_party/blink/renderer/core/svg/svg_filter_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_filter_element.cc
@@ -113,18 +113,12 @@
 void SVGFilterElement::PrimitiveAttributeChanged(
     SVGFilterPrimitiveStandardAttributes& primitive,
     const QualifiedName& attribute) {
-  if (LayoutObject* layout_object = GetLayoutObject()) {
-    ToLayoutSVGResourceFilter(layout_object)
-        ->PrimitiveAttributeChanged(primitive, attribute);
-  } else if (LocalSVGResource* resource = AssociatedResource()) {
-    resource->NotifyContentChanged(SVGResourceClient::kPaintInvalidation);
-  }
+  if (LocalSVGResource* resource = AssociatedResource())
+    resource->NotifyFilterPrimitiveChanged(primitive, attribute);
 }
 
 void SVGFilterElement::InvalidateFilterChain() {
-  if (LayoutObject* layout_object = GetLayoutObject()) {
-    ToLayoutSVGResourceFilter(layout_object)->RemoveAllClientsFromCache();
-  } else if (LocalSVGResource* resource = AssociatedResource()) {
+  if (LocalSVGResource* resource = AssociatedResource()) {
     resource->NotifyContentChanged(SVGResourceClient::kLayoutInvalidation |
                                    SVGResourceClient::kBoundariesInvalidation);
   }
diff --git a/third_party/blink/renderer/core/svg/svg_resource.cc b/third_party/blink/renderer/core/svg/svg_resource.cc
index 9421f73..9cf40b6 100644
--- a/third_party/blink/renderer/core/svg/svg_resource.cc
+++ b/third_party/blink/renderer/core/svg/svg_resource.cc
@@ -81,6 +81,16 @@
     client->ResourceContentChanged(invalidation_mask);
 }
 
+void LocalSVGResource::NotifyFilterPrimitiveChanged(
+    SVGFilterPrimitiveStandardAttributes& primitive,
+    const QualifiedName& attribute) {
+  HeapVector<Member<SVGResourceClient>> clients;
+  CopyToVector(clients_, clients);
+
+  for (SVGResourceClient* client : clients)
+    client->FilterPrimitiveChanged(primitive, attribute);
+}
+
 void LocalSVGResource::NotifyResourceAttached(
     LayoutSVGResourceContainer& attached_resource) {
   // Checking the element here because
@@ -129,7 +139,22 @@
   options.initiator_info.name = fetch_initiator_type_names::kCSS;
   FetchParameters params(ResourceRequest(url_), options);
   params.MutableResourceRequest().SetMode(
-      network::mojom::RequestMode::kSameOrigin);
+      network::mojom::blink::RequestMode::kSameOrigin);
+  resource_document_ =
+      DocumentResource::FetchSVGDocument(params, document.Fetcher(), this);
+  target_ = ResolveTarget();
+}
+
+void ExternalSVGResource::LoadWithoutCSP(const Document& document) {
+  if (resource_document_)
+    return;
+  ResourceLoaderOptions options;
+  options.initiator_info.name = fetch_initiator_type_names::kCSS;
+  FetchParameters params(ResourceRequest(url_), options);
+  params.SetContentSecurityCheck(
+      network::mojom::blink::CSPDisposition::DO_NOT_CHECK);
+  params.MutableResourceRequest().SetMode(
+      network::mojom::blink::RequestMode::kSameOrigin);
   resource_document_ =
       DocumentResource::FetchSVGDocument(params, document.Fetcher(), this);
   target_ = ResolveTarget();
diff --git a/third_party/blink/renderer/core/svg/svg_resource.h b/third_party/blink/renderer/core/svg/svg_resource.h
index f71d94de..aab5fd2 100644
--- a/third_party/blink/renderer/core/svg/svg_resource.h
+++ b/third_party/blink/renderer/core/svg/svg_resource.h
@@ -64,6 +64,7 @@
   virtual ~SVGResource();
 
   virtual void Load(const Document&) {}
+  virtual void LoadWithoutCSP(const Document&) {}
 
   Element* Target() const { return target_; }
   LayoutSVGResourceContainer* ResourceContainer() const;
@@ -93,6 +94,9 @@
   void Unregister();
 
   void NotifyContentChanged(InvalidationModeMask);
+  void NotifyFilterPrimitiveChanged(
+      SVGFilterPrimitiveStandardAttributes& primitive,
+      const QualifiedName& attribute);
 
   void NotifyResourceAttached(LayoutSVGResourceContainer&);
   void NotifyResourceDestroyed(LayoutSVGResourceContainer&);
@@ -114,6 +118,7 @@
   explicit ExternalSVGResource(const KURL&);
 
   void Load(const Document&) override;
+  void LoadWithoutCSP(const Document&) override;
 
   void Trace(Visitor*) override;
 
diff --git a/third_party/blink/renderer/core/svg/svg_resource_client.h b/third_party/blink/renderer/core/svg/svg_resource_client.h
index 60ac325..1b5a587 100644
--- a/third_party/blink/renderer/core/svg/svg_resource_client.h
+++ b/third_party/blink/renderer/core/svg/svg_resource_client.h
@@ -11,6 +11,8 @@
 namespace blink {
 
 class LayoutSVGResourceContainer;
+class QualifiedName;
+class SVGFilterPrimitiveStandardAttributes;
 
 typedef unsigned InvalidationModeMask;
 
@@ -24,12 +26,17 @@
     kLayoutInvalidation = 1 << 0,
     kBoundariesInvalidation = 1 << 1,
     kPaintInvalidation = 1 << 2,
-    kSkipAncestorInvalidation = 1 << 3,
   };
   virtual void ResourceContentChanged(InvalidationModeMask) = 0;
   virtual void ResourceElementChanged() = 0;
   virtual void ResourceDestroyed(LayoutSVGResourceContainer*) {}
 
+  virtual void FilterPrimitiveChanged(
+      SVGFilterPrimitiveStandardAttributes& primitive,
+      const QualifiedName& attribute) {
+    ResourceContentChanged(kPaintInvalidation);
+  }
+
  protected:
   SVGResourceClient() = default;
 };
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 6e2672e8..2191278 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -678,6 +678,8 @@
     "fonts/shaping/shape_result_view.h",
     "fonts/shaping/shaping_line_breaker.cc",
     "fonts/shaping/shaping_line_breaker.h",
+    "fonts/shaping/stretchy_operator_shaper.cc",
+    "fonts/shaping/stretchy_operator_shaper.h",
     "fonts/simple_font_data.cc",
     "fonts/simple_font_data.h",
     "fonts/skia/font_cache_skia.cc",
@@ -1777,6 +1779,7 @@
     "fonts/shaping/shape_result_test.cc",
     "fonts/shaping/shape_result_view_test.cc",
     "fonts/shaping/shaping_line_breaker_test.cc",
+    "fonts/shaping/stretchy_operator_shaper_test.cc",
     "fonts/small_caps_iterator_test.cc",
     "fonts/symbols_iterator_test.cc",
     "fonts/typesetting_features_test.cc",
diff --git a/third_party/blink/renderer/platform/fonts/opentype/open_type_math_stretch_data.h b/third_party/blink/renderer/platform/fonts/opentype/open_type_math_stretch_data.h
index 0799e41..af3cc0599 100644
--- a/third_party/blink/renderer/platform/fonts/opentype/open_type_math_stretch_data.h
+++ b/third_party/blink/renderer/platform/fonts/opentype/open_type_math_stretch_data.h
@@ -8,6 +8,7 @@
 #include "base/optional.h"
 #include "third_party/blink/renderer/platform/fonts/glyph.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 
@@ -28,6 +29,15 @@
     float full_advance;
     bool is_extender;
   };
+
+  // https://mathml-refresh.github.io/mathml-core/#the-glyphassembly-table
+  struct AssemblyParameters {
+    float connector_overlap{0};
+    unsigned repetition_count{0};
+    unsigned glyph_count{0};
+    float stretch_size{0};
+    Vector<GlyphPartRecord> parts;
+  };
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/fonts/opentype/open_type_math_support_test.cc b/third_party/blink/renderer/platform/fonts/opentype/open_type_math_support_test.cc
index af881c7..8852fc0 100644
--- a/third_party/blink/renderer/platform/fonts/opentype/open_type_math_support_test.cc
+++ b/third_party/blink/renderer/platform/fonts/opentype/open_type_math_support_test.cc
@@ -297,7 +297,8 @@
   auto over_brace = math.PrimaryFont()->GlyphForCharacter(kOverBraceCodePoint);
 
   // Calculate glyph indices from the last unicode character in the font.
-  // TODO(fwang): Find a better way to access these glyph indices.
+  // TODO(https://crbug.com/1057596): Find a better way to access these glyph
+  // indices.
   auto v0 = math.PrimaryFont()->GlyphForCharacter(
                 kArabicMathOperatorHahWithDalCodePoint) +
             1;
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc b/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc
index 3a73c9a9..f801778 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc
@@ -1450,6 +1450,96 @@
   return result;
 }
 
+scoped_refptr<ShapeResult> ShapeResult::CreateForStretchyMathOperator(
+    const Font* font,
+    TextDirection direction,
+    OpenTypeMathStretchData::StretchAxis stretch_axis,
+    Glyph glyph_variant,
+    float stretch_size) {
+  bool is_horizontal_assembly =
+      stretch_axis == OpenTypeMathStretchData::StretchAxis::Horizontal;
+  unsigned start_index = 0;
+  unsigned num_characters = 1;
+  scoped_refptr<ShapeResult> result =
+      ShapeResult::Create(font, start_index, num_characters, direction);
+
+  hb_direction_t hb_direction =
+      is_horizontal_assembly ? HB_DIRECTION_LTR : HB_DIRECTION_TTB;
+  unsigned glyph_index = 0;
+  scoped_refptr<ShapeResult::RunInfo> run = RunInfo::Create(
+      font->PrimaryFont(), hb_direction, CanvasRotationInVertical::kRegular,
+      HB_SCRIPT_COMMON, start_index, 1 /* num_glyph */, num_characters);
+  run->glyph_data_[glyph_index] = {glyph_variant, 0 /* character index */,
+                                   true /* IsSafeToBreakBefore */,
+                                   stretch_size};
+  run->width_ = std::max(0.0f, stretch_size);
+
+  result->width_ = run->width_;
+  result->num_glyphs_ = run->NumGlyphs();
+  result->runs_.push_back(std::move(run));
+
+  return result;
+}
+
+scoped_refptr<ShapeResult> ShapeResult::CreateForStretchyMathOperator(
+    const Font* font,
+    TextDirection direction,
+    OpenTypeMathStretchData::StretchAxis stretch_axis,
+    const OpenTypeMathStretchData::AssemblyParameters& assembly_parameters) {
+  DCHECK(!assembly_parameters.parts.IsEmpty());
+  DCHECK_LE(assembly_parameters.glyph_count, HarfBuzzRunGlyphData::kMaxGlyphs);
+
+  bool is_horizontal_assembly =
+      stretch_axis == OpenTypeMathStretchData::StretchAxis::Horizontal;
+  unsigned start_index = 0;
+  unsigned num_characters = 1;
+  scoped_refptr<ShapeResult> result =
+      ShapeResult::Create(font, start_index, num_characters, direction);
+
+  hb_direction_t hb_direction =
+      is_horizontal_assembly ? HB_DIRECTION_LTR : HB_DIRECTION_TTB;
+  scoped_refptr<ShapeResult::RunInfo> run = RunInfo::Create(
+      font->PrimaryFont(), hb_direction, CanvasRotationInVertical::kRegular,
+      HB_SCRIPT_COMMON, start_index, assembly_parameters.glyph_count,
+      num_characters);
+
+  float overlap = assembly_parameters.connector_overlap;
+  unsigned part_index = 0;
+  for (const auto& part : assembly_parameters.parts) {
+    unsigned repetition_count =
+        part.is_extender ? assembly_parameters.repetition_count : 1;
+    if (!repetition_count)
+      continue;
+    DCHECK(part_index < assembly_parameters.glyph_count);
+    for (unsigned repetition_index = 0; repetition_index < repetition_count;
+         repetition_index++) {
+      unsigned glyph_index =
+          is_horizontal_assembly
+              ? part_index
+              : assembly_parameters.glyph_count - 1 - part_index;
+      float full_advance = glyph_index == assembly_parameters.glyph_count - 1
+                               ? part.full_advance
+                               : part.full_advance - overlap;
+      run->glyph_data_[glyph_index] = {part.glyph, 0 /* character index */,
+                                       !glyph_index /* IsSafeToBreakBefore */,
+                                       full_advance};
+      if (!is_horizontal_assembly) {
+        GlyphOffset glyph_offset(
+            0, -assembly_parameters.stretch_size + part.full_advance);
+        run->glyph_data_.SetOffsetAt(glyph_index, glyph_offset);
+        result->has_vertical_offsets_ |= (glyph_offset.Height() != 0);
+      }
+      part_index++;
+    }
+  }
+  run->width_ = std::max(0.0f, assembly_parameters.stretch_size);
+
+  result->width_ = run->width_;
+  result->num_glyphs_ = run->NumGlyphs();
+  result->runs_.push_back(std::move(run));
+  return result;
+}
+
 void ShapeResult::ToString(StringBuilder* output) const {
   output->Append("#chars=");
   output->AppendNumber(num_characters_);
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
index eeb7e99..d63fdd6 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
@@ -35,6 +35,7 @@
 #include "base/containers/span.h"
 #include "third_party/blink/renderer/platform/fonts/canvas_rotation_in_vertical.h"
 #include "third_party/blink/renderer/platform/fonts/glyph.h"
+#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_stretch_data.h"
 #include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
 #include "third_party/blink/renderer/platform/geometry/layout_unit.h"
@@ -142,6 +143,17 @@
                                                     unsigned start_index,
                                                     unsigned length,
                                                     float width);
+  static scoped_refptr<ShapeResult> CreateForStretchyMathOperator(
+      const Font*,
+      TextDirection,
+      OpenTypeMathStretchData::StretchAxis,
+      Glyph,
+      float stretch_size);
+  static scoped_refptr<ShapeResult> CreateForStretchyMathOperator(
+      const Font*,
+      TextDirection,
+      OpenTypeMathStretchData::StretchAxis,
+      const OpenTypeMathStretchData::AssemblyParameters&);
   ~ShapeResult();
 
   // Returns a mutable unique instance. If |this| has more than 1 ref count,
@@ -495,6 +507,7 @@
   friend class ShapeResultBloberizer;
   friend class ShapeResultView;
   friend class ShapeResultTest;
+  friend class StretchyOperatorShaper;
 
   template <bool has_non_zero_glyph_offsets>
   float ForEachGlyphImpl(float initial_advance,
diff --git a/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper.cc b/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper.cc
new file mode 100644
index 0000000..99c80b3
--- /dev/null
+++ b/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper.cc
@@ -0,0 +1,223 @@
+// 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/platform/fonts/shaping/stretchy_operator_shaper.h"
+
+#include <hb-ot.h>
+#include <hb.h>
+#include <unicode/uchar.h>
+
+#include "third_party/blink/renderer/platform/fonts/canvas_rotation_in_vertical.h"
+#include "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h"
+#include "third_party/blink/renderer/platform/geometry/float_rect.h"
+#include "third_party/blink/renderer/platform/wtf/text/unicode.h"
+#include "ui/gfx/skia_util.h"
+
+namespace blink {
+
+namespace {
+
+// HarfBuzz' hb_position_t is a 16.16 fixed-point value.
+inline float HarfBuzzUnitsToFloat(hb_position_t value) {
+  static const float kFloatToHbRatio = 1.0f / (1 << 16);
+  return kFloatToHbRatio * value;
+}
+
+inline float GetGlyphStretchSize(
+    FloatRect bounds,
+    OpenTypeMathStretchData::StretchAxis stretch_axis) {
+  return stretch_axis == OpenTypeMathStretchData::StretchAxis::Horizontal
+             ? bounds.Width()
+             : bounds.Height();
+}
+
+inline StretchyOperatorShaper::Metrics ToMetrics(FloatRect bounds) {
+  return {bounds.Width(), -bounds.Y(), bounds.MaxY()};
+}
+
+base::Optional<OpenTypeMathStretchData::AssemblyParameters>
+GetAssemblyParameters(const HarfBuzzFace* harfbuzz_face,
+                      Glyph base_glyph,
+                      OpenTypeMathStretchData::StretchAxis stretch_axis,
+                      float target_size) {
+  Vector<OpenTypeMathStretchData::GlyphPartRecord> parts =
+      OpenTypeMathSupport::GetGlyphPartRecords(harfbuzz_face, base_glyph,
+                                               stretch_axis);
+  if (parts.IsEmpty())
+    return base::nullopt;
+
+  hb_font_t* hb_font =
+      harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::NoVerticalLayout);
+
+  auto hb_stretch_axis =
+      stretch_axis == OpenTypeMathStretchData::StretchAxis::Horizontal
+          ? HB_DIRECTION_LTR
+          : HB_DIRECTION_BTT;
+
+  // Go over the assembly parts and determine parameters used below.
+  // https://mathml-refresh.github.io/mathml-core/#the-glyphassembly-table
+  float min_connector_overlap = HarfBuzzUnitsToFloat(
+      hb_ot_math_get_min_connector_overlap(hb_font, hb_stretch_axis));
+  float max_connector_overlap = std::numeric_limits<float>::max();
+  float non_extender_advance_sum = 0, extender_advance_sum = 0;
+  unsigned non_extender_count = 0, extender_count = 0;
+
+  for (auto& part : parts) {
+    // Calculate the count and advance sums of extender and non-extender glyphs.
+    if (part.is_extender) {
+      extender_count++;
+      extender_advance_sum += part.full_advance;
+    } else {
+      non_extender_count++;
+      non_extender_advance_sum += part.full_advance;
+    }
+
+    // Take into account start connector length for all but the first glyph.
+    if (part.is_extender || &part != &parts.front()) {
+      max_connector_overlap =
+          std::min(max_connector_overlap, part.start_connector_length);
+    }
+
+    // Take into account end connector length for all but the last glyph.
+    if (part.is_extender || &part != &parts.back()) {
+      max_connector_overlap =
+          std::min(max_connector_overlap, part.end_connector_length);
+    }
+  }
+
+  // Check validity conditions indicated in MathML core.
+  float extender_non_overlapping_advance_sum =
+      extender_advance_sum - min_connector_overlap * extender_count;
+  if (extender_count == 0 || max_connector_overlap < min_connector_overlap ||
+      extender_non_overlapping_advance_sum <= 0)
+    return base::nullopt;
+
+  // Calculate the minimal number of repetitions needed to obtain an assembly
+  // size of size at least target size (r_min in MathML Core).
+  unsigned repetition_count = std::max<float>(
+      std::ceil((target_size - non_extender_advance_sum +
+                 min_connector_overlap * (non_extender_count - 1)) /
+                extender_non_overlapping_advance_sum),
+      0);
+
+  // Calculate the number of glyphs, limiting repetition_count to ensure the
+  // assembly does not have more than HarfBuzzRunGlyphData::kMaxGlyphs.
+  DCHECK_LE(non_extender_count, HarfBuzzRunGlyphData::kMaxGlyphs);
+  repetition_count = std::min<unsigned>(
+      repetition_count,
+      (HarfBuzzRunGlyphData::kMaxGlyphs - non_extender_count) / extender_count);
+  unsigned glyph_count = non_extender_count + repetition_count * extender_count;
+  DCHECK_LE(glyph_count, HarfBuzzRunGlyphData::kMaxGlyphs);
+
+  // Calculate the maximum overlap (called o_max in MathML Core) and the number
+  // of glyph in such an assembly (called N in MathML Core).
+  float connector_overlap = max_connector_overlap;
+  if (glyph_count > 1) {
+    float max_connector_overlap_theorical =
+        (non_extender_advance_sum + repetition_count * extender_advance_sum -
+         target_size) /
+        (glyph_count - 1);
+    connector_overlap =
+        std::max(min_connector_overlap,
+                 std::min(connector_overlap, max_connector_overlap_theorical));
+  }
+
+  // Calculate the assembly size (called  AssemblySize(o, r) in MathML Core).
+  float stretch_size = non_extender_advance_sum +
+                       repetition_count * extender_advance_sum -
+                       connector_overlap * (glyph_count - 1);
+
+  return base::Optional<OpenTypeMathStretchData::AssemblyParameters>(
+      {connector_overlap, repetition_count, glyph_count, stretch_size,
+       std::move(parts)});
+}
+
+}  // namespace
+
+StretchyOperatorShaper::Metrics StretchyOperatorShaper::GetMetrics(
+    const Font* font,
+    float target_size) const {
+  const SimpleFontData* primary_font = font->PrimaryFont();
+  const HarfBuzzFace* harfbuzz_face =
+      primary_font->PlatformData().GetHarfBuzzFace();
+  Glyph base_glyph = primary_font->GlyphForCharacter(stretchy_character_);
+
+  FloatRect bounds;
+
+  // Try different glyph variants.
+  for (auto& variant : OpenTypeMathSupport::GetGlyphVariantRecords(
+           harfbuzz_face, base_glyph, stretch_axis_)) {
+    bounds = primary_font->BoundsForGlyph(variant);
+    if (GetGlyphStretchSize(bounds, stretch_axis_) >= target_size)
+      return ToMetrics(bounds);
+  }
+
+  // Try a glyph assembly.
+  auto params = GetAssemblyParameters(harfbuzz_face, base_glyph, stretch_axis_,
+                                      target_size);
+  if (!params)
+    return ToMetrics(bounds);
+
+  bounds = stretch_axis_ == OpenTypeMathStretchData::StretchAxis::Horizontal
+               ? FloatRect(0, 0, params->stretch_size, 0)
+               : FloatRect(0, -params->stretch_size, 0, params->stretch_size);
+
+  for (auto& part : params->parts) {
+    // Include dimension of the part, orthogonal to the stretch axis.
+    auto glyph_bounds = primary_font->BoundsForGlyph(part.glyph);
+    if (stretch_axis_ == OpenTypeMathStretchData::StretchAxis::Horizontal) {
+      glyph_bounds.SetX(0);
+      glyph_bounds.SetWidth(0);
+    } else {
+      glyph_bounds.SetY(0);
+      glyph_bounds.SetHeight(0);
+    }
+    bounds.UniteEvenIfEmpty(glyph_bounds);
+  }
+
+  return ToMetrics(bounds);
+}
+
+scoped_refptr<ShapeResult> StretchyOperatorShaper::Shape(
+    const Font* font,
+    float target_size) const {
+  const SimpleFontData* primary_font = font->PrimaryFont();
+  const HarfBuzzFace* harfbuzz_face =
+      primary_font->PlatformData().GetHarfBuzzFace();
+  Glyph base_glyph = primary_font->GlyphForCharacter(stretchy_character_);
+
+  Glyph glyph_variant;
+  float glyph_variant_stretch_size;
+  TextDirection direction = TextDirection::kLtr;
+
+  // Try different glyph variants.
+  for (auto& variant : OpenTypeMathSupport::GetGlyphVariantRecords(
+           harfbuzz_face, base_glyph, stretch_axis_)) {
+    glyph_variant = variant;
+    auto bounds = primary_font->BoundsForGlyph(glyph_variant);
+    glyph_variant_stretch_size = GetGlyphStretchSize(bounds, stretch_axis_);
+    if (glyph_variant_stretch_size >= target_size) {
+      return ShapeResult::CreateForStretchyMathOperator(
+          font, direction, stretch_axis_, glyph_variant,
+          glyph_variant_stretch_size);
+    }
+  }
+
+  // Try a glyph assembly.
+  auto params = GetAssemblyParameters(harfbuzz_face, base_glyph, stretch_axis_,
+                                      target_size);
+  if (!params) {
+    return ShapeResult::CreateForStretchyMathOperator(
+        font, direction, stretch_axis_, glyph_variant,
+        glyph_variant_stretch_size);
+  }
+
+  return ShapeResult::CreateForStretchyMathOperator(
+      font, direction, stretch_axis_, std::move(*params));
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper.h b/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper.h
new file mode 100644
index 0000000..4df0eda
--- /dev/null
+++ b/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper.h
@@ -0,0 +1,59 @@
+// 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_PLATFORM_FONTS_SHAPING_STRETCHY_OPERATOR_SHAPER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_STRETCHY_OPERATOR_SHAPER_H_
+
+#include <base/memory/scoped_refptr.h>
+#include <unicode/uchar.h>
+#include "third_party/blink/renderer/platform/fonts/glyph.h"
+#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.h"
+#include "third_party/blink/renderer/platform/text/text_direction.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
+
+namespace blink {
+
+class Font;
+class ShapeResult;
+class StretchyOperatorShaper;
+
+// TODO(https://crbug.com/1057589): Add a TextDirection parameter, so that it's
+// possible to perform glyph-level (rtlm feature) or character-level mirroring
+// before stretching.
+// https://mathml-refresh.github.io/mathml-core/#algorithms-for-glyph-stretching
+class PLATFORM_EXPORT StretchyOperatorShaper final {
+  DISALLOW_NEW();
+
+ public:
+  StretchyOperatorShaper(UChar stretchy_character,
+                         OpenTypeMathStretchData::StretchAxis stretch_axis)
+      : stretchy_character_(stretchy_character), stretch_axis_(stretch_axis) {}
+
+  // Returns the metrics of the stretched operator for layout purpose.
+  // May be called multiple times; font and direction may vary between calls.
+  // https://mathml-refresh.github.io/mathml-core/#dfn-box-metrics-of-a-stretchy-glyph
+  struct Metrics {
+    float advance;
+    float ascent;
+    float descent;
+    // TODO(https://crbug.com/1057592): Add italic correction.
+  };
+  Metrics GetMetrics(const Font*, float target_size) const;
+
+  // Shape the stretched operator. The coordinates of the glyph(s) use the same
+  // origin as the rectangle returned by GetMetrics.
+  // May be called multiple times; font and direction may vary between calls.
+  // https://mathml-refresh.github.io/mathml-core/#dfn-shape-a-stretchy-glyph
+  scoped_refptr<ShapeResult> Shape(const Font*, float target_size) const;
+
+  ~StretchyOperatorShaper() = default;
+
+ private:
+  const UChar stretchy_character_;
+  const OpenTypeMathStretchData::StretchAxis stretch_axis_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_STRETCHY_OPERATOR_SHAPER_H_
diff --git a/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper_test.cc b/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper_test.cc
new file mode 100644
index 0000000..87b30e6
--- /dev/null
+++ b/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper_test.cc
@@ -0,0 +1,265 @@
+// 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/platform/fonts/shaping/stretchy_operator_shaper.h"
+#include "base/memory/scoped_refptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/fonts/opentype/open_type_types.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h"
+#include "third_party/blink/renderer/platform/testing/font_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+
+namespace blink {
+
+namespace {
+
+const UChar32 kLeftBraceCodePoint = '{';
+const UChar32 kOverBraceCodePoint = 0x23DE;
+const UChar32 kArabicMathOperatorHahWithDalCodePoint = 0x1EEF1;
+float kSizeError = .1;
+
+ShapeResultTestInfo* TestInfo(const scoped_refptr<ShapeResult>& result) {
+  return static_cast<ShapeResultTestInfo*>(result.get());
+}
+
+}  // namespace
+
+class StretchyOperatorShaperTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    font_description.SetComputedSize(10.0);
+    font = Font(font_description);
+    font.Update(nullptr);
+  }
+
+  void TearDown() override {}
+
+  Font CreateMathFont(const String& name, float size = 1000) {
+    FontDescription::VariantLigatures ligatures;
+    return blink::test::CreateTestFont(
+        "MathTestFont",
+        blink::test::BlinkWebTestsFontsTestDataPath(String("math/") + name),
+        size, &ligatures);
+  }
+
+  FontDescription font_description;
+  Font font;
+};
+
+// See createStretchy() in
+// third_party/blink/web_tests/external/wpt/mathml/tools/operator-dictionary.py
+TEST_F(StretchyOperatorShaperTest, GlyphVariants) {
+  Font math = CreateMathFont("operators.woff");
+
+  StretchyOperatorShaper vertical_shaper(
+      kLeftBraceCodePoint, OpenTypeMathStretchData::StretchAxis::Vertical);
+  StretchyOperatorShaper horizontal_shaper(
+      kOverBraceCodePoint, OpenTypeMathStretchData::StretchAxis::Horizontal);
+
+  auto left_brace = math.PrimaryFont()->GlyphForCharacter(kLeftBraceCodePoint);
+  auto over_brace = math.PrimaryFont()->GlyphForCharacter(kOverBraceCodePoint);
+
+  // Calculate glyph indices from the last unicode character in the font.
+  // TODO(https://crbug.com/1057596): Find a better way to access these glyph
+  // indices.
+  auto v0 = math.PrimaryFont()->GlyphForCharacter(
+                kArabicMathOperatorHahWithDalCodePoint) +
+            1;
+  auto h0 = v0 + 1;
+  auto v1 = h0 + 1;
+  auto h1 = v1 + 1;
+  auto v2 = h1 + 1;
+  auto h2 = v2 + 1;
+
+  // Stretch operators to target sizes (in font units) 125, 250, 375, 500, 625,
+  // 750, 875, 1000, 1125, ..., 3750, 3875, 4000.
+  //
+  // Shaper tries glyphs over_brace/left_brace, h0/v0, h1/v1, h2/v2, h3/v3 of
+  // respective sizes 1000, 1000, 2000, 3000 and 4000. It returns the smallest
+  // glyph larger than the target size.
+  const unsigned size_count = 4;
+  const unsigned subdivision = 8;
+  for (unsigned i = 0; i < size_count; i++) {
+    for (unsigned j = 1; j <= subdivision; j++) {
+      // Due to floating-point errors, the actual metrics of the size variants
+      // might actually be slightly smaller than expected. Reduce the
+      // target_size by kSizeError to ensure that the shaper picks the desired
+      // size variant.
+      float target_size = i * 1000 + (j * 1000 / subdivision) - kSizeError;
+
+      // Metrics of horizontal size variants.
+      {
+        auto metrics = horizontal_shaper.GetMetrics(&math, target_size);
+        EXPECT_NEAR(metrics.advance, (i + 1) * 1000, kSizeError);
+        EXPECT_NEAR(metrics.ascent, 1000, kSizeError);
+        EXPECT_FLOAT_EQ(metrics.descent, 0);
+      }
+
+      // Metrics of vertical size variants.
+
+      {
+        auto metrics = vertical_shaper.GetMetrics(&math, target_size);
+        EXPECT_NEAR(metrics.advance, 1000, kSizeError);
+        EXPECT_NEAR(metrics.ascent, (i + 1) * 1000, kSizeError);
+        EXPECT_FLOAT_EQ(metrics.descent, 0);
+      }
+
+      // Shaping of horizontal size variants.
+      {
+        scoped_refptr<ShapeResult> result =
+            horizontal_shaper.Shape(&math, target_size);
+        EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
+        EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(), 1u);
+        Glyph expected_variant = i ? h0 + 2 * i : over_brace;
+        EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, 0), expected_variant);
+        EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, 0), (i + 1) * 1000,
+                    kSizeError);
+      }
+
+      // Shaping of vertical size variants.
+      {
+        scoped_refptr<ShapeResult> result =
+            vertical_shaper.Shape(&math, target_size);
+        EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
+        EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(), 1u);
+        Glyph expected_variant = i ? v0 + 2 * i : left_brace;
+        EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, 0), expected_variant);
+        EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, 0), (i + 1) * 1000,
+                    kSizeError);
+      }
+    }
+  }
+
+  // Stretch an operator to target sizes (in font units) much larger than 4000.
+  //
+  // This will force an assembly with the following parts:
+  // _____________________________________________________________
+  // Part  | MaxStartOverlap | MaxEndOverlap | Advance | Extender |
+  // h2/v2 |     0           |    1000       | 3000    |   false  |
+  // h1/v1 |    1000         |    1000       | 2000    |   true   |
+  //
+  // For an assembly made of one non-extender glyph h2/v2 and repetition_count
+  // copies of extenders h1/v1, the size is
+  // advance(h2/v2) + repetition_count * (advance(h1/v1) - overlap).
+  //
+  // For repetition_count = k and overlap = 750, the size is X = 1250k + 3000.
+  //
+  // Since the font min overlap is 500, for repetition_count = k - 1 the size
+  // is at most Y = 1500k + 1500.
+  //
+  // Since the max overlap of parts is 1000, for repetition_count = k + 1 the
+  // size is at least Z = 1000k + 4000.
+  //
+  // { X - 4000 = 1250k - 1000 >= 250 >> kSizeError for k >= 1.
+  // { X - Y = 1500 - 250k >= 250 >> kSizeError for k <= 5.
+  // Hence setting the target size to 1250k + 3000 will ensure an assembly of
+  // k + 1 glyphs and overlap close to 750 for 1 <= k <= 5.
+  //
+  // Additionally, X - Z = 250k - 1000 = 250 >> kSizeError for k = 5 so this
+  // case also verifies that the minimal number of repetitions is actually used.
+  //
+  for (unsigned repetition_count = 1; repetition_count <= 5;
+       repetition_count++) {
+    // It is not necessary to decrease the target_size by kSizeError here. The
+    // shaper can just increase overlap by kSizeError / repetition_count to
+    // reduce the actual size of the assembly.
+    float overlap = 750;
+    float target_size = 3000 + repetition_count * (2000 - overlap);
+
+    // Metrics of horizontal assembly.
+    {
+      auto metrics = horizontal_shaper.GetMetrics(&math, target_size);
+      EXPECT_NEAR(metrics.advance, target_size, kSizeError);
+      EXPECT_NEAR(metrics.ascent, 1000, kSizeError);
+      EXPECT_FLOAT_EQ(metrics.descent, 0);
+    }
+
+    // Metrics of vertical assembly.
+    {
+      auto metrics = vertical_shaper.GetMetrics(&math, target_size);
+      EXPECT_NEAR(metrics.advance, 1000, kSizeError);
+      EXPECT_NEAR(metrics.ascent, target_size, kSizeError);
+      EXPECT_FLOAT_EQ(metrics.descent, 0);
+    }
+
+    // Shaping of horizontal assembly.
+    // From left to right: h2, h1, h1, h1, ...
+    {
+      scoped_refptr<ShapeResult> result =
+          horizontal_shaper.Shape(&math, target_size);
+
+      EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
+      EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(),
+                repetition_count + 1);
+      EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, 0), h2);
+      EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, 0), 3000 - overlap,
+                  kSizeError);
+      for (unsigned i = 0; i < repetition_count - 1; i++) {
+        EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, i + 1), h1);
+        EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, i + 1),
+                    2000 - overlap, kSizeError);
+      }
+      EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, repetition_count), h1);
+      EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, repetition_count),
+                  2000, kSizeError);
+    }
+
+    // Shaping of vertical assembly.
+    // From bottom to top: v2, v1, v1, v1, ...
+    {
+      scoped_refptr<ShapeResult> result =
+          vertical_shaper.Shape(&math, target_size);
+
+      EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
+      EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(),
+                repetition_count + 1);
+      for (unsigned i = 0; i < repetition_count; i++) {
+        EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, i), v1);
+        EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, i), 2000 - overlap,
+                    kSizeError);
+      }
+      EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, repetition_count), v2);
+      EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, repetition_count),
+                  3000, kSizeError);
+    }
+  }
+
+  // Stretch an operator to edge target size values.
+  //
+  // These tests verify that it does not cause any assertion or crashes.
+  {
+    // Zero.
+    float target_size = 0;
+    horizontal_shaper.Shape(&math, target_size);
+    vertical_shaper.Shape(&math, target_size);
+
+    // Negative.
+    target_size = -5500;
+    horizontal_shaper.Shape(&math, target_size);
+    vertical_shaper.Shape(&math, target_size);
+
+    // Max limit.
+    target_size = std::numeric_limits<float>::max();
+    horizontal_shaper.Shape(&math, target_size);
+    vertical_shaper.Shape(&math, target_size);
+
+    // Min limit.
+    target_size = std::numeric_limits<float>::min();
+    horizontal_shaper.Shape(&math, target_size);
+    vertical_shaper.Shape(&math, target_size);
+
+    // More than the max number of glyphs.
+    // The size of an assembly with one non-extender v2/h2 and k - 1 extenders
+    // h1/v1 and minimal overlap 500 is Y = 1500k + 1500.
+    // So target_size - Y >= 250 >> kSizeError if the assembly does not have
+    // more than the max number of glyphs.
+    target_size = 1500 * HarfBuzzRunGlyphData::kMaxGlyphs + 1750;
+    horizontal_shaper.Shape(&math, target_size);
+    vertical_shaper.Shape(&math, target_size);
+  }
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/heap/collection_support/heap_hash_table_backing.h b/third_party/blink/renderer/platform/heap/collection_support/heap_hash_table_backing.h
index 0ebd9664..c6ee2e6c 100644
--- a/third_party/blink/renderer/platform/heap/collection_support/heap_hash_table_backing.h
+++ b/third_party/blink/renderer/platform/heap/collection_support/heap_hash_table_backing.h
@@ -118,9 +118,6 @@
 
   template <WTF::WeakHandlingFlag WeakHandling = WTF::kNoWeakHandling>
   static void Trace(Visitor* visitor, void* self) {
-    if (visitor->ConcurrentTracingBailOut({self, &Trace}))
-      return;
-
     static_assert(WTF::IsTraceableInCollectionTrait<Traits>::value ||
                       WTF::IsWeak<ValueType>::value,
                   "T should not be traced");
diff --git a/third_party/blink/renderer/platform/heap/heap.cc b/third_party/blink/renderer/platform/heap/heap.cc
index 7fe59a2..09ffccc 100644
--- a/third_party/blink/renderer/platform/heap/heap.cc
+++ b/third_party/blink/renderer/platform/heap/heap.cc
@@ -261,39 +261,6 @@
   }
 }
 
-void ThreadHeap::InvokeEphemeronCallbacks(MarkingVisitor* visitor) {
-  // Mark any strong pointers that have now become reachable in ephemeron maps.
-  ThreadHeapStatsCollector::Scope stats_scope(
-      stats_collector(),
-      ThreadHeapStatsCollector::kMarkInvokeEphemeronCallbacks);
-
-  // We first reiterate over known callbacks from previous iterations.
-  for (auto& tuple : ephemeron_callbacks_)
-    tuple.value(visitor, tuple.key);
-
-  DCHECK_EQ(WorklistTaskId::MutatorThread, visitor->task_id());
-
-  // Then we iterate over the new callbacks found by the marking visitor.
-  // Callbacks found by the concurrent marking will be flushed eventually
-  // and then invoked by the mutator thread (in the atomic pause at latest).
-  while (
-      !weak_table_worklist_->IsLocalViewEmpty(WorklistTaskId::MutatorThread)) {
-    // Read ephemeron callbacks from worklist to ephemeron_callbacks_ hashmap.
-    WeakTableWorklist::View ephemerons_worklist(weak_table_worklist_.get(),
-                                                WorklistTaskId::MutatorThread);
-    WeakTableItem item;
-    while (ephemerons_worklist.Pop(&item)) {
-      auto result =
-          ephemeron_callbacks_.insert(item.base_object_payload, item.callback);
-      DCHECK(result.is_new_entry ||
-             result.stored_value->value == item.callback);
-      if (result.is_new_entry) {
-        item.callback(visitor, item.base_object_payload);
-      }
-    }
-  }
-}
-
 namespace {
 
 template <typename Worklist, typename Callback>
@@ -319,6 +286,45 @@
 
 }  // namespace
 
+bool ThreadHeap::InvokeEphemeronCallbacks(MarkingVisitor* visitor,
+                                          base::TimeTicks deadline) {
+  // Mark any strong pointers that have now become reachable in ephemeron maps.
+  ThreadHeapStatsCollector::Scope stats_scope(
+      stats_collector(),
+      ThreadHeapStatsCollector::kMarkInvokeEphemeronCallbacks);
+
+  // We first reiterate over known callbacks from previous iterations.
+  constexpr size_t kDeadlineCheckInterval = 250;
+  size_t processed_callback_count = 0;
+  for (auto& tuple : ephemeron_callbacks_) {
+    tuple.value(visitor, tuple.key);
+    if (++processed_callback_count == kDeadlineCheckInterval) {
+      if (deadline <= base::TimeTicks::Now()) {
+        return false;
+      }
+      processed_callback_count = 0;
+    }
+  }
+
+  DCHECK_EQ(WorklistTaskId::MutatorThread, visitor->task_id());
+
+  // Then we iterate over the new callbacks found by the marking visitor.
+  // Callbacks found by the concurrent marking will be flushed eventually
+  // and then invoked by the mutator thread (in the atomic pause at latest).
+  return DrainWorklistWithDeadline(
+      deadline, weak_table_worklist_.get(),
+      [this, visitor](const WeakTableItem& item) {
+        auto result = ephemeron_callbacks_.insert(item.base_object_payload,
+                                                  item.callback);
+        DCHECK(result.is_new_entry ||
+               result.stored_value->value == item.callback);
+        if (result.is_new_entry) {
+          item.callback(visitor, item.base_object_payload);
+        }
+      },
+      WorklistTaskId::MutatorThread);
+}
+
 bool ThreadHeap::AdvanceMarking(MarkingVisitor* visitor,
                                 base::TimeTicks deadline) {
   DCHECK_EQ(WorklistTaskId::MutatorThread, visitor->task_id());
@@ -390,7 +396,9 @@
         break;
     }
 
-    InvokeEphemeronCallbacks(visitor);
+    finished = InvokeEphemeronCallbacks(visitor, deadline);
+    if (!finished)
+      break;
 
     // Rerun loop if ephemeron processing queued more objects for tracing.
   } while (!marking_worklist_->IsLocalViewEmpty(WorklistTaskId::MutatorThread));
diff --git a/third_party/blink/renderer/platform/heap/heap.h b/third_party/blink/renderer/platform/heap/heap.h
index 4debc48..5cd67b8 100644
--- a/third_party/blink/renderer/platform/heap/heap.h
+++ b/third_party/blink/renderer/platform/heap/heap.h
@@ -404,7 +404,7 @@
   void DestroyMarkingWorklists(BlinkGC::StackState);
   void DestroyCompactionWorklists();
 
-  void InvokeEphemeronCallbacks(MarkingVisitor*);
+  bool InvokeEphemeronCallbacks(MarkingVisitor*, base::TimeTicks);
 
   bool FlushV8References(base::TimeTicks);
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc b/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
index 6b28c4858..449d18c4 100644
--- a/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
@@ -173,7 +173,7 @@
   RevalidationStartForbiddenScope revalidation_start_forbidden_scope(this);
   RawResourceClient* client = static_cast<RawResourceClient*>(c);
   for (const auto& redirect : RedirectChain()) {
-    client->RedirectReceived(this, redirect.request_,
+    client->RedirectReceived(this, ResourceRequest(redirect.request_),
                              redirect.redirect_response_);
     if (!HasClient(c))
       return;
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource.cc b/third_party/blink/renderer/platform/loader/fetch/resource.cc
index 4a887fa..2f7d7eb 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource.cc
@@ -472,7 +472,7 @@
   return CurrentAge(response, response_timestamp) <= max_life;
 }
 
-const ResourceRequest& Resource::LastResourceRequest() const {
+const ResourceRequestHead& Resource::LastResourceRequest() const {
   if (!redirect_chain_.size())
     return GetResourceRequest();
   return redirect_chain_.back().request_;
@@ -1067,8 +1067,7 @@
          GetResourceRequest().CacheControlContainsNoStore();
 }
 
-static bool ShouldRevalidateStaleResponse(const ResourceRequest& request,
-                                          const ResourceResponse& response,
+static bool ShouldRevalidateStaleResponse(const ResourceResponse& response,
                                           base::Time response_timestamp) {
   base::TimeDelta staleness = response.CacheControlStaleWhileRevalidate();
   if (staleness.is_zero())
@@ -1082,15 +1081,14 @@
   for (auto& redirect : redirect_chain_) {
     // Use |response_timestamp_| since we don't store the timestamp
     // of each redirect response.
-    if (blink::ShouldRevalidateStaleResponse(redirect.request_,
-                                             redirect.redirect_response_,
+    if (blink::ShouldRevalidateStaleResponse(redirect.redirect_response_,
                                              response_timestamp_)) {
       return true;
     }
   }
 
-  return blink::ShouldRevalidateStaleResponse(
-      GetResourceRequest(), GetResponse(), response_timestamp_);
+  return blink::ShouldRevalidateStaleResponse(GetResponse(),
+                                              response_timestamp_);
 }
 
 bool Resource::StaleRevalidationRequested() const {
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource.h b/third_party/blink/renderer/platform/loader/fetch/resource.h
index e10f36b..ffb2d1e 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource.h
@@ -179,7 +179,7 @@
   const ResourceRequest& GetResourceRequest() const {
     return resource_request_;
   }
-  const ResourceRequest& LastResourceRequest() const;
+  const ResourceRequestHead& LastResourceRequest() const;
   const ResourceResponse* LastResourceResponse() const;
 
   virtual void SetRevalidatingRequest(const ResourceRequest&);
@@ -466,13 +466,11 @@
     DISALLOW_NEW();
 
    public:
-    explicit RedirectPair(const ResourceRequest& request,
+    explicit RedirectPair(const ResourceRequestHead& request,
                           const ResourceResponse& redirect_response)
-        : redirect_response_(redirect_response) {
-      request_.CopyFrom(request);
-    }
+        : request_(request), redirect_response_(redirect_response) {}
 
-    ResourceRequest request_;
+    ResourceRequestHead request_;
     ResourceResponse redirect_response_;
   };
   const Vector<RedirectPair>& RedirectChain() const { return redirect_chain_; }
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index cc3431e..e95156fc 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -2030,10 +2030,11 @@
   ResourceLoaderOptions options = resource->Options();
   options.initiator_info.name = initiator_name;
   FetchParameters params(std::move(resource_request), options);
-  Context().CanRequest(resource->GetType(), resource->LastResourceRequest(),
-                       resource->LastResourceRequest().Url(), params.Options(),
+  ResourceRequest last_resource_request(resource->LastResourceRequest());
+  Context().CanRequest(resource->GetType(), last_resource_request,
+                       last_resource_request.Url(), params.Options(),
                        ReportingDisposition::kReport,
-                       resource->LastResourceRequest().GetRedirectStatus());
+                       last_resource_request.GetRedirectStatus());
   DidLoadResourceFromMemoryCache(resource->InspectorId(), resource,
                                  params.GetResourceRequest(),
                                  false /* is_static_data */);
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
index 4edab9bad..9db847ab 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
@@ -804,18 +804,19 @@
       redirect_response_with_type ? *redirect_response_with_type
                                   : redirect_response;
 
-  // The following two calls may rewrite the new_request.Url() to
+  // The following two calls may rewrite the new_request->Url() to
   // something else not for rejecting redirect but for other reasons.
   // E.g. WebFrameTestClient::WillSendRequest() and
   // RenderFrameImpl::WillSendRequest(). We should reflect the
-  // rewriting but currently we cannot. So, compare new_request.Url() and
+  // rewriting but currently we cannot. So, compare new_request->Url() and
   // new_url after calling them, and return false to make the redirect fail on
   // mismatch.
 
   WebScopedVirtualTimePauser unused_virtual_time_pauser;
+  // TODO(yoichio): Have PrepareRequest use ResourceRequestHead.
   Context().PrepareRequest(*new_request, resource_->Options().initiator_info,
-                           unused_virtual_time_pauser,
-                           resource_->GetType());
+                           unused_virtual_time_pauser, resource_->GetType());
+  DCHECK(!new_request->HttpBody());
   if (auto* observer = fetcher_->GetResourceLoadObserver()) {
     observer->WillSendRequest(resource_->InspectorId(), *new_request,
                               redirect_response_to_pass, resource_->GetType(),
@@ -957,8 +958,10 @@
             kEnableCorsHandlingByResourceFetcher &&
         request_mode == network::mojom::RequestMode::kCors &&
         response.WasFallbackRequiredByServiceWorker()) {
+      DCHECK(resource_->RedirectChain().IsEmpty());
       ResourceRequest last_request;
-      last_request.CopyFrom(resource_->LastResourceRequest());
+      last_request.CopyHeadFrom(resource_->GetResourceRequest());
+      last_request.SetHttpBody(resource_->GetResourceRequest().HttpBody());
       DCHECK(!last_request.GetSkipServiceWorker());
       // This code handles the case when a controlling service worker doesn't
       // handle a cross origin request.
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_request.cc b/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
index a7b4142a..6428325 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
@@ -117,7 +117,7 @@
   this->ResourceRequestHead::operator=(src);
 }
 
-std::unique_ptr<ResourceRequest> ResourceRequest::CreateRedirectRequest(
+std::unique_ptr<ResourceRequest> ResourceRequestHead::CreateRedirectRequest(
     const KURL& new_url,
     const AtomicString& new_method,
     const net::SiteForCookies& new_site_for_cookies,
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_request.h b/third_party/blink/renderer/platform/loader/fetch/resource_request.h
index be58e01..d1135ec 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_request.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_request.h
@@ -67,13 +67,24 @@
   ResourceRequestHead();
   explicit ResourceRequestHead(const KURL&);
 
-  explicit ResourceRequestHead(const ResourceRequestHead&);
+  ResourceRequestHead(const ResourceRequestHead&);
   ResourceRequestHead& operator=(const ResourceRequestHead&);
-  explicit ResourceRequestHead(ResourceRequestHead&&);
+  ResourceRequestHead(ResourceRequestHead&&);
   ResourceRequestHead& operator=(ResourceRequestHead&&);
 
   ~ResourceRequestHead();
 
+  // Constructs a new ResourceRequest for a redirect from this instance.
+  // Since body for a redirect request is kept and handled in the network
+  // service, the returned instance here in blink side doesn't contain body.
+  std::unique_ptr<ResourceRequest> CreateRedirectRequest(
+      const KURL& new_url,
+      const AtomicString& new_method,
+      const net::SiteForCookies& new_site_for_cookies,
+      const String& new_referrer,
+      network::mojom::ReferrerPolicy new_referrer_policy,
+      bool skip_service_worker) const;
+
   bool IsNull() const;
 
   const KURL& Url() const;
@@ -559,15 +570,6 @@
   void CopyFrom(const ResourceRequest&);
   void CopyHeadFrom(const ResourceRequestHead&);
 
-  // Constructs a new ResourceRequest for a redirect from this instance.
-  std::unique_ptr<ResourceRequest> CreateRedirectRequest(
-      const KURL& new_url,
-      const AtomicString& new_method,
-      const net::SiteForCookies& new_site_for_cookies,
-      const String& new_referrer,
-      network::mojom::ReferrerPolicy new_referrer_policy,
-      bool skip_service_worker) const;
-
   EncodedFormData* HttpBody() const;
   void SetHttpBody(scoped_refptr<EncodedFormData>);
 
diff --git a/third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h b/third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h
index a430593..cfc8b6e 100644
--- a/third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h
+++ b/third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h
@@ -37,11 +37,13 @@
   }
   mojo::PendingRemote<Interface> BindNewPipeAndPassRemote(
       scoped_refptr<base::SequencedTaskRunner> task_runner) WARN_UNUSED_RESULT {
+    DCHECK(task_runner);
     return wrapper_->receiver().BindNewPipeAndPassRemote(
         std::move(task_runner));
   }
   void Bind(mojo::PendingReceiver<Interface> pending_receiver,
             scoped_refptr<base::SequencedTaskRunner> task_runner) {
+    DCHECK(task_runner);
     wrapper_->receiver().Bind(std::move(pending_receiver),
                               std::move(task_runner));
   }
diff --git a/third_party/blink/renderer/platform/mojo/heap_mojo_receiver_set.h b/third_party/blink/renderer/platform/mojo/heap_mojo_receiver_set.h
index 3b84521f..26920f9 100644
--- a/third_party/blink/renderer/platform/mojo/heap_mojo_receiver_set.h
+++ b/third_party/blink/renderer/platform/mojo/heap_mojo_receiver_set.h
@@ -31,6 +31,7 @@
   mojo::ReceiverId Add(ImplPointerType impl,
                        mojo::PendingReceiver<Interface> receiver,
                        scoped_refptr<base::SequencedTaskRunner> task_runner) {
+    DCHECK(task_runner);
     return wrapper_->receiver_set().Add(std::move(impl), std::move(receiver));
   }
   void Clear() { wrapper_->receiver_set().Clear(); }
diff --git a/third_party/blink/renderer/platform/mojo/heap_mojo_remote.h b/third_party/blink/renderer/platform/mojo/heap_mojo_remote.h
index 4457f95..a3ee535 100644
--- a/third_party/blink/renderer/platform/mojo/heap_mojo_remote.h
+++ b/third_party/blink/renderer/platform/mojo/heap_mojo_remote.h
@@ -38,11 +38,13 @@
   }
   mojo::PendingReceiver<Interface> BindNewPipeAndPassReceiver(
       scoped_refptr<base::SequencedTaskRunner> task_runner) WARN_UNUSED_RESULT {
+    DCHECK(task_runner);
     return wrapper_->remote().BindNewPipeAndPassReceiver(
         std::move(task_runner));
   }
   void Bind(mojo::PendingRemote<Interface> pending_remote,
             scoped_refptr<base::SequencedTaskRunner> task_runner) {
+    DCHECK(task_runner);
     wrapper_->remote().Bind(std::move(pending_remote), std::move(task_runner));
   }
 
diff --git a/third_party/blink/renderer/platform/wtf/hash_table.h b/third_party/blink/renderer/platform/wtf/hash_table.h
index 7b3a156d..b6055474 100644
--- a/third_party/blink/renderer/platform/wtf/hash_table.h
+++ b/third_party/blink/renderer/platform/wtf/hash_table.h
@@ -2140,15 +2140,6 @@
 std::enable_if_t<A::kIsGarbageCollected>
 HashTable<Key, Value, Extractor, HashFunctions, Traits, KeyTraits, Allocator>::
     Trace(VisitorDispatcher visitor) {
-  // bail out for concurrent marking
-  if (visitor->ConcurrentTracingBailOut(
-          {this, [](blink::Visitor* visitor, void* object) {
-             reinterpret_cast<HashTable<Key, Value, Extractor, HashFunctions,
-                                        Traits, KeyTraits, Allocator>*>(object)
-                 ->Trace(visitor);
-           }}))
-    return;
-
   static_assert(WTF::IsWeak<ValueType>::value ||
                     IsTraceableInCollectionTrait<Traits>::value,
                 "Value should not be traced");
diff --git a/third_party/blink/renderer/platform/wtf/linked_hash_set.h b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
index 5bf2569e..1be1dbc 100644
--- a/third_party/blink/renderer/platform/wtf/linked_hash_set.h
+++ b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
@@ -355,14 +355,6 @@
 
   template <typename VisitorDispatcher>
   void Trace(VisitorDispatcher visitor) {
-    if (visitor->ConcurrentTracingBailOut(
-            {this, [](blink::Visitor* visitor, void* object) {
-               reinterpret_cast<LinkedHashSet<ValueArg, HashFunctions,
-                                              TraitsArg, Allocator>*>(object)
-                   ->Trace(visitor);
-             }}))
-      return;
-
     impl_.Trace(visitor);
     // Should the underlying table be moved by GC, register a callback
     // that fixes up the interior pointers that the (Heap)LinkedHashSet keeps.
diff --git a/third_party/blink/renderer/platform/wtf/list_hash_set.h b/third_party/blink/renderer/platform/wtf/list_hash_set.h
index 2fc5e5e..7329cfa 100644
--- a/third_party/blink/renderer/platform/wtf/list_hash_set.h
+++ b/third_party/blink/renderer/platform/wtf/list_hash_set.h
@@ -516,13 +516,6 @@
 
   template <typename VisitorDispatcher, typename A = NodeAllocator>
   std::enable_if_t<A::kIsGarbageCollected> Trace(VisitorDispatcher visitor) {
-    if (visitor->ConcurrentTracingBailOut(
-            {this, [](blink::Visitor* visitor, void* object) {
-               reinterpret_cast<ListHashSetNode<ValueArg, AllocatorArg>*>(
-                   object)
-                   ->Trace(visitor);
-             }}))
-      return;
     // The conservative stack scan can find nodes that have been removed
     // from the set and destructed. We don't need to trace these, and it
     // would be wrong to do so, because the class will not expect the trace
@@ -1206,13 +1199,6 @@
 template <typename VisitorDispatcher, typename A>
 std::enable_if_t<A::kIsGarbageCollected>
 ListHashSet<T, inlineCapacity, U, V>::Trace(VisitorDispatcher visitor) {
-  if (visitor->ConcurrentTracingBailOut(
-          {this, [](blink::Visitor* visitor, void* object) {
-             reinterpret_cast<ListHashSet<T, inlineCapacity, U, V>*>(object)
-                 ->Trace(visitor);
-           }}))
-    return;
-
   static_assert(!IsWeak<T>::value,
                 "HeapListHashSet does not support weakness, consider using "
                 "HeapLinkedHashSet instead.");
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 410ca88b..e35a693 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -56,8 +56,6 @@
 crbug.com/906791 external/wpt/fullscreen/api/element-ready-check-allowed-cross-origin-manual.sub.html [ Timeout ]
 crbug.com/860713 external/wpt/bluetooth/requestDevice/cross-origin-iframe.sub.https.html [ Failure Timeout ]
 crbug.com/860713 external/wpt/bluetooth/requestDevice/request-from-sandboxed-iframe.https.html [ Failure Timeout ]
-crbug.com/860713 virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/cross-origin-iframe.sub.https.html [ Failure Timeout ]
-crbug.com/860713 virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/request-from-sandboxed-iframe.https.html [ Failure Timeout ]
 
 # The following tests would pass with User Activation Delegation.
 crbug.com/928838 external/wpt/html/user-activation/activation-transfer-with-click.tentative.html [ Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 4aab5a1..6770420 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -731,10 +731,5 @@
     "prefix": "storage-access-api",
     "bases": [ "external/wpt/storage-access-api" ],
     "args": [ "--enable-features=StorageAccessAPI" ]
-  },
-  {
-    "prefix": "web-bluetooth-new-permissions-backend",
-    "bases": ["bluetooth", "external/wpt/bluetooth"],
-    "args": ["--enable-features=WebBluetoothNewPermissionsBackend"]
   }
 ]
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/nonces-expected.txt b/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/nonces-expected.txt
deleted file mode 100644
index b9c30eda2..0000000
--- a/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/nonces-expected.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-This is a testharness.js-based test.
-PASS Basic nonce tests for meh in HTML namespace
-PASS Ensure that removal of content attribute does not affect IDL attribute for meh in HTML namespace
-PASS Basic nonce tests for div in HTML namespace
-PASS Ensure that removal of content attribute does not affect IDL attribute for div in HTML namespace
-PASS Basic nonce tests for script in HTML namespace
-PASS Ensure that removal of content attribute does not affect IDL attribute for script in HTML namespace
-FAIL Basic nonce tests for meh in SVG namespace assert_equals: IDL attribute is modified after content attribute set expected "x" but got ""
-FAIL Ensure that removal of content attribute does not affect IDL attribute for meh in SVG namespace assert_equals: IDL attribute is modified after content attribute set expected "x" but got ""
-FAIL Basic nonce tests for svg in SVG namespace assert_equals: IDL attribute is modified after content attribute set expected "x" but got ""
-FAIL Ensure that removal of content attribute does not affect IDL attribute for svg in SVG namespace assert_equals: IDL attribute is modified after content attribute set expected "x" but got ""
-FAIL Basic nonce tests for script in SVG namespace assert_equals: IDL attribute is modified after content attribute set expected "x" but got ""
-FAIL Ensure that removal of content attribute does not affect IDL attribute for script in SVG namespace assert_equals: IDL attribute is modified after content attribute set expected "x" but got ""
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/svgscript-nonces-hidden-expected.txt b/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/svgscript-nonces-hidden-expected.txt
deleted file mode 100644
index 05dc387..0000000
--- a/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/svgscript-nonces-hidden-expected.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-This is a testharness.js-based test.
-PASS Reading 'nonce' content attribute and IDL attribute.
-PASS Cloned node retains nonce.
-PASS Cloned node retains nonce when inserted.
-FAIL Writing 'nonce' content attribute. assert_equals: expected "foo" but got "abc"
-PASS Writing 'nonce' IDL attribute.
-PASS Document-written script executes.
-PASS Document-written script's nonce value.
-PASS createElement.nonce.
-PASS createElement.setAttribute.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub-expected.txt b/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub-expected.txt
deleted file mode 100644
index 05dc387..0000000
--- a/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub-expected.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-This is a testharness.js-based test.
-PASS Reading 'nonce' content attribute and IDL attribute.
-PASS Cloned node retains nonce.
-PASS Cloned node retains nonce when inserted.
-FAIL Writing 'nonce' content attribute. assert_equals: expected "foo" but got "abc"
-PASS Writing 'nonce' IDL attribute.
-PASS Document-written script executes.
-PASS Document-written script's nonce value.
-PASS createElement.nonce.
-PASS createElement.setAttribute.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/dedicated-worker-cache-storage.https.html b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/dedicated-worker-cache-storage.https.html
index 2559de8..dced705 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/dedicated-worker-cache-storage.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/dedicated-worker-cache-storage.https.html
@@ -5,6 +5,7 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="/common/get-host-info.sub.js"></script>
 <script>
+// See also: ./shared-worker-cache-storage.https.html
 
 function remote(path) {
   const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
@@ -12,11 +13,11 @@
 }
 
 const iframe_path = "./resources/iframe.html?pipe=";
-const dedicated_worker_path = "./dedicated-worker.js?pipe=";
+const dedicated_worker_path = "./universal-worker.js?pipe=";
 const ressource_path = "/images/blue.png?pipe=";
 
 const coep_header= {
-  "coep-none"         : "|header(Cross-Origin-Embedder-Policy,none)",
+  "coep-none"         : "",
   "coep-require-corp" : "|header(Cross-Origin-Embedder-Policy,require-corp)",
 }
 
@@ -107,7 +108,7 @@
     iframe.contentWindow.postMessage(iframe_eval);
 
     const {data} = await iframe_response;
-    assert_equals(data == "success", loaded);
+    assert_equals(data === "success", loaded);
   }, `${iframe_coep} ${worker_coep} ${response_corp}`)
 }
 
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/resources/dedicated-worker.js b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/resources/universal-worker.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/resources/dedicated-worker.js
rename to third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/resources/universal-worker.js
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/service-worker-cache-storage.https-expected.txt b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/service-worker-cache-storage.https-expected.txt
new file mode 100644
index 0000000..10fe541
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/service-worker-cache-storage.https-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+PASS A ServiceWorker with coep-none use CacheStorage to get a corp-undefined response.
+PASS A ServiceWorker with coep-none use CacheStorage to get a corp-cross-origin response.
+FAIL A ServiceWorker with coep-require-corp use CacheStorage to get a corp-undefined response. assert_equals: expected false but got true
+PASS A ServiceWorker with coep-require-corp use CacheStorage to get a corp-cross-origin response.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/service-worker-cache-storage.https.html b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/service-worker-cache-storage.https.html
new file mode 100644
index 0000000..873f06c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/service-worker-cache-storage.https.html
@@ -0,0 +1,117 @@
+<!doctype html>
+<html>
+<title> Check enforcement of COEP in a ServiceWorker using CacheStorage. </title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+// See also: ./dedicated-worker-cache-storage.https.html
+
+function remote(path) {
+  const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+  return new URL(path, REMOTE_ORIGIN);
+}
+
+const iframe_path = "./resources/iframe.html?pipe=";
+const service_worker_path = "./resources/universal-worker.js?pipe=";
+const ressource_path = "/images/blue.png?pipe=";
+
+const coep_header= {
+  "coep-none"         : "",
+  "coep-require-corp" : "|header(Cross-Origin-Embedder-Policy,require-corp)",
+}
+
+const corp_header = {
+  "corp-undefined"    : "",
+  "corp-cross-origin" : "|header(Cross-Origin-Resource-Policy,cross-origin)",
+}
+
+// Send a message to the |worker| and wait for its response.
+function executeCommandInServiceWorker(worker, command) {
+  const channel = new MessageChannel();
+  const response = new Promise(resolve => channel.port1.onmessage = resolve);
+  worker.postMessage(command, [ channel.port2 ]);
+  return response;
+}
+
+// Check enforcement of COEP in a ServiceWorker using CacheStorage.
+//
+// 1) Fetch a response from a document with COEP:none. Store it in the
+//    CacheStorage. The response is cross-origin without any CORS header.
+// 2) From a ServiceWorker, retrieve the response from the CacheStorage.
+//
+// Test parameters:
+// - |worker_coep| the COEP header of the ServiceWorker's script response.
+// - |response_corp| the CORP header of the response.
+//
+// Test expectations:
+// |loaded| is true whenever the worker is able to fetch the response from
+// the CacheStorage. According to the specification:
+// https://mikewest.github.io/corpp/#initialize-embedder-policy-for-global
+// it must be false when:
+// - |worker_coep| is 'coep-require-corp' and
+// - |response-corp| is 'corp-undefined'.
+function check(
+  // Test parameters:
+  worker_coep,
+  response_corp,
+
+  // Test expectations:
+  loaded) {
+
+  promise_test(async (t) => {
+    // 1) Fetch a response from a document with COEP:none. Store it in the
+    //    CacheStorage. The response is cross-origin without any CORS header.
+    const resource_path = ressource_path + corp_header[response_corp];
+    const resource_url = remote(resource_path);
+    const fetch_request = new Request(resource_url, {mode: 'no-cors'});
+    const cache = await caches.open('v1');
+    const fetch_response = await fetch(fetch_request);
+    await cache.put(fetch_request, fetch_response);
+
+    // 2) Start a ServiceWorker.
+    const SCOPE= new URL(location.href).pathname;
+    const service_worker_allowed = `|header(service-worker-allowed,${SCOPE})`;
+    const SCRIPT =
+      service_worker_path +
+      coep_header[worker_coep] +
+      service_worker_allowed;
+
+    const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+    add_completion_callback(() => reg.unregister());
+
+    // Start talking to the ServiceWorker, no matter its state.
+    const worker = reg.installing || reg.waiting || reg.active;
+
+    // 3) From the service worker, try to retrieve the response from the
+    //    CacheStorage.
+    const response = executeCommandInServiceWorker(worker, `
+      (async function() {
+        const cache = await caches.open('v1');
+        const request = new Request('${resource_url}', {
+          mode: 'no-cors'
+        });
+        try {
+          const response = await cache.match(request);
+          message.ports[0].postMessage('success');
+        } catch(error) {
+          message.ports[0].postMessage('error');
+        }
+      })()
+    `);
+    const {data} = await response;
+    assert_equals(data === "success", loaded);
+  }, `A ServiceWorker with ${worker_coep} use CacheStorage to get a ${response_corp} response.`)
+}
+
+// ------------------------------------------------------
+//    worker_coep         , response_corp       , loaded
+// ------------------------------------------------------
+check("coep-none"         , "corp-undefined"    , true);
+check("coep-none"         , "corp-cross-origin" , true);
+check("coep-require-corp" , "corp-undefined"    , false);
+check("coep-require-corp" , "corp-cross-origin" , true);
+
+</script>
+</html>
diff --git a/third_party/blink/web_tests/http/tests/devtools/startup/console-exception-while-no-inspector-expected.txt b/third_party/blink/web_tests/http/tests/devtools/startup/console-exception-while-no-inspector-expected.txt
deleted file mode 100644
index f5fd37d..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/startup/console-exception-while-no-inspector-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-Tests that console will NOT contain stack trace for exception thrown when inspector front-end was closed. Bug 109427. https://bugs.webkit.org/show_bug.cgi?id=109427
-
-SUCCESS: message doesn't have stack trace
-TEST COMPLETE.
-
diff --git a/third_party/blink/web_tests/http/tests/devtools/startup/console-exception-while-no-inspector.js b/third_party/blink/web_tests/http/tests/devtools/startup/console-exception-while-no-inspector.js
deleted file mode 100644
index 8f552294..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/startup/console-exception-while-no-inspector.js
+++ /dev/null
@@ -1,20 +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.
-
-(async function() {
-  await TestRunner.setupStartupTest('resources/console-exception-while-no-inspector.html');
-  TestRunner.addResult(
-      `Tests that console will NOT contain stack trace for exception thrown when inspector front-end was closed. Bug 109427. https://bugs.webkit.org/show_bug.cgi?id=109427\n`);
-  await TestRunner.waitForEvent(SDK.ConsoleModel.Events.MessageAdded, SDK.consoleModel);
-
-  var message = SDK.consoleModel.messages()[0];
-  var stack = message.stackTrace;
-  if (stack && stack.callFrames.length)
-    TestRunner.addResult('FAIL: found message with stack trace');
-  else
-    TestRunner.addResult('SUCCESS: message doesn\'t have stack trace');
-
-  TestRunner.addResult('TEST COMPLETE.');
-  TestRunner.completeTest();
-})();
diff --git a/third_party/blink/web_tests/http/tests/devtools/startup/pause-on-start-expected.txt b/third_party/blink/web_tests/http/tests/devtools/startup/pause-on-start-expected.txt
deleted file mode 100644
index cca4143..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/startup/pause-on-start-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Tests that tools pause on start.
-
-noop
-
diff --git a/third_party/blink/web_tests/http/tests/devtools/startup/pause-on-start.js b/third_party/blink/web_tests/http/tests/devtools/startup/pause-on-start.js
deleted file mode 100644
index 563a11aa..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/startup/pause-on-start.js
+++ /dev/null
@@ -1,16 +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.
-
-(async function() {
-  Root.Runtime._queryParamsObject.set('panel', 'sources');
-  await TestRunner.setupStartupTest('resources/pause-on-start.html');
-  TestRunner.addResult(
-      `Tests that tools pause on start.\n`);
-  SDK.targetManager.addModelListener(
-      SDK.DebuggerModel, SDK.DebuggerModel.Events.DebuggerPaused, (event) => {
-    const name = event.data.debuggerPausedDetails().callFrames[0].functionName;
-    TestRunner.addResult(name);
-    TestRunner.completeTest();
-  });
-})();
diff --git a/third_party/blink/web_tests/http/tests/devtools/startup/resources/cached-resource-metadata.html b/third_party/blink/web_tests/http/tests/devtools/startup/resources/cached-resource-metadata.html
deleted file mode 100644
index 4e97a32..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/startup/resources/cached-resource-metadata.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<html>
-<head>
-<script>
-function addScript()
-{
-    var fulfill;
-    var promise = new Promise(x => fulfill = x);
-    var script = document.createElement("script");
-    script.type = "text/javascript";
-    script.src = "../../resource-tree/resources/script-with-constant-last-modified.php";
-    script.onload = fulfill;
-    document.body.appendChild(script);
-    return promise;
-}
-
-function addScriptAndRunTest()
-{
-    addScript().then(() => testRunner.inspectSecondaryWindow());
-}
-</script>
-</head>
-<body onload="addScriptAndRunTest()">
-</body>
-</html>
diff --git a/third_party/blink/web_tests/http/tests/devtools/startup/resources/console-exception-while-no-inspector.html b/third_party/blink/web_tests/http/tests/devtools/startup/resources/console-exception-while-no-inspector.html
deleted file mode 100644
index bd3e16c..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/startup/resources/console-exception-while-no-inspector.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<html>
-<head>
-<script>
-function throwException() {
-    throw 2013;
-}
-
-function baz()
-{
-    throwException();
-}
-
-function bar(callback)
-{
-    callback()
-}
-
-function foo()
-{
-    bar(baz.bind(this));
-}
-
-
-function handleLoaded()
-{
-    setTimeout(showInspectorAndRunTest, 0);
-    foo();
-}
-
-
-function showInspectorAndRunTest()
-{
-    testRunner.inspectSecondaryWindow();
-}
-</script>
-</head>
-<body onload="handleLoaded()">
-</body>
-</html>
diff --git a/third_party/blink/web_tests/http/tests/devtools/startup/resources/pause-on-start.html b/third_party/blink/web_tests/http/tests/devtools/startup/resources/pause-on-start.html
deleted file mode 100644
index a7d689dc..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/startup/resources/pause-on-start.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<script>
-function onload() {
-  testRunner.inspectSecondaryWindow();
-  setInterval(noop, 10);
-}
-
-function noop() {}
-</script>
-<body onload="onload()"></body>
diff --git a/third_party/blink/web_tests/http/tests/devtools/startup/resources/shadow-dom-rules.html b/third_party/blink/web_tests/http/tests/devtools/startup/resources/shadow-dom-rules.html
deleted file mode 100644
index 7d51298..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/startup/resources/shadow-dom-rules.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<html>
-<head>
-<script>
-
-function createShadowRoot()
-{
-    var template = document.querySelector('#tmpl');
-    var root = document.querySelector('#host').createShadowRoot();
-    root.appendChild(template.content.cloneNode(true));
-    testRunner.inspectSecondaryWindow();
-}
-</script>
-</head>
-
-<body onload="createShadowRoot()">
-<div id="host"></div>
-<template id="tmpl">
-    <style> .red { color: red; } </style>
-    <div id="inner" class="red">hi!</div>
-</template>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/http/tests/devtools/startup/resources/style.css b/third_party/blink/web_tests/http/tests/devtools/startup/resources/style.css
deleted file mode 100644
index eba2cb6b..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/startup/resources/style.css
+++ /dev/null
@@ -1,3 +0,0 @@
-#testDiv {
-    color: red;
-}
\ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/devtools/startup/shadow-dom-rules-expected.txt b/third_party/blink/web_tests/http/tests/devtools/startup/shadow-dom-rules-expected.txt
deleted file mode 100644
index d1e9c2a..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/startup/shadow-dom-rules-expected.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-This test checks that style sheets hosted inside shadow roots could be inspected.
-
-
-Running: testInit
-
-Running: testDumpStyles
-[expanded] 
-element.style { ()
-
-[expanded] 
-.red { (<style>)
-    color: red;
-
-[expanded] 
-div { (user agent stylesheet)
-    display: block;
-
-
diff --git a/third_party/blink/web_tests/http/tests/devtools/startup/shadow-dom-rules.js b/third_party/blink/web_tests/http/tests/devtools/startup/shadow-dom-rules.js
deleted file mode 100644
index 3f7539d..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/startup/shadow-dom-rules.js
+++ /dev/null
@@ -1,20 +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.
-
-(async function() {
-  await TestRunner.setupStartupTest('resources/shadow-dom-rules.html');
-  TestRunner.addResult(`This test checks that style sheets hosted inside shadow roots could be inspected.\n`);
-  await TestRunner.loadModule('elements_test_runner');
-
-  TestRunner.runTestSuite([
-    function testInit(next) {
-      ElementsTestRunner.selectNodeAndWaitForStyles('inner', next);
-    },
-
-    async function testDumpStyles(next) {
-      await ElementsTestRunner.dumpSelectedElementStyles(true);
-      next();
-    }
-  ]);
-})();
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/coep-load-error-reporting-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/coep-load-error-reporting-expected.txt
new file mode 100644
index 0000000..d22db3a
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/coep-load-error-reporting-expected.txt
@@ -0,0 +1,12 @@
+Tests that cross-origin embedder policy (COEP) related blocking is reported correctly.
+https://devtools.test:8443/inspector-protocol/network/cross-origin-isolation/resources/coep-page-with-resources.php: *loading finished*
+https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php: net::ERR_BLOCKED_BY_RESPONSE corp-not-same-origin-after-defaulted-to-same-origin-by-coep
+https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?corp=cross-origin: *loading finished*
+https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?corp=same-site: net::ERR_BLOCKED_BY_RESPONSE corp-not-same-site
+https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?corp=same-origin: net::ERR_BLOCKED_BY_RESPONSE corp-not-same-origin
+https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php: net::ERR_BLOCKED_BY_RESPONSE coep-frame-resource-needs-coep-header
+https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?coep: net::ERR_BLOCKED_BY_RESPONSE corp-not-same-origin-after-defaulted-to-same-origin-by-coep
+https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?coep&corp=same-site: net::ERR_BLOCKED_BY_RESPONSE corp-not-same-site
+https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?coep&corp=same-origin: net::ERR_BLOCKED_BY_RESPONSE corp-not-same-origin
+https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php&coop: net::ERR_BLOCKED_BY_RESPONSE coep-frame-resource-needs-coep-header
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/coep-load-error-reporting.js b/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/coep-load-error-reporting.js
new file mode 100644
index 0000000..1facafa
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/coep-load-error-reporting.js
@@ -0,0 +1,54 @@
+(async function(testRunner) {
+  const {page, session, dp} = await testRunner.startBlank(
+      `Tests that cross-origin embedder policy (COEP) related blocking is reported correctly.`);
+
+  await session.protocol.Network.clearBrowserCache();
+  await session.protocol.Network.setCacheDisabled({cacheDisabled: true});
+  await dp.Target.setAutoAttach({autoAttach: true, waitForDebuggerOnStart: true, flatten: true});
+
+  let numberOfMessages = 0;
+  const expectedNumberOfMessages = 21;
+  const resources = new Map();
+
+  function record(requestId, info) {
+    resources.set(requestId, {...resources.get(requestId), ...info});
+
+    if (++numberOfMessages === expectedNumberOfMessages) {
+      function compareInfo(a, b) {
+        return a.requestWillBeSent?.request?.url < b.requestWillBeSent?.request?.url;
+      }
+      const entries = Array.from(resources.values()).sort(compareInfo);
+      for (const entry of entries) {
+        if (entry.loadingFailed) {
+          testRunner.log(`${entry.requestWillBeSent.request?.url}: ${entry.loadingFailed.errorText} ${entry.loadingFailed.blockedReason}`);
+        }
+        if (entry.loadingFinished) {
+          testRunner.log(`${entry.requestWillBeSent.request?.url}: *loading finished*`);
+        }
+      }
+      testRunner.completeTest();
+    }
+  }
+
+  async function initalizeTarget(dp) {
+    dp.Network.onLoadingFailed(event => record(event.params.requestId, {loadingFailed: event.params})),
+    dp.Network.onLoadingFinished(event => record(event.params.requestId, {loadingFinished: event.params})),
+    dp.Network.onRequestWillBeSent(event => record(event.params.requestId, {requestWillBeSent: event.params})),
+    await Promise.all([
+      dp.Network.enable(),
+      dp.Page.enable()
+    ]);
+  }
+
+  await initalizeTarget(dp);
+
+  dp.Target.onAttachedToTarget(async e => {
+    const dp = session.createChild(e.params.sessionId).protocol;
+    await initalizeTarget(dp);
+  });
+
+  page.navigate('https://devtools.test:8443/inspector-protocol/network/cross-origin-isolation/resources/coep-page-with-resources.php');
+
+  // `record` above makes sure to complete the test.
+})
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/resources/coep-page-with-resources.php b/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/resources/coep-page-with-resources.php
new file mode 100644
index 0000000..275a50bd
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/resources/coep-page-with-resources.php
@@ -0,0 +1,49 @@
+<?php
+
+header("Cross-Origin-Embedder-Policy: require-corp");
+
+?>
+
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>Page with Cross-Origin-Embedder-Policy</title>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <div>
+      This is a COEP page embedding resources.
+    </div>
+    None/None<br/>
+    <iframe src="https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php">
+    </iframe><br/>
+    COEP/None<br/>
+    <iframe src="https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?coep">
+    </iframe><br/>
+    COEP/Same-Site<br/>
+    <iframe src="https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?coep&corp=same-site">
+    </iframe><br/>
+    COEP/Same-Origin<br/>
+    <iframe src="https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?coep&corp=same-origin">
+    </iframe><br/>
+    COEP/Cross-Origin<br/>
+    <iframe src="https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?coep&corp=cross-origin">
+    </iframe><br/>
+    Script CORP None <span id="script-corp-none">not loaded</span>
+    <script src="https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php" defer>
+    </script><br/>
+    Script CORP cross origin <span id="script-corp-cross-origin">not loaded</span>
+    <script src="https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?corp=cross-origin" defer>
+    </script>    <br/>
+    Script CORP same site <span id="script-corp-same-site">not loaded</span>
+    <script src="https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?corp=same-site" defer>
+    </script><br/>
+    Script CORP same origin <span id="script-corp-same-origin">not loaded</span>
+    <script src="https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php?corp=same-origin" defer>
+    </script><br/>
+    Sandboxed COOP iframe<br/>
+    <iframe src="https://devtools.oopif.test:8443/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php&coop">
+    </iframe><br/>
+  </body>
+</html>
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php b/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php
new file mode 100644
index 0000000..39e1604
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/cross-origin-isolation/resources/page-with-coep-corp.php
@@ -0,0 +1,18 @@
+<?php
+
+if (isset($_GET['corp'])) {
+  header("Cross-Origin-Resource-Policy: " . $_GET['corp']);
+}
+
+if (isset($_GET['coop'])) {
+  header("Cross-Origin-Opener-Policy: same-origin");
+}
+
+
+if (isset($_GET['coep'])) {
+  header("Cross-Origin-Embedder-Policy: require-corp");
+}
+
+echo "This is some content";
+
+?>
diff --git a/third_party/blink/web_tests/inspector-protocol/emulation/set-vision-deficiency-expected.txt b/third_party/blink/web_tests/inspector-protocol/emulation/set-vision-deficiency-expected.txt
new file mode 100644
index 0000000..1699891
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/emulation/set-vision-deficiency-expected.txt
@@ -0,0 +1,42 @@
+Tests that vision deficiencies can be emulated.
+<p>Emulating none: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFRJREFUWIXt18ENACAMw8DA5N0chnCQePgGiKz+ujI5aZru3K6uPWAgZSBlIGUgZSBlIGUgZSBlILWS030iyr6/oIGUgZSBlIGUgZSBlIGUgZSB1AXNwwVLtuf+dAAAAABJRU5ErkJggg==">
+<p>Emulating achromatomaly: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAGFJREFUWIXt10ENwCAUBFFAATbQxBErtVNdyAARs016mCdgM/m3X8capwTtdybnSouufcBAykDKQMpAykDKQMpAykDKQKr2/kR/krTfX9BAykDKQMpAykDKQMpAykDKQOoCayUFlTeCp/sAAAAASUVORK5CYII=">
+<p>Emulating achromatopsia: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFpJREFUWIXt10ENADEMxMD2eARR2IfQFYS3Uh8eACsrv+zu/lfQzCTn1hddu8BAykDKQMpAykDKQMpAykDKQGpXVfQnSXv+ggZSBlIGUgZSBlIGUgZSBlIGUgcPVwX7L3bHewAAAABJRU5ErkJggg==">
+<p>Emulating blurredVision: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAehJREFUWIXtl81u4jAURo8TN7RpASXqohKLvsG89DzXLJFm1ZZCyb89i5tAWtAQYlfqIp90FWEl1yfX186H4tdvy3wN8Qvc7CGoQVnA8n8psAqMhiqGfQrbFWye4X0FWSrjRst9qAv5zkuzWEPyBx7+QrQDXbZwAwBRUEdQPsDuSYbKOeQJFAtQZhTUZ8C7F4FbrGG2EUBlhgHaQACLhQzlCURbCAtZicOLjqueAN7spXK3G5i9jQCcyfz5TlokLI9tMp6rBxjUkjQsBU4X1wEC1GUPrOn18KUcQwCV4WwM6kF6z1hvVfsMeJA9E5d07hk/lesUeMv0TZoAXTUBumoCdFXvHFRf4pKuvX+cNDbgJIDBn7qT8AusMRqaSKKO2rmvdDPd80aDDb1CaqpY/Fy+FKZRdmspOaq4B9pV0xUwS8VsAkRLN8OapWJYmxmYEB/9qXlfCUue+LH8WQplfKyiozSbZ3nr7bb1dM0AuB6kCWVZy7nAfTxCdS9jHjaNVDBLxKh2Nl0NBOwmN1qcdRVDed/7s+ReQcXtqyWor1ja0xSHpe5OBKPbHnTfKIqwsIddqxgJ2F39n4cK1bREri5YHa+2/9s5q/UM6FdfvsU/Tz/ezUyArpoAXTUBumoCdNUE6KoJ0FX/AAow6PKlz4YvAAAAAElFTkSuQmCC">
+<p>Emulating none: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFRJREFUWIXt18ENACAMw8DA5N0chnCQePgGiKz+ujI5aZru3K6uPWAgZSBlIGUgZSBlIGUgZSBlILWS030iyr6/oIGUgZSBlIGUgZSBlIGUgZSB1AXNwwVLtuf+dAAAAABJRU5ErkJggg==">
+<p>Emulating deuteranomaly: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAGFJREFUWIXt1zENwDAQBMG3IaQ0uWAzHGNwHxoOiL1IKXYAnFbffRv3dSromSs5Vz269gEDKQMpAykDKQMpAykDKQMpA6lWtaM/SdrvL2ggZSBlIGUgZSBlIGUgZSBlIPUCZwcGA/y3590AAAAASUVORK5CYII=">
+<p>Emulating deuteranopia: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAF9JREFUWIXt17ENwCAQBMGHYohdqKuhEsduBorYQyLYKeC0+uzbM8aqoO9/k3PVo2sHGEgZSBlIGUgZSBlIGUgZSBlItaoZ/UnSrr+ggZSBlIGUgZSBlIGUgZSBlIHUBmSnBbHzufJtAAAAAElFTkSuQmCC">
+<p>Emulating none: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFRJREFUWIXt18ENACAMw8DA5N0chnCQePgGiKz+ujI5aZru3K6uPWAgZSBlIGUgZSBlIGUgZSBlILWS030iyr6/oIGUgZSBlIGUgZSBlIGUgZSB1AXNwwVLtuf+dAAAAABJRU5ErkJggg==">
+<p>Emulating protanomaly: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAGFJREFUWIXt1zENwDAQBMG3EaQzsqA0GENwHRQOiL1IKXYAnFbffRv3dSromSs5Vz269gEDKQMpAykDKQMpAykDKQMpA6lWtaM/SdrvL2ggZSBlIGUgZSBlIGUgZSBlIPUC9+8GC+XyxMUAAAAASUVORK5CYII=">
+<p>Emulating protanopia: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAF9JREFUWIXt10ENwCAQBdEFI5VBahcnnEhVURHzm/QwT8DPZG/bxn2dCnr2TM5Vj659wEDKQMpAykDKQMpAykDKQMpAqlWt6E+S9vsLGkgZSBlIGUgZSBlIGUgZSBlIvZuvBc8DKP+1AAAAAElFTkSuQmCC">
+<p>Emulating tritanomaly: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFxJREFUWIXt18EJACEQBEEVczA78zQhoxA0iJ6De3QFMDT729rnuCXo7JWcKy269gEDKQMpAykDKQMpAykDKQMpA6me/iHSfn9BAykDKQMpAykDKQMpAykDKQOpB4zjBwAkamC5AAAAAElFTkSuQmCC">
+<p>Emulating tritanopia: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAF5JREFUWIXt17ENwCAQBEGwEC0RuQBaoP8qnJgi9pAIdgo4rT772sf7l6BvzeRceaJrBxhIGUgZSBlIGUgZSBlIGUgZSLX0D5F2/QUNpAykDKQMpAykDKQMpAykDKQ2h2YFBXR3unwAAAAASUVORK5CYII=">
+<p>Emulating tritanopia: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAF5JREFUWIXt17ENwCAQBEGwEC0RuQBaoP8qnJgi9pAIdgo4rT772sf7l6BvzeRceaJrBxhIGUgZSBlIGUgZSBlIGUgZSLX0D5F2/QUNpAykDKQMpAykDKQMpAykDKQ2h2YFBXR3unwAAAAASUVORK5CYII=">
+<p>Emulating some-invalid-deficiency: 
+{
+  "code": -32602,
+  "message": "Unknown vision deficiency type"
+}
+<p>Emulating : 
+{
+  "code": -32602,
+  "message": "Unknown vision deficiency type"
+}
+<p>Navigating&mldr;
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAF5JREFUWIXt17ENwCAQBEGwEC0RuQBaoP8qnJgi9pAIdgo4rT772sf7l6BvzeRceaJrBxhIGUgZSBlIGUgZSBlIGUgZSLX0D5F2/QUNpAykDKQMpAykDKQMpAykDKQ2h2YFBXR3unwAAAAASUVORK5CYII=">
+<p>Emulating achromatopsia: 
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFpJREFUWIXt10ENADEMxMD2eARR2IfQFYS3Uh8eACsrv+zu/lfQzCTn1hddu8BAykDKQMpAykDKQMpAykDKQGpXVfQnSXv+ggZSBlIGUgZSBlIGUgZSBlIGUgcPVwX7L3bHewAAAABJRU5ErkJggg==">
+
diff --git a/third_party/blink/web_tests/inspector-protocol/emulation/set-vision-deficiency.js b/third_party/blink/web_tests/inspector-protocol/emulation/set-vision-deficiency.js
new file mode 100644
index 0000000..6adc8ca2
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/emulation/set-vision-deficiency.js
@@ -0,0 +1,59 @@
+(async function(testRunner) {
+  const {page, session, dp} = await testRunner.startBlank(
+      'Tests that vision deficiencies can be emulated.');
+  // Note: the output log for this test can be viewed as HTML to
+  // simplify review.
+
+  await session.navigate('../resources/vision-deficiency.html');
+
+  async function logScreenshotData() {
+    const response = await dp.Page.captureScreenshot({
+      clip: {
+        x: 0,
+        y: 0,
+        width: 40,
+        height: 40,
+        scale: 1,
+      },
+    });
+    const imageData = response.result.data;
+    testRunner.log(`<img src="data:image/png;base64,${imageData}">`);
+  }
+
+  async function setEmulatedVisionDeficiency(id) {
+    testRunner.log(`<p>Emulating ${id}: `);
+    const response = await dp.Emulation.setEmulatedVisionDeficiency({
+      type: id,
+    });
+    if (response.error) {
+      testRunner.log(JSON.stringify(response.error, null, 2));
+      return;
+    }
+    await logScreenshotData();
+  }
+
+  await setEmulatedVisionDeficiency('none');
+  await setEmulatedVisionDeficiency('achromatomaly');
+  await setEmulatedVisionDeficiency('achromatopsia');
+  await setEmulatedVisionDeficiency('blurredVision');
+  await setEmulatedVisionDeficiency('none');
+  await setEmulatedVisionDeficiency('deuteranomaly');
+  await setEmulatedVisionDeficiency('deuteranopia');
+  await setEmulatedVisionDeficiency('none');
+  await setEmulatedVisionDeficiency('protanomaly');
+  await setEmulatedVisionDeficiency('protanopia');
+  await setEmulatedVisionDeficiency('tritanomaly');
+  await setEmulatedVisionDeficiency('tritanopia');
+  // Test setting the already-active vision deficiency.
+  await setEmulatedVisionDeficiency('tritanopia');
+  // Test setting unknown vision deficiencies.
+  await setEmulatedVisionDeficiency('some-invalid-deficiency');
+  await setEmulatedVisionDeficiency('');
+
+  testRunner.log(`<p>Navigating&mldr;`);
+  await session.navigate('../resources/vision-deficiency.html');
+  await logScreenshotData();
+  await setEmulatedVisionDeficiency('achromatopsia');
+
+  testRunner.completeTest();
+});
diff --git a/third_party/blink/web_tests/inspector-protocol/resources/vision-deficiency.css b/third_party/blink/web_tests/inspector-protocol/resources/vision-deficiency.css
new file mode 100644
index 0000000..17733910
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/resources/vision-deficiency.css
@@ -0,0 +1,12 @@
+html {
+  background: blue;
+}
+html, body {
+  margin: 0;
+  padding: 0;
+}
+div {
+  width: 20px;
+  height: 20px;
+  background: green;
+}
diff --git a/third_party/blink/web_tests/inspector-protocol/resources/vision-deficiency.html b/third_party/blink/web_tests/inspector-protocol/resources/vision-deficiency.html
new file mode 100644
index 0000000..3273a67
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/resources/vision-deficiency.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<!--
+Note: These tests should pass even when the document's CSP disallows
+      data: URLs.
+-->
+<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
+<link rel="stylesheet" href="vision-deficiency.css">
+<div></div>
diff --git a/third_party/blink/web_tests/virtual/web-bluetooth-new-permissions-backend/README.md b/third_party/blink/web_tests/virtual/web-bluetooth-new-permissions-backend/README.md
deleted file mode 100644
index dd5209b..0000000
--- a/third_party/blink/web_tests/virtual/web-bluetooth-new-permissions-backend/README.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Web Bluetooth New Permissions Backend
-
-This virtual test suite runs content_shell with
-`--enable-features=WebBluetoothNewPermissionsBackend`. This flag enables the
-Web Bluetooth tests to use the
-[`FakeBluetoothDelegate`](https://source.chromium.org/chromium/chromium/src/+/master:content/shell/browser/web_test/fake_bluetooth_delegate.h)
-interface for granting and checking permissions. This class emulates the
-behavior of the new Web Bluetooth permissions backend based on
-[`ChooserContextBase`](https://source.chromium.org/chromium/chromium/src/+/master:chrome/browser/permissions/chooser_context_base.h).
-
-The new permissions backend is implemented as part of the [Web Bluetooth
-Persistent Permissions project](https://docs.google.com/document/d/1h3uAVXJARHrNWaNACUPiQhLt7XI-fFFQoARSs1WgMDM).
-
-TODO(https://crbug.com/589228): Remove this virtual test suite when the
-`WebBluetoothNewPermissionsBackend` flag is enabled by default.
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/web-bluetooth-new-permissions-backend/bluetooth/README.txt b/third_party/blink/web_tests/virtual/web-bluetooth-new-permissions-backend/bluetooth/README.txt
deleted file mode 100644
index a43806c..0000000
--- a/third_party/blink/web_tests/virtual/web-bluetooth-new-permissions-backend/bluetooth/README.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This directory includes Web Bluetooth tests that use the [out of date Web
-Bluetooth test API](https://webbluetoothcg.github.io/web-bluetooth/tests.html)
-as well as some tests using the [redesigned test
-API](https://docs.google.com/document/d/1Nhv_oVDCodd1pEH_jj9k8gF4rPGb_84VYaZ9IG8M_WY).
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/README.txt b/third_party/blink/web_tests/virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/README.txt
deleted file mode 100644
index c49c2d09..0000000
--- a/third_party/blink/web_tests/virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/README.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This directory includes Web Bluetooth tests that use the [redesigned Web
-Bluetooth test
-API](https://docs.google.com/document/d/1Nhv_oVDCodd1pEH_jj9k8gF4rPGb_84VYaZ9IG8M_WY)
-and are available in
-[web-platform-tests/wpt](https://github.com/web-platform-tests/wpt).
\ No newline at end of file
diff --git a/third_party/closure_compiler/externs/webview_tag.js b/third_party/closure_compiler/externs/webview_tag.js
index 2e890874..cbf2d20 100644
--- a/third_party/closure_compiler/externs/webview_tag.js
+++ b/third_party/closure_compiler/externs/webview_tag.js
@@ -226,6 +226,53 @@
 WebView.prototype.request;
 
 /**
+ * @constructor
+ * @see https://developer.chrome.com/apps/tags/webview#type-ContextMenus
+ */
+function ContextMenus() {}
+ContextMenus.prototype.onShow;
+ContextMenus.prototype.onShow.addListener = function() {};
+
+/**
+ * @type {ContextMenus}
+ * @see https://developer.chrome.com/apps/tags/webview#property-contextMenus
+ */
+WebView.prototype.contextMenus;
+
+/**
+ * @typedef{{
+ *   code: ?string,
+ *   files: ?Array<string>
+ * }}
+ * @see https://developer.chrome.com/apps/tags/webview#type-InjectionItems
+ */
+var InjectionItems;
+
+/**
+ * Details of the content script to inject.
+ * @typedef{{
+ *   name: string,
+ *   matches: Array<string>,
+ *   exclude_matches: ?Array<string>,
+ *   match_about_blank: ?boolean,
+ *   css: ?InjectionItems,
+ *   js: ?InjectionItems,
+ *   run_at: ?string,
+ *   all_frames: ?boolean,
+ *   include_globs: ?Array<string>,
+ *   exclude_globs: ?Array<string>,
+ * }}
+ * @see https://developer.chrome.com/apps/tags/webview#type-ContentScriptDetails
+ */
+var ContentScriptDetails;
+
+/**
+ * @param {Array<ContentScriptDetails>} contentScriptList
+ * @see https://developer.chrome.com/apps/tags/webview#method-addContentScripts
+ */
+WebView.prototype.addContentScripts = function(contentScriptList) {};
+
+/**
  * @see https://developer.chrome.com/apps/tags/webview#method-back
  */
 WebView.prototype.back = function() {};
@@ -319,6 +366,12 @@
 WebView.prototype.reload = function() {};
 
 /**
+ * @param {Array<string>} contentScriptList
+ * @see https://developer.chrome.com/apps/tags/webview#method-removeContentScripts
+ */
+WebView.prototype.removeContentScripts = function(contentScriptList) {};
+
+/**
  * @param {string} userAgent
  * @see https://developer.chrome.com/apps/tags/webview#method-setUserAgentOverride
  */
@@ -332,6 +385,14 @@
 WebView.prototype.setZoom = function(zoomFactor, opt_callback) {};
 
 /**
+ * @param {string} zoomMode Allowed values: "per-origin", "per-view", "disabled"
+ * @see https://developer.chrome.com/apps/tags/webview#type-ZoomMode
+ * @param {Function=} opt_callback
+ * @see https://developer.chrome.com/apps/tags/webview#method-setZoomMode
+ */
+WebView.prototype.setZoomMode = function(zoomMode, opt_callback) {};
+
+/**
  * @see https://developer.chrome.com/apps/tags/webview#method-stop
  */
 WebView.prototype.stop = function() {};
diff --git a/tools/clang/blink_gc_plugin/tests/finalize_after_dispatch.cpp b/tools/clang/blink_gc_plugin/tests/finalize_after_dispatch.cpp
index 14166a78..c7e932e 100644
--- a/tools/clang/blink_gc_plugin/tests/finalize_after_dispatch.cpp
+++ b/tools/clang/blink_gc_plugin/tests/finalize_after_dispatch.cpp
@@ -23,9 +23,7 @@
     }
 }
 
-void A::TraceAfterDispatch(Visitor* visitor)
-{
-}
+void A::TraceAfterDispatch(Visitor* visitor) const {}
 
 void A::FinalizeGarbageCollectedObject()
 {
@@ -42,22 +40,18 @@
     }
 }
 
-void B::TraceAfterDispatch(Visitor* visitor)
-{
-    visitor->Trace(m_a);
-    A::TraceAfterDispatch(visitor);
+void B::TraceAfterDispatch(Visitor* visitor) const {
+  visitor->Trace(m_a);
+  A::TraceAfterDispatch(visitor);
 }
 
-void C::TraceAfterDispatch(Visitor* visitor)
-{
-    visitor->Trace(m_a);
-    A::TraceAfterDispatch(visitor);
+void C::TraceAfterDispatch(Visitor* visitor) const {
+  visitor->Trace(m_a);
+  A::TraceAfterDispatch(visitor);
 }
 
-void D::TraceAfterDispatch(Visitor* visitor)
-{
-    visitor->Trace(m_a);
-    Abstract::TraceAfterDispatch(visitor);
+void D::TraceAfterDispatch(Visitor* visitor) const {
+  visitor->Trace(m_a);
+  Abstract::TraceAfterDispatch(visitor);
 }
-
 }
diff --git a/tools/clang/blink_gc_plugin/tests/finalize_after_dispatch.h b/tools/clang/blink_gc_plugin/tests/finalize_after_dispatch.h
index f256bd03..9a67d8c 100644
--- a/tools/clang/blink_gc_plugin/tests/finalize_after_dispatch.h
+++ b/tools/clang/blink_gc_plugin/tests/finalize_after_dispatch.h
@@ -19,14 +19,14 @@
 class NeedsFinalizedBase : public GarbageCollected<NeedsFinalizedBase> {
 public:
     void Trace(Visitor*) { };
-    void TraceAfterDispatch(Visitor*) { };
+    void TraceAfterDispatch(Visitor*) const {};
     void FinalizeGarbageCollectedObject() { };
 };
 
 class A : GarbageCollected<A> {
  public:
   void Trace(Visitor*);
-  void TraceAfterDispatch(Visitor*);
+  void TraceAfterDispatch(Visitor*) const;
   void FinalizeGarbageCollectedObject();
 
  protected:
@@ -41,16 +41,18 @@
 public:
     B() : A(TB) { }
     ~B() { }
-    void TraceAfterDispatch(Visitor*);
-private:
+    void TraceAfterDispatch(Visitor*) const;
+
+   private:
     Member<A> m_a;
 };
 
 class C : public A {
 public:
     C() : A(TC) { }
-    void TraceAfterDispatch(Visitor*);
-private:
+    void TraceAfterDispatch(Visitor*) const;
+
+   private:
     Member<A> m_a;
 };
 
@@ -63,8 +65,9 @@
 class D : public Abstract {
 public:
     D() : Abstract(TD) { }
-    void TraceAfterDispatch(Visitor*);
-private:
+    void TraceAfterDispatch(Visitor*) const;
+
+   private:
     Member<A> m_a;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.cpp b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.cpp
index 4c62354..80f382b 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.cpp
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.cpp
@@ -23,28 +23,22 @@
     }
 }
 
-void A::TraceAfterDispatch(Visitor* visitor)
-{
+void A::TraceAfterDispatch(Visitor* visitor) const {}
+
+void B::TraceAfterDispatch(Visitor* visitor) const {
+  visitor->Trace(m_a);
+  // Missing A::TraceAfterDispatch(visitor);
+  // Also check that calling Trace does not count.
+  A::Trace(visitor);
 }
 
-void B::TraceAfterDispatch(Visitor* visitor)
-{
-    visitor->Trace(m_a);
-    // Missing A::TraceAfterDispatch(visitor);
-    // Also check that calling Trace does not count.
-    A::Trace(visitor);
+void C::TraceAfterDispatch(Visitor* visitor) const {
+  // Missing visitor->Trace(m_a);
+  A::TraceAfterDispatch(visitor);
 }
 
-void C::TraceAfterDispatch(Visitor* visitor)
-{
-    // Missing visitor->Trace(m_a);
-    A::TraceAfterDispatch(visitor);
+void D::TraceAfterDispatch(Visitor* visitor) const {
+  visitor->Trace(m_a);
+  Abstract::TraceAfterDispatch(visitor);
 }
-
-void D::TraceAfterDispatch(Visitor* visitor)
-{
-    visitor->Trace(m_a);
-    Abstract::TraceAfterDispatch(visitor);
-}
-
 }
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.h b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.h
index 0a5a7c7..b72e618 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.h
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.h
@@ -12,8 +12,9 @@
 class A : public GarbageCollected<A> {
 public:
     void Trace(Visitor*);
-    void TraceAfterDispatch(Visitor*);
-protected:
+    void TraceAfterDispatch(Visitor*) const;
+
+   protected:
     enum Type { TB, TC, TD };
     A(Type type) : m_type(type) { }
 private:
@@ -23,16 +24,18 @@
 class B : public A {
 public:
     B() : A(TB) { }
-    void TraceAfterDispatch(Visitor*);
-private:
+    void TraceAfterDispatch(Visitor*) const;
+
+   private:
     Member<A> m_a;
 };
 
 class C : public A {
 public:
     C() : A(TC) { }
-    void TraceAfterDispatch(Visitor*);
-private:
+    void TraceAfterDispatch(Visitor*) const;
+
+   private:
     Member<A> m_a;
 };
 
@@ -45,8 +48,9 @@
 class D : public Abstract {
 public:
     D() : Abstract(TD) { }
-    void TraceAfterDispatch(Visitor*);
-private:
+    void TraceAfterDispatch(Visitor*) const;
+
+   private:
     Member<A> m_a;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.txt b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.txt
index 4873999..7e25b03 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.txt
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch.txt
@@ -2,10 +2,10 @@
 void A::Trace(Visitor* visitor)
 ^
 trace_after_dispatch.cpp:30:1: warning: [blink-gc] Base class 'A' of derived class 'B' requires tracing.
-void B::TraceAfterDispatch(Visitor* visitor)
+void B::TraceAfterDispatch(Visitor* visitor) const
 ^
 trace_after_dispatch.cpp:38:1: warning: [blink-gc] Class 'C' has untraced fields that require tracing.
-void C::TraceAfterDispatch(Visitor* visitor)
+void C::TraceAfterDispatch(Visitor* visitor) const
 ^
 ./trace_after_dispatch.h:36:5: note: [blink-gc] Untraced field 'm_a' declared here:
     Member<A> m_a;
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.cpp b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.cpp
index c8cd1df..337ee8a38 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.cpp
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.cpp
@@ -29,13 +29,13 @@
   }
 }
 
-void TraceAfterDispatchExternBase::TraceAfterDispatch(Visitor* visitor) {
+void TraceAfterDispatchExternBase::TraceAfterDispatch(Visitor* visitor) const {
   visitor->Trace(x_base_);
 }
 
-void TraceAfterDispatchExternDerived::TraceAfterDispatch(Visitor* visitor) {
+void TraceAfterDispatchExternDerived::TraceAfterDispatch(
+    Visitor* visitor) const {
   visitor->Trace(x_derived_);
   TraceAfterDispatchExternBase::TraceAfterDispatch(visitor);
 }
-
 }
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.h b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.h
index 967793f..d86f072a 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.h
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.h
@@ -25,7 +25,7 @@
 
   void Trace(Visitor*);
 
-  void TraceAfterDispatch(Visitor* visitor) { visitor->Trace(x_base_); }
+  void TraceAfterDispatch(Visitor* visitor) const { visitor->Trace(x_base_); }
 
  private:
   ClassTag tag_;
@@ -36,7 +36,7 @@
  public:
   TraceAfterDispatchInlinedDerived() : TraceAfterDispatchInlinedBase(DERIVED) {}
 
-  void TraceAfterDispatch(Visitor* visitor) {
+  void TraceAfterDispatch(Visitor* visitor) const {
     visitor->Trace(x_derived_);
     TraceAfterDispatchInlinedBase::TraceAfterDispatch(visitor);
   }
@@ -52,7 +52,7 @@
 
   void Trace(Visitor* visitor);
 
-  void TraceAfterDispatch(Visitor* visitor);
+  void TraceAfterDispatch(Visitor* visitor) const;
 
  private:
   ClassTag tag_;
@@ -63,7 +63,7 @@
  public:
   TraceAfterDispatchExternDerived() : TraceAfterDispatchExternBase(DERIVED) {}
 
-  void TraceAfterDispatch(Visitor* visitor);
+  void TraceAfterDispatch(Visitor* visitor) const;
 
  private:
   Member<X> x_derived_;
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.cpp b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.cpp
index 76dc9761..9d9e01305 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.cpp
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.cpp
@@ -31,12 +31,12 @@
   }
 }
 
-void TraceAfterDispatchExternBase::TraceAfterDispatch(Visitor* visitor) {
+void TraceAfterDispatchExternBase::TraceAfterDispatch(Visitor* visitor) const {
   // No Trace call.
 }
 
-void TraceAfterDispatchExternDerived::TraceAfterDispatch(Visitor* visitor) {
+void TraceAfterDispatchExternDerived::TraceAfterDispatch(
+    Visitor* visitor) const {
   // Ditto.
 }
-
 }
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.h b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.h
index 00c7986..35b34b9 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.h
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.h
@@ -25,7 +25,7 @@
 
   void Trace(Visitor*);
 
-  void TraceAfterDispatch(Visitor* visitor) {
+  void TraceAfterDispatch(Visitor* visitor) const {
     // No Trace call; should get a warning.
   }
 
@@ -38,7 +38,7 @@
  public:
   TraceAfterDispatchInlinedDerived() : TraceAfterDispatchInlinedBase(DERIVED) {}
 
-  void TraceAfterDispatch(Visitor* visitor) {
+  void TraceAfterDispatch(Visitor* visitor) const {
     // No Trace call (for member and base class).
   }
 
@@ -53,7 +53,7 @@
 
   void Trace(Visitor* visitor);
 
-  void TraceAfterDispatch(Visitor* visitor);
+  void TraceAfterDispatch(Visitor* visitor) const;
 
  private:
   ClassTag tag_;
@@ -64,7 +64,7 @@
  public:
   TraceAfterDispatchExternDerived() : TraceAfterDispatchExternBase(DERIVED) {}
 
-  void TraceAfterDispatch(Visitor* visitor);
+  void TraceAfterDispatch(Visitor* visitor) const;
 
  private:
   Member<X> x_derived_;
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.txt b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.txt
index 85f57af..dbe1329 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.txt
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.txt
@@ -6,26 +6,26 @@
 ^
 In file included from trace_after_dispatch_impl_error.cpp:5:
 ./trace_after_dispatch_impl_error.h:28:3: warning: [blink-gc] Class 'TraceAfterDispatchInlinedBase' has untraced fields that require tracing.
-  void TraceAfterDispatch(Visitor* visitor) {
+  void TraceAfterDispatch(Visitor* visitor) const {
   ^
 ./trace_after_dispatch_impl_error.h:34:3: note: [blink-gc] Untraced field 'x_base_' declared here:
   Member<X> x_base_;
   ^
 ./trace_after_dispatch_impl_error.h:41:3: warning: [blink-gc] Base class 'TraceAfterDispatchInlinedBase' of derived class 'TraceAfterDispatchInlinedDerived' requires tracing.
-  void TraceAfterDispatch(Visitor* visitor) {
+  void TraceAfterDispatch(Visitor* visitor) const {
   ^
 ./trace_after_dispatch_impl_error.h:41:3: warning: [blink-gc] Class 'TraceAfterDispatchInlinedDerived' has untraced fields that require tracing.
 ./trace_after_dispatch_impl_error.h:46:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
   Member<X> x_derived_;
   ^
 trace_after_dispatch_impl_error.cpp:34:1: warning: [blink-gc] Class 'TraceAfterDispatchExternBase' has untraced fields that require tracing.
-void TraceAfterDispatchExternBase::TraceAfterDispatch(Visitor* visitor) {
+void TraceAfterDispatchExternBase::TraceAfterDispatch(Visitor* visitor) const {
 ^
 ./trace_after_dispatch_impl_error.h:60:3: note: [blink-gc] Untraced field 'x_base_' declared here:
   Member<X> x_base_;
   ^
 trace_after_dispatch_impl_error.cpp:38:1: warning: [blink-gc] Base class 'TraceAfterDispatchExternBase' of derived class 'TraceAfterDispatchExternDerived' requires tracing.
-void TraceAfterDispatchExternDerived::TraceAfterDispatch(Visitor* visitor) {
+void TraceAfterDispatchExternDerived::TraceAfterDispatch(Visitor* visitor) const {
 ^
 trace_after_dispatch_impl_error.cpp:38:1: warning: [blink-gc] Class 'TraceAfterDispatchExternDerived' has untraced fields that require tracing.
 ./trace_after_dispatch_impl_error.h:70:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
diff --git a/tools/clang/blink_gc_plugin/tests/trace_wrapper.h b/tools/clang/blink_gc_plugin/tests/trace_wrapper.h
index 898eb63e..c09c03f 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_wrapper.h
+++ b/tools/clang/blink_gc_plugin/tests/trace_wrapper.h
@@ -26,12 +26,12 @@
 class B : public GarbageCollected<B> {
  public:
   void Trace(Visitor* visitor);
-  void TraceAfterDispatch(Visitor*) {}
+  void TraceAfterDispatch(Visitor*) const {}
 };
 
 class C : public B {
  public:
-  void TraceAfterDispatch(Visitor*) {
+  void TraceAfterDispatch(Visitor*) const {
     // Missing visitor->Trace(str_);
   }
 
diff --git a/tools/clang/blink_gc_plugin/tests/trace_wrapper.txt b/tools/clang/blink_gc_plugin/tests/trace_wrapper.txt
index e6c1076..b5a2b72 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_wrapper.txt
+++ b/tools/clang/blink_gc_plugin/tests/trace_wrapper.txt
@@ -6,7 +6,7 @@
   TraceWrapperV8Reference<v8::String> str_;
   ^
 ./trace_wrapper.h:34:3: warning: [blink-gc] Base class 'B' of derived class 'C' requires tracing.
-  void TraceAfterDispatch(Visitor*) {
+  void TraceAfterDispatch(Visitor*) const {
   ^
 ./trace_wrapper.h:34:3: warning: [blink-gc] Class 'C' has untraced fields that require tracing.
 ./trace_wrapper.h:39:3: note: [blink-gc] Untraced field 'str_' declared here:
diff --git a/tools/clang/blink_gc_plugin/tests/virtual_and_trace_after_dispatch.cpp b/tools/clang/blink_gc_plugin/tests/virtual_and_trace_after_dispatch.cpp
index b284ddf..87719d99 100644
--- a/tools/clang/blink_gc_plugin/tests/virtual_and_trace_after_dispatch.cpp
+++ b/tools/clang/blink_gc_plugin/tests/virtual_and_trace_after_dispatch.cpp
@@ -17,14 +17,10 @@
     }
 }
 
-void A::TraceAfterDispatch(Visitor* visitor)
-{
-}
+void A::TraceAfterDispatch(Visitor* visitor) const {}
 
-void B::TraceAfterDispatch(Visitor* visitor)
-{
-    visitor->Trace(m_a);
-    A::TraceAfterDispatch(visitor);
+void B::TraceAfterDispatch(Visitor* visitor) const {
+  visitor->Trace(m_a);
+  A::TraceAfterDispatch(visitor);
 }
-
 }
diff --git a/tools/clang/blink_gc_plugin/tests/virtual_and_trace_after_dispatch.h b/tools/clang/blink_gc_plugin/tests/virtual_and_trace_after_dispatch.h
index c6a7c959..9017e86 100644
--- a/tools/clang/blink_gc_plugin/tests/virtual_and_trace_after_dispatch.h
+++ b/tools/clang/blink_gc_plugin/tests/virtual_and_trace_after_dispatch.h
@@ -12,8 +12,9 @@
 class A : public GarbageCollected<A> {
 public:
     void Trace(Visitor*);
-    void TraceAfterDispatch(Visitor*);
-protected:
+    void TraceAfterDispatch(Visitor*) const;
+
+   protected:
     enum Type { TB };
     A(Type type) : m_type(type) { }
 private:
@@ -23,7 +24,7 @@
 class B : public A {
 public:
     B() : A(TB) { }
-    void TraceAfterDispatch(Visitor*);
+    void TraceAfterDispatch(Visitor*) const;
     virtual void foo() { }
 private:
     Member<A> m_a;
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index 79ce436..fe26a76 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -200,6 +200,9 @@
   "chrome/browser/resources/bluetooth_internals/resources.grd": {
     "includes": [2020],
   },
+  "chrome/browser/resources/gaia_auth_host/gaia_auth_host_resources.grd": {
+    "includes": [2030],
+  },
   "chrome/browser/resources/invalidations/invalidations_resources.grd": {
     "includes": [2040],
   },
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 02cc9c86..f53f57b0 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -37434,6 +37434,8 @@
   <int value="-1776351704" label="DesktopPWAsOmniboxInstall:disabled"/>
   <int value="-1774818943" label="VrWebInputEditing:enabled"/>
   <int value="-1772942854" label="LongPressBackForHistory:enabled"/>
+  <int value="-1772905637"
+      label="RunVideoCaptureServiceInBrowserProcess:enabled"/>
   <int value="-1772172557" label="enable-osk-overscroll"/>
   <int value="-1768672408" label="ChromeDuplex:disabled"/>
   <int value="-1768308156" label="OmniboxDedupeGoogleDriveURLs:enabled"/>
@@ -38897,6 +38899,8 @@
   <int value="-20438829" label="SyncUSSAutofillProfile:enabled"/>
   <int value="-20329017" label="EduCoexistence:disabled"/>
   <int value="-20267582" label="ResourceLoadingHints:disabled"/>
+  <int value="-19498390"
+      label="RunVideoCaptureServiceInBrowserProcess:disabled"/>
   <int value="-18464041" label="AutofillPrefilledFields:disabled"/>
   <int value="-17698200" label="DoubleTapToZoomInTabletMode:disabled"/>
   <int value="-17373827" label="SharingDeviceExpiration:enabled"/>
diff --git a/tools/perf/core/minidump_unittest.py b/tools/perf/core/minidump_unittest.py
index ccaa19ff..8562d8d2 100644
--- a/tools/perf/core/minidump_unittest.py
+++ b/tools/perf/core/minidump_unittest.py
@@ -20,7 +20,8 @@
   # still read-only, so skip the test in that case.
   # TODO(crbug.com/1038043): Test is failing on chromeos-betty-chrome.
   # TODO(crbug.com/1056235): Re-enable on Linux once the crashes are fixed.
-  @decorators.Disabled('chromeos-local', 'chromeos-board-betty', 'linux')
+  @decorators.Disabled('chromeos-local', 'chromeos-board-betty',
+                       'chromeos-amd64-generic-rel', 'linux')
   def testSymbolizeMinidump(self):
     # Wait for the browser to restart fully before crashing
     self._LoadPageThenWait('var sam = "car";', 'sam')
@@ -64,7 +65,8 @@
   # still read-only, so skip the test in that case.
   # TODO(crbug.com/1038043): Test is failing on chromeos-betty-chrome.
   # TODO(crbug.com/1056235): Re-enable on Linux once the crashes are fixed.
-  @decorators.Disabled('chromeos-local', 'chromeos-board-betty', 'linux')
+  @decorators.Disabled('chromeos-local', 'chromeos-board-betty',
+                       'chromeos-amd64-generic-rel', 'linux')
   def testMultipleCrashMinidumps(self):
     # Wait for the browser to restart fully before crashing
     self._LoadPageThenWait('var cat = "dog";', 'cat')
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index 715a7ff..5fc8dcd 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -335,7 +335,7 @@
     "//base:jni_java",
     "//third_party/android_deps:android_support_v7_appcompat_java",
     "//third_party/android_deps:androidx_annotation_annotation_java",
-    "//third_party/android_deps:com_android_support_asynclayoutinflater_java",
+    "//third_party/android_deps:androidx_asynclayoutinflater_asynclayoutinflater_java",
     "//third_party/android_deps:com_android_support_recyclerview_v7_java",
     "//ui/base/mojom:cursor_type_java",
   ]
diff --git a/ui/android/java/src/org/chromium/ui/AsyncViewStub.java b/ui/android/java/src/org/chromium/ui/AsyncViewStub.java
index a1d864e..f17f90530 100644
--- a/ui/android/java/src/org/chromium/ui/AsyncViewStub.java
+++ b/ui/android/java/src/org/chromium/ui/AsyncViewStub.java
@@ -8,7 +8,6 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.support.v4.view.AsyncLayoutInflater;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -16,6 +15,7 @@
 import android.view.ViewParent;
 
 import androidx.annotation.NonNull;
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 
 import org.chromium.base.Callback;
 import org.chromium.base.ObserverList;
diff --git a/ui/android/junit/src/org/chromium/ui/shadows/ShadowAsyncLayoutInflater.java b/ui/android/junit/src/org/chromium/ui/shadows/ShadowAsyncLayoutInflater.java
index 352dfce..1778a82 100644
--- a/ui/android/junit/src/org/chromium/ui/shadows/ShadowAsyncLayoutInflater.java
+++ b/ui/android/junit/src/org/chromium/ui/shadows/ShadowAsyncLayoutInflater.java
@@ -5,7 +5,6 @@
 package org.chromium.ui.shadows;
 
 import android.content.Context;
-import android.support.v4.view.AsyncLayoutInflater;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -13,6 +12,7 @@
 import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
diff --git a/ui/events/blink/BUILD.gn b/ui/events/blink/BUILD.gn
index 2b8760e..b4c7d56 100644
--- a/ui/events/blink/BUILD.gn
+++ b/ui/events/blink/BUILD.gn
@@ -72,6 +72,7 @@
 
   deps = [
     "//cc:cc",
+    "//services/tracing/public/cpp:cpp",
     "//third_party/blink/public:blink_headers",
     "//third_party/one_euro_filter",
     "//ui/base:base",
diff --git a/ui/events/blink/DEPS b/ui/events/blink/DEPS
index 2f904b2..0a508d0 100644
--- a/ui/events/blink/DEPS
+++ b/ui/events/blink/DEPS
@@ -21,4 +21,6 @@
   "+ui/gfx/geometry",
 
   "+third_party/one_euro_filter/src/one_euro_filter.h",
+
+  "+services/tracing/public/cpp",
 ]
diff --git a/ui/events/blink/input_handler_proxy.cc b/ui/events/blink/input_handler_proxy.cc
index 0956f07..0ba5c5b1 100644
--- a/ui/events/blink/input_handler_proxy.cc
+++ b/ui/events/blink/input_handler_proxy.cc
@@ -22,6 +22,8 @@
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "cc/input/main_thread_scrolling_reason.h"
+#include "services/tracing/public/cpp/perfetto/flow_event_utils.h"
+#include "services/tracing/public/cpp/perfetto/macros.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
 #include "third_party/blink/public/common/input/web_touch_event.h"
@@ -43,6 +45,8 @@
 using blink::WebMouseWheelEvent;
 using blink::WebTouchEvent;
 using blink::WebTouchPoint;
+using perfetto::protos::pbzero::ChromeLatencyInfo;
+using perfetto::protos::pbzero::TrackEvent;
 
 namespace {
 
@@ -241,10 +245,15 @@
     EventDispositionCallback callback) {
   DCHECK(input_handler_);
 
-  TRACE_EVENT_WITH_FLOW1("input,benchmark", "LatencyInfo.Flow",
-                         TRACE_ID_GLOBAL(latency_info.trace_id()),
-                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
-                         "step", "HandleInputEventImpl");
+  TRACE_EVENT("input,benchmark", "LatencyInfo.Flow",
+              [&latency_info](perfetto::EventContext ctx) {
+                ChromeLatencyInfo* info =
+                    ctx.event()->set_chrome_latency_info();
+                info->set_trace_id(latency_info.trace_id());
+                info->set_step(ChromeLatencyInfo::STEP_HANDLE_INPUT_EVENT_IMPL);
+                tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_INOUT,
+                                       latency_info.trace_id());
+              });
 
   std::unique_ptr<EventWithCallback> event_with_callback =
       std::make_unique<EventWithCallback>(std::move(event), latency_info,
diff --git a/ui/latency/BUILD.gn b/ui/latency/BUILD.gn
index 13978ba..0e3639e5 100644
--- a/ui/latency/BUILD.gn
+++ b/ui/latency/BUILD.gn
@@ -18,6 +18,7 @@
 
   deps = [
     "//base",
+    "//services/tracing/public/cpp:cpp",
     "//ui/gfx",
   ]
 
diff --git a/ui/latency/DEPS b/ui/latency/DEPS
index 40d9b9f..2a39e69c 100644
--- a/ui/latency/DEPS
+++ b/ui/latency/DEPS
@@ -1,5 +1,7 @@
 include_rules = [
   "+services/metrics/public/cpp",
+  "+services/tracing/public/cpp",
+  "+third_party/perfetto/protos/perfetto/trace/track_event",
   "+ui/gfx",
 ]
 
diff --git a/ui/latency/latency_info.cc b/ui/latency/latency_info.cc
index 6dca0cd..90c77e1 100644
--- a/ui/latency/latency_info.cc
+++ b/ui/latency/latency_info.cc
@@ -15,9 +15,14 @@
 #include "base/macros.h"
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/trace_event.h"
+#include "services/tracing/public/cpp/perfetto/flow_event_utils.h"
+#include "services/tracing/public/cpp/perfetto/macros.h"
 
 namespace {
 
+using perfetto::protos::pbzero::ChromeLatencyInfo;
+using perfetto::protos::pbzero::TrackEvent;
+
 const size_t kMaxLatencyInfoNumber = 100;
 
 const char* GetComponentName(ui::LatencyComponentType type) {
@@ -148,14 +153,20 @@
 
 void LatencyInfo::TraceIntermediateFlowEvents(
     const std::vector<LatencyInfo>& latency_info,
-    const char* event_name) {
+    perfetto::protos::pbzero::ChromeLatencyInfo::Step step) {
   for (auto& latency : latency_info) {
     if (latency.trace_id() == -1)
       continue;
-    TRACE_EVENT_WITH_FLOW1("input,benchmark", "LatencyInfo.Flow",
-                           TRACE_ID_GLOBAL(latency.trace_id()),
-                           TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
-                           "step", event_name);
+
+    TRACE_EVENT(
+        "input,benchmark", "LatencyInfo.Flow",
+        [&latency, &step](perfetto::EventContext ctx) {
+          ChromeLatencyInfo* info = ctx.event()->set_chrome_latency_info();
+          info->set_step(step);
+          info->set_trace_id(latency.trace_id());
+          tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_INOUT,
+                                 latency.trace_id());
+        });
   }
 }
 
@@ -260,9 +271,14 @@
           TRACE_ID_GLOBAL(trace_id_), ts);
     }
 
-    TRACE_EVENT_WITH_FLOW1("input,benchmark", "LatencyInfo.Flow",
-                           TRACE_ID_GLOBAL(trace_id_),
-                           TRACE_EVENT_FLAG_FLOW_OUT, "trace_id", trace_id_);
+    TRACE_EVENT("input,benchmark", "LatencyInfo.Flow",
+                [this](perfetto::EventContext ctx) {
+                  ChromeLatencyInfo* info =
+                      ctx.event()->set_chrome_latency_info();
+                  info->set_trace_id(trace_id_);
+                  tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_OUT,
+                                         trace_id_);
+                });
   }
 
   auto it = latency_components_.find(component);
@@ -293,8 +309,14 @@
                                     "data", AsTraceableData());
   }
 
-  TRACE_EVENT_WITH_FLOW0("input,benchmark", "LatencyInfo.Flow",
-                         TRACE_ID_GLOBAL(trace_id_), TRACE_EVENT_FLAG_FLOW_IN);
+  TRACE_EVENT("input,benchmark", "LatencyInfo.Flow",
+              [this](perfetto::EventContext ctx) {
+                ChromeLatencyInfo* info =
+                    ctx.event()->set_chrome_latency_info();
+                info->set_trace_id(trace_id_);
+                tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_IN,
+                                       trace_id_);
+              });
 }
 
 void LatencyInfo::CoalesceScrollUpdateWith(const LatencyInfo& other) {
diff --git a/ui/latency/latency_info.h b/ui/latency/latency_info.h
index 24322a0d..fb1a15a 100644
--- a/ui/latency/latency_info.h
+++ b/ui/latency/latency_info.h
@@ -16,6 +16,7 @@
 #include "base/containers/flat_map.h"
 #include "base/time/time.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
+#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.pbzero.h"
 #include "ui/gfx/geometry/point_f.h"
 
 #if !defined(OS_IOS)
@@ -128,7 +129,7 @@
   // Adds trace flow events only to LatencyInfos that are being traced.
   static void TraceIntermediateFlowEvents(
       const std::vector<LatencyInfo>& latency_info,
-      const char* trace_name);
+      perfetto::protos::pbzero::ChromeLatencyInfo::Step step);
 
   // Copy timestamp with type |type| from |other| into |this|.
   void CopyLatencyFrom(const LatencyInfo& other, LatencyComponentType type);
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index 054cd18..ac9538b 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -22,6 +22,8 @@
     "common/wayland_util.h",
     "gpu/drm_render_node_path_finder.cc",
     "gpu/drm_render_node_path_finder.h",
+    "gpu/gl_surface_egl_readback_wayland.cc",
+    "gpu/gl_surface_egl_readback_wayland.h",
     "gpu/gl_surface_wayland.cc",
     "gpu/gl_surface_wayland.h",
     "gpu/wayland_buffer_manager_gpu.cc",
diff --git a/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc b/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc
new file mode 100644
index 0000000..700cb969
--- /dev/null
+++ b/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc
@@ -0,0 +1,178 @@
+// 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 "ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.h"
+
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/numerics/checked_math.h"
+#include "ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h"
+
+namespace ui {
+
+namespace {
+
+constexpr size_t kMaxBuffers = 2;
+
+constexpr size_t kBytesPerPixelBGRA = 4;
+
+}  // namespace
+
+GLSurfaceEglReadbackWayland::PixelBuffer::PixelBuffer(
+    base::WritableSharedMemoryMapping shm_mapping,
+    uint32_t buffer_id)
+    : shm_mapping_(std::move(shm_mapping)), buffer_id_(buffer_id) {}
+
+GLSurfaceEglReadbackWayland::PixelBuffer::~PixelBuffer() = default;
+
+GLSurfaceEglReadbackWayland::GLSurfaceEglReadbackWayland(
+    gfx::AcceleratedWidget widget,
+    WaylandBufferManagerGpu* buffer_manager)
+    : PbufferGLSurfaceEGL(gfx::Size(1, 1)),
+      widget_(widget),
+      buffer_manager_(buffer_manager) {
+  buffer_manager_->RegisterSurface(widget_, this);
+}
+
+void GLSurfaceEglReadbackWayland::Destroy() {
+  DestroyBuffers();
+  buffer_manager_->UnregisterSurface(widget_);
+
+  PbufferGLSurfaceEGL::Destroy();
+}
+
+bool GLSurfaceEglReadbackWayland::Resize(const gfx::Size& size,
+                                         float scale_factor,
+                                         const gfx::ColorSpace& color_space,
+                                         bool has_alpha) {
+  DestroyBuffers();
+
+  pending_frames_ = 0;
+
+  if (!PbufferGLSurfaceEGL::Resize(size, scale_factor, color_space, has_alpha))
+    return false;
+
+  for (size_t i = 0; i < kMaxBuffers; ++i) {
+    base::CheckedNumeric<size_t> checked_length(size.width());
+    checked_length *= size.height();
+    checked_length *= kBytesPerPixelBGRA;
+    if (!checked_length.IsValid())
+      return false;
+
+    base::UnsafeSharedMemoryRegion shm_region =
+        base::UnsafeSharedMemoryRegion::Create(checked_length.ValueOrDie());
+    if (!shm_region.IsValid())
+      return false;
+
+    auto shm_mapping = shm_region.Map();
+    if (!shm_mapping.IsValid())
+      return false;
+
+    base::subtle::PlatformSharedMemoryRegion platform_shm =
+        base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+            std::move(shm_region));
+    base::subtle::ScopedFDPair fd_pair = platform_shm.PassPlatformHandle();
+
+    auto buffer_id = buffer_manager_->AllocateBufferID();
+    available_buffers_.push_back(
+        std::make_unique<PixelBuffer>(std::move(shm_mapping), buffer_id));
+
+    buffer_manager_->CreateShmBasedBuffer(
+        std::move(fd_pair.fd), checked_length.ValueOrDie(), size, buffer_id);
+  }
+
+  return true;
+}
+
+bool GLSurfaceEglReadbackWayland::IsOffscreen() {
+  return false;
+}
+
+bool GLSurfaceEglReadbackWayland::SupportsAsyncSwap() {
+  return true;
+}
+
+gfx::SwapResult GLSurfaceEglReadbackWayland::SwapBuffers(
+    PresentationCallback callback) {
+  NOTREACHED();
+  return gfx::SwapResult::SWAP_FAILED;
+}
+
+void GLSurfaceEglReadbackWayland::SwapBuffersAsync(
+    SwapCompletionCallback completion_callback,
+    PresentationCallback presentation_callback) {
+  DCHECK(pending_frames_ < kMaxBuffers);
+
+  // Increase pending frames number.
+  ++pending_frames_;
+
+  completion_callbacks_.push_back(std::move(completion_callback));
+  presentation_callbacks_.push_back(std::move(presentation_callback));
+
+  DCHECK(!available_buffers_.empty());
+  in_flight_pixel_buffers_.push_back(std::move(available_buffers_.front()));
+  auto* next_buffer = in_flight_pixel_buffers_.back().get();
+  available_buffers_.erase(available_buffers_.begin());
+
+  const gfx::Size size = GetSize();
+  CHECK(next_buffer->shm_mapping_.memory());
+  glReadPixels(0, 0, size.width(), size.height(), GL_BGRA, GL_UNSIGNED_BYTE,
+               next_buffer->shm_mapping_.memory());
+
+  buffer_manager_->CommitBuffer(widget_, next_buffer->buffer_id_,
+                                {{0, 0}, size});
+}
+
+gfx::SurfaceOrigin GLSurfaceEglReadbackWayland::GetOrigin() const {
+  // GLSurfaceEglReadbackWayland's y-axis is flipped compare to GL - (0,0) is at
+  // top left corner.
+  return gfx::SurfaceOrigin::kTopLeft;
+}
+
+GLSurfaceEglReadbackWayland::~GLSurfaceEglReadbackWayland() {
+  Destroy();
+}
+
+void GLSurfaceEglReadbackWayland::OnSubmission(
+    uint32_t buffer_id,
+    const gfx::SwapResult& swap_result) {
+  --pending_frames_;
+
+  if (in_flight_pixel_buffers_.front()) {
+    if (displayed_buffer_)
+      available_buffers_.push_back(std::move(displayed_buffer_));
+    displayed_buffer_ = std::move(in_flight_pixel_buffers_.front());
+    DCHECK_EQ(displayed_buffer_->buffer_id_, buffer_id);
+  }
+
+  in_flight_pixel_buffers_.pop_front();
+
+  DCHECK(!completion_callbacks_.empty());
+  std::move(completion_callbacks_.front()).Run(swap_result, nullptr);
+  completion_callbacks_.erase(completion_callbacks_.begin());
+}
+
+void GLSurfaceEglReadbackWayland::OnPresentation(
+    uint32_t buffer_id,
+    const gfx::PresentationFeedback& feedback) {
+  DCHECK(!presentation_callbacks_.empty());
+  std::move(presentation_callbacks_.front()).Run(feedback);
+  presentation_callbacks_.erase(presentation_callbacks_.begin());
+}
+
+void GLSurfaceEglReadbackWayland::DestroyBuffers() {
+  for (const auto& pixel_buffer : available_buffers_)
+    buffer_manager_->DestroyBuffer(widget_, pixel_buffer->buffer_id_);
+  for (const auto& pixel_buffer : in_flight_pixel_buffers_)
+    buffer_manager_->DestroyBuffer(widget_, pixel_buffer->buffer_id_);
+
+  if (displayed_buffer_)
+    buffer_manager_->DestroyBuffer(widget_, displayed_buffer_->buffer_id_);
+
+  available_buffers_.clear();
+  in_flight_pixel_buffers_.clear();
+  displayed_buffer_.reset();
+}
+
+}  // namespace ui
diff --git a/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.h b/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.h
new file mode 100644
index 0000000..de83a75
--- /dev/null
+++ b/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.h
@@ -0,0 +1,95 @@
+// 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 UI_OZONE_PLATFORM_WAYLAND_GPU_GL_SURFACE_EGL_READBACK_WAYLAND_H_
+#define UI_OZONE_PLATFORM_WAYLAND_GPU_GL_SURFACE_EGL_READBACK_WAYLAND_H_
+
+#include "base/containers/circular_deque.h"
+#include "base/containers/flat_map.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "ui/ozone/common/gl_surface_egl_readback.h"
+#include "ui/ozone/platform/wayland/gpu/wayland_surface_gpu.h"
+
+namespace ui {
+
+class WaylandBufferManagerGpu;
+
+// GLSurface implementation that copies pixels from readback to shared memory
+// and let Wayland compositor to present them.
+class GLSurfaceEglReadbackWayland : public gl::PbufferGLSurfaceEGL,
+                                    public WaylandSurfaceGpu {
+ public:
+  GLSurfaceEglReadbackWayland(gfx::AcceleratedWidget widget,
+                              WaylandBufferManagerGpu* buffer_manager);
+  GLSurfaceEglReadbackWayland(const GLSurfaceEglReadbackWayland&) = delete;
+  GLSurfaceEglReadbackWayland& operator=(const GLSurfaceEglReadbackWayland&) =
+      delete;
+
+  // gl::GLSurface:
+  void Destroy() override;
+  bool Resize(const gfx::Size& size,
+              float scale_factor,
+              const gfx::ColorSpace& color_space,
+              bool has_alpha) override;
+  bool IsOffscreen() override;
+  gfx::SwapResult SwapBuffers(PresentationCallback callback) override;
+  bool SupportsAsyncSwap() override;
+  void SwapBuffersAsync(SwapCompletionCallback completion_callback,
+                        PresentationCallback presentation_callback) override;
+  gfx::SurfaceOrigin GetOrigin() const override;
+
+ private:
+  struct PixelBuffer {
+    PixelBuffer(base::WritableSharedMemoryMapping shm_mapping,
+                uint32_t buffer_id);
+    ~PixelBuffer();
+    PixelBuffer(const PixelBuffer&) = delete;
+    PixelBuffer& operator=(const PixelBuffer&) = delete;
+
+    // Shared memory mapping that readback pixels are written to so that Wayland
+    // is able to turn them in light.
+    base::WritableSharedMemoryMapping shm_mapping_;
+
+    // The buffer id that corresponds to the |wl_buffer| created on the browser
+    // process side.
+    uint32_t buffer_id_ = 0;
+  };
+
+  ~GLSurfaceEglReadbackWayland() override;
+
+  // WaylandSurfaceGpu:
+  void OnSubmission(uint32_t buffer_id,
+                    const gfx::SwapResult& swap_result) override;
+  void OnPresentation(uint32_t buffer_id,
+                      const gfx::PresentationFeedback& feedback) override;
+
+  void DestroyBuffers();
+
+  // Widget of the window that this readback writes pixels to.
+  const gfx::AcceleratedWidget widget_;
+
+  WaylandBufferManagerGpu* const buffer_manager_;
+
+  // Size of the buffer.
+  gfx::Size size_;
+
+  // Available pixel buffers based on shared memory.
+  std::vector<std::unique_ptr<PixelBuffer>> available_buffers_;
+
+  // Displayed buffer that will become available after another buffer is
+  // submitted.
+  std::unique_ptr<PixelBuffer> displayed_buffer_;
+
+  // Submitted buffers waiting to be displayed.
+  base::circular_deque<std::unique_ptr<PixelBuffer>> in_flight_pixel_buffers_;
+
+  std::vector<SwapCompletionCallback> completion_callbacks_;
+  std::vector<PresentationCallback> presentation_callbacks_;
+
+  size_t pending_frames_ = 0;
+};
+
+}  // namespace ui
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_GPU_GL_SURFACE_EGL_READBACK_WAYLAND_H_
diff --git a/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc b/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
index 9e11755..8611c123 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
@@ -11,6 +11,7 @@
 #include "ui/ozone/common/egl_util.h"
 #include "ui/ozone/common/gl_ozone_egl.h"
 #include "ui/ozone/platform/wayland/common/wayland_object.h"
+#include "ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.h"
 #include "ui/ozone/platform/wayland/gpu/gl_surface_wayland.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_canvas_surface.h"
@@ -77,10 +78,11 @@
 
 scoped_refptr<gl::GLSurface> GLOzoneEGLWayland::CreateSurfacelessViewGLSurface(
     gfx::AcceleratedWidget window) {
-  // Only EGLGLES2 is supported with surfaceless view gl.
-  if (gl::GetGLImplementation() != gl::kGLImplementationEGLGLES2)
-    return nullptr;
-
+  if (gl::GetGLImplementation() == gl::kGLImplementationSwiftShaderGL) {
+    return gl::InitializeGLSurface(
+        base::MakeRefCounted<GLSurfaceEglReadbackWayland>(window,
+                                                          buffer_manager_));
+  } else {
 #if defined(WAYLAND_GBM)
   // If there is a gbm device available, use surfaceless gl surface.
   if (!buffer_manager_->gbm_device())
@@ -90,6 +92,7 @@
 #else
   return nullptr;
 #endif
+  }
 }
 
 scoped_refptr<gl::GLSurface> GLOzoneEGLWayland::CreateOffscreenGLSurface(