diff --git a/DEPS b/DEPS
index b4d7ea51..b52e57d81 100644
--- a/DEPS
+++ b/DEPS
@@ -175,7 +175,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '7c095dc67ce6145ca1e5a6503f56fc126d700a67',
+  'skia_revision': 'c65d0069ec56aa972e1fd18c3d3915db5bad3498',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -187,11 +187,11 @@
   # 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': 'fca5a005aa880a51672c091fcb82f084b620670a',
+  'angle_revision': '9fac1817ede6b2f1dc61468e2625cbc30d35077c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '59465799210b3f4962af1a9dc44a4ffecb422c10',
+  'swiftshader_revision': '10a900e5ffaffdffe2806b1507af43a74acdfe9e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -238,7 +238,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '6043069708e1e7a33d4eda61115ee12d1a808972',
+  'catapult_revision': 'c9e75ab1ff4788c19a7b525fe3f35022b2f4a9b6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # 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': '139deb3609b0a58ef3c663c2c993a6b51df28474',
+  'devtools_frontend_revision': 'c7539abc143c72521e494589f3784dbea891408c',
   # 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.
@@ -538,7 +538,7 @@
   },
 
   'src/ios/third_party/material_font_disk_loader_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-foundation/material-font-disk-loader-ios.git' + '@' + '93acc021e3034898716028822cb802a3a816be7e',
+      'url': Var('chromium_git') + '/external/github.com/material-foundation/material-font-disk-loader-ios.git' + '@' + '8e30188777b016182658fbaa0a4a020a48183224',
       'condition': 'checkout_ios',
   },
 
@@ -864,7 +864,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '5b94048566ab2a458316af49f35655586fe503b0',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '471dda709844bf3c646cdaefbd522550ad5030b2',
       'condition': 'checkout_linux',
   },
 
@@ -1282,7 +1282,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'f04938adfc40c1ae4425fdc6a70f9b14961146f5',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '7449dc8a7f22156ee7a2bc2157260480938850e1',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1483,7 +1483,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '53655df4cde60b121fc530842ba9a6d5dfec1ae1',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '8ac79125c05256f4b6ad29533b99c7733e5ac219',
+    Var('webrtc_git') + '/src.git' + '@' + '9d2c2dba28656b3c913797daaf99b8bf56ca6103',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1553,7 +1553,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@a2520bfd110b8af07309f81764382cdae38ba213',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@576e75e99e18ccfc3d6ab087bf29d83c8bf697ff',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index e9c965c..3574496 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -102,7 +102,6 @@
 #include "services/network/public/mojom/cookie_manager.mojom-forward.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
-#include "storage/browser/quota/quota_settings.h"
 #include "third_party/blink/public/common/loader/url_loader_throttle.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/resource/resource_bundle_android.h"
@@ -497,15 +496,6 @@
   return new AwQuotaPermissionContext;
 }
 
-void AwContentBrowserClient::GetQuotaSettings(
-    content::BrowserContext* context,
-    content::StoragePartition* partition,
-    storage::OptionalQuotaSettingsCallback callback) {
-  storage::GetNominalDynamicSettings(
-      partition->GetPath(), context->IsOffTheRecord(),
-      storage::GetDefaultDeviceInfoHelper(), std::move(callback));
-}
-
 content::GeneratedCodeCacheSettings
 AwContentBrowserClient::GetGeneratedCodeCacheSettings(
     content::BrowserContext* context) {
diff --git a/android_webview/browser/aw_content_browser_client.h b/android_webview/browser/aw_content_browser_client.h
index e329711f..4c8d6fc5 100644
--- a/android_webview/browser/aw_content_browser_client.h
+++ b/android_webview/browser/aw_content_browser_client.h
@@ -84,10 +84,6 @@
                      content::BrowserContext* context) override;
   scoped_refptr<content::QuotaPermissionContext> CreateQuotaPermissionContext()
       override;
-  void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      storage::OptionalQuotaSettingsCallback callback) override;
   content::GeneratedCodeCacheSettings GetGeneratedCodeCacheSettings(
       content::BrowserContext* context) override;
   void AllowCertificateError(
diff --git a/ash/shell/content/client/shell_content_browser_client.cc b/ash/shell/content/client/shell_content_browser_client.cc
index 7a648118..83e5587 100644
--- a/ash/shell/content/client/shell_content_browser_client.cc
+++ b/ash/shell/content/client/shell_content_browser_client.cc
@@ -33,13 +33,5 @@
   return std::make_unique<ShellBrowserMainParts>(parameters);
 }
 
-void ShellContentBrowserClient::GetQuotaSettings(
-    content::BrowserContext* context,
-    content::StoragePartition* partition,
-    storage::OptionalQuotaSettingsCallback callback) {
-  // This should not be called in ash content environment.
-  CHECK(false);
-}
-
 }  // namespace shell
 }  // namespace ash
diff --git a/ash/shell/content/client/shell_content_browser_client.h b/ash/shell/content/client/shell_content_browser_client.h
index 23878ab..c7c9fedd 100644
--- a/ash/shell/content/client/shell_content_browser_client.h
+++ b/ash/shell/content/client/shell_content_browser_client.h
@@ -11,7 +11,6 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "content/public/browser/content_browser_client.h"
-#include "storage/browser/quota/quota_settings.h"
 
 namespace ash {
 namespace shell {
@@ -24,10 +23,6 @@
   // Overridden from content::ContentBrowserClient:
   std::unique_ptr<content::BrowserMainParts> CreateBrowserMainParts(
       const content::MainFunctionParams& parameters) override;
-  void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      storage::OptionalQuotaSettingsCallback callback) override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ShellContentBrowserClient);
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index ef5a1e4..a5b410d 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -94,9 +94,9 @@
   // first position in the grid. |use_spawn_animation| has no effect if either
   // |animate| or |reposition| are false.
   //
-  // Note: This function should only be called by |OverviewSession::AddItem|.
-  // |overview_session_| keeps count of all overview items, but this function
-  // does not update the tally.
+  // Note: This function should only be called by |OverviewSession::AddItem| and
+  // |OverviewGrid::AppendItem|. |overview_session_| keeps count of all overview
+  // items, but this function does not update the tally.
   void AddItem(aura::Window* window,
                bool reposition,
                bool animate,
@@ -105,20 +105,23 @@
                bool use_spawn_animation = false);
 
   // Similar to the above function, but adds the window to the end of the grid.
+  // Note: This function should only be called by |OverviewSession::AppendItem|.
+  // |overview_session_| keeps count of all overview items, but this function
+  // does not update the tally.
   void AppendItem(aura::Window* window,
                   bool reposition,
                   bool animate,
                   bool use_spawn_animation = false);
 
   // Removes |overview_item| from the grid. |overview_item| cannot already be
-  // absent from the grid. No items are repositioned.
+  // absent from the grid. If |item_destroying| is true, we may want to notify
+  // |overview_session_| that this grid has become empty. If |item_destroying|
+  // and |reposition| are both true, all items are repositioned with animation.
+  // |reposition| has no effect if |item_destroying| is false.
   //
   // Note: This function should only be called by |OverviewSession::RemoveItem|
   // and |OverviewGrid::Shutdown|. |overview_session_| keeps count of all
-  // overview items, but this function does not update the tally. If
-  // |item_destroying| is true, we may want to notify |overview_session_| that
-  // there are no longer any items. Calls |PositionWindows| to animate the items
-  // to their new locations if |reposition| is true.
+  // overview items, but this function does not update the tally.
   void RemoveItem(OverviewItem* overview_item,
                   bool item_destroying = false,
                   bool reposition = false);
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 11780f3..8f1e9093f 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8892888623443193504
\ No newline at end of file
+8892617276415118848
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 5557aad..0e9ebd0a 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8892888622585646768
\ No newline at end of file
+8892617275597794992
\ No newline at end of file
diff --git a/build_overrides/vulkan_headers.gni b/build_overrides/vulkan_headers.gni
new file mode 100644
index 0000000..6782a1f
--- /dev/null
+++ b/build_overrides/vulkan_headers.gni
@@ -0,0 +1,7 @@
+# Copyright 2019 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.
+
+import("//build/config/ui.gni")
+
+vulkan_use_x11 = use_x11
diff --git a/cc/animation/animation_host.cc b/cc/animation/animation_host.cc
index 10c6f2ec..ceffe592 100644
--- a/cc/animation/animation_host.cc
+++ b/cc/animation/animation_host.cc
@@ -654,15 +654,13 @@
 }
 
 bool AnimationHost::ImplOnlyScrollAnimationUpdateTarget(
-    ElementId element_id,
     const gfx::Vector2dF& scroll_delta,
     const gfx::ScrollOffset& max_scroll_offset,
     base::TimeTicks frame_monotonic_time,
     base::TimeDelta delayed_by) {
   DCHECK(scroll_offset_animations_impl_);
   return scroll_offset_animations_impl_->ScrollAnimationUpdateTarget(
-      element_id, scroll_delta, max_scroll_offset, frame_monotonic_time,
-      delayed_by);
+      scroll_delta, max_scroll_offset, frame_monotonic_time, delayed_by);
 }
 
 ScrollOffsetAnimations& AnimationHost::scroll_offset_animations() const {
diff --git a/cc/animation/animation_host.h b/cc/animation/animation_host.h
index a313470..af99fca9 100644
--- a/cc/animation/animation_host.h
+++ b/cc/animation/animation_host.h
@@ -179,7 +179,6 @@
       base::TimeDelta delayed_by,
       base::TimeDelta animation_start_offset) override;
   bool ImplOnlyScrollAnimationUpdateTarget(
-      ElementId element_id,
       const gfx::Vector2dF& scroll_delta,
       const gfx::ScrollOffset& max_scroll_offset,
       base::TimeTicks frame_monotonic_time,
diff --git a/cc/animation/animation_host_unittest.cc b/cc/animation/animation_host_unittest.cc
index 45fd8cc..9e24db2 100644
--- a/cc/animation/animation_host_unittest.cc
+++ b/cc/animation/animation_host_unittest.cc
@@ -137,14 +137,14 @@
 
   time += base::TimeDelta::FromSecondsD(0.1);
   EXPECT_TRUE(host_impl_->ImplOnlyScrollAnimationUpdateTarget(
-      element_id_, scroll_delta, max_scroll_offset, time, base::TimeDelta()));
+      scroll_delta, max_scroll_offset, time, base::TimeDelta()));
 
   // Detach all animations from layers and timelines.
   host_impl_->ClearMutators();
 
   time += base::TimeDelta::FromSecondsD(0.1);
   EXPECT_FALSE(host_impl_->ImplOnlyScrollAnimationUpdateTarget(
-      element_id_, scroll_delta, max_scroll_offset, time, base::TimeDelta()));
+      scroll_delta, max_scroll_offset, time, base::TimeDelta()));
 }
 
 // Tests that verify interaction of AnimationHost with LayerTreeMutator.
diff --git a/cc/animation/scroll_offset_animations_impl.cc b/cc/animation/scroll_offset_animations_impl.cc
index bd2df460..1a15151 100644
--- a/cc/animation/scroll_offset_animations_impl.cc
+++ b/cc/animation/scroll_offset_animations_impl.cc
@@ -88,7 +88,6 @@
 }
 
 bool ScrollOffsetAnimationsImpl::ScrollAnimationUpdateTarget(
-    ElementId element_id,
     const gfx::Vector2dF& scroll_delta,
     const gfx::ScrollOffset& max_scroll_offset,
     base::TimeTicks frame_monotonic_time,
@@ -100,8 +99,6 @@
     return false;
   }
 
-  DCHECK_EQ(element_id, scroll_offset_animation_->element_id());
-
   KeyframeModel* keyframe_model =
       scroll_offset_animation_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET);
   if (!keyframe_model) {
diff --git a/cc/animation/scroll_offset_animations_impl.h b/cc/animation/scroll_offset_animations_impl.h
index 3f5b8b3..e9f192b6 100644
--- a/cc/animation/scroll_offset_animations_impl.h
+++ b/cc/animation/scroll_offset_animations_impl.h
@@ -47,8 +47,7 @@
                                        base::TimeDelta delayed_by,
                                        base::TimeDelta animation_start_offset);
 
-  bool ScrollAnimationUpdateTarget(ElementId element_id,
-                                   const gfx::Vector2dF& scroll_delta,
+  bool ScrollAnimationUpdateTarget(const gfx::Vector2dF& scroll_delta,
                                    const gfx::ScrollOffset& max_scroll_offset,
                                    base::TimeTicks frame_monotonic_time,
                                    base::TimeDelta delayed_by);
diff --git a/cc/input/README.md b/cc/input/README.md
index a5337e9..900820c4 100644
--- a/cc/input/README.md
+++ b/cc/input/README.md
@@ -104,3 +104,51 @@
 Also called the "Layout Viewport" in web/Blink terminology. This is the main
 "content scroller" in a given page, typically the document (`<html>`) element.
 This is the scroller to which position: fixed elements remain fixed to.
+
+## Compositor threaded scrollbar scrolling
+Contact: arakeri@microsoft.com
+
+### Introduction
+Scrollbar scrolling using the mouse happens on the main thread in Chromium. If
+the main thread is busy (due to reasons like long running JS, etc), scrolling
+by clicking on the scrollbar will appear to be janky. To provide a better user
+experience, we have enabled off-main-thread scrollbar interaction for composited
+scrollers. This frees up the main thread to perform other tasks like processing
+javascript, etc. The core principal here is that MouseEvent(s) are converted to
+GestureEvent(s) and dispatched in a VSync aligned manner. Choosing this design
+also helps with the grand scrolling unification.
+
+### High-level design:
+
+![Image has moved. Contact arakeri@microsoft.com](https://github.com/rahul8805/CompositorThreadedScrollbarDocs/blob/master/designDiag.PNG?raw=true)
+
+### Core Implementation Details:
+This is the basic principle:
+- A new class called "cc::ScrollbarController" manages the state and behavior
+ related to translating Mouse events into GestureScrolls.
+- When a kMouseDown arrives at InputHandlerProxy::RouteToTypeSpecificHandler,
+ it gets passed to the ScrollbarController to determine if this event will cause
+ scrollbar manipulation.
+- The ScrollbarController returns enough data to the InputHandlerProxy to inject
+ gesture events to the CompositorThreadEventQueue (CTEQ). For example, in the
+ case of a mouse down, a GestureScrollBegin(GSB) and a GestureScrollUpdate(GSU)
+ are added to the CTEQ.
+- Depending on the action, there can be more synthetic GSUs that get added to
+ the CTEQ. (For eg: thumb drags).
+- The WebInputEvent::kMouseUp is responsible for cleaning up the scroll state.
+- GestureScrollBegin gets dispatched first. This sets up the scroll_node and
+ other state necessary to begin scrolling in LayerTreeHostImpl::ScrollBegin.
+ This is as usual for all gesture based scrolls.
+- GestureScrollUpdate(s) get handled next. Scroll deltas get applied to the node
+ that was set up during GestureScrollBegin. Depending on the type of scroll,
+ this may lead to an animated scroll (eg: LayerTreeHostImpl::ScrollAnimated for
+ autoscroll/mouse clicks) or a regular scroll. (eg: LayerTreeHostImpl::ScrollBy
+ for thumb drags)
+- Finally, the GestureScrollEnd is dispatched and it clears the scrolling state
+ (like the CurrentlyScrollingNode) and calls SetNeedsCommitOnImplThread().
+
+### Miscellaneous resources.
+- [Demo page](https://rahul8805.github.io/DemoPages/BouncyMoon.html)
+- [Lightning talk](https://www.youtube.com/watch?v=FOCHCuGA_MA&t=1150s)
+- [input-dev thread](https://groups.google.com/a/chromium.org/forum/#!topic/input-dev/6ACOSDoAik4)
+- [Full design doc](https://docs.google.com/document/d/1JqykSXnCkqwA1E3bUhhIi-IgEvM9HZdKtIu_S4Ncm6o/edit#heading=h.agf18oiankjh)
diff --git a/cc/input/scrollbar_controller.h b/cc/input/scrollbar_controller.h
index 80a49f3f..01f933b 100644
--- a/cc/input/scrollbar_controller.h
+++ b/cc/input/scrollbar_controller.h
@@ -11,6 +11,106 @@
 #include "cc/layers/layer_impl.h"
 #include "cc/layers/painted_scrollbar_layer_impl.h"
 
+// High level documentation:
+// https://source.chromium.org/chromium/chromium/src/+/master:cc/input/README.md
+
+// Click scrolling.
+// - A click is considered as a kMouseDown and a kMouseUp in quick succession.
+// Every click on a composited non-custom arrow leads to 3 GestureEvents in
+// total.
+// - GSB and GSU on get queued in the CTEQ on mousedown and a GSE on mouseup.
+// - The delta scrolled is constant at 40px (scaled by the device_scale_factor)
+// for scrollbar arrows and a function of the viewport length in the case of
+// track autoscroll.
+
+// Thumb dragging.
+// - The sequence of events in the CTEQ would be something like GSB, GSU, GSU,
+// GSU..., GSE
+// - On every pointermove, the scroll delta is determined is as current pointer
+// position - the point at which we got the initial mousedown.
+// - The delta is then scaled by the scroller to scrollbar ratio so that
+// dragging the thumb moves the scroller proportionately.
+// - This ratio is calculated as:
+// (scroll_layer_length - viewport_length) /
+// (scrollbar_track_length - scrollbar_thumb_length)
+// - On pointerup, the GSE clears state as mentioned above.
+
+// VSync aligned autoscroll.
+// - Autoscroll is implemented as a "scroll animation" which has a linear timing
+// function (see cc::LinearTimingFunction) and a curve with a constant velocity.
+// - The main thread does autoscrolling by pumping events at 50ms interval. To
+// have a similar kind of behaviour on the compositor thread, the autoscroll
+// velocity is set to 800px per second for scrollbar arrows.
+// - For track autoscrolling however, the velocity is a function of the viewport
+// length.
+// - Based on this velocity, an autoscroll curve is created.
+// - An autoscroll animation is set up. (via
+// LayerTreeHostImpl::ScrollAnimationCreateInternal) on the the known
+// scroll_node and the scroller starts animation when the pointer is held.
+
+// Nuances:
+// Thumb snapping.
+// - During a thumb drag, if a pointer moves too far away from the scrollbar
+// track, the thumb is supposed to snap back to it original place (i.e to the
+// point before the thumb drag started).
+// - This is done by having an imaginary no_snap_rect around the scrollbar
+// track. This extends about 8 times the width of the track on either side. When
+// a manipulation is in progress, the mouse is expected to stay within the
+// bounds of this rect. Assuming a standard scrollbar, 17px wide, this is how
+// it'd look like.
+// https://github.com/rahul8805/CompositorThreadedScrollbarDocs/blob/master/snap.PNG?raw=true
+
+// - When a pointerdown is received, record the original offset of the thumb.
+// - On every pointermove, check if the pointer is within the bounds of the
+// no_snap_rect. If false, snap to the initial_scroll_offset and stop processing
+// pointermove(s) until the pointer reenters the bounds of the rect.
+// - The moment the mouse re-enters the bounds of the no_snap_rect, we snap to
+// the initial_scroll_offset + event.PositionInWidget.
+
+// Thumb anchoring.
+// - During a thumb drag, if the pointer runs off the track, there should be no
+// additional scrolling until the pointer reenters the track and crosses the
+// original mousedown point.
+// - This is done by sending "clamped" deltas. The amount of scrollable delta is
+// computed using LayerTreeHostImpl::ComputeScrollDelta.
+// - Since the deltas are clamped, overscroll doesn't occur if it can't be
+// consumed by the CurrentlyScrollingNode.
+
+// Autoscroll play/pause.
+// - When the pointer moves in and out of bounds of a scrollbar part that can
+// initiate autoscrolls (like arrows or track), the autoscroll animation is
+// expected to play or pause accordingly.
+// - On every ScrollbarController::WillBeginMainFrame, the pointer location is
+// constantly checked and if it is outside the bounds of the scrollbar part that
+// initiated the autoscroll, the autoscroll is stopped.
+// - Similarly, when the pointer reenters the bounds, autoscroll is restarted
+// again. All the vital information during autoscrolling such the velocity,
+// direction, scroll layer length etc is held in
+// cc::ScrollbarController::AutoscrollState.
+
+// Shift + click.
+// - Doing a shift click on any part of a scrollbar track is supposed to do an
+// instant scroll to that location (such that the thumb is still centered on the
+// pointer).
+// - When the MouseEvent reaches the
+// InputHandlerProxy::RouteToTypeSpecificHandler, if the event is found to have
+// a "Shift" modifier, the ScrollbarController calculates the offset based on
+// the pointers current location on the track.
+// - Once the offset is determined, the InputHandlerProxy creates a GSU with
+// state that tells the LayerTreeHostImpl to perform a non-animated scroll to
+// the offset.
+
+// Continuous autoscrolling.
+// - This builds on top of the autoscolling implementation. "Continuous"
+// autoscrolling is when an autoscroll is in progress and the size of the
+// content keeps increasing. For eg: When you keep the down arrow pressed on
+// websites like Facebook, the autoscrolling is expected to keep on going until
+// the mouse is released.
+// - This is implemented by monitoring the length of the scroller layer at every
+// frame and if the length increases (and if autoscroll in the forward direction
+// is already in progress), the old animation is aborted and a new autoscroll
+// animation with the new scroller length is kicked off.
+
 namespace cc {
 // This class is responsible for hit testing composited scrollbars, event
 // handling and creating gesture scroll deltas.
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 524b1309..7e6ebca 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -4116,9 +4116,9 @@
   // that ScrollBy uses for non-animated wheel scrolls.
   scroll_status = ScrollBegin(scroll_state, WHEEL);
   if (scroll_status.thread == SCROLL_ON_IMPL_THREAD) {
-    scroll_animating_latched_element_id_ = ElementId();
-    scroll_animating_overscroll_target_element_id_ = ElementId();
-    ScrollEndImpl();
+    scroll_animating_latched_element_id_ = CurrentlyScrollingNode()->element_id;
+    scroll_animating_overscroll_target_element_id_ =
+        CurrentlyScrollingNode()->element_id;
   }
   return scroll_status;
 }
@@ -4177,8 +4177,6 @@
     return false;
   }
 
-  scroll_tree.set_currently_scrolling_node(scroll_node->id);
-
   gfx::ScrollOffset current_offset =
       scroll_tree.current_scroll_offset(scroll_node->element_id);
   gfx::ScrollOffset target_offset = scroll_tree.ClampScrollOffsetToLimits(
@@ -4224,7 +4222,7 @@
 
   scroll_accumulated_this_frame_ += gfx::ScrollOffset(scroll_delta);
 
-  if (scroll_node) {
+  if (scroll_node && mutator_host_->IsImplOnlyScrollAnimating()) {
     // Flash the overlay scrollbar even if the scroll dalta is 0.
     if (settings_.scrollbar_flash_after_any_scroll_update) {
       FlashAllScrollbars(false);
@@ -4261,9 +4259,26 @@
     return scroll_status;
   }
 
+  // TODO(bokan): This method currently does both targeting/latching as well
+  // as updating. As such, it's a combination of a gesture Begin and Update.
+  // We'll soon transform it so that this method can assume a scroller is
+  // already latched.  The only decision it needs to make is whether to create
+  // a new animation or update an existing one, however, the scroll_node should
+  // already be latched. However, to avoid changing too much at once, for now
+  // this re-targeting remains here but if we're already latched we'll force
+  // targeting the same node so we don't change the latch mid-stream.
+  // https://crbug.com/1016229.
   ScrollStateData scroll_state_data;
   scroll_state_data.position_x = viewport_point.x();
   scroll_state_data.position_y = viewport_point.y();
+  scroll_state_data.is_beginning = true;
+  scroll_state_data.delta_x_hint = scroll_delta.x();
+  scroll_state_data.delta_y_hint = scroll_delta.y();
+  if (scroll_node) {
+    scroll_state_data.set_current_native_scrolling_element(
+        scroll_node->element_id);
+  }
+
   ScrollState scroll_state(scroll_state_data);
 
   // ScrollAnimated is used for animated wheel scrolls. We find the first layer
@@ -4341,31 +4356,11 @@
         break;
       }
     }
-    // Set overscroll event target since neither an ongoing scroll animation has
-    // been updated nor a new scroll animation has been created for the current
-    // GSU.
-    if (!scroll_animating_latched_element_id_) {
-      // When no scroll animation has been created during the current scroll
-      // sequence (i.e. scroll_animating_latched_element_id_ == ElementId()) we
-      // need to set last_scroller_element_id_ here since scrollEnd won't get
-      // called.
-      last_scroller_element_id_ = scroll_node->element_id;
-      // We will send the overscroll events to viewport or the last element in
-      // the cut chain when no scroll animation has been created during the
-      // current scroll sequence.
-      scroll_animating_overscroll_target_element_id_ = scroll_node->element_id;
-    } else {
-      // When a scroll animation has been created during the current scroll
-      // sequence, the overscroll events target should be the element that
-      // scrolling is latched to.
-      scroll_animating_overscroll_target_element_id_ =
-          scroll_animating_latched_element_id_;
-    }
+
     overscroll_delta_for_main_thread_ += pending_delta;
     client_->SetNeedsCommitOnImplThread();
   }
 
-  ScrollEndImpl();
   if (scroll_status.thread == SCROLL_ON_IMPL_THREAD) {
     // Update scroll_status.thread to SCROLL_IGNORED when there is no ongoing
     // scroll animation, we can scroll on impl thread and yet, we couldn't
@@ -4657,6 +4652,8 @@
                        TRACE_EVENT_SCOPE_THREAD, "isNull",
                        scroll_node ? false : true);
   active_tree_->SetCurrentlyScrollingNode(scroll_node);
+  last_scroller_element_id_ =
+      scroll_node ? scroll_node->element_id : ElementId();
 }
 
 bool LayerTreeHostImpl::CanConsumeDelta(const ScrollNode& scroll_node,
@@ -4974,22 +4971,6 @@
   scroll_animating_snap_target_ids_ = TargetSnapAreaElementIds();
 }
 
-void LayerTreeHostImpl::ScrollEndImpl() {
-  // In smooth-scrolling path when the GSE arrives after scroll animation
-  // completion, CurrentlyScrollingNode() is already cleared due to
-  // ScrollEndImpl call inside ScrollOffsetAnimationFinished. In this case
-  // last_scroller_element_id_ is already set in the same ScrollEndImpl call and
-  // we should not reset it here.
-  if (!last_scroller_element_id_ && CurrentlyScrollingNode())
-    last_scroller_element_id_ = CurrentlyScrollingNode()->element_id;
-
-  browser_controls_offset_manager_->ScrollEnd();
-  ClearCurrentlyScrollingNode();
-  frame_trackers_.StopSequence(wheel_scrolling_
-                                   ? FrameSequenceTrackerType::kWheelScroll
-                                   : FrameSequenceTrackerType::kTouchScroll);
-}
-
 void LayerTreeHostImpl::ScrollEnd(bool should_snap) {
   if ((should_snap && SnapAtScrollEnd()) ||
       mutator_host_->IsImplOnlyScrollAnimating()) {
@@ -4997,7 +4978,13 @@
     deferred_scroll_end_ = true;
     return;
   }
-  ScrollEndImpl();
+
+  browser_controls_offset_manager_->ScrollEnd();
+  ClearCurrentlyScrollingNode();
+  frame_trackers_.StopSequence(wheel_scrolling_
+                                   ? FrameSequenceTrackerType::kWheelScroll
+                                   : FrameSequenceTrackerType::kTouchScroll);
+
   deferred_scroll_end_ = false;
   scroll_gesture_did_end_ = true;
   client_->SetNeedsCommitOnImplThread();
@@ -5935,7 +5922,7 @@
   gfx::Vector2dF scaled_delta =
       gfx::ScaleVector2d(scroll_delta, 1.f / scale_factor);
   bool animation_updated = mutator_host_->ImplOnlyScrollAnimationUpdateTarget(
-      scroll_node->element_id, scaled_delta,
+      scaled_delta,
       active_tree_->property_trees()->scroll_tree.MaxScrollOffset(
           scroll_node->id),
       CurrentBeginFrameArgs().frame_time, delayed_by);
@@ -6116,8 +6103,6 @@
     ScrollEnd(/*should_snap=*/false);
     return;
   }
-
-  ScrollEndImpl();
 }
 
 void LayerTreeHostImpl::NotifyAnimationWorkletStateChange(
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index 1af0122..9f7f8ae6 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -960,8 +960,6 @@
                                    const gfx::Vector2dF& scroll_delta,
                                    base::TimeDelta delayed_by);
 
-  void ScrollEndImpl();
-
   // Creates an animation curve and returns true if we need to update the
   // scroll position to a snap point. Otherwise returns false.
   bool SnapAtScrollEnd();
@@ -1273,7 +1271,7 @@
   // ended.
   bool scroll_gesture_did_end_;
 
-  // Set in ScrollEnd before clearing the currently scrolling node. This is
+  // Set in ScrollBegin and outlives the currently scrolling node so it can be
   // used to send the scrollend DOM event when scrolling has happened on CC.
   ElementId last_scroller_element_id_;
 
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 28777d6..21ce721 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -1660,6 +1660,7 @@
 
   EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
             host_impl_->ScrollAnimated(pointer_position, delta).thread);
+  host_impl_->ScrollEnd();
 
   EXPECT_EQ(overflow->scroll_tree_index(),
             host_impl_->CurrentlyScrollingNode()->id);
@@ -11032,6 +11033,7 @@
     EXPECT_EQ(
         InputHandler::SCROLL_ON_IMPL_THREAD,
         host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);
+    host_impl_->ScrollEnd();
 
     EXPECT_EQ(0, set_needs_commit_count);
     EXPECT_EQ(1, set_needs_redraw_count);
@@ -11099,7 +11101,9 @@
         host_impl_->ScrollAnimated(gfx::Point(10, 10), gfx::Vector2d(0, 20))
             .thread);
 
-    EXPECT_EQ(scrolling_layer->scroll_tree_index(),
+    // Scrolling the inner viewport happens through the Viewport class which
+    // uses the outer viewport to represent "latched to the viewport".
+    EXPECT_EQ(OuterViewportScrollLayer()->scroll_tree_index(),
               host_impl_->CurrentlyScrollingNode()->id);
   }
 
@@ -11232,6 +11236,8 @@
                     BeginState(gfx::Point(), gfx::Vector2dF(0, 10)).get())
                 .thread);
 
+  host_impl_->ScrollEnd();
+
   // The second ScrollAnimatedBegin should not get ignored.
   EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
             host_impl_
@@ -11471,6 +11477,7 @@
   EXPECT_EQ(
       InputHandler::SCROLL_ON_IMPL_THREAD,
       host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);
+  host_impl_->ScrollEnd();
   host_impl_->DidFinishImplFrame();
 
   begin_frame_args.frame_time =
@@ -11518,7 +11525,8 @@
   host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);
 
   // Scroll by a small amount, there should be no bubbling to the outer
-  // viewport.
+  // viewport (but scrolling the viewport always sets the outer as the
+  // currently scrolling node).
   base::TimeTicks start_time =
       base::TimeTicks() + base::TimeDelta::FromMilliseconds(250);
   viz::BeginFrameArgs begin_frame_args =
@@ -11528,7 +11536,7 @@
       host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(10, 20)).thread);
   host_impl_->Animate();
   host_impl_->UpdateAnimationState(true);
-  EXPECT_EQ(inner_scroll_layer->scroll_tree_index(),
+  EXPECT_EQ(outer_scroll_layer->scroll_tree_index(),
             host_impl_->CurrentlyScrollingNode()->id);
 
   begin_frame_args.frame_id.sequence_number++;
@@ -11546,7 +11554,7 @@
       host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(100, 100)).thread);
   host_impl_->Animate();
   host_impl_->UpdateAnimationState(true);
-  EXPECT_EQ(inner_scroll_layer->scroll_tree_index(),
+  EXPECT_EQ(outer_scroll_layer->scroll_tree_index(),
             host_impl_->CurrentlyScrollingNode()->id);
 
   begin_frame_args.frame_id.sequence_number++;
@@ -11582,7 +11590,7 @@
                 .thread);
   host_impl_->Animate();
   host_impl_->UpdateAnimationState(true);
-  EXPECT_EQ(inner_scroll_layer->scroll_tree_index(),
+  EXPECT_EQ(outer_scroll_layer->scroll_tree_index(),
             host_impl_->CurrentlyScrollingNode()->id);
 
   begin_frame_args.frame_id.sequence_number++;
@@ -11621,7 +11629,9 @@
       host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(90, 90)).thread);
   host_impl_->Animate();
   host_impl_->UpdateAnimationState(true);
-  EXPECT_EQ(inner_scroll_layer->scroll_tree_index(),
+  // When either the inner or outer node is being scrolled, the outer node is
+  // the one that's "latched".
+  EXPECT_EQ(outer_scroll_layer->scroll_tree_index(),
             host_impl_->CurrentlyScrollingNode()->id);
 
   begin_frame_args.frame_id.sequence_number++;
@@ -11639,7 +11649,7 @@
       host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(50, 50)).thread);
   host_impl_->Animate();
   host_impl_->UpdateAnimationState(true);
-  EXPECT_EQ(inner_scroll_layer->scroll_tree_index(),
+  EXPECT_EQ(outer_scroll_layer->scroll_tree_index(),
             host_impl_->CurrentlyScrollingNode()->id);
 
   // Verify that all the delta is applied to the inner viewport and nothing is
@@ -11729,8 +11739,13 @@
 
   EXPECT_VECTOR_EQ(gfx::ScrollOffset(0, 100),
                    scrolling_layer->CurrentScrollOffset());
-  EXPECT_EQ(nullptr, host_impl_->CurrentlyScrollingNode());
+  // The CurrentlyScrollingNode shouldn't be cleared until a GestureScrollEnd.
+  EXPECT_EQ(scrolling_layer->scroll_tree_index(),
+            host_impl_->CurrentlyScrollingNode()->id);
   host_impl_->DidFinishImplFrame();
+
+  host_impl_->ScrollEnd();
+  EXPECT_EQ(nullptr, host_impl_->CurrentlyScrollingNode());
 }
 
 // Test that smooth scrolls clamp correctly when bounds change mid-animation.
diff --git a/cc/trees/mutator_host.h b/cc/trees/mutator_host.h
index a77f841..fa0fe4f 100644
--- a/cc/trees/mutator_host.h
+++ b/cc/trees/mutator_host.h
@@ -145,7 +145,6 @@
       base::TimeDelta delayed_by,
       base::TimeDelta animation_start_offset) = 0;
   virtual bool ImplOnlyScrollAnimationUpdateTarget(
-      ElementId element_id,
       const gfx::Vector2dF& scroll_delta,
       const gfx::ScrollOffset& max_scroll_offset,
       base::TimeTicks frame_monotonic_time,
diff --git a/chrome/android/java/res/layout/new_tab_page_offline_card.xml b/chrome/android/java/res/layout/new_tab_page_offline_card.xml
index 58f691f..1129025c 100644
--- a/chrome/android/java/res/layout/new_tab_page_offline_card.xml
+++ b/chrome/android/java/res/layout/new_tab_page_offline_card.xml
@@ -30,6 +30,8 @@
         android:layout_toEndOf="@id/icon"
         android:layout_marginStart="16dp"
         android:text="@string/explore_offline_card_title"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
         android:textAppearance="@style/TextAppearance.BlackTitle1" />
 
     <TextView
@@ -40,6 +42,8 @@
         android:layout_alignStart="@id/title"
         android:layout_marginTop="6dp"
         android:text="@string/explore_offline_card_description"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
         android:textAppearance="@style/TextAppearance.BlackHint2" />
 
     <org.chromium.components.browser_ui.widget.DualControlLayout
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/sync/AccountManagementFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/sync/AccountManagementFragment.java
index 7318f31..5ed0bce 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/settings/sync/AccountManagementFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/sync/AccountManagementFragment.java
@@ -318,19 +318,17 @@
 
                 if (intent != null) {
                     startActivity(intent);
-                    return;
+                } else {
+                    // AccountManagerFacade couldn't create intent, use SigninUtils to open settings
+                    // instead.
+                    SigninUtils.openSettingsForAllAccounts(getActivity());
                 }
 
-                // AccountManagerFacade couldn't create intent, use SigninUtils to open settings
-                // instead.
-                SigninUtils.openSettingsForAllAccounts(getActivity());
+                // Return to the last opened tab if triggered from the content area.
+                if (mGaiaServiceType != GAIAServiceType.GAIA_SERVICE_TYPE_NONE) {
+                    if (isAdded()) getActivity().finish();
+                }
             });
-
-            // Return to the last opened tab if triggered from the content area.
-            if (mGaiaServiceType != GAIAServiceType.GAIA_SERVICE_TYPE_NONE) {
-                if (isAdded()) getActivity().finish();
-            }
-
             return true;
         });
         addAccountPreference.setManagedPreferenceDelegate(preference -> !canAddAccounts());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java
index 3a091cb..da972a8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java
@@ -170,7 +170,9 @@
         }
 
         @Override
-        public void notifyAllAnimationsFinished(Item frontInfoBar) {}
+        public void notifyAllAnimationsFinished(Item frontInfoBar) {
+            mDoneAnimating = true;
+        }
     }
 
     private MockAppDetailsDelegate mDetailsDelegate;
@@ -594,14 +596,14 @@
         String webBannerUrl = WebappTestPage.getServiceWorkerUrl(mTestServer);
         resetEngagementForUrl(webBannerUrl, 10);
 
-        Tab tab = mTabbedActivityTestRule.getActivity().getActivityTab();
-        new TabLoadObserver(tab).fullyLoadUrl(webBannerUrl);
-        waitUntilAmbientBadgeInfoBarAppears(mTabbedActivityTestRule);
-
         InfoBarContainer container = mTabbedActivityTestRule.getInfoBarContainer();
         final InfobarListener listener = new InfobarListener();
         container.addAnimationListener(listener);
 
+        Tab tab = mTabbedActivityTestRule.getActivity().getActivityTab();
+        new TabLoadObserver(tab).fullyLoadUrl(webBannerUrl);
+        waitUntilAmbientBadgeInfoBarAppears(mTabbedActivityTestRule);
+
         // Explicitly dismiss the ambient badge.
         CriteriaHelper.pollUiThread(() -> listener.mDoneAnimating);
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 759fad14..7926632 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -611,8 +611,6 @@
 
 namespace {
 
-const storage::QuotaSettings* g_default_quota_settings;
-
 #if BUILDFLAG(ENABLE_PLUGINS)
 // TODO(teravest): Add renderer-side API-specific checking for these APIs so
 // that blanket permission isn't granted to all dev channel APIs for these.
@@ -2635,20 +2633,6 @@
   return new ChromeQuotaPermissionContext();
 }
 
-void ChromeContentBrowserClient::GetQuotaSettings(
-    content::BrowserContext* context,
-    content::StoragePartition* partition,
-    storage::OptionalQuotaSettingsCallback callback) {
-  if (g_default_quota_settings) {
-    // For debugging tests harness can inject settings.
-    std::move(callback).Run(*g_default_quota_settings);
-    return;
-  }
-  storage::GetNominalDynamicSettings(
-      partition->GetPath(), context->IsOffTheRecord(),
-      storage::GetDefaultDeviceInfoHelper(), std::move(callback));
-}
-
 content::GeneratedCodeCacheSettings
 ChromeContentBrowserClient::GetGeneratedCodeCacheSettings(
     content::BrowserContext* context) {
@@ -4911,12 +4895,6 @@
   return ui::NativeTheme::GetInstanceForWeb();
 }
 
-// static
-void ChromeContentBrowserClient::SetDefaultQuotaSettingsForTesting(
-    const storage::QuotaSettings* settings) {
-  g_default_quota_settings = settings;
-}
-
 scoped_refptr<safe_browsing::UrlCheckerDelegate>
 ChromeContentBrowserClient::GetSafeBrowsingUrlCheckerDelegate(
     content::ResourceContext* resource_context) {
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 2b1501f..2ff5032 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -31,7 +31,6 @@
 #include "ppapi/buildflags/buildflags.h"
 #include "services/network/public/mojom/network_context.mojom-forward.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
-#include "storage/browser/quota/quota_settings.h"
 
 class ChromeContentBrowserClientParts;
 class PrefRegistrySimple;
@@ -278,10 +277,6 @@
 #endif
   scoped_refptr<content::QuotaPermissionContext> CreateQuotaPermissionContext()
       override;
-  void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      storage::OptionalQuotaSettingsCallback callback) override;
   content::GeneratedCodeCacheSettings GetGeneratedCodeCacheSettings(
       content::BrowserContext* context) override;
   void AllowCertificateError(
@@ -690,11 +685,6 @@
       bool allow);
 #endif
 
-  // The value pointed to by |settings| should remain valid until the
-  // the function is called again with a new value or a nullptr.
-  static void SetDefaultQuotaSettingsForTesting(
-      const storage::QuotaSettings *settings);
-
   scoped_refptr<safe_browsing::UrlCheckerDelegate>
   GetSafeBrowsingUrlCheckerDelegate(content::ResourceContext* resource_context);
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 1d743d7c..3d4de44 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -2376,6 +2376,7 @@
   ]
 
   if (use_cups) {
+    configs += [ "//printing:cups" ]
     deps += [
       "//chrome/services/cups_proxy",
       "//chrome/services/cups_proxy/public/mojom",
diff --git a/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
index 6f41081..45ce2741 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
@@ -38,7 +38,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/common/extensions/api/downloads.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -4431,7 +4431,7 @@
       base::Bind(&OnDangerPromptCreated);
   DownloadsAcceptDangerFunction::OnPromptCreatedForTesting(
       &callback);
-  BrowserActionTestUtil::Create(current_browser())->Press(0);
+  ExtensionActionTestHelper::Create(current_browser())->Press(0);
   observer->WaitForFinished();
 }
 
diff --git a/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc b/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
index a0a030c..33f31b8 100644
--- a/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
+++ b/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
@@ -31,7 +31,7 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
@@ -112,9 +112,9 @@
   }
 
  protected:
-  BrowserActionTestUtil* GetBrowserActionsBar() {
+  ExtensionActionTestHelper* GetBrowserActionsBar() {
     if (!browser_action_test_util_)
-      browser_action_test_util_ = BrowserActionTestUtil::Create(browser());
+      browser_action_test_util_ = ExtensionActionTestHelper::Create(browser());
     return browser_action_test_util_.get();
   }
 
@@ -146,7 +146,7 @@
   }
 
  private:
-  std::unique_ptr<BrowserActionTestUtil> browser_action_test_util_;
+  std::unique_ptr<ExtensionActionTestHelper> browser_action_test_util_;
 
   DISALLOW_COPY_AND_ASSIGN(BrowserActionApiTest);
 };
@@ -523,7 +523,7 @@
 IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, DISABLED_BrowserActionPopup) {
   ASSERT_TRUE(
       LoadExtension(test_data_dir_.AppendASCII("browser_action/popup")));
-  BrowserActionTestUtil* actions_bar = GetBrowserActionsBar();
+  ExtensionActionTestHelper* actions_bar = GetBrowserActionsBar();
   const Extension* extension = GetSingleLoadedExtension();
   ASSERT_TRUE(extension) << message_;
 
@@ -658,7 +658,7 @@
   // default.
   Browser* incognito_browser = CreateIncognitoBrowser(browser()->profile());
 
-  ASSERT_EQ(0, BrowserActionTestUtil::Create(incognito_browser)
+  ASSERT_EQ(0, ExtensionActionTestHelper::Create(incognito_browser)
                    ->NumberOfBrowserActions());
 
   ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
@@ -674,7 +674,7 @@
       extension->id(), browser()->profile(), true);
   extension = registry_observer.WaitForExtensionLoaded();
 
-  ASSERT_EQ(1, BrowserActionTestUtil::Create(incognito_browser)
+  ASSERT_EQ(1, ExtensionActionTestHelper::Create(incognito_browser)
                    ->NumberOfBrowserActions());
 
   ASSERT_TRUE(incognito_ready_listener.WaitUntilSatisfied());
@@ -704,7 +704,7 @@
   // default.
   Browser* incognito_browser = CreateIncognitoBrowser(browser()->profile());
 
-  ASSERT_EQ(0, BrowserActionTestUtil::Create(incognito_browser)
+  ASSERT_EQ(0, ExtensionActionTestHelper::Create(incognito_browser)
                    ->NumberOfBrowserActions());
 
   // Set up a listener so we can reply for the extension to do the update.
@@ -719,7 +719,7 @@
   extensions::util::SetIsIncognitoEnabled(extension->id(), browser()->profile(),
                                           true);
   extension = registry_observer.WaitForExtensionLoaded();
-  ASSERT_EQ(1, BrowserActionTestUtil::Create(incognito_browser)
+  ASSERT_EQ(1, ExtensionActionTestHelper::Create(incognito_browser)
                    ->NumberOfBrowserActions());
 
   ASSERT_TRUE(incognito_ready_listener.WaitUntilSatisfied());
@@ -755,7 +755,7 @@
 
   // Open an incognito browser.
   Browser* incognito_browser = CreateIncognitoBrowser(browser()->profile());
-  ASSERT_EQ(1, BrowserActionTestUtil::Create(incognito_browser)
+  ASSERT_EQ(1, ExtensionActionTestHelper::Create(incognito_browser)
                    ->NumberOfBrowserActions());
 
   // A click in the regular profile should open a tab in the regular profile.
@@ -944,7 +944,7 @@
 
   ASSERT_TRUE(LoadExtension(
       test_data_dir_.AppendASCII("browser_action/popup_with_iframe")));
-  BrowserActionTestUtil* actions_bar = GetBrowserActionsBar();
+  ExtensionActionTestHelper* actions_bar = GetBrowserActionsBar();
   const Extension* extension = GetSingleLoadedExtension();
   ASSERT_TRUE(extension) << message_;
 
diff --git a/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc b/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
index fba1e17..cdc5e9f 100644
--- a/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
+++ b/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
 #include "chrome/test/base/interactive_test_utils.h"
@@ -129,7 +129,7 @@
   }
 
   void EnsurePopupActive() {
-    auto test_util = BrowserActionTestUtil::Create(browser());
+    auto test_util = ExtensionActionTestHelper::Create(browser());
     EXPECT_TRUE(test_util->HasPopup());
     EXPECT_TRUE(test_util->WaitForPopup());
     EXPECT_TRUE(test_util->HasPopup());
@@ -162,17 +162,19 @@
     content::WindowedNotificationObserver frame_observer(
         content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
         content::NotificationService::AllSources());
-    BrowserActionTestUtil::Create(browser())->Press(0);
+    ExtensionActionTestHelper::Create(browser())->Press(0);
     frame_observer.Wait();
     EnsurePopupActive();
   }
 
   // Close the popup window directly.
-  void ClosePopup() { BrowserActionTestUtil::Create(browser())->HidePopup(); }
+  void ClosePopup() {
+    ExtensionActionTestHelper::Create(browser())->HidePopup();
+  }
 
   // Trigger a focus loss to close the popup.
   void ClosePopupViaFocusLoss() {
-    EXPECT_TRUE(BrowserActionTestUtil::Create(browser())->HasPopup());
+    EXPECT_TRUE(ExtensionActionTestHelper::Create(browser())->HasPopup());
     content::WindowedNotificationObserver observer(
         extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
         content::NotificationService::AllSources());
@@ -191,7 +193,7 @@
 #endif
 
     // The window disappears immediately.
-    EXPECT_FALSE(BrowserActionTestUtil::Create(browser())->HasPopup());
+    EXPECT_FALSE(ExtensionActionTestHelper::Create(browser())->HasPopup());
 
     // Wait for the notification to achieve a consistent state and verify that
     // the popup was properly torn down.
@@ -212,7 +214,7 @@
   if (!ShouldRunPopupTest())
     return;
 
-  auto browserActionBar = BrowserActionTestUtil::Create(browser());
+  auto browserActionBar = ExtensionActionTestHelper::Create(browser());
   // Setup extension message listener to wait for javascript to finish running.
   ExtensionTestMessageListener listener("ready", true);
   {
@@ -248,10 +250,10 @@
     // Show second popup in new window.
     listener.Reply("show another");
     frame_observer.Wait();
-    EXPECT_TRUE(BrowserActionTestUtil::Create(new_browser)->HasPopup());
+    EXPECT_TRUE(ExtensionActionTestHelper::Create(new_browser)->HasPopup());
   }
   ASSERT_TRUE(catcher.GetNextResult()) << message_;
-  BrowserActionTestUtil::Create(new_browser)->HidePopup();
+  ExtensionActionTestHelper::Create(new_browser)->HidePopup();
 }
 
 // Tests opening a popup in an incognito window.
@@ -271,10 +273,10 @@
   // have popups if there is any popup open.
 #if !(defined(OS_LINUX) && !defined(USE_AURA))
   // Starting window does not have a popup.
-  EXPECT_FALSE(BrowserActionTestUtil::Create(browser())->HasPopup());
+  EXPECT_FALSE(ExtensionActionTestHelper::Create(browser())->HasPopup());
 #endif
   // Incognito window should have a popup.
-  auto test_util = BrowserActionTestUtil::Create(
+  auto test_util = ExtensionActionTestHelper::Create(
       BrowserList::GetInstance()->GetLastActive());
   EXPECT_TRUE(test_util->HasPopup());
   test_util->HidePopup();
@@ -299,7 +301,7 @@
       OpenURLOffTheRecord(profile(), GURL("chrome://newtab/"));
   EXPECT_TRUE(listener.WaitUntilSatisfied());
   EXPECT_EQ(std::string("opened"), listener.message());
-  auto test_util = BrowserActionTestUtil::Create(incognito_browser);
+  auto test_util = ExtensionActionTestHelper::Create(incognito_browser);
   EXPECT_TRUE(test_util->HasPopup());
   test_util->HidePopup();
 }
@@ -394,7 +396,7 @@
       0, {TabStripModel::GestureType::kOther});
   observer.Wait();
 
-  EXPECT_FALSE(BrowserActionTestUtil::Create(browser())->HasPopup());
+  EXPECT_FALSE(ExtensionActionTestHelper::Create(browser())->HasPopup());
 }
 
 IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest,
@@ -404,7 +406,7 @@
 
   // First, we open a popup.
   OpenPopupViaAPI(false);
-  auto browser_action_test_util = BrowserActionTestUtil::Create(browser());
+  auto browser_action_test_util = ExtensionActionTestHelper::Create(browser());
   EXPECT_TRUE(browser_action_test_util->HasPopup());
 
   // Then, find the extension that created it.
@@ -518,9 +520,9 @@
   content::WindowedNotificationObserver frame_observer(
       content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
       content::NotificationService::AllSources());
-  BrowserActionTestUtil::Create(browser())->InspectPopup(0);
+  ExtensionActionTestHelper::Create(browser())->InspectPopup(0);
   frame_observer.Wait();
-  EXPECT_TRUE(BrowserActionTestUtil::Create(browser())->HasPopup());
+  EXPECT_TRUE(ExtensionActionTestHelper::Create(browser())->HasPopup());
 
   // Close the browser window, this should not cause a crash.
   chrome::CloseWindow(browser());
@@ -533,7 +535,7 @@
     return;
 
   OpenPopupViaAPI(false);
-  auto test_util = BrowserActionTestUtil::Create(browser());
+  auto test_util = ExtensionActionTestHelper::Create(browser());
   const gfx::NativeView popup_view = test_util->GetPopupNativeView();
   EXPECT_NE(static_cast<gfx::NativeView>(nullptr), popup_view);
   const HWND popup_hwnd = views::HWNDForNativeView(popup_view);
diff --git a/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc b/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc
index dc6716b..165069f 100644
--- a/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc
+++ b/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc
@@ -19,7 +19,7 @@
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/sessions/session_tab_helper.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/extensions/extension_test_util.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -473,11 +473,11 @@
       FILE_PATH_LITERAL("background.js"),
       base::StringPrintf(kBackgroundJsTemplate, GetAPIName(GetParam())));
 
-  // Though this says "BrowserActionTestUtil", it's actually used for all
+  // Though this says "ExtensionActionTestHelper", it's actually used for all
   // toolbar actions.
   // TODO(devlin): Rename it to ToolbarActionTestUtil.
-  std::unique_ptr<BrowserActionTestUtil> toolbar_helper =
-      BrowserActionTestUtil::Create(browser());
+  std::unique_ptr<ExtensionActionTestHelper> toolbar_helper =
+      ExtensionActionTestHelper::Create(browser());
   EXPECT_EQ(0, toolbar_helper->NumberOfBrowserActions());
   const Extension* extension = LoadExtension(test_dir.UnpackedPath());
   ASSERT_TRUE(extension);
@@ -526,8 +526,8 @@
   const Extension* extension = LoadExtension(test_dir.UnpackedPath());
   ASSERT_TRUE(extension);
 
-  std::unique_ptr<BrowserActionTestUtil> toolbar_helper =
-      BrowserActionTestUtil::Create(browser());
+  std::unique_ptr<ExtensionActionTestHelper> toolbar_helper =
+      ExtensionActionTestHelper::Create(browser());
 
   ExtensionAction* action = GetExtensionAction(*extension);
   ASSERT_TRUE(action);
@@ -615,8 +615,8 @@
   EXPECT_TRUE(ActionHasDefaultState(*action, tab_id));
   EnsureActionIsEnabledOnActiveTab(action);
 
-  std::unique_ptr<BrowserActionTestUtil> toolbar_helper =
-      BrowserActionTestUtil::Create(browser());
+  std::unique_ptr<ExtensionActionTestHelper> toolbar_helper =
+      ExtensionActionTestHelper::Create(browser());
 
   ASSERT_EQ(1, toolbar_helper->NumberOfBrowserActions());
   EXPECT_EQ(extension->id(), toolbar_helper->GetExtensionId(0));
diff --git a/chrome/browser/extensions/autoplay_browsertest.cc b/chrome/browser/extensions/autoplay_browsertest.cc
index accbc33..14d5f92 100644
--- a/chrome/browser/extensions/autoplay_browsertest.cc
+++ b/chrome/browser/extensions/autoplay_browsertest.cc
@@ -4,7 +4,7 @@
 
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/extensions/extension_apitest.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
@@ -35,8 +35,8 @@
       LoadExtension(test_data_dir_.AppendASCII("autoplay_iframe"));
   ASSERT_TRUE(extension) << message_;
 
-  std::unique_ptr<BrowserActionTestUtil> browser_action_test_util =
-      BrowserActionTestUtil::Create(browser());
+  std::unique_ptr<ExtensionActionTestHelper> browser_action_test_util =
+      ExtensionActionTestHelper::Create(browser());
   extensions::ResultCatcher catcher;
   content::WindowedNotificationObserver popup_observer(
       content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
diff --git a/chrome/browser/extensions/crx_installer_browsertest.cc b/chrome/browser/extensions/crx_installer_browsertest.cc
index 619822c..1fc80caa 100644
--- a/chrome/browser/extensions/crx_installer_browsertest.cc
+++ b/chrome/browser/extensions/crx_installer_browsertest.cc
@@ -32,7 +32,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/web_application_info.h"
 #include "chrome/grit/generated_resources.h"
diff --git a/chrome/browser/extensions/extension_incognito_apitest.cc b/chrome/browser/extensions/extension_incognito_apitest.cc
index 7d432e6..ce72ae9f6 100644
--- a/chrome/browser/extensions/extension_incognito_apitest.cc
+++ b/chrome/browser/extensions/extension_incognito_apitest.cc
@@ -8,7 +8,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -185,7 +185,7 @@
       embedded_test_server()->GetURL("/extensions/test_file.html"));
 
   // Simulate the incognito's browser action being clicked.
-  BrowserActionTestUtil::Create(incognito_browser)->Press(0);
+  ExtensionActionTestHelper::Create(incognito_browser)->Press(0);
 
   EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
 }
diff --git a/chrome/browser/extensions/extension_keybinding_apitest.cc b/chrome/browser/extensions/extension_keybinding_apitest.cc
index 697a0fa..40257ddb 100644
--- a/chrome/browser/extensions/extension_keybinding_apitest.cc
+++ b/chrome/browser/extensions/extension_keybinding_apitest.cc
@@ -20,7 +20,7 @@
 #include "chrome/browser/ui/browser_command_controller.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
@@ -282,7 +282,7 @@
   // immaterial to this test).
   ASSERT_TRUE(RunExtensionTest("keybinding/conflicting")) << message_;
 
-  auto browser_actions_bar = BrowserActionTestUtil::Create(browser());
+  auto browser_actions_bar = ExtensionActionTestHelper::Create(browser());
   // Test that there are two browser actions in the toolbar.
   ASSERT_EQ(2, browser_actions_bar->NumberOfBrowserActions());
 
@@ -373,8 +373,8 @@
         ToolbarActionsModel::Get(profile());
     toolbar_actions_model->SetVisibleIconCount(0);
   }
-  std::unique_ptr<BrowserActionTestUtil> test_helper =
-      BrowserActionTestUtil::Create(browser());
+  std::unique_ptr<ExtensionActionTestHelper> test_helper =
+      ExtensionActionTestHelper::Create(browser());
   EXPECT_EQ(0, test_helper->VisibleBrowserActions());
 
   ui_test_utils::NavigateToURL(
diff --git a/chrome/browser/extensions/lazy_background_page_apitest.cc b/chrome/browser/extensions/lazy_background_page_apitest.cc
index 9807ed3b..191173a 100644
--- a/chrome/browser/extensions/lazy_background_page_apitest.cc
+++ b/chrome/browser/extensions/lazy_background_page_apitest.cc
@@ -19,7 +19,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_features.h"
@@ -152,7 +152,7 @@
   // Observe background page being created and closed after
   // the browser action is clicked.
   LazyBackgroundObserver page_complete;
-  BrowserActionTestUtil::Create(browser())->Press(0);
+  ExtensionActionTestHelper::Create(browser())->Press(0);
   page_complete.Wait();
 
   // Background page created a new tab before it closed.
@@ -174,7 +174,7 @@
   // Observe background page being created and closed after
   // the browser action is clicked.
   LazyBackgroundObserver page_complete;
-  BrowserActionTestUtil::Create(browser())->Press(0);
+  ExtensionActionTestHelper::Create(browser())->Press(0);
   page_complete.Wait();
 
   // Background page is closed after creating a new tab.
@@ -351,7 +351,7 @@
   {
     ExtensionTestMessageListener nacl_module_loaded("nacl_module_loaded",
                                                     false);
-    BrowserActionTestUtil::Create(browser())->Press(0);
+    ExtensionActionTestHelper::Create(browser())->Press(0);
     EXPECT_TRUE(nacl_module_loaded.WaitUntilSatisfied());
     content::RunAllTasksUntilIdle();
     EXPECT_TRUE(IsBackgroundPageAlive(last_loaded_extension_id()));
@@ -361,7 +361,7 @@
   // down.
   {
     LazyBackgroundObserver page_complete;
-    BrowserActionTestUtil::Create(browser())->Press(0);
+    ExtensionActionTestHelper::Create(browser())->Press(0);
     page_complete.WaitUntilClosed();
   }
 
@@ -467,7 +467,7 @@
     ExtensionTestMessageListener listener_incognito("waiting_incognito", false);
 
     LazyBackgroundObserver page_complete(browser()->profile());
-    BrowserActionTestUtil::Create(browser())->Press(0);
+    ExtensionActionTestHelper::Create(browser())->Press(0);
     page_complete.Wait();
 
     // Only the original event page received the message.
@@ -543,7 +543,7 @@
   EXPECT_FALSE(IsBackgroundPageAlive(last_loaded_extension_id()));
 
   // The browser action has a new title.
-  auto browser_action = BrowserActionTestUtil::Create(browser());
+  auto browser_action = ExtensionActionTestHelper::Create(browser());
   ASSERT_EQ(1, browser_action->NumberOfBrowserActions());
   EXPECT_EQ("Success", browser_action->GetTooltip(0));
 }
@@ -663,7 +663,7 @@
   // Click on the browser action icon to load video.
   {
     ExtensionTestMessageListener video_loaded("video_loaded", false);
-    BrowserActionTestUtil::Create(browser())->Press(0);
+    ExtensionActionTestHelper::Create(browser())->Press(0);
     EXPECT_TRUE(video_loaded.WaitUntilSatisfied());
   }
 
@@ -677,7 +677,7 @@
                 testing::Not(testing::Contains(pip_activity)));
 
     ExtensionTestMessageListener entered_pip("entered_pip", false);
-    BrowserActionTestUtil::Create(browser())->Press(0);
+    ExtensionActionTestHelper::Create(browser())->Press(0);
     EXPECT_TRUE(entered_pip.WaitUntilSatisfied());
     EXPECT_THAT(pm->GetLazyKeepaliveActivities(extension),
                 testing::Contains(pip_activity));
@@ -687,7 +687,7 @@
   // Background Page shuts down.
   {
     LazyBackgroundObserver page_complete;
-    BrowserActionTestUtil::Create(browser())->Press(0);
+    ExtensionActionTestHelper::Create(browser())->Press(0);
     page_complete.WaitUntilClosed();
     EXPECT_FALSE(IsBackgroundPageAlive(extension->id()));
   }
diff --git a/chrome/browser/extensions/manifest_v3_browsertest.cc b/chrome/browser/extensions/manifest_v3_browsertest.cc
index 6de51c8..eecfc83f 100644
--- a/chrome/browser/extensions/manifest_v3_browsertest.cc
+++ b/chrome/browser/extensions/manifest_v3_browsertest.cc
@@ -6,7 +6,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/version_info/channel.h"
@@ -126,8 +126,8 @@
   ASSERT_TRUE(extension);
   ASSERT_TRUE(listener.WaitUntilSatisfied());
 
-  std::unique_ptr<BrowserActionTestUtil> action_test_util =
-      BrowserActionTestUtil::Create(browser());
+  std::unique_ptr<ExtensionActionTestHelper> action_test_util =
+      ExtensionActionTestHelper::Create(browser());
   ASSERT_EQ(1, action_test_util->NumberOfBrowserActions());
   EXPECT_EQ(extension->id(), action_test_util->GetExtensionId(0));
 
diff --git a/chrome/browser/extensions/process_manager_browsertest.cc b/chrome/browser/extensions/process_manager_browsertest.cc
index 8acfdb8..87a6a644 100644
--- a/chrome/browser/extensions/process_manager_browsertest.cc
+++ b/chrome/browser/extensions/process_manager_browsertest.cc
@@ -22,7 +22,7 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/permissions/permission_request_manager.h"
 #include "chrome/browser/ui/browser_commands.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
@@ -415,7 +415,7 @@
   EXPECT_FALSE(pm->IsBackgroundHostClosing(popup->id()));
 
   // Simulate clicking on the action to open a popup.
-  auto test_util = BrowserActionTestUtil::Create(browser());
+  auto test_util = ExtensionActionTestHelper::Create(browser());
   content::WindowedNotificationObserver frame_observer(
       content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
       content::NotificationService::AllSources());
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index c05db95..2a70d38 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -34,7 +34,6 @@
 #include "chrome/browser/push_messaging/push_messaging_app_identifier.h"
 #include "chrome/browser/push_messaging/push_messaging_service_factory.h"
 #include "chrome/browser/push_messaging/push_messaging_service_impl.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/api/web_navigation.h"
diff --git a/chrome/browser/extensions/user_script_listener.cc b/chrome/browser/extensions/user_script_listener.cc
index 2a0dd5c..8668003 100644
--- a/chrome/browser/extensions/user_script_listener.cc
+++ b/chrome/browser/extensions/user_script_listener.cc
@@ -228,9 +228,8 @@
 
   URLPatterns new_patterns;
   CollectURLPatterns(extension, &new_patterns);
-  if (!new_patterns.empty()) {
-    AppendNewURLPatterns(browser_context, new_patterns);
-  }
+  DCHECK(!new_patterns.empty());
+  AppendNewURLPatterns(browser_context, new_patterns);
 }
 
 void UserScriptListener::OnExtensionUnloaded(
diff --git a/chrome/browser/media/router/BUILD.gn b/chrome/browser/media/router/BUILD.gn
index 295eb33..b2d7b40 100644
--- a/chrome/browser/media/router/BUILD.gn
+++ b/chrome/browser/media/router/BUILD.gn
@@ -62,6 +62,8 @@
     "presentation/presentation_service_delegate_observers.h",
     "presentation/receiver_presentation_service_delegate_impl.cc",
     "presentation/receiver_presentation_service_delegate_impl.h",
+    "presentation/web_contents_presentation_manager.cc",
+    "presentation/web_contents_presentation_manager.h",
     "route_message_observer.cc",
     "route_message_observer.h",
     "route_message_util.cc",
diff --git a/chrome/browser/media/router/presentation/presentation_service_delegate_impl.cc b/chrome/browser/media/router/presentation/presentation_service_delegate_impl.cc
index 12b75d3..d2e8d71c 100644
--- a/chrome/browser/media/router/presentation/presentation_service_delegate_impl.cc
+++ b/chrome/browser/media/router/presentation/presentation_service_delegate_impl.cc
@@ -444,8 +444,7 @@
   DCHECK(!callback.is_null());
   default_presentation_started_callback_ = std::move(callback);
   default_presentation_request_ = request;
-  for (auto& observer : default_presentation_request_observers_)
-    observer.OnDefaultPresentationChanged(*default_presentation_request_);
+  NotifyDefaultPresentationChanged(&request);
 }
 
 void PresentationServiceDelegateImpl::OnJoinRouteResponse(
@@ -503,6 +502,8 @@
     const MediaRoute& route) {
   auto* presentation_frame = GetOrAddPresentationFrame(render_frame_host_id);
   presentation_frame->AddPresentation(presentation_info, route);
+  // TODO(crbug.com/1031672): Notify WebContentsPresentationManager::Observer
+  // that the presentation routes have changed for the WebContents.
 }
 
 void PresentationServiceDelegateImpl::RemovePresentation(
@@ -511,6 +512,8 @@
   const auto it = presentation_frames_.find(render_frame_host_id);
   if (it != presentation_frames_.end())
     it->second->RemovePresentation(presentation_id);
+  // TODO(crbug.com/1031672): Notify WebContentsPresentationManager::Observer
+  // that the presentation routes have changed for the WebContents.
 }
 
 void PresentationServiceDelegateImpl::StartPresentation(
@@ -668,7 +671,27 @@
     it->second->ListenForConnectionStateChange(connection, state_changed_cb);
 }
 
-void PresentationServiceDelegateImpl::OnRouteResponse(
+void PresentationServiceDelegateImpl::AddObserver(
+    WebContentsPresentationManager::Observer* observer) {
+  presentation_observers_.AddObserver(observer);
+}
+
+void PresentationServiceDelegateImpl::RemoveObserver(
+    WebContentsPresentationManager::Observer* observer) {
+  presentation_observers_.RemoveObserver(observer);
+}
+
+bool PresentationServiceDelegateImpl::HasDefaultPresentationRequest() const {
+  return default_presentation_request_.has_value();
+}
+
+const content::PresentationRequest&
+PresentationServiceDelegateImpl::GetDefaultPresentationRequest() const {
+  DCHECK(HasDefaultPresentationRequest());
+  return *default_presentation_request_;
+}
+
+void PresentationServiceDelegateImpl::OnPresentationResponse(
     const content::PresentationRequest& presentation_request,
     mojom::RoutePresentationConnectionPtr connection,
     const RouteRequestResult& result) {
@@ -695,26 +718,6 @@
   }
 }
 
-void PresentationServiceDelegateImpl::AddDefaultPresentationRequestObserver(
-    DefaultPresentationRequestObserver* observer) {
-  default_presentation_request_observers_.AddObserver(observer);
-}
-
-void PresentationServiceDelegateImpl::RemoveDefaultPresentationRequestObserver(
-    DefaultPresentationRequestObserver* observer) {
-  default_presentation_request_observers_.RemoveObserver(observer);
-}
-
-const content::PresentationRequest&
-PresentationServiceDelegateImpl::GetDefaultPresentationRequest() const {
-  DCHECK(HasDefaultPresentationRequest());
-  return *default_presentation_request_;
-}
-
-bool PresentationServiceDelegateImpl::HasDefaultPresentationRequest() const {
-  return !!default_presentation_request_;
-}
-
 base::WeakPtr<PresentationServiceDelegateImpl>
 PresentationServiceDelegateImpl::GetWeakPtr() {
   return weak_factory_.GetWeakPtr();
@@ -737,8 +740,7 @@
     return;
 
   default_presentation_request_.reset();
-  for (auto& observer : default_presentation_request_observers_)
-    observer.OnDefaultPresentationRemoved();
+  NotifyDefaultPresentationChanged(nullptr);
 }
 
 std::unique_ptr<media::FlingingController>
@@ -798,6 +800,14 @@
   }
 }
 
+void PresentationServiceDelegateImpl::NotifyDefaultPresentationChanged(
+    const content::PresentationRequest* request) {
+  for (WebContentsPresentationManager::Observer& presentation_observer :
+       presentation_observers_) {
+    presentation_observer.OnDefaultPresentationChanged(request);
+  }
+}
+
 WEB_CONTENTS_USER_DATA_KEY_IMPL(PresentationServiceDelegateImpl)
 
 }  // namespace media_router
diff --git a/chrome/browser/media/router/presentation/presentation_service_delegate_impl.h b/chrome/browser/media/router/presentation/presentation_service_delegate_impl.h
index e9cd3a7d6..a183bb0 100644
--- a/chrome/browser/media/router/presentation/presentation_service_delegate_impl.h
+++ b/chrome/browser/media/router/presentation/presentation_service_delegate_impl.h
@@ -19,6 +19,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/media/router/media_router.h"
 #include "chrome/browser/media/router/presentation/presentation_service_delegate_observers.h"
+#include "chrome/browser/media/router/presentation/web_contents_presentation_manager.h"
 #include "chrome/common/media_router/media_source.h"
 #include "chrome/common/media_router/mojom/media_router.mojom.h"
 #include "content/public/browser/presentation_request.h"
@@ -87,27 +88,14 @@
 // it also provides default presentation URL that is required for creating
 // browser-initiated presentations.  It is scoped to the lifetime of a
 // WebContents, and is managed by the associated WebContents.
+// It is accessed through the WebContentsPresentationManager interface by
+// clients (e.g. the UI code) that is interested in the presentation status of
+// the WebContents, but not in other aspects such as the render frame.
 class PresentationServiceDelegateImpl
     : public content::WebContentsUserData<PresentationServiceDelegateImpl>,
-      public content::ControllerPresentationServiceDelegate {
+      public content::ControllerPresentationServiceDelegate,
+      public WebContentsPresentationManager {
  public:
-  // Observer interface for listening to default presentation request
-  // changes for the WebContents.
-  class DefaultPresentationRequestObserver {
-   public:
-    virtual ~DefaultPresentationRequestObserver() = default;
-
-    // Called when default presentation request for the corresponding
-    // WebContents is set or changed.
-    // |default_presentation_info|: New default presentation request.
-    virtual void OnDefaultPresentationChanged(
-        const content::PresentationRequest& default_presentation_request) = 0;
-
-    // Called when default presentation request for the corresponding
-    // WebContents has been removed.
-    virtual void OnDefaultPresentationRemoved() = 0;
-  };
-
   // Retrieves the instance of PresentationServiceDelegateImpl that was attached
   // to the specified WebContents.  If no instance was attached, creates one,
   // and attaches it to the specified WebContents.
@@ -160,34 +148,22 @@
       const content::PresentationConnectionStateChangedCallback&
           state_changed_cb) override;
 
-  // Callback invoked when a default PresentationRequest is started from a
-  // browser-initiated dialog.
-  void OnRouteResponse(const content::PresentationRequest& request,
-                       mojom::RoutePresentationConnectionPtr connection,
-                       const RouteRequestResult& result);
+  // WebContentsPresentationManager implementation.
+  void AddObserver(WebContentsPresentationManager::Observer* observer) override;
+  void RemoveObserver(
+      WebContentsPresentationManager::Observer* observer) override;
+  bool HasDefaultPresentationRequest() const override;
+  const content::PresentationRequest& GetDefaultPresentationRequest()
+      const override;
+  void OnPresentationResponse(const content::PresentationRequest& request,
+                              mojom::RoutePresentationConnectionPtr connection,
+                              const RouteRequestResult& result) override;
 
-  // Adds / removes an observer for listening to default PresentationRequest
-  // changes. This class does not own |observer|. When |observer| is about to
-  // be destroyed, |RemoveDefaultPresentationRequestObserver| must be called.
-  void AddDefaultPresentationRequestObserver(
-      DefaultPresentationRequestObserver* observer);
-  void RemoveDefaultPresentationRequestObserver(
-      DefaultPresentationRequestObserver* observer);
-
-  // Gets the default presentation request for the owning WebContents. It
-  // is an error to call this method if the default presentation request does
-  // not exist.
-  const content::PresentationRequest& GetDefaultPresentationRequest() const;
-
-  // Returns true if there is a default presentation request for the owning tab
-  // WebContents.
-  bool HasDefaultPresentationRequest() const;
+  base::WeakPtr<PresentationServiceDelegateImpl> GetWeakPtr();
 
   // Returns the WebContents that owns this instance.
   content::WebContents* web_contents() const { return web_contents_; }
 
-  base::WeakPtr<PresentationServiceDelegateImpl> GetWeakPtr();
-
   bool HasScreenAvailabilityListenerForTest(
       int render_process_id,
       int render_frame_id,
@@ -276,6 +252,9 @@
       const blink::mojom::PresentationInfo& presentation_info,
       mojom::RoutePresentationConnectionPtr* connection);
 
+  void NotifyDefaultPresentationChanged(
+      const content::PresentationRequest* request);
+
   // References to the WebContents that owns this instance, and associated
   // browser profile's MediaRouter instance.
   content::WebContents* const web_contents_;
@@ -283,8 +262,8 @@
 
   // References to the observers listening for changes to the default
   // presentation of the associated WebContents.
-  base::ObserverList<DefaultPresentationRequestObserver>::Unchecked
-      default_presentation_request_observers_;
+  base::ObserverList<WebContentsPresentationManager::Observer>
+      presentation_observers_;
 
   // Default presentation request for the owning WebContents.
   base::Optional<content::PresentationRequest> default_presentation_request_;
diff --git a/chrome/browser/media/router/presentation/presentation_service_delegate_impl_unittest.cc b/chrome/browser/media/router/presentation/presentation_service_delegate_impl_unittest.cc
index e6464a3f49..b3cbe43 100644
--- a/chrome/browser/media/router/presentation/presentation_service_delegate_impl_unittest.cc
+++ b/chrome/browser/media/router/presentation/presentation_service_delegate_impl_unittest.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/media/router/media_router_factory.h"
 #include "chrome/browser/media/router/presentation/local_presentation_manager.h"
 #include "chrome/browser/media/router/presentation/local_presentation_manager_factory.h"
+#include "chrome/browser/media/router/presentation/web_contents_presentation_manager.h"
 #include "chrome/browser/media/router/test/mock_media_router.h"
 #include "chrome/browser/media/router/test/mock_screen_availability_listener.h"
 #include "chrome/browser/media/router/test/test_helper.h"
@@ -67,13 +68,27 @@
   MOCK_METHOD1(OnDefaultPresentationStarted, void(const PresentationInfo&));
 };
 
-class MockDefaultPresentationRequestObserver
-    : public PresentationServiceDelegateImpl::
-          DefaultPresentationRequestObserver {
+class MockWebContentsPresentationObserver
+    : public WebContentsPresentationManager::Observer {
  public:
+  explicit MockWebContentsPresentationObserver(
+      content::WebContents* web_contents) {
+    presentation_manager_ = WebContentsPresentationManager::Get(web_contents);
+    presentation_manager_->AddObserver(this);
+  }
+
+  ~MockWebContentsPresentationObserver() override {
+    if (presentation_manager_)
+      presentation_manager_->RemoveObserver(this);
+  }
+
+  MOCK_METHOD1(OnMediaRoutesChanged,
+               void(const std::vector<MediaRoute>& routes));
   MOCK_METHOD1(OnDefaultPresentationChanged,
-               void(const content::PresentationRequest&));
-  MOCK_METHOD0(OnDefaultPresentationRemoved, void());
+               void(const content::PresentationRequest* presentation_request));
+
+ private:
+  base::WeakPtr<WebContentsPresentationManager> presentation_manager_;
 };
 
 class MockCreatePresentationConnnectionCallbacks {
@@ -178,8 +193,8 @@
     // Should not trigger callback since route response is error.
     std::unique_ptr<RouteRequestResult> result = RouteRequestResult::FromError(
         "Error", RouteRequestResult::UNKNOWN_ERROR);
-    delegate_impl_->OnRouteResponse(request, /** connection */ nullptr,
-                                    *result);
+    delegate_impl_->OnPresentationResponse(request, /** connection */ nullptr,
+                                           *result);
     EXPECT_TRUE(Mock::VerifyAndClearExpectations(this));
 
     // Should not trigger callback since request doesn't match.
@@ -191,8 +206,8 @@
     media_route.set_incognito(incognito);
     result =
         RouteRequestResult::FromSuccess(media_route, "differentPresentationId");
-    delegate_impl_->OnRouteResponse(different_request,
-                                    /** connection */ nullptr, *result);
+    delegate_impl_->OnPresentationResponse(different_request,
+                                           /** connection */ nullptr, *result);
     EXPECT_TRUE(Mock::VerifyAndClearExpectations(this));
 
     // Should trigger callback since request matches.
@@ -200,8 +215,8 @@
     MediaRoute media_route2("routeId", source1_, "mediaSinkId", "", true, true);
     media_route2.set_incognito(incognito);
     result = RouteRequestResult::FromSuccess(media_route2, "presentationId");
-    delegate_impl_->OnRouteResponse(request, /** connection */ nullptr,
-                                    *result);
+    delegate_impl_->OnPresentationResponse(request, /** connection */ nullptr,
+                                           *result);
   }
 
   void SetMainFrame() {
@@ -379,13 +394,12 @@
 }
 
 TEST_F(PresentationServiceDelegateImplTest,
-       DefaultPresentationRequestObserver) {
+       NotifyWebContentsPresentationObservers) {
   auto callback = base::BindRepeating(
       &PresentationServiceDelegateImplTest::OnDefaultPresentationStarted,
       base::Unretained(this));
 
-  StrictMock<MockDefaultPresentationRequestObserver> observer;
-  delegate_impl_->AddDefaultPresentationRequestObserver(&observer);
+  StrictMock<MockWebContentsPresentationObserver> observer(GetWebContents());
 
   content::WebContentsTester::For(GetWebContents())
       ->NavigateAndCommit(frame_url_);
@@ -409,7 +423,7 @@
   EXPECT_TRUE(Mock::VerifyAndClearExpectations(&observer));
 
   // Remove default presentation URL.
-  EXPECT_CALL(observer, OnDefaultPresentationRemoved()).Times(1);
+  EXPECT_CALL(observer, OnDefaultPresentationChanged(nullptr)).Times(1);
   content::PresentationRequest empty_request(
       {main_frame_process_id_, main_frame_routing_id_}, std::vector<GURL>(),
       frame_origin_);
diff --git a/chrome/browser/media/router/presentation/web_contents_presentation_manager.cc b/chrome/browser/media/router/presentation/web_contents_presentation_manager.cc
new file mode 100644
index 0000000..17d69184
--- /dev/null
+++ b/chrome/browser/media/router/presentation/web_contents_presentation_manager.cc
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/router/presentation/web_contents_presentation_manager.h"
+
+#include "chrome/browser/media/router/presentation/presentation_service_delegate_impl.h"
+
+namespace media_router {
+
+// static
+base::WeakPtr<WebContentsPresentationManager>
+WebContentsPresentationManager::Get(content::WebContents* web_contents) {
+  return PresentationServiceDelegateImpl::GetOrCreateForWebContents(
+             web_contents)
+      ->GetWeakPtr();
+}
+
+WebContentsPresentationManager::~WebContentsPresentationManager() = default;
+
+}  // namespace media_router
diff --git a/chrome/browser/media/router/presentation/web_contents_presentation_manager.h b/chrome/browser/media/router/presentation/web_contents_presentation_manager.h
new file mode 100644
index 0000000..d3158a166
--- /dev/null
+++ b/chrome/browser/media/router/presentation/web_contents_presentation_manager.h
@@ -0,0 +1,77 @@
+// Copyright 2019 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_MEDIA_ROUTER_PRESENTATION_WEB_CONTENTS_PRESENTATION_MANAGER_H_
+#define CHROME_BROWSER_MEDIA_ROUTER_PRESENTATION_WEB_CONTENTS_PRESENTATION_MANAGER_H_
+
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "chrome/common/media_router/mojom/media_router.mojom.h"
+
+namespace content {
+struct PresentationRequest;
+class WebContents;
+}  // namespace content
+
+namespace media_router {
+
+class MediaRoute;
+class RouteRequestResult;
+
+// Keeps track of the default PresentationRequest and presentation MediaRoutes
+// associated with a WebContents. Its lifetime is tied to that of the
+// WebContents.
+class WebContentsPresentationManager {
+ public:
+  class Observer : public base::CheckedObserver {
+   public:
+    // Called whenever presentation MediaRoutes associated with the WebContents
+    // are added, removed, or have their attributes changed.
+    virtual void OnMediaRoutesChanged(const std::vector<MediaRoute>& routes) {}
+
+    // |presentation_request| is a nullptr if the default PresentationRequest
+    // has been removed.
+    virtual void OnDefaultPresentationChanged(
+        const content::PresentationRequest* presentation_request) {}
+
+   protected:
+    Observer() = default;
+  };
+
+  static base::WeakPtr<WebContentsPresentationManager> Get(
+      content::WebContents* web_contents);
+
+  virtual ~WebContentsPresentationManager() = 0;
+
+  virtual void AddObserver(Observer* observer) = 0;
+  virtual void RemoveObserver(Observer* observer) = 0;
+
+  // Returns true if there is a default presentation request for the
+  // WebContents.
+  virtual bool HasDefaultPresentationRequest() const = 0;
+
+  // Gets the default presentation request for the WebContents. It is an error
+  // to call this method if the default presentation request does not exist.
+  virtual const content::PresentationRequest& GetDefaultPresentationRequest()
+      const = 0;
+
+  // Invoked by Media Router when a PresentationRequest is started from a
+  // browser-initiated dialog. Updates the internal state and propagates the
+  // request result to the presentation controller.
+  virtual void OnPresentationResponse(
+      const content::PresentationRequest& presentation_request,
+      mojom::RoutePresentationConnectionPtr connection,
+      const RouteRequestResult& result) = 0;
+
+ protected:
+  WebContentsPresentationManager() = default;
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_MEDIA_ROUTER_PRESENTATION_WEB_CONTENTS_PRESENTATION_MANAGER_H_
diff --git a/chrome/browser/net/dns_over_https_browsertest.cc b/chrome/browser/net/dns_over_https_browsertest.cc
new file mode 100644
index 0000000..61ff5d3
--- /dev/null
+++ b/chrome/browser/net/dns_over_https_browsertest.cc
@@ -0,0 +1,74 @@
+// Copyright 2019 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/test/scoped_feature_list.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/dns_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+struct DohParameter {
+  std::string doh_provider;
+  std::string doh_template;
+  bool is_valid;
+};
+
+std::vector<DohParameter> GetDohServerTestCases() {
+  std::vector<DohParameter> doh_test_cases;
+  const auto& doh_server_templates = net::GetDohServerTemplatesListForTesting();
+  for (const auto& server_template : doh_server_templates) {
+    doh_test_cases.push_back(
+        DohParameter({server_template.first, server_template.second, true}));
+  }
+  // Negative test-case
+  doh_test_cases.push_back(DohParameter(
+      {"NegativeTestExampleCom", "https://www.example.com", false}));
+  return doh_test_cases;
+}
+
+}  // namespace
+
+class DohBrowserTest : public InProcessBrowserTest,
+                       public testing::WithParamInterface<DohParameter> {
+ public:
+  DohBrowserTest() : test_url_("https://www.google.com") {
+    // Allow test to use full host resolver code, instead of the test resolver
+    set_allow_network_access_to_host_resolutions();
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {// {features::kNetworkServiceInProcess, {}}, // Turn on for debugging
+         {features::kDnsOverHttps,
+          {{"Fallback", "false"}, {"Templates", GetParam().doh_template}}}},
+        {});
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  const GURL test_url_;
+};
+
+IN_PROC_BROWSER_TEST_P(DohBrowserTest, MANUAL_ExternalDohServers) {
+  content::TestNavigationObserver nav_observer(
+      browser()->tab_strip_model()->GetActiveWebContents(), 1);
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url_));
+  nav_observer.WaitForNavigationFinished();
+  EXPECT_EQ(GetParam().is_valid, nav_observer.last_navigation_succeeded());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    DohBrowserParameterizedTest,
+    DohBrowserTest,
+    ::testing::ValuesIn(GetDohServerTestCases()),
+    [](const testing::TestParamInfo<DohBrowserTest::ParamType>& info) {
+      return info.param.doh_provider;
+    });
diff --git a/chrome/browser/no_best_effort_tasks_browsertest.cc b/chrome/browser/no_best_effort_tasks_browsertest.cc
index 029e8884..244d76b2 100644
--- a/chrome/browser/no_best_effort_tasks_browsertest.cc
+++ b/chrome/browser/no_best_effort_tasks_browsertest.cc
@@ -253,9 +253,9 @@
 // use BEST_EFFORT tasks.
 class NoBestEffortTasksTestWithQuota : public NoBestEffortTasksTest {
  protected:
-  std::unique_ptr<storage::QuotaSettings> CreateQuotaSettings() override {
-    // Return nullptr to use the real quota subsystem.
-    return nullptr;
+  bool UseProductionQuotaSettings() override {
+    // Return true to use the real quota subsystem.
+    return true;
   }
 };
 
diff --git a/chrome/browser/optimization_guide/optimization_guide_util.cc b/chrome/browser/optimization_guide/optimization_guide_util.cc
index c977223..c3d4254 100644
--- a/chrome/browser/optimization_guide/optimization_guide_util.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_util.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/optimization_guide/optimization_guide_util.h"
 
 #include "base/logging.h"
+#include "net/base/url_util.h"
+#include "url/url_canon.h"
 
 std::string GetStringNameForOptimizationTarget(
     optimization_guide::proto::OptimizationTarget optimization_target) {
@@ -17,3 +19,15 @@
   NOTREACHED();
   return std::string();
 }
+
+bool IsHostValidToFetchFromRemoteOptimizationGuide(const std::string& host) {
+  if (net::HostStringIsLocalhost(host))
+    return false;
+  url::CanonHostInfo host_info;
+  std::string canonicalized_host(net::CanonicalizeHost(host, &host_info));
+  if (host_info.IsIPAddress() ||
+      !net::IsCanonicalizedHostCompliant(canonicalized_host)) {
+    return false;
+  }
+  return true;
+}
diff --git a/chrome/browser/optimization_guide/optimization_guide_util.h b/chrome/browser/optimization_guide/optimization_guide_util.h
index f399abe..f596cb7 100644
--- a/chrome/browser/optimization_guide/optimization_guide_util.h
+++ b/chrome/browser/optimization_guide/optimization_guide_util.h
@@ -16,4 +16,8 @@
 std::string GetStringNameForOptimizationTarget(
     optimization_guide::proto::OptimizationTarget optimization_target);
 
+// Returns false if the host is an IP address, localhosts, or an invalid
+// host that is not supported by the remote optimization guide.
+bool IsHostValidToFetchFromRemoteOptimizationGuide(const std::string& host);
+
 #endif  // CHROME_BROWSER_OPTIMIZATION_GUIDE_OPTIMIZATION_GUIDE_UTIL_H_
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager.cc b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
index d8e0f59..ff16540 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
@@ -186,7 +186,9 @@
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     PrefService* pref_service,
     Profile* profile)
-    : session_fcp_(),
+    : host_model_features_cache_(
+          std::max(features::MaxHostModelFeaturesCacheSize(), size_t(1))),
+      session_fcp_(),
       top_host_provider_(top_host_provider),
       model_and_features_store_(std::move(model_and_features_store)),
       url_loader_factory_(url_loader_factory),
@@ -327,7 +329,7 @@
 
 base::flat_map<std::string, float> PredictionManager::BuildFeatureMap(
     content::NavigationHandle* navigation_handle,
-    const base::flat_set<std::string>& model_features) const {
+    const base::flat_set<std::string>& model_features) {
   SEQUENCE_CHECKER(sequence_checker_);
   base::flat_map<std::string, float> feature_map;
   if (model_features.size() == 0)
@@ -336,8 +338,8 @@
   const base::flat_map<std::string, float>* host_model_features = nullptr;
 
   std::string host = navigation_handle->GetURL().host();
-  auto it = host_model_features_map_.find(host);
-  if (it != host_model_features_map_.end())
+  auto it = host_model_features_cache_.Get(host);
+  if (it != host_model_features_cache_.end())
     host_model_features = &(it->second);
 
   UMA_HISTOGRAM_BOOLEAN(
@@ -367,7 +369,7 @@
 
 OptimizationTargetDecision PredictionManager::ShouldTargetNavigation(
     content::NavigationHandle* navigation_handle,
-    proto::OptimizationTarget optimization_target) const {
+    proto::OptimizationTarget optimization_target) {
   SEQUENCE_CHECKER(sequence_checker_);
   DCHECK(navigation_handle->GetURL().SchemeIsHTTPOrHTTPS());
 
@@ -457,9 +459,9 @@
   return nullptr;
 }
 
-base::flat_map<std::string, base::flat_map<std::string, float>>
+const HostModelFeaturesMRUCache*
 PredictionManager::GetHostModelFeaturesForTesting() const {
-  return host_model_features_map_;
+  return &host_model_features_cache_;
 }
 
 void PredictionManager::SetPredictionModelFetcherForTesting(
@@ -486,6 +488,19 @@
 
   std::vector<std::string> top_hosts = top_host_provider_->GetTopHosts();
 
+  // Remove hosts that are already available in the host model features cache.
+  // The request should still be made in case there is a new model or a model
+  // that does not rely on host model features to be fetched.
+  auto it = top_hosts.begin();
+  while (it != top_hosts.end()) {
+    if (host_model_features_cache_.Peek(*it) !=
+        host_model_features_cache_.end()) {
+      it = top_hosts.erase(it);
+      continue;
+    }
+    ++it;
+  }
+
   if (!prediction_model_fetcher_) {
     prediction_model_fetcher_ = std::make_unique<PredictionModelFetcher>(
         url_loader_factory_,
@@ -620,6 +635,9 @@
   // Clear any data remaining in the stored get models response.
   get_models_response_data_to_store_.reset();
 
+  // Purge any expired host model features from the store.
+  model_and_features_store_->PurgeExpiredHostModelFeatures();
+
   // TODO(crbug/1027596): Stopping the timer can be removed once the fetch
   // callback refactor is done. Otherwise, at the start of a fetch, a timer is
   // running to handle the cases that a fetch fails but the callback is not run.
@@ -668,7 +686,7 @@
   }
   UMA_HISTOGRAM_COUNTS_1000(
       "OptimizationGuide.PredictionManager.HostModelFeaturesMapSize",
-      host_model_features_map_.size());
+      host_model_features_cache_.size());
 
   // Load the prediction models for all the registered optimization targets now
   // that it is not blocked by loading the host model features.
@@ -771,8 +789,8 @@
   }
   if (model_features_for_host.size() == 0)
     return false;
-  host_model_features_map_[host_model_features.host()] =
-      model_features_for_host;
+  host_model_features_cache_.Put(host_model_features.host(),
+                                 model_features_for_host);
   return true;
 }
 
@@ -831,9 +849,17 @@
 }
 
 void PredictionManager::ClearHostModelFeatures() {
-  host_model_features_map_.clear();
+  host_model_features_cache_.Clear();
   if (model_and_features_store_)
     model_and_features_store_->ClearHostModelFeaturesFromDatabase();
 }
 
+base::Optional<base::flat_map<std::string, float>>
+PredictionManager::GetHostModelFeaturesForHost(const std::string& host) const {
+  auto it = host_model_features_cache_.Peek(host);
+  if (it == host_model_features_cache_.end())
+    return base::nullopt;
+  return it->second;
+}
+
 }  // namespace optimization_guide
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager.h b/chrome/browser/optimization_guide/prediction/prediction_manager.h
index 9d094df..9c0c30a 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager.h
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager.h
@@ -11,6 +11,7 @@
 
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
+#include "base/containers/mru_cache.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
@@ -49,6 +50,9 @@
 class PredictionModelFetcher;
 class TopHostProvider;
 
+using HostModelFeaturesMRUCache =
+    base::HashingMRUCache<std::string, base::flat_map<std::string, float>>;
+
 // A PredictionManager supported by the optimization guide that makes an
 // OptimizationTargetDecision by evaluating the corresponding prediction model
 // for an OptimizationTarget.
@@ -87,7 +91,7 @@
   // if model for the optimization target is not currently on the client.
   OptimizationTargetDecision ShouldTargetNavigation(
       content::NavigationHandle* navigation_handle,
-      proto::OptimizationTarget optimization_target) const;
+      proto::OptimizationTarget optimization_target);
 
   // Update |session_fcp_| and |previous_fcp_| with |fcp|.
   void UpdateFCPSessionStatistics(base::TimeDelta fcp);
@@ -133,8 +137,11 @@
 
   // Return the host model features for all hosts used by this
   // PredictionManager for testing.
-  base::flat_map<std::string, base::flat_map<std::string, float>>
-  GetHostModelFeaturesForTesting() const;
+  const HostModelFeaturesMRUCache* GetHostModelFeaturesForTesting() const;
+
+  // Returns the host model features for a host if available.
+  base::Optional<base::flat_map<std::string, float>>
+  GetHostModelFeaturesForHost(const std::string& host) const;
 
   // Return the set of features that each host in |host_model_features_map_|
   // contains for testing.
@@ -166,10 +173,11 @@
                       optimization_targets_at_intialization);
 
   // Construct and return a map containing the current feature values for the
-  // requested set of model features.
+  // requested set of model features. The host model features cache is updated
+  // based on if host model features were used.
   base::flat_map<std::string, float> BuildFeatureMap(
       content::NavigationHandle* navigation_handle,
-      const base::flat_set<std::string>& model_features) const;
+      const base::flat_set<std::string>& model_features);
 
   // Calculate and return the current value for the client feature specified
   // by |model_feature|. Return nullopt if the client does not support the
@@ -273,12 +281,8 @@
   // prediction manager.
   base::flat_set<proto::OptimizationTarget> registered_optimization_targets_;
 
-  // A map of host to host model features known to the prediction manager.
-  //
-  // TODO(crbug/1001194): When loading features for the map, the size should be
-  // restricted.
-  base::flat_map<std::string, base::flat_map<std::string, float>>
-      host_model_features_map_;
+  // A MRU cache of host to host model features known to the prediction manager.
+  HostModelFeaturesMRUCache host_model_features_cache_;
 
   // The current session's FCP statistics for HTTP/HTTPS navigations.
   OptimizationGuideSessionStatistic session_fcp_;
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc b/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
index f8ffc7ef..2d328d8a 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
@@ -185,6 +185,7 @@
       return false;
     }
 
+    count_hosts_fetched_ = hosts.size();
     switch (fetch_state_) {
       case PredictionModelFetcherEndState::kFetchFailed:
         std::move(models_fetched_callback).Run(base::nullopt);
@@ -222,9 +223,11 @@
   }
 
   bool models_fetched() { return models_fetched_; }
+  size_t hosts_fetched() { return count_hosts_fetched_; }
 
  private:
   bool models_fetched_ = false;
+  size_t count_hosts_fetched_ = 0;
   // The desired behavior of the TestPredictionModelFetcher.
   PredictionModelFetcherEndState fetch_state_;
 };
@@ -362,6 +365,7 @@
     create_valid_prediction_model_ = create_valid_prediction_model;
   }
 
+  using PredictionManager::GetHostModelFeaturesForHost;
   using PredictionManager::GetHostModelFeaturesForTesting;
   using PredictionManager::GetPredictionModelForTesting;
 
@@ -1163,19 +1167,17 @@
   prediction_manager()->UpdateHostModelFeaturesForTesting(
       get_models_response.get());
 
-  EXPECT_TRUE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "example2.com"));
-  base::flat_map<std::string, base::flat_map<std::string, float>>
-      host_model_features_map =
-          prediction_manager()->GetHostModelFeaturesForTesting();
-  EXPECT_TRUE(host_model_features_map.contains("example1.com"));
-  EXPECT_TRUE(host_model_features_map.contains("example2.com"));
-  auto it = host_model_features_map.find("example1.com");
-  EXPECT_TRUE(it->second.contains("host_feat1"));
-  EXPECT_EQ(2.0, it->second["host_feat1"]);
-  it = host_model_features_map.find("example2.com");
-  EXPECT_TRUE(it->second.contains("host_feat1"));
-  EXPECT_EQ(2.0, it->second["host_feat1"]);
+  base::Optional<base::flat_map<std::string, float>> host_model_features_map =
+      prediction_manager()->GetHostModelFeaturesForHost("example1.com");
+  EXPECT_TRUE(host_model_features_map);
+  EXPECT_TRUE(host_model_features_map->contains("host_feat1"));
+  EXPECT_EQ(2.0, (*host_model_features_map)["host_feat1"]);
+
+  host_model_features_map =
+      prediction_manager()->GetHostModelFeaturesForHost("example2.com");
+  EXPECT_TRUE(host_model_features_map);
+  EXPECT_TRUE(host_model_features_map->contains("host_feat1"));
+  EXPECT_EQ(2.0, (*host_model_features_map)["host_feat1"]);
 }
 
 TEST_F(PredictionManagerTest, NoHostModelFeaturesForHost) {
@@ -1213,14 +1215,12 @@
       false, 1);
   EXPECT_LT(test_prediction_model->last_evaluated_features()["host_feat1"], 0);
 
-  EXPECT_FALSE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "bar.com"));
+  EXPECT_FALSE(prediction_manager()->GetHostModelFeaturesForHost("bar.com"));
   // One item loaded from the store when initialized.
-  EXPECT_EQ(1u, prediction_manager()->GetHostModelFeaturesForTesting().size());
+  EXPECT_EQ(1u, prediction_manager()->GetHostModelFeaturesForTesting()->size());
 }
 
 TEST_F(PredictionManagerTest, UpdateHostModelFeaturesMissingHost) {
-  base::HistogramTester histogram_tester;
 
   CreatePredictionManager({});
   prediction_manager()->SetPredictionModelFetcherForTesting(
@@ -1239,14 +1239,13 @@
   prediction_manager()->UpdateHostModelFeaturesForTesting(
       get_models_response.get());
 
-  EXPECT_FALSE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "example1.com"));
+  EXPECT_FALSE(
+      prediction_manager()->GetHostModelFeaturesForHost("example1.com"));
   // One item loaded from the store when initialized.
-  EXPECT_EQ(1u, prediction_manager()->GetHostModelFeaturesForTesting().size());
+  EXPECT_EQ(1u, prediction_manager()->GetHostModelFeaturesForTesting()->size());
 }
 
 TEST_F(PredictionManagerTest, UpdateHostModelFeaturesNoFeature) {
-  base::HistogramTester histogram_tester;
 
   CreatePredictionManager({});
   prediction_manager()->SetPredictionModelFetcherForTesting(
@@ -1264,14 +1263,13 @@
   prediction_manager()->UpdateHostModelFeaturesForTesting(
       get_models_response.get());
 
-  EXPECT_FALSE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "example1.com"));
+  EXPECT_FALSE(
+      prediction_manager()->GetHostModelFeaturesForHost("example1.com"));
   // One item loaded from the store when initialized.
-  EXPECT_EQ(1u, prediction_manager()->GetHostModelFeaturesForTesting().size());
+  EXPECT_EQ(1u, prediction_manager()->GetHostModelFeaturesForTesting()->size());
 }
 
 TEST_F(PredictionManagerTest, UpdateHostModelFeaturesNoFeatureName) {
-  base::HistogramTester histogram_tester;
 
   CreatePredictionManager({});
   prediction_manager()->SetPredictionModelFetcherForTesting(
@@ -1292,15 +1290,13 @@
   prediction_manager()->UpdateHostModelFeaturesForTesting(
       get_models_response.get());
 
-  EXPECT_FALSE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "example1.com"));
+  EXPECT_FALSE(
+      prediction_manager()->GetHostModelFeaturesForHost("example1.com"));
   // One item loaded from the store when initialized.
-  EXPECT_EQ(1u, prediction_manager()->GetHostModelFeaturesForTesting().size());
+  EXPECT_EQ(1u, prediction_manager()->GetHostModelFeaturesForTesting()->size());
 }
 
 TEST_F(PredictionManagerTest, UpdateHostModelFeaturesDoubleValue) {
-  base::HistogramTester histogram_tester;
-
   CreatePredictionManager({});
   prediction_manager()->SetPredictionModelFetcherForTesting(
       BuildTestPredictionModelFetcher(
@@ -1319,16 +1315,13 @@
   prediction_manager()->UpdateHostModelFeaturesForTesting(
       get_models_response.get());
 
-  EXPECT_TRUE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "example1.com"));
-  EXPECT_EQ(
-      3.0,
-      prediction_manager()
-          ->GetHostModelFeaturesForTesting()["example1.com"]["host_feat1"]);
+  auto host_model_features =
+      prediction_manager()->GetHostModelFeaturesForHost("example1.com");
+  EXPECT_TRUE(host_model_features);
+  EXPECT_EQ(3.0, (*host_model_features)["host_feat1"]);
 }
 
 TEST_F(PredictionManagerTest, UpdateHostModelFeaturesIntValue) {
-  base::HistogramTester histogram_tester;
 
   CreatePredictionManager({});
   prediction_manager()->SetPredictionModelFetcherForTesting(
@@ -1348,14 +1341,62 @@
   prediction_manager()->UpdateHostModelFeaturesForTesting(
       get_models_response.get());
 
-  EXPECT_TRUE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "example1.com"));
+  auto host_model_features =
+      prediction_manager()->GetHostModelFeaturesForHost("example1.com");
+  EXPECT_TRUE(host_model_features);
   // We expect the value to be stored as a float but is created from an int64
   // value.
-  EXPECT_EQ(
-      4.0,
-      prediction_manager()
-          ->GetHostModelFeaturesForTesting()["example1.com"]["host_feat1"]);
+  EXPECT_EQ(4.0, (*host_model_features)["host_feat1"]);
+}
+
+TEST_F(PredictionManagerTest, RestrictHostModelFeaturesCacheSize) {
+  CreatePredictionManager({});
+  prediction_manager()->SetPredictionModelFetcherForTesting(
+      BuildTestPredictionModelFetcher(
+          PredictionModelFetcherEndState::kFetchFailed));
+
+  prediction_manager()->RegisterOptimizationTargets(
+      {proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD});
+
+  SetStoreInitialized();
+  std::vector<std::string> hosts;
+  for (size_t i = 0; i <= features::MaxHostModelFeaturesCacheSize() + 1; i++)
+    hosts.push_back("host" + base::NumberToString(i) + ".com");
+  std::unique_ptr<proto::GetModelsResponse> get_models_response =
+      BuildGetModelsResponse(hosts, {});
+
+  prediction_manager()->UpdateHostModelFeaturesForTesting(
+      get_models_response.get());
+
+  auto* host_model_features_cache =
+      prediction_manager()->GetHostModelFeaturesForTesting();
+  EXPECT_EQ(features::MaxHostModelFeaturesCacheSize(),
+            host_model_features_cache->size());
+}
+
+TEST_F(PredictionManagerTest, FetchHostModelFeaturesNotInCache) {
+  base::HistogramTester histogram_tester;
+
+  CreatePredictionManager({});
+  prediction_manager()->SetPredictionModelFetcherForTesting(
+      BuildTestPredictionModelFetcher(
+          PredictionModelFetcherEndState::
+              kFetchSuccessWithModelsAndHostsModelFeatures));
+
+  std::unique_ptr<proto::GetModelsResponse> get_models_response =
+      BuildGetModelsResponse({"example1.com", "bar.com"}, {});
+  prediction_manager()->UpdateHostModelFeaturesForTesting(
+      get_models_response.get());
+
+  prediction_manager()->RegisterOptimizationTargets(
+      {proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD});
+
+  SetStoreInitialized();
+
+  EXPECT_TRUE(prediction_model_fetcher()->models_fetched());
+  // Only 1 of the 2 top hosts should have features fetched for it as the other
+  // is already in the host model features cache.
+  EXPECT_EQ(prediction_model_fetcher()->hosts_fetched(), 1ul);
 }
 
 TEST_F(PredictionManagerTest, UpdateHostModelFeaturesUpdateDataInMap) {
@@ -1379,14 +1420,12 @@
   prediction_manager()->UpdateHostModelFeaturesForTesting(
       get_models_response.get());
 
-  EXPECT_TRUE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "example1.com"));
+  auto host_model_features =
+      prediction_manager()->GetHostModelFeaturesForHost("example1.com");
+  EXPECT_TRUE(host_model_features);
   // We expect the value to be stored as a float but is created from an int64
   // value.
-  EXPECT_EQ(
-      4.0,
-      prediction_manager()
-          ->GetHostModelFeaturesForTesting()["example1.com"]["host_feat1"]);
+  EXPECT_EQ(4.0, (*host_model_features)["host_feat1"]);
 
   get_models_response = BuildGetModelsResponse({"example1.com"}, {});
   get_models_response->mutable_host_model_features(0)
@@ -1400,20 +1439,15 @@
   prediction_manager()->UpdateHostModelFeaturesForTesting(
       get_models_response.get());
 
-  EXPECT_TRUE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "example1.com"));
+  host_model_features =
+      prediction_manager()->GetHostModelFeaturesForHost("example1.com");
+  EXPECT_TRUE(host_model_features);
+
   // We expect the value to be stored as a float but is created from an int64
   // value.
-  EXPECT_EQ(
-      5.0,
-      prediction_manager()
-          ->GetHostModelFeaturesForTesting()["example1.com"]["host_feat1"]);
-  EXPECT_TRUE(prediction_manager()
-                  ->GetHostModelFeaturesForTesting()["example1.com"]
-                  .contains("host_feat_added"));
-  EXPECT_EQ(6.0, prediction_manager()
-                     ->GetHostModelFeaturesForTesting()["example1.com"]
-                                                       ["host_feat_added"]);
+  EXPECT_EQ(5.0, (*host_model_features)["host_feat1"]);
+  EXPECT_TRUE((*host_model_features).contains("host_feat_added"));
+  EXPECT_EQ(6.0, (*host_model_features)["host_feat_added"]);
 }
 
 TEST_P(PredictionManagerTest, ClientFeature) {
@@ -1534,14 +1568,12 @@
       {optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD});
   EXPECT_FALSE(models_and_features_store()->WasHostModelFeaturesLoaded());
   EXPECT_FALSE(models_and_features_store()->WasModelLoaded());
-  EXPECT_FALSE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "foo.com"));
+  EXPECT_FALSE(prediction_manager()->GetHostModelFeaturesForHost("foo.com"));
 
   SetStoreInitialized();
   EXPECT_TRUE(models_and_features_store()->WasHostModelFeaturesLoaded());
   EXPECT_TRUE(models_and_features_store()->WasModelLoaded());
-  EXPECT_TRUE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "foo.com"));
+  EXPECT_TRUE(prediction_manager()->GetHostModelFeaturesForHost("foo.com"));
 
   EXPECT_FALSE(prediction_model_fetcher()->models_fetched());
 }
@@ -1557,16 +1589,14 @@
 
   EXPECT_FALSE(models_and_features_store()->WasHostModelFeaturesLoaded());
   EXPECT_FALSE(models_and_features_store()->WasModelLoaded());
-  EXPECT_FALSE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "foo.com"));
+  EXPECT_FALSE(prediction_manager()->GetHostModelFeaturesForHost("foo.com"));
   prediction_manager()->RegisterOptimizationTargets(
       {optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD});
   RunUntilIdle();
 
   EXPECT_TRUE(models_and_features_store()->WasHostModelFeaturesLoaded());
   EXPECT_TRUE(models_and_features_store()->WasModelLoaded());
-  EXPECT_TRUE(prediction_manager()->GetHostModelFeaturesForTesting().contains(
-      "foo.com"));
+  EXPECT_TRUE(prediction_manager()->GetHostModelFeaturesForHost("foo.com"));
 
   EXPECT_FALSE(prediction_model_fetcher()->models_fetched());
 }
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_fetcher.cc b/chrome/browser/optimization_guide/prediction/prediction_model_fetcher.cc
index d28aeed2..e91876b 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_fetcher.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_model_fetcher.cc
@@ -12,6 +12,7 @@
 #include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "chrome/browser/optimization_guide/optimization_guide_util.h"
 #include "components/optimization_guide/optimization_guide_features.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "content/public/browser/network_service_instance.h"
@@ -70,8 +71,19 @@
 
   pending_models_request_->set_request_context(request_context);
 
-  for (const auto& host : hosts)
+  // Limit the number of hosts to fetch features for, the list of hosts
+  // is assumed to be ordered from most to least important by the top
+  // host provider.
+  for (const auto& host : hosts) {
+    // Skip over localhosts, IP addresses, and invalid hosts.
+    if (!IsHostValidToFetchFromRemoteOptimizationGuide(host))
+      continue;
     pending_models_request_->add_hosts(host);
+    if (static_cast<size_t>(pending_models_request_->hosts_size()) >=
+        features::MaxHostsForOptimizationGuideServiceModelsFetch()) {
+      break;
+    }
+  }
 
   for (const auto& model_request_info : models_request_info)
     *pending_models_request_->add_requested_models() = model_request_info;
@@ -122,7 +134,7 @@
   UMA_HISTOGRAM_COUNTS_100(
       "OptimizationGuide.PredictionModelFetcher."
       "GetModelsRequest.HostCount",
-      hosts.size());
+      pending_models_request_->hosts_size());
 
   // |url_loader_| should not retry on 5xx errors since the server may already
   // be overloaded.  |url_loader_| should retry on network changes since the
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_fetcher_unittest.cc b/chrome/browser/optimization_guide/prediction/prediction_model_fetcher_unittest.cc
index 1272eadf..2124fd2 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_fetcher_unittest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_model_fetcher_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "base/callback.h"
@@ -10,10 +11,12 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/optional.h"
 #include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "chrome/browser/optimization_guide/prediction/prediction_model_fetcher.h"
+#include "components/optimization_guide/optimization_guide_features.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "net/base/url_util.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -138,6 +141,59 @@
       0, 1);
 }
 
+TEST_F(PredictionModelFetcherTest,
+       FetchOptimizationGuideServiceModelsLimitHosts) {
+  base::HistogramTester histogram_tester;
+  std::string response_content;
+  std::vector<std::string> hosts;
+  for (size_t i = 0;
+       i <= features::MaxHostsForOptimizationGuideServiceModelsFetch() + 1; i++)
+    hosts.push_back("host" + base::NumberToString(i) + ".com");
+  std::vector<optimization_guide::proto::ModelInfo> models_request_info({});
+  EXPECT_TRUE(FetchModels(
+      models_request_info, hosts,
+      optimization_guide::proto::RequestContext::CONTEXT_BATCH_UPDATE));
+  VerifyHasPendingFetchRequests();
+
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelFetcher.GetModelsRequest.HostCount",
+      features::MaxHostsForOptimizationGuideServiceModelsFetch(), 1);
+
+  EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
+  EXPECT_TRUE(models_fetched());
+
+  // No HostModelFeatures are returned.
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelFetcher.GetModelsResponse."
+      "HostModelFeatureCount",
+      0, 1);
+}
+
+TEST_F(PredictionModelFetcherTest, FetchFilterInvalidHosts) {
+  base::HistogramTester histogram_tester;
+  std::string response_content;
+  std::vector<std::string> hosts = {"192.168.1.1", "_abc", "localhost",
+                                    "foo.com"};
+  std::vector<optimization_guide::proto::ModelInfo> models_request_info({});
+  EXPECT_TRUE(FetchModels(
+      models_request_info, hosts,
+      optimization_guide::proto::RequestContext::CONTEXT_BATCH_UPDATE));
+  VerifyHasPendingFetchRequests();
+
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelFetcher.GetModelsRequest.HostCount", 1,
+      1);
+
+  EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
+  EXPECT_TRUE(models_fetched());
+
+  // No HostModelFeatures are returned.
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelFetcher.GetModelsResponse."
+      "HostModelFeatureCount",
+      0, 1);
+}
+
 // Tests 404 response from request.
 TEST_F(PredictionModelFetcherTest, FetchReturned404) {
   base::HistogramTester histogram_tester;
diff --git a/chrome/browser/prerender/prerender_browsertest.cc b/chrome/browser/prerender/prerender_browsertest.cc
index 6d18f89e..ff9cfd9 100644
--- a/chrome/browser/prerender/prerender_browsertest.cc
+++ b/chrome/browser/prerender/prerender_browsertest.cc
@@ -242,7 +242,6 @@
     // We'll crash the renderer after it's loaded.
     case FINAL_STATUS_RENDERER_CRASHED:
     case FINAL_STATUS_CANCELLED:
-    case FINAL_STATUS_PAGE_BEING_CAPTURED:
     case FINAL_STATUS_NAVIGATION_UNCOMMITTED:
     case FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE:
       return false;
@@ -267,11 +266,9 @@
 // been called, before checking its state.
 class ChannelDestructionWatcher {
  public:
-  ChannelDestructionWatcher() : channel_destroyed_(false) {
-  }
+  ChannelDestructionWatcher() : channel_destroyed_(false) {}
 
-  ~ChannelDestructionWatcher() {
-  }
+  ~ChannelDestructionWatcher() = default;
 
   void WatchChannel(content::RenderProcessHost* host) {
     host->AddFilter(new DestructionMessageFilter(this));
@@ -287,10 +284,8 @@
   // Ignores all messages.
   class DestructionMessageFilter : public content::BrowserMessageFilter {
    public:
-     explicit DestructionMessageFilter(ChannelDestructionWatcher* watcher)
-         : BrowserMessageFilter(0),
-           watcher_(watcher) {
-    }
+    explicit DestructionMessageFilter(ChannelDestructionWatcher* watcher)
+        : BrowserMessageFilter(0), watcher_(watcher) {}
 
    private:
     ~DestructionMessageFilter() override {
@@ -361,13 +356,9 @@
     tab_strip_model_->RemoveObserver(this);
   }
 
-  void set_did_start_loading() {
-    did_start_loading_ = true;
-  }
+  void set_did_start_loading() { did_start_loading_ = true; }
 
-  void Wait() {
-    loop_.Run();
-  }
+  void Wait() { loop_.Run(); }
 
   // WebContentsObserver implementation:
   void DidStartLoading() override { did_start_loading_ = true; }
@@ -533,9 +524,8 @@
   }
 
   // Opens the url in a new tab, with no opener.
-  void NavigateToDestURLWithDisposition(
-      WindowOpenDisposition disposition,
-      bool expect_swap_to_succeed) const {
+  void NavigateToDestURLWithDisposition(WindowOpenDisposition disposition,
+                                        bool expect_swap_to_succeed) const {
     NavigateToURLWithParams(
         content::OpenURLParams(dest_url_, Referrer(), disposition,
                                ui::PAGE_TRANSITION_TYPED, false),
@@ -568,9 +558,7 @@
     NavigateToURLImpl(params, expect_swap_to_succeed);
   }
 
-  void OpenDestURLViaClick() const {
-    OpenURLViaClick(dest_url_);
-  }
+  void OpenDestURLViaClick() const { OpenURLViaClick(dest_url_); }
 
   void OpenURLViaClick(const GURL& url) const {
     OpenURLWithJSImpl("Click", url, GURL(), false);
@@ -596,9 +584,7 @@
 #endif
   }
 
-  void OpenDestURLViaWindowOpen() const {
-    OpenURLViaWindowOpen(dest_url_);
-  }
+  void OpenDestURLViaWindowOpen() const { OpenURLViaWindowOpen(dest_url_); }
 
   void OpenURLViaWindowOpen(const GURL& url) const {
     OpenURLWithJSImpl("WindowOpen", url, GURL(), true);
@@ -638,15 +624,11 @@
     EXPECT_TRUE(original_prerender_page);
   }
 
-  void DisableJavascriptCalls() {
-    call_javascript_ = false;
-  }
+  void DisableJavascriptCalls() { call_javascript_ = false; }
 
   void EnableJavascriptCalls() { call_javascript_ = true; }
 
-  void DisableLoadEventCheck() {
-    check_load_events_ = false;
-  }
+  void DisableLoadEventCheck() { check_load_events_ = false; }
 
   const PrerenderLinkManager* GetPrerenderLinkManager() const {
     PrerenderLinkManager* prerender_link_manager =
@@ -659,10 +641,11 @@
     int event_count;
     std::string expression = base::StringPrintf(
         "window.domAutomationController.send("
-        "    GetPrerenderEventCount(%d, '%s'))", index, type.c_str());
+        "    GetPrerenderEventCount(%d, '%s'))",
+        index, type.c_str());
 
-    CHECK(content::ExecuteScriptAndExtractInt(
-        GetActiveWebContents(), expression, &event_count));
+    CHECK(content::ExecuteScriptAndExtractInt(GetActiveWebContents(),
+                                              expression, &event_count));
     return event_count;
   }
 
@@ -692,8 +675,8 @@
         "        window.domAutomationController, 0))",
         index, type.c_str(), count);
 
-    CHECK(content::ExecuteScriptAndExtractInt(
-        GetActiveWebContents(), expression, &dummy));
+    CHECK(content::ExecuteScriptAndExtractInt(GetActiveWebContents(),
+                                              expression, &dummy));
     CHECK_EQ(0, dummy);
   }
 
@@ -742,13 +725,9 @@
     loader_host_override_ = host;
   }
 
-  void set_loader_path(const std::string& path) {
-    loader_path_ = path;
-  }
+  void set_loader_path(const std::string& path) { loader_path_ = path; }
 
-  void set_loader_query(const std::string& query) {
-    loader_query_ = query;
-  }
+  void set_loader_query(const std::string& query) { loader_query_ = query; }
 
   GURL GetCrossDomainTestUrl(const std::string& path) {
     static const std::string secondary_domain = "www.foo.com";
@@ -758,9 +737,7 @@
     return GURL(url_str);
   }
 
-  const GURL& dest_url() const {
-    return dest_url_;
-  }
+  const GURL& dest_url() const { return dest_url_; }
 
   bool DidPrerenderPass(WebContents* web_contents) const {
     bool prerender_test_result = false;
@@ -789,8 +766,8 @@
   }
 
   void AddPrerender(const GURL& url, int index) {
-    std::string javascript = base::StringPrintf(
-        "AddPrerender('%s', %d)", url.spec().c_str(), index);
+    std::string javascript =
+        base::StringPrintf("AddPrerender('%s', %d)", url.spec().c_str(), index);
     RenderFrameHost* render_frame_host = GetActiveWebContents()->GetMainFrame();
     render_frame_host->ExecuteJavaScriptForTests(base::ASCIIToUTF16(javascript),
                                                  base::NullCallback());
@@ -935,9 +912,9 @@
     WebContents* web_contents = GetActiveWebContents();
     RenderFrameHost* render_frame_host = web_contents->GetMainFrame();
     // Extra arguments in JS are ignored.
-    std::string javascript = base::StringPrintf(
-        "%s('%s', '%s')", javascript_function_name.c_str(),
-        url.spec().c_str(), ping_url.spec().c_str());
+    std::string javascript =
+        base::StringPrintf("%s('%s', '%s')", javascript_function_name.c_str(),
+                           url.spec().c_str(), ping_url.spec().c_str());
 
     if (new_web_contents) {
       NewTabNavigationOrSwapObserver observer;
@@ -968,8 +945,6 @@
   std::unique_ptr<content::URLLoaderInterceptor> interceptor_;
 };
 
-
-
 // Checks that the correct page load metrics observers are produced without a
 // prerender.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PageLoadMetricsSimple) {
@@ -1032,8 +1007,8 @@
   EXPECT_TRUE(IsEmptyPrerenderLinkManager());
 }
 
-IN_PROC_BROWSER_TEST_F(
-    PrerenderBrowserTest, PrerenderPageRemovingLinkWithTwoLinks) {
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+                       PrerenderPageRemovingLinkWithTwoLinks) {
   GetPrerenderManager()->mutable_config().max_link_concurrency = 2;
   GetPrerenderManager()->mutable_config().max_link_concurrency_per_launcher = 2;
 
@@ -1060,8 +1035,8 @@
   EXPECT_TRUE(IsEmptyPrerenderLinkManager());
 }
 
-IN_PROC_BROWSER_TEST_F(
-    PrerenderBrowserTest, PrerenderPageRemovingLinkWithTwoLinksOneLate) {
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+                       PrerenderPageRemovingLinkWithTwoLinksOneLate) {
   GetPrerenderManager()->mutable_config().max_link_concurrency = 2;
   GetPrerenderManager()->mutable_config().max_link_concurrency_per_launcher = 2;
 
@@ -1092,9 +1067,8 @@
   EXPECT_TRUE(IsEmptyPrerenderLinkManager());
 }
 
-IN_PROC_BROWSER_TEST_F(
-    PrerenderBrowserTest,
-    PrerenderPageRemovingLinkWithTwoLinksRemovingOne) {
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+                       PrerenderPageRemovingLinkWithTwoLinksRemovingOne) {
   GetPrerenderManager()->mutable_config().max_link_concurrency = 2;
   GetPrerenderManager()->mutable_config().max_link_concurrency_per_launcher = 2;
   set_loader_query("links_to_insert=2");
@@ -1196,16 +1170,16 @@
 #if defined(USE_AURA) && !defined(OS_WIN)
 // http://crbug.com/103496
 #define MAYBE_PrerenderIframeDelayLoadPlugin \
-        DISABLED_PrerenderIframeDelayLoadPlugin
+  DISABLED_PrerenderIframeDelayLoadPlugin
 #elif defined(OS_MACOSX)
 // http://crbug.com/100514
 #define MAYBE_PrerenderIframeDelayLoadPlugin \
-        DISABLED_PrerenderIframeDelayLoadPlugin
+  DISABLED_PrerenderIframeDelayLoadPlugin
 #elif defined(OS_WIN)
 // TODO(jschuh): Failing plugin tests. https://crbug.com/244653,
 // https://crbug.com/876872
 #define MAYBE_PrerenderIframeDelayLoadPlugin \
-        DISABLED_PrerenderIframeDelayLoadPlugin
+  DISABLED_PrerenderIframeDelayLoadPlugin
 #else
 #define MAYBE_PrerenderIframeDelayLoadPlugin PrerenderIframeDelayLoadPlugin
 #endif
@@ -1435,18 +1409,14 @@
       prerender->contents()->web_contents());
   ASSERT_TRUE(prerender->contents());
   ASSERT_TRUE(prerender->contents()->prerender_contents());
-  prerender->contents()->prerender_contents()->GetController().
-      LoadURL(
-          GURL(content::kChromeUICrashURL),
-          content::Referrer(),
-          ui::PAGE_TRANSITION_TYPED,
-          std::string());
+  prerender->contents()->prerender_contents()->GetController().LoadURL(
+      GURL(content::kChromeUICrashURL), content::Referrer(),
+      ui::PAGE_TRANSITION_TYPED, std::string());
   prerender->WaitForStop();
 }
 #endif
 
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
-                       PrerenderPageWithFragment) {
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderPageWithFragment) {
   PrerenderTestURL("/prerender/prerender_page.html#fragment", FINAL_STATUS_USED,
                    1);
 
@@ -1577,8 +1547,7 @@
 
 // Checks that a top-level page which would normally request an SSL client
 // certificate will never be seen since it's an https top-level resource.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
-                       PrerenderSSLClientCertTopLevel) {
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderSSLClientCertTopLevel) {
   ProfileNetworkContextServiceFactory::GetForContext(
       current_browser()->profile())
       ->set_client_cert_store_factory_for_testing(base::BindRepeating(
@@ -1618,8 +1587,7 @@
   std::string replacement_path = net::test_server::GetFilePathWithReplacements(
       "/prerender/prerender_with_image.html", replacement_text);
   PrerenderTestURL(replacement_path,
-                   FINAL_STATUS_SSL_CLIENT_CERTIFICATE_REQUESTED,
-                   0);
+                   FINAL_STATUS_SSL_CLIENT_CERTIFICATE_REQUESTED, 0);
 }
 
 // Checks that an SSL Client Certificate request that originates from an
@@ -1645,8 +1613,7 @@
   std::string replacement_path = net::test_server::GetFilePathWithReplacements(
       "/prerender/prerender_with_iframe.html", replacement_text);
   PrerenderTestURL(replacement_path,
-                   FINAL_STATUS_SSL_CLIENT_CERTIFICATE_REQUESTED,
-                   0);
+                   FINAL_STATUS_SSL_CLIENT_CERTIFICATE_REQUESTED, 0);
 }
 
 // Ensures that we do not prerender pages with a safe browsing
@@ -1689,9 +1656,7 @@
       std::make_pair("REPLACE_WITH_IMAGE_URL", image_url.spec()));
   std::string replacement_path = net::test_server::GetFilePathWithReplacements(
       "/prerender/prerender_with_image.html", replacement_text);
-  PrerenderTestURL(replacement_path,
-                   FINAL_STATUS_SAFE_BROWSING,
-                   0);
+  PrerenderTestURL(replacement_path, FINAL_STATUS_SAFE_BROWSING, 0);
 }
 
 // Ensures that we do not prerender pages which have a malware iframe.
@@ -1705,9 +1670,7 @@
       std::make_pair("REPLACE_WITH_URL", iframe_url.spec()));
   std::string replacement_path = net::test_server::GetFilePathWithReplacements(
       "/prerender/prerender_with_iframe.html", replacement_text);
-  PrerenderTestURL(replacement_path,
-                   FINAL_STATUS_SAFE_BROWSING,
-                   0);
+  PrerenderTestURL(replacement_path, FINAL_STATUS_SAFE_BROWSING, 0);
 }
 
 // Checks that the favicon is properly loaded on prerender.
@@ -1813,8 +1776,7 @@
 }
 
 // Checks that the referrer policy is used when prerendering on HTTPS.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
-                       PrerenderSSLReferrerPolicy) {
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderSSLReferrerPolicy) {
   UseHttpsSrcServer();
   set_loader_path("/prerender/prerender_loader_with_referrer_policy.html");
   PrerenderTestURL("/prerender/prerender_referrer_policy.html",
@@ -1977,13 +1939,6 @@
   NavigateToURLWithParams(params, false);
 }
 
-// Checks that the prerendering of a page is canceled correctly when the
-// prerendered page tries to make a second navigation entry.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderNewNavigationEntry) {
-  PrerenderTestURL("/prerender/prerender_new_entry.html",
-                   FINAL_STATUS_NEW_NAVIGATION_ENTRY, 1);
-}
-
 // Attempt a swap-in in a new tab. The session storage doesn't match, so it
 // should not swap.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderPageNewTab) {
@@ -2066,8 +2021,8 @@
   scoped_refptr<password_manager::TestPasswordStore> password_store =
       static_cast<password_manager::TestPasswordStore*>(
           PasswordStoreFactory::GetForProfile(
-              current_browser()->profile(),
-              ServiceAccessType::IMPLICIT_ACCESS).get());
+              current_browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS)
+              .get());
   autofill::PasswordForm signin_form;
   signin_form.signon_realm = embedded_test_server()->base_url().spec();
   signin_form.password_value = base::ASCIIToUTF16("password");
@@ -2127,9 +2082,7 @@
     return current_browser()->window()->GetLocationBar();
   }
 
-  OmniboxView* GetOmniboxView() {
-    return GetLocationBar()->GetOmniboxView();
-  }
+  OmniboxView* GetOmniboxView() { return GetLocationBar()->GetOmniboxView(); }
 
   predictors::AutocompleteActionPredictor* GetAutocompleteActionPredictor() {
     Profile* profile = current_browser()->profile();
@@ -2144,8 +2097,7 @@
         ExpectPrerender(expected_final_status);
     WebContents* web_contents = GetActiveWebContents();
     GetAutocompleteActionPredictor()->StartPrerendering(
-        url,
-        web_contents->GetController().GetDefaultSessionStorageNamespace(),
+        url, web_contents->GetController().GetDefaultSessionStorageNamespace(),
         gfx::Size(50, 50));
     prerender->WaitForStart();
     return prerender;
@@ -2232,9 +2184,8 @@
   WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   bool display_test_result = false;
-  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(web_contents,
-                                                   "DidDisplayReallyPass()",
-                                                   &display_test_result));
+  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+      web_contents, "DidDisplayReallyPass()", &display_test_result));
   ASSERT_TRUE(display_test_result);
 }
 #endif  // BUILDFLAG(ENABLE_NACL)
diff --git a/chrome/browser/prerender/prerender_contents.cc b/chrome/browser/prerender/prerender_contents.cc
index 89fe0a3c..02e437c 100644
--- a/chrome/browser/prerender/prerender_contents.cc
+++ b/chrome/browser/prerender/prerender_contents.cc
@@ -579,19 +579,6 @@
     return;
   }
 
-  // If the prerender made a second navigation entry, abort the prerender. This
-  // avoids having to correctly implement a complex history merging case (this
-  // interacts with location.replace) and correctly synchronize with the
-  // renderer. The final status may be monitored to see we need to revisit this
-  // decision. This does not affect client redirects as those do not push new
-  // history entries. (Calls to location.replace, navigations before onload, and
-  // <meta http-equiv=refresh> with timeouts under 1 second do not create
-  // entries in Blink.)
-  if (prerender_contents_->GetController().GetEntryCount() > 1) {
-    Destroy(FINAL_STATUS_NEW_NAVIGATION_ENTRY);
-    return;
-  }
-
   // Add each redirect as an alias. |navigation_handle->GetURL()| is included in
   // |navigation_handle->GetRedirectChain()|.
   //
diff --git a/chrome/browser/prerender/prerender_final_status.h b/chrome/browser/prerender/prerender_final_status.h
index e8206c1..ef00240f 100644
--- a/chrome/browser/prerender/prerender_final_status.h
+++ b/chrome/browser/prerender/prerender_final_status.h
@@ -61,10 +61,10 @@
   // Obsolete: FINAL_STATUS_WOULD_HAVE_BEEN_USED = 41,
   FINAL_STATUS_REGISTER_PROTOCOL_HANDLER = 42,
   FINAL_STATUS_CREATING_AUDIO_STREAM = 43,
-  FINAL_STATUS_PAGE_BEING_CAPTURED = 44,
+  // Obsolete: FINAL_STATUS_PAGE_BEING_CAPTURED = 44,
   FINAL_STATUS_BAD_DEFERRED_REDIRECT = 45,
   FINAL_STATUS_NAVIGATION_UNCOMMITTED = 46,
-  FINAL_STATUS_NEW_NAVIGATION_ENTRY = 47,
+  // Obsolete: FINAL_STATUS_NEW_NAVIGATION_ENTRY = 47,
   // Obsolete: FINAL_STATUS_COOKIE_STORE_NOT_LOADED = 48,
   // Obsolete: FINAL_STATUS_COOKIE_CONFLICT = 49,
   FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE = 50,
diff --git a/chrome/browser/prerender/prerender_manager.cc b/chrome/browser/prerender/prerender_manager.cc
index f406a71..02e3312 100644
--- a/chrome/browser/prerender/prerender_manager.cc
+++ b/chrome/browser/prerender/prerender_manager.cc
@@ -251,9 +251,8 @@
       origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
     }
     // TODO(ajwong): This does not correctly handle storage for isolated apps.
-    session_storage_namespace =
-        source_web_contents->GetController()
-            .GetDefaultSessionStorageNamespace();
+    session_storage_namespace = source_web_contents->GetController()
+                                    .GetDefaultSessionStorageNamespace();
   }
   return AddPrerenderWithPreconnectFallback(origin, url, referrer,
                                             initiator_origin, gfx::Rect(size),
@@ -357,8 +356,7 @@
   // namespace.
   // TODO(ajwong): This doesn't handle isolated apps correctly.
   PrerenderData* prerender_data = FindPrerenderData(
-      url,
-      web_contents->GetController().GetDefaultSessionStorageNamespace());
+      url, web_contents->GetController().GetDefaultSessionStorageNamespace());
   if (!prerender_data)
     return false;
   DCHECK(prerender_data->contents());
@@ -397,7 +395,7 @@
   }
 
   if (WebContents* new_web_contents =
-      prerender_data->contents()->prerender_contents()) {
+          prerender_data->contents()->prerender_contents()) {
     if (web_contents == new_web_contents)
       return nullptr;  // Do not swap in to ourself.
 
@@ -416,8 +414,8 @@
   // Do not swap if the target WebContents is not the only WebContents in its
   // current BrowsingInstance.
   if (web_contents->GetSiteInstance()->GetRelatedActiveContentsCount() != 1u) {
-    DCHECK_GT(
-        web_contents->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
+    DCHECK_GT(web_contents->GetSiteInstance()->GetRelatedActiveContentsCount(),
+              1u);
     prerender_data->contents()->Destroy(
         FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE);
     return nullptr;
@@ -429,12 +427,6 @@
     return nullptr;
   }
 
-  // Do not swap in the prerender if the current WebContents is being captured.
-  if (web_contents->IsBeingCaptured()) {
-    prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
-    return nullptr;
-  }
-
   DCHECK(prerender_data->contents()->prerendering_has_started());
 
   // At this point, we've determined that we will use the prerender.
@@ -542,8 +534,8 @@
 bool PrerenderManager::HasPrerenderedUrl(
     GURL url,
     content::WebContents* web_contents) const {
-  content::SessionStorageNamespace* session_storage_namespace = web_contents->
-      GetController().GetDefaultSessionStorageNamespace();
+  content::SessionStorageNamespace* session_storage_namespace =
+      web_contents->GetController().GetDefaultSessionStorageNamespace();
 
   for (const auto& prerender_data : active_prerenders_) {
     PrerenderContents* prerender_contents = prerender_data->contents();
@@ -647,8 +639,8 @@
   auto dict_value = std::make_unique<base::DictionaryValue>();
   dict_value->Set("history", prerender_history_->CopyEntriesAsValue());
   dict_value->Set("active", GetActivePrerendersAsValue());
-  dict_value->SetBoolean("enabled",
-      GetPredictionStatus() == NetworkPredictionStatus::ENABLED);
+  dict_value->SetBoolean(
+      "enabled", GetPredictionStatus() == NetworkPredictionStatus::ENABLED);
   std::string disabled_note;
   if (GetPredictionStatus() == NetworkPredictionStatus::DISABLED_ALWAYS)
     disabled_note = "Disabled by user setting";
@@ -848,8 +840,8 @@
   // TODO(ppi): Check whether there are usually enough render processes
   // available on Android. If not, kill an existing renderers so that we can
   // create a new one.
-  if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
-          profile_, url) &&
+  if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(profile_,
+                                                                    url) &&
       !content::RenderProcessHost::run_renderer_in_process()) {
     SkipPrerenderContentsAndMaybePreconnect(url, origin,
                                             FINAL_STATUS_TOO_MANY_PROCESSES);
@@ -1093,7 +1085,7 @@
   if (!config_.rate_limit_enabled)
     return true;
   return elapsed_time >=
-      base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
+         base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
 }
 
 void PrerenderManager::DeleteOldWebContents() {
@@ -1189,8 +1181,7 @@
 
 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
   PrerenderHistory::Entry entry(contents->prerender_url(),
-                                contents->final_status(),
-                                contents->origin(),
+                                contents->final_status(), contents->origin(),
                                 base::Time::Now());
   prerender_history_->AddEntry(entry);
 }
diff --git a/chrome/browser/resources/chromeos/camera/.eslintrc.js b/chrome/browser/resources/chromeos/camera/.eslintrc.js
index 432de3b..568edb00 100644
--- a/chrome/browser/resources/chromeos/camera/.eslintrc.js
+++ b/chrome/browser/resources/chromeos/camera/.eslintrc.js
@@ -395,8 +395,7 @@
     'indent': 'off',
 
     // TODO(shik): temporarily disable the rules we violate (b/117810572).
-    'no-redeclare': 'off',  // 3 errors
-    'no-var': 'off',        // 181 errors
+    'no-var': 'off',        // 64 errors
     'prefer-const': 'off',  // 27 errors
   }),
 };
diff --git a/chrome/browser/ssl/ssl_error_handler.cc b/chrome/browser/ssl/ssl_error_handler.cc
index 4ee5efb..4c892401 100644
--- a/chrome/browser/ssl/ssl_error_handler.cc
+++ b/chrome/browser/ssl/ssl_error_handler.cc
@@ -23,6 +23,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
+#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/interstitials/enterprise_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ssl/bad_clock_blocking_page.h"
diff --git a/chrome/browser/ssl/ssl_error_handler.h b/chrome/browser/ssl/ssl_error_handler.h
index 350f1d5e..3994a7fb 100644
--- a/chrome/browser/ssl/ssl_error_handler.h
+++ b/chrome/browser/ssl/ssl_error_handler.h
@@ -12,8 +12,6 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ssl/common_name_mismatch_handler.h"
 #include "chrome/browser/ssl/ssl_error_assistant.pb.h"
 #include "components/security_interstitials/content/security_interstitial_page.h"
diff --git a/chrome/browser/ssl/ssl_error_handler_unittest.cc b/chrome/browser/ssl/ssl_error_handler_unittest.cc
index a3d08283..ee6f125 100644
--- a/chrome/browser/ssl/ssl_error_handler_unittest.cc
+++ b/chrome/browser/ssl/ssl_error_handler_unittest.cc
@@ -19,6 +19,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/captive_portal/captive_portal_service.h"
+#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ssl/common_name_mismatch_handler.h"
 #include "chrome/browser/ssl/ssl_error_assistant.h"
diff --git a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
index 5b3bf3f..ba3cafb 100644
--- a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
@@ -138,9 +138,8 @@
 
 IN_PROC_BROWSER_TEST_F(SubresourceFilterListInsertingBrowserTest,
                        MainFrameActivation_SubresourceFilterList) {
-  content::ConsoleObserverDelegate console_observer(web_contents(),
-                                                    kActivationConsoleMessage);
-  web_contents()->SetDelegate(&console_observer);
+  content::WebContentsConsoleObserver console_observer(web_contents());
+  console_observer.SetPattern(kActivationConsoleMessage);
   GURL url(GetTestUrl("subresource_filter/frame_with_included_script.html"));
   ConfigureAsSubresourceFilterOnlyURL(url);
   ASSERT_NO_FATAL_FAILURE(SetRulesetToDisallowURLsWithPathSuffix(
@@ -159,7 +158,7 @@
   ui_test_utils::NavigateToURL(browser(), url);
   EXPECT_FALSE(WasParsedScriptElementLoaded(web_contents()->GetMainFrame()));
 
-  EXPECT_EQ(kActivationConsoleMessage, console_observer.message());
+  EXPECT_FALSE(console_observer.messages().empty());
 
   // The main frame document should never be filtered.
   SetRulesetToDisallowURLsWithPathSuffix("frame_with_included_script.html");
@@ -232,9 +231,8 @@
 }
 
 IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest, MainFrameActivation) {
-  content::ConsoleObserverDelegate console_observer(web_contents(),
-                                                    kActivationConsoleMessage);
-  web_contents()->SetDelegate(&console_observer);
+  content::WebContentsConsoleObserver console_observer(web_contents());
+  console_observer.SetPattern(kActivationConsoleMessage);
   GURL url(GetTestUrl("subresource_filter/frame_with_included_script.html"));
   ConfigureAsPhishingURL(url);
   ASSERT_NO_FATAL_FAILURE(SetRulesetToDisallowURLsWithPathSuffix(
@@ -247,7 +245,7 @@
   ui_test_utils::NavigateToURL(browser(), url);
   EXPECT_FALSE(WasParsedScriptElementLoaded(web_contents()->GetMainFrame()));
 
-  EXPECT_EQ(console_observer.message(), kActivationConsoleMessage);
+  EXPECT_FALSE(console_observer.messages().empty());
 
   // The main frame document should never be filtered.
   SetRulesetToDisallowURLsWithPathSuffix("frame_with_included_script.html");
@@ -279,9 +277,8 @@
 IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest, SubFrameActivation) {
   std::string message_filter =
       base::StringPrintf(kBlinkDisallowSubframeConsoleMessageFormat, "*");
-  content::ConsoleObserverDelegate console_observer(web_contents(),
-                                                    message_filter);
-  web_contents()->SetDelegate(&console_observer);
+  content::WebContentsConsoleObserver console_observer(web_contents());
+  console_observer.SetPattern(message_filter);
 
   GURL url(GetTestUrl(kTestFrameSetPath));
   ConfigureAsPhishingURL(url);
@@ -299,8 +296,9 @@
                            SubresourceFilterAction::kUIShown, 1);
 
   // Console message for subframe blocking should be displayed.
+  EXPECT_FALSE(console_observer.messages().empty());
   EXPECT_TRUE(base::MatchPattern(
-      console_observer.message(),
+      base::UTF16ToUTF8(console_observer.messages()[0].message),
       base::StringPrintf(kBlinkDisallowSubframeConsoleMessageFormat,
                          "*included_script.js")));
 }
@@ -309,9 +307,8 @@
                        ActivationDisabled_NoConsoleMessage) {
   std::string message_filter =
       base::StringPrintf(kBlinkDisallowSubframeConsoleMessageFormat, "*");
-  content::ConsoleObserverDelegate console_observer(web_contents(),
-                                                    message_filter);
-  web_contents()->SetDelegate(&console_observer);
+  content::WebContentsConsoleObserver console_observer(web_contents());
+  console_observer.SetPattern(message_filter);
 
   Configuration config(
       subresource_filter::mojom::ActivationLevel::kDisabled,
@@ -327,16 +324,15 @@
 
   // Console message for subframe blocking should not be displayed as filtering
   // is disabled.
-  EXPECT_TRUE(console_observer.message().empty());
+  EXPECT_TRUE(console_observer.messages().empty());
 }
 
 IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest,
                        ActivationDryRun_NoConsoleMessage) {
   std::string message_filter =
       base::StringPrintf(kBlinkDisallowSubframeConsoleMessageFormat, "*");
-  content::ConsoleObserverDelegate console_observer(web_contents(),
-                                                    message_filter);
-  web_contents()->SetDelegate(&console_observer);
+  content::WebContentsConsoleObserver console_observer(web_contents());
+  console_observer.SetPattern(message_filter);
 
   Configuration config(
       subresource_filter::mojom::ActivationLevel::kDryRun,
@@ -352,7 +348,7 @@
 
   // Console message for subframe blocking should not be displayed as filtering
   // is enabled in dryrun mode.
-  EXPECT_TRUE(console_observer.message().empty());
+  EXPECT_TRUE(console_observer.messages().empty());
 }
 
 IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest,
@@ -419,9 +415,8 @@
 
 IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest,
                        HistoryNavigationActivation) {
-  content::ConsoleObserverDelegate console_observer(web_contents(),
-                                                    kActivationConsoleMessage);
-  web_contents()->SetDelegate(&console_observer);
+  content::WebContentsConsoleObserver console_observer(web_contents());
+  console_observer.SetPattern(kActivationConsoleMessage);
   GURL url_with_activation(GetTestUrl(kTestFrameSetPath));
   GURL url_without_activation(
       embedded_test_server()->GetURL("a.com", kTestFrameSetPath));
@@ -440,14 +435,14 @@
       kSubframeNames, kExpectScriptInFrameToLoadWithoutActivation));
 
   // No message should be displayed for navigating to URL without activation.
-  EXPECT_TRUE(console_observer.message().empty());
+  EXPECT_TRUE(console_observer.messages().empty());
 
   ui_test_utils::NavigateToURL(browser(), url_with_activation);
   ASSERT_NO_FATAL_FAILURE(ExpectParsedScriptElementLoadedStatusInFrames(
       kSubframeNames, kExpectScriptInFrameToLoadWithActivation));
 
   // Console message should now be displayed.
-  EXPECT_EQ(console_observer.message(), kActivationConsoleMessage);
+  EXPECT_EQ(1u, console_observer.messages().size());
 
   ASSERT_TRUE(web_contents()->GetController().CanGoBack());
   content::WindowedNotificationObserver back_navigation_stop_observer(
@@ -506,9 +501,8 @@
 // dynamically inserting a subframe afterwards, and still expecting activation.
 IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest,
                        PageLevelActivationOutlivesSameDocumentNavigation) {
-  content::ConsoleObserverDelegate console_observer(web_contents(),
-                                                    kActivationConsoleMessage);
-  web_contents()->SetDelegate(&console_observer);
+  content::WebContentsConsoleObserver console_observer(web_contents());
+  console_observer.SetPattern(kActivationConsoleMessage);
   GURL url(GetTestUrl(kTestFrameSetPath));
   ConfigureAsPhishingURL(url);
   ASSERT_NO_FATAL_FAILURE(
@@ -526,7 +520,7 @@
   ASSERT_TRUE(dynamic_frame);
   EXPECT_FALSE(WasParsedScriptElementLoaded(dynamic_frame));
 
-  EXPECT_EQ(console_observer.message(), kActivationConsoleMessage);
+  EXPECT_EQ(1u, console_observer.messages().size());
 }
 
 // If a navigation starts but aborts before commit, page level activation should
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index bf51004..63807dbd 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1399,6 +1399,7 @@
       "//chrome/browser/resource_coordinator:tab_metrics_event_proto",
       "//chrome/browser/resource_coordinator/tab_ranker",
       "//chrome/browser/safe_browsing:advanced_protection",
+      "//chrome/browser/ui/color:color_headers",
       "//chrome/browser/ui/webui/app_management:mojo_bindings",
       "//chrome/common:buildflags",
       "//chrome/common:search_mojom",
@@ -1452,10 +1453,7 @@
     }
 
     if (use_color_pipeline) {
-      deps += [
-        "//chrome/browser/ui/color:color",
-        "//chrome/browser/ui/color:mixers",
-      ]
+      deps += [ "//chrome/browser/ui/color:mixers" ]
     }
   }
 
@@ -2889,6 +2887,8 @@
       "views/frame/toolbar_button_provider.h",
       "views/frame/top_container_background.cc",
       "views/frame/top_container_background.h",
+      "views/frame/top_container_loading_bar.cc",
+      "views/frame/top_container_loading_bar.h",
       "views/frame/top_container_view.cc",
       "views/frame/top_container_view.h",
       "views/frame/top_controls_slide_controller.h",
@@ -4193,19 +4193,20 @@
       "//ui/views:test_support",
     ]
     sources += [
-      "extensions/browser_action_test_util.h",
+      "extensions/extension_action_test_helper.h",
       "views/extensions/extensions_menu_test_util.cc",
       "views/extensions/extensions_menu_test_util.h",
       "views/find_bar_host_unittest_util_views.cc",
       "views/payments/test_chrome_payment_request_delegate.cc",
       "views/payments/test_chrome_payment_request_delegate.h",
-      "views/toolbar/browser_action_test_util_views.cc",
-      "views/toolbar/browser_action_test_util_views_mac.mm",
+      "views/toolbar/extension_action_test_helper_mac.mm",
+      "views/toolbar/extension_action_test_helper_views.cc",
+      "views/toolbar/extension_action_test_helper_views.h",
       "views/webauthn/authenticator_request_dialog_view_test_api.cc",
       "views/webauthn/authenticator_request_dialog_view_test_api.h",
     ]
     if (use_aura) {
-      sources += [ "views/toolbar/browser_action_test_util_views_aura.cc" ]
+      sources += [ "views/toolbar/extension_action_test_helper_aura.cc" ]
       deps += [
         "//ui/aura",
         "//ui/wm",
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc
index ce0b46b..ba050ef 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc
@@ -98,6 +98,13 @@
           chromeos::ProfileHelper::Get()->GetProfileByAccountId(
               primary_account_id));
 
+  // Windows without an application id set will get filtered out here.
+  const std::string& crostini_shelf_app_id =
+      registry_service->GetCrostiniShelfAppId(
+          exo::GetShellApplicationId(window), exo::GetShellStartupId(window));
+  if (crostini_shelf_app_id.empty())
+    return;
+
   // At this point, all remaining windows are Crostini windows. Firstly, we add
   // support for forcibly closing it. We use the registration to retrieve the
   // app's name, but this may be null in the case of apps with no associated
diff --git a/chrome/browser/ui/color/BUILD.gn b/chrome/browser/ui/color/BUILD.gn
index af9c61b..9dfbdcd 100644
--- a/chrome/browser/ui/color/BUILD.gn
+++ b/chrome/browser/ui/color/BUILD.gn
@@ -4,42 +4,47 @@
 
 import("//ui/base/ui_features.gni")
 
-assert(use_color_pipeline)
-
-source_set("color") {
+source_set("color_headers") {
   sources = [
     "chrome_color_id.h",
   ]
 
   public_deps = [
-    "//ui/color:color",
+    "//ui/color:color_headers",
   ]
+
+  if (!use_color_pipeline) {
+    public_deps += [ "//chrome/browser:theme_properties" ]
+  }
 }
 
-executable("dump_colors") {
-  testonly = true
+if (use_color_pipeline) {
+  executable("dump_colors") {
+    testonly = true
 
-  sources = [
-    "tools/dump_colors.cc",
-  ]
+    sources = [
+      "tools/dump_colors.cc",
+    ]
 
-  deps = [
-    ":color",
-    ":mixers",
-    "//ui/color:mixers",
-  ]
-}
+    deps = [
+      ":color_headers",
+      ":mixers",
+      "//ui/color:mixers",
+    ]
+  }
 
-source_set("mixers") {
-  sources = [
-    "chrome_color_mixers.cc",
-    "chrome_color_mixers.h",
-    "omnibox_color_mixers.cc",
-    "omnibox_color_mixers.h",
-  ]
+  source_set("mixers") {
+    sources = [
+      "chrome_color_mixers.cc",
+      "chrome_color_mixers.h",
+      "omnibox_color_mixers.cc",
+      "omnibox_color_mixers.h",
+    ]
 
-  deps = [
-    ":color",
-    "//ui/color:mixers",
-  ]
+    deps = [
+      ":color_headers",
+      "//ui/color:color",
+      "//ui/color:mixers",
+    ]
+  }
 }
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h
index 07c5ffc..129343f 100644
--- a/chrome/browser/ui/color/chrome_color_id.h
+++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -5,56 +5,80 @@
 #ifndef CHROME_BROWSER_UI_COLOR_CHROME_COLOR_ID_H_
 #define CHROME_BROWSER_UI_COLOR_CHROME_COLOR_ID_H_
 
+#include "ui/color/color_buildflags.h"
 #include "ui/color/color_id.h"
 
+#if !BUILDFLAG(USE_COLOR_PIPELINE)
+#include "chrome/browser/themes/theme_properties.h"  // nogncheck
+#endif
+
 // TODO(pkasting): Add the rest of the colors.
 
 // clang-format off
 #define CHROME_COLOR_IDS \
   /* Omnibox output colors. */ \
-  E(kColorOmniboxBackground, kChromeColorsStart), \
-  E(kColorOmniboxBackgroundHovered), \
-  E(kColorOmniboxBubbleOutline), \
-  E(kColorOmniboxBubbleOutlineExperimentalKeywordMode), \
-  E(kColorOmniboxResultsBackground), \
-  E(kColorOmniboxResultsBackgroundHovered), \
-  E(kColorOmniboxResultsBackgroundSelected), \
-  E(kColorOmniboxResultsIcon), \
-  E(kColorOmniboxResultsIconSelected), \
-  E(kColorOmniboxResultsTextDimmed), \
-  E(kColorOmniboxResultsTextDimmedSelected), \
-  E(kColorOmniboxResultsTextSelected), \
-  E(kColorOmniboxResultsUrl), \
-  E(kColorOmniboxResultsUrlSelected), \
-  E(kColorOmniboxSecurityChipDangerous), \
-  E(kColorOmniboxSecurityChipDefault), \
-  E(kColorOmniboxSecurityChipSecure), \
-  E(kColorOmniboxSelectedKeyword), \
-  E(kColorOmniboxText), \
-  E(kColorOmniboxTextDimmed), \
+  E(kColorOmniboxBackground, \
+    ThemeProperties::COLOR_OMNIBOX_BACKGROUND, kChromeColorsStart) \
+  E(kColorOmniboxBackgroundHovered, \
+    ThemeProperties::COLOR_OMNIBOX_BACKGROUND_HOVERED) \
+  E(kColorOmniboxBubbleOutline, \
+    ThemeProperties::COLOR_OMNIBOX_BUBBLE_OUTLINE) \
+  E(kColorOmniboxBubbleOutlineExperimentalKeywordMode, \
+    ThemeProperties::COLOR_OMNIBOX_BUBBLE_OUTLINE_EXPERIMENTAL_KEYWORD_MODE) \
+  E(kColorOmniboxResultsBackground, \
+    ThemeProperties::COLOR_OMNIBOX_RESULTS_BG) \
+  E(kColorOmniboxResultsBackgroundHovered, \
+    ThemeProperties::COLOR_OMNIBOX_RESULTS_BG_HOVERED) \
+  E(kColorOmniboxResultsBackgroundSelected, \
+    ThemeProperties::COLOR_OMNIBOX_RESULTS_BG_SELECTED) \
+  E(kColorOmniboxResultsIcon, ThemeProperties::COLOR_OMNIBOX_RESULTS_ICON) \
+  E(kColorOmniboxResultsIconSelected, \
+    ThemeProperties::COLOR_OMNIBOX_RESULTS_ICON_SELECTED) \
+  E(kColorOmniboxResultsTextDimmed, \
+    ThemeProperties::COLOR_OMNIBOX_RESULTS_TEXT_DIMMED) \
+  E(kColorOmniboxResultsTextDimmedSelected, \
+    ThemeProperties::COLOR_OMNIBOX_RESULTS_TEXT_DIMMED_SELECTED) \
+  E(kColorOmniboxResultsTextSelected, \
+    ThemeProperties::COLOR_OMNIBOX_RESULTS_TEXT_SELECTED) \
+  E(kColorOmniboxResultsUrl, ThemeProperties::COLOR_OMNIBOX_RESULTS_URL) \
+  E(kColorOmniboxResultsUrlSelected, \
+    ThemeProperties::COLOR_OMNIBOX_RESULTS_URL_SELECTED) \
+  E(kColorOmniboxSecurityChipDangerous, \
+    ThemeProperties::COLOR_OMNIBOX_SECURITY_CHIP_DANGEROUS) \
+  E(kColorOmniboxSecurityChipDefault, \
+    ThemeProperties::COLOR_OMNIBOX_SECURITY_CHIP_DEFAULT) \
+  E(kColorOmniboxSecurityChipSecure, \
+    ThemeProperties::COLOR_OMNIBOX_SECURITY_CHIP_SECURE) \
+  E(kColorOmniboxSelectedKeyword, \
+    ThemeProperties::COLOR_OMNIBOX_SELECTED_KEYWORD) \
+  E(kColorOmniboxText, ThemeProperties::COLOR_OMNIBOX_TEXT) \
+  E(kColorOmniboxTextDimmed, ThemeProperties::COLOR_OMNIBOX_TEXT_DIMMED) \
   \
-  E(kColorToolbar)
-// clang-format on
+  E(kColorToolbar, ThemeProperties::COLOR_TOOLBAR)
 
 #include "ui/color/color_id_macros.inc"
 
 enum ChromeColorIds : ui::ColorId {
   kChromeColorsStart = ui::kUiColorsEnd,
 
-  CHROME_COLOR_IDS,
+  CHROME_COLOR_IDS
 
   kChromeColorsEnd,
 };
 
 #include "ui/color/color_id_macros.inc"
 
+// clang-format on
+
 static_assert(ui::ColorId{kChromeColorsEnd} <= ui::ColorId{ui::kUiColorsLast},
               "Embedder colors must not exceed allowed space");
 
+#if BUILDFLAG(USE_COLOR_PIPELINE)
 enum ChromeColorSetIds : ui::ColorSetId {
   kColorSetCustomTheme = ui::kUiColorSetsEnd,
 
   kChromeColorSetsEnd,
 };
+#endif  // BUILDFLAG(USE_COLOR_PIPELINE)
 
 #endif  // CHROME_BROWSER_UI_COLOR_CHROME_COLOR_ID_H_
diff --git a/chrome/browser/ui/color/tools/dump_colors.cc b/chrome/browser/ui/color/tools/dump_colors.cc
index 34e664e5..288e81d 100644
--- a/chrome/browser/ui/color/tools/dump_colors.cc
+++ b/chrome/browser/ui/color/tools/dump_colors.cc
@@ -19,10 +19,12 @@
 #define STRINGIZE_COLOR_IDS
 #include "ui/color/color_id_macros.inc"
 
+// clang-format off
 const char* enum_names[] = {
-  COLOR_IDS,
-  CHROME_COLOR_IDS,
+  COLOR_IDS
+  CHROME_COLOR_IDS
 };
+// clang-format on
 
 #include "ui/color/color_id_macros.inc"
 
diff --git a/chrome/browser/ui/extensions/blocked_action_bubble_browsertest.cc b/chrome/browser/ui/extensions/blocked_action_bubble_browsertest.cc
index 9ba1e34..9da7fc4 100644
--- a/chrome/browser/ui/extensions/blocked_action_bubble_browsertest.cc
+++ b/chrome/browser/ui/extensions/blocked_action_bubble_browsertest.cc
@@ -9,7 +9,7 @@
 #include "chrome/browser/extensions/scripting_permissions_modifier.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
@@ -93,7 +93,7 @@
   ASSERT_EQ(1u, toolbar_actions_bar->GetActions().size());
   EXPECT_TRUE(toolbar_actions_bar->GetActions()[0]->WantsToRun(tab));
 
-  BrowserActionTestUtil::Create(browser())->Press(0);
+  ExtensionActionTestHelper::Create(browser())->Press(0);
 
   EXPECT_TRUE(toolbar_actions_bar->is_showing_bubble());
 }
diff --git a/chrome/browser/ui/extensions/browser_action_test_util.h b/chrome/browser/ui/extensions/extension_action_test_helper.h
similarity index 82%
rename from chrome/browser/ui/extensions/browser_action_test_util.h
rename to chrome/browser/ui/extensions/extension_action_test_helper.h
index 2a5002402..a0b4609 100644
--- a/chrome/browser/ui/extensions/browser_action_test_util.h
+++ b/chrome/browser/ui/extensions/extension_action_test_helper.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_EXTENSIONS_BROWSER_ACTION_TEST_UTIL_H_
-#define CHROME_BROWSER_UI_EXTENSIONS_BROWSER_ACTION_TEST_UTIL_H_
+#ifndef CHROME_BROWSER_UI_EXTENSIONS_EXTENSION_ACTION_TEST_HELPER_H_
+#define CHROME_BROWSER_UI_EXTENSIONS_EXTENSION_ACTION_TEST_HELPER_H_
 
 #include <stddef.h>
 
@@ -22,16 +22,16 @@
 class Size;
 }  // namespace gfx
 
-class BrowserActionTestUtil {
+class ExtensionActionTestHelper {
  public:
-  // Constructs a BrowserActionTestUtil which, if |is_real_window| is false,
+  // Constructs a ExtensionActionTestHelper which, if |is_real_window| is false,
   // will create its own browser actions container. This is useful in unit
   // tests, when the |browser|'s window doesn't create platform-specific views.
-  static std::unique_ptr<BrowserActionTestUtil> Create(
+  static std::unique_ptr<ExtensionActionTestHelper> Create(
       Browser* browser,
       bool is_real_window = true);
 
-  virtual ~BrowserActionTestUtil() {}
+  virtual ~ExtensionActionTestHelper() {}
 
   // Returns the number of browser action buttons in the window toolbar.
   virtual int NumberOfBrowserActions() = 0;
@@ -91,9 +91,9 @@
   // Returns the associated ExtensionsContainer.
   virtual ExtensionsContainer* GetExtensionsContainer() = 0;
 
-  // Creates and returns a BrowserActionTestUtil with an "overflow" container,
-  // with this object's container as the main bar.
-  virtual std::unique_ptr<BrowserActionTestUtil> CreateOverflowBar(
+  // Creates and returns a ExtensionActionTestHelper with an "overflow"
+  // container, with this object's container as the main bar.
+  virtual std::unique_ptr<ExtensionActionTestHelper> CreateOverflowBar(
       Browser* browser) = 0;
 
   // Returns the minimum allowed size of an extension popup.
@@ -109,10 +109,10 @@
   virtual bool CanBeResized() = 0;
 
  protected:
-  BrowserActionTestUtil() {}
+  ExtensionActionTestHelper() {}
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(BrowserActionTestUtil);
+  DISALLOW_COPY_AND_ASSIGN(ExtensionActionTestHelper);
 };
 
-#endif  // CHROME_BROWSER_UI_EXTENSIONS_BROWSER_ACTION_TEST_UTIL_H_
+#endif  // CHROME_BROWSER_UI_EXTENSIONS_EXTENSION_ACTION_TEST_HELPER_H_
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc b/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
index d6eae57..5efd0d41 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
@@ -44,7 +44,7 @@
 class LegacyToolbarTestHelper {
  public:
   explicit LegacyToolbarTestHelper(Browser* browser)
-      : test_util_(BrowserActionTestUtil::Create(browser, false)),
+      : test_util_(ExtensionActionTestHelper::Create(browser, false)),
         overflow_test_util_(test_util_->CreateOverflowBar(browser)),
         main_bar_(test_util_->GetToolbarActionsBar()),
         overflow_bar_(overflow_test_util_->GetToolbarActionsBar()) {
@@ -61,8 +61,8 @@
   ToolbarActionsBar* overflow_bar() { return overflow_bar_; }
 
  private:
-  std::unique_ptr<BrowserActionTestUtil> test_util_;
-  std::unique_ptr<BrowserActionTestUtil> overflow_test_util_;
+  std::unique_ptr<ExtensionActionTestHelper> test_util_;
+  std::unique_ptr<ExtensionActionTestHelper> overflow_test_util_;
   ToolbarActionsBar* main_bar_ = nullptr;
   ToolbarActionsBar* overflow_bar_ = nullptr;
 };
@@ -112,7 +112,7 @@
     extension_service_ =
         extensions::ExtensionSystem::Get(profile())->extension_service();
 
-    test_util_ = BrowserActionTestUtil::Create(browser(), false);
+    test_util_ = ExtensionActionTestHelper::Create(browser(), false);
 
     view_size_ = test_util_->GetToolbarActionSize();
   }
@@ -172,7 +172,7 @@
   // ToolbarActionsModel associated with the main profile.
   ToolbarActionsModel* toolbar_model_ = nullptr;
 
-  std::unique_ptr<BrowserActionTestUtil> test_util_;
+  std::unique_ptr<ExtensionActionTestHelper> test_util_;
 
   // The standard size associated with a toolbar action view.
   gfx::Size view_size_;
diff --git a/chrome/browser/ui/search/local_ntp_js_browsertest.cc b/chrome/browser/ui/search/local_ntp_js_browsertest.cc
index 6ac8c42..a868a53 100644
--- a/chrome/browser/ui/search/local_ntp_js_browsertest.cc
+++ b/chrome/browser/ui/search/local_ntp_js_browsertest.cc
@@ -80,8 +80,9 @@
   EXPECT_TRUE(success);
 }
 
+// TODO(crbug.com/1038385): This test is flaky.
 // This runs a bunch of pure JS-side tests for the richer picker.
-IN_PROC_BROWSER_TEST_F(LocalNTPJavascriptTest, CustomizeMenuTests) {
+IN_PROC_BROWSER_TEST_F(LocalNTPJavascriptTest, DISABLED_CustomizeMenuTests) {
   content::WebContents* active_tab = local_ntp_test_utils::OpenNewTab(
       browser(), GURL(chrome::kChromeUINewTabURL));
   ASSERT_TRUE(search::IsInstantNTP(active_tab));
diff --git a/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.cc b/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.cc
index 7dbbb287..e0faeeb0 100644
--- a/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.cc
+++ b/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.cc
@@ -22,7 +22,7 @@
 #include "chrome/browser/extensions/scripting_permissions_modifier.h"
 #include "chrome/browser/sessions/session_tab_helper.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/browser/ui/extensions/extension_action_view_controller.h"
 #include "chrome/browser/ui/extensions/icon_with_badge_image_source.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -106,7 +106,7 @@
 
 void BrowserActionsBarBrowserTest::SetUpOnMainThread() {
   extensions::ExtensionBrowserTest::SetUpOnMainThread();
-  browser_actions_bar_ = BrowserActionTestUtil::Create(browser());
+  browser_actions_bar_ = ExtensionActionTestHelper::Create(browser());
   toolbar_model_ = ToolbarActionsModel::Get(profile());
 }
 
@@ -396,7 +396,7 @@
 
 IN_PROC_BROWSER_TEST_F(BrowserActionsBarBrowserTest,
                        OverflowedBrowserActionPopupTest) {
-  std::unique_ptr<BrowserActionTestUtil> overflow_bar =
+  std::unique_ptr<ExtensionActionTestHelper> overflow_bar =
       browser_actions_bar()->CreateOverflowBar(browser());
 
   // Load up two extensions that have browser action popups.
@@ -483,7 +483,7 @@
 // Regression test for crbug.com/599467.
 IN_PROC_BROWSER_TEST_F(BrowserActionsBarBrowserTest,
                        OverflowedBrowserActionPopupTestRemoval) {
-  std::unique_ptr<BrowserActionTestUtil> overflow_bar =
+  std::unique_ptr<ExtensionActionTestHelper> overflow_bar =
       browser_actions_bar()->CreateOverflowBar(browser());
 
   // Install an extension and shrink the visible count to zero so the extension
diff --git a/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.h b/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.h
index e2bcb4c5..bb3f0d22 100644
--- a/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.h
+++ b/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.h
@@ -16,7 +16,7 @@
 class Extension;
 }
 
-class BrowserActionTestUtil;
+class ExtensionActionTestHelper;
 class ToolbarActionsModel;
 
 // A platform-independent browser test class for the browser actions bar.
@@ -29,7 +29,7 @@
   void SetUpOnMainThread() override;
   void TearDownOnMainThread() override;
 
-  BrowserActionTestUtil* browser_actions_bar() {
+  ExtensionActionTestHelper* browser_actions_bar() {
     return browser_actions_bar_.get();
   }
   ToolbarActionsModel* toolbar_model() { return toolbar_model_; }
@@ -52,7 +52,7 @@
  private:
   base::test::ScopedFeatureList feature_list_;
 
-  std::unique_ptr<BrowserActionTestUtil> browser_actions_bar_;
+  std::unique_ptr<ExtensionActionTestHelper> browser_actions_bar_;
 
   // The associated toolbar model, weak.
   ToolbarActionsModel* toolbar_model_;
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc b/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc
index a20d6252..de9663c 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc
@@ -58,7 +58,7 @@
 // order matches, the return value is empty; otherwise, it contains the error.
 std::string VerifyToolbarOrderForBar(
     const ToolbarActionsBar* actions_bar,
-    BrowserActionTestUtil* browser_action_test_util,
+    ExtensionActionTestHelper* browser_action_test_util,
     const char* expected_names[],
     size_t total_size,
     size_t visible_count) {
@@ -172,7 +172,8 @@
           profile());
 
   ToolbarActionsBar::disable_animations_for_testing_ = true;
-  browser_action_test_util_ = BrowserActionTestUtil::Create(browser(), false);
+  browser_action_test_util_ =
+      ExtensionActionTestHelper::Create(browser(), false);
 
   overflow_browser_action_test_util_ =
       browser_action_test_util_->CreateOverflowBar(browser());
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.h b/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.h
index 78a0a0f..6385e6d 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.h
@@ -11,7 +11,7 @@
 
 #include "base/macros.h"
 #include "base/test/scoped_feature_list.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "extensions/common/extension_builder.h"
 
@@ -75,10 +75,10 @@
     return overflow_browser_action_test_util_->GetToolbarActionsBar();
   }
   ToolbarActionsModel* toolbar_model() { return toolbar_model_; }
-  BrowserActionTestUtil* browser_action_test_util() {
+  ExtensionActionTestHelper* browser_action_test_util() {
     return browser_action_test_util_.get();
   }
-  BrowserActionTestUtil* overflow_browser_action_test_util() {
+  ExtensionActionTestHelper* overflow_browser_action_test_util() {
     return overflow_browser_action_test_util_.get();
   }
 
@@ -88,12 +88,12 @@
   // The associated ToolbarActionsModel (owned by the keyed service setup).
   ToolbarActionsModel* toolbar_model_;
 
-  // A BrowserActionTestUtil object constructed with the associated
+  // A ExtensionActionTestHelper object constructed with the associated
   // ToolbarActionsBar.
-  std::unique_ptr<BrowserActionTestUtil> browser_action_test_util_;
+  std::unique_ptr<ExtensionActionTestHelper> browser_action_test_util_;
 
-  // The overflow container's BrowserActionTestUtil.
-  std::unique_ptr<BrowserActionTestUtil> overflow_browser_action_test_util_;
+  // The overflow container's ExtensionActionTestHelper.
+  std::unique_ptr<ExtensionActionTestHelper> overflow_browser_action_test_util_;
 
   std::unique_ptr<ui::test::MaterialDesignControllerTestAPI>
       material_design_state_;
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.cc b/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.cc
index f0d6be4..aea9b9e 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.cc
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.cc
@@ -22,11 +22,12 @@
 
 LocalCardMigrationIconView::LocalCardMigrationIconView(
     CommandUpdater* command_updater,
-    PageActionIconView::Delegate* delegate)
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
     : PageActionIconView(command_updater,
                          IDC_MIGRATE_LOCAL_CREDIT_CARD_FOR_PAGE,
-                         delegate) {
-  DCHECK(delegate);
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate) {
   SetID(VIEW_ID_MIGRATE_LOCAL_CREDIT_CARD_BUTTON);
   if (base::FeatureList::IsEnabled(
           features::kAutofillCreditCardUploadFeedback)) {
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.h b/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.h
index a4c65ec..877e7d3 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.h
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.h
@@ -18,8 +18,10 @@
 // bubble.
 class LocalCardMigrationIconView : public PageActionIconView {
  public:
-  LocalCardMigrationIconView(CommandUpdater* command_updater,
-                             PageActionIconView::Delegate* delegate);
+  LocalCardMigrationIconView(
+      CommandUpdater* command_updater,
+      IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+      PageActionIconView::Delegate* page_action_icon_delegate);
   ~LocalCardMigrationIconView() override;
 
   // PageActionIconView:
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_icon_view.cc b/chrome/browser/ui/views/autofill/payments/save_card_icon_view.cc
index a6d0fb5..5dae0121 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_icon_view.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_card_icon_view.cc
@@ -19,12 +19,14 @@
 
 namespace autofill {
 
-SaveCardIconView::SaveCardIconView(CommandUpdater* command_updater,
-                                   PageActionIconView::Delegate* delegate)
+SaveCardIconView::SaveCardIconView(
+    CommandUpdater* command_updater,
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
     : PageActionIconView(command_updater,
                          IDC_SAVE_CREDIT_CARD_FOR_PAGE,
-                         delegate) {
-  DCHECK(delegate);
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate) {
   SetID(VIEW_ID_SAVE_CREDIT_CARD_BUTTON);
 
   if (base::FeatureList::IsEnabled(
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_icon_view.h b/chrome/browser/ui/views/autofill/payments/save_card_icon_view.h
index 8306c762..601d1a7 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_icon_view.h
+++ b/chrome/browser/ui/views/autofill/payments/save_card_icon_view.h
@@ -20,7 +20,8 @@
 class SaveCardIconView : public PageActionIconView {
  public:
   SaveCardIconView(CommandUpdater* command_updater,
-                   PageActionIconView::Delegate* delegate);
+                   IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+                   PageActionIconView::Delegate* page_action_icon_delegate);
   ~SaveCardIconView() override;
 
   // PageActionIconView:
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_test_util.cc b/chrome/browser/ui/views/extensions/extensions_menu_test_util.cc
index 9c73dce..95a3385 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_test_util.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_test_util.cc
@@ -178,7 +178,7 @@
   return extensions_container_;
 }
 
-std::unique_ptr<BrowserActionTestUtil>
+std::unique_ptr<ExtensionActionTestHelper>
 ExtensionsMenuTestUtil::CreateOverflowBar(Browser* browser) {
   // There is no overflow bar with the ExtensionsMenu implementation.
   NOTREACHED();
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_test_util.h b/chrome/browser/ui/views/extensions/extensions_menu_test_util.h
index 1c7f391..b963740 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_test_util.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_test_util.h
@@ -9,22 +9,22 @@
 
 #include "base/auto_reset.h"
 #include "base/macros.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 
 class Browser;
 class ExtensionsMenuItemView;
 class ExtensionsMenuView;
 class ExtensionsToolbarContainer;
 
-// An implementation of BrowserActionTestUtil that works with the ExtensionsMenu
-// (i.e., when features::kExtensionsToolbarMenu is enabled).
-class ExtensionsMenuTestUtil : public BrowserActionTestUtil {
+// An implementation of ExtensionActionTestHelper that works with the
+// ExtensionsMenu (i.e., when features::kExtensionsToolbarMenu is enabled).
+class ExtensionsMenuTestUtil : public ExtensionActionTestHelper {
  public:
   ExtensionsMenuTestUtil(Browser* browser, bool is_real_window);
 
   ~ExtensionsMenuTestUtil() override;
 
-  // BrowserActionTestUtil:
+  // ExtensionActionTestHelper:
   int NumberOfBrowserActions() override;
   int VisibleBrowserActions() override;
   void InspectPopup(int index) override;
@@ -41,10 +41,10 @@
   void SetWidth(int width) override;
   ToolbarActionsBar* GetToolbarActionsBar() override;
   ExtensionsContainer* GetExtensionsContainer() override;
-  std::unique_ptr<BrowserActionTestUtil> CreateOverflowBar(
+  std::unique_ptr<ExtensionActionTestHelper> CreateOverflowBar(
       Browser* browser) override;
   // TODO(devlin): Some of these popup methods have a common implementation
-  // between this and BrowserActionTestUtilViews. It would make sense to
+  // between this and ExtensionActionTestHelperViews. It would make sense to
   // extract them (since they aren't dependent on the extension action UI
   // implementation).
   gfx::Size GetMinPopupSize() override;
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
index bbdd28e..4695525 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
@@ -750,14 +750,14 @@
 class WebAppNonClientFrameViewAshTest
     : public TopChromeMdParamTest<BrowserActionsBarBrowserTest> {
  public:
-  WebAppNonClientFrameViewAshTest()
-      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+  WebAppNonClientFrameViewAshTest() = default;
 
   ~WebAppNonClientFrameViewAshTest() override = default;
 
-  GURL GetAppURL() {
+  GURL GetAppURL() const {
     return https_server_.GetURL("app.com", "/ssl/google.html");
   }
+
   static SkColor GetThemeColor() { return SK_ColorBLUE; }
 
   Browser* app_browser_ = nullptr;
@@ -832,9 +832,11 @@
 
   AppMenu* GetAppMenu() { return web_app_menu_button_->app_menu(); }
 
-  SkColor GetActiveColor() { return web_app_frame_toolbar_->active_color_; }
+  SkColor GetActiveColor() const {
+    return web_app_frame_toolbar_->active_color_;
+  }
 
-  bool GetPaintingAsActive() {
+  bool GetPaintingAsActive() const {
     return web_app_frame_toolbar_->paint_as_active_;
   }
 
@@ -853,9 +855,9 @@
 
     return *std::find_if(
         content_setting_views_->begin(), content_setting_views_->end(),
-        [](auto v) {
-          return ContentSettingImageModel::ImageType::GEOLOCATION ==
-                 v->GetTypeForTesting();
+        [](const auto* view) {
+          return view->GetTypeForTesting() ==
+                 ContentSettingImageModel::ImageType::GEOLOCATION;
         });
   }
 
@@ -873,7 +875,7 @@
 
  private:
   // For mocking a secure site.
-  net::EmbeddedTestServer https_server_;
+  net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
   content::ContentMockCertVerifier cert_verifier_;
 
   DISALLOW_COPY_AND_ASSIGN(WebAppNonClientFrameViewAshTest);
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 5698f4d0..a0435f11 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -87,6 +87,7 @@
 #include "chrome/browser/ui/views/frame/contents_layout_manager.h"
 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
 #include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
+#include "chrome/browser/ui/views/frame/top_container_loading_bar.h"
 #include "chrome/browser/ui/views/frame/top_container_view.h"
 #include "chrome/browser/ui/views/frame/web_contents_close_handler.h"
 #include "chrome/browser/ui/views/frame/web_footer_experiment_view.h"
@@ -898,6 +899,8 @@
       // read out to screen readers, even if focus doesn't actually change.
       GetWidget()->GetFocusManager()->ClearFocus();
     }
+    if (loading_bar_)
+      loading_bar_->SetWebContents(nullptr);
     contents_web_view_->SetWebContents(nullptr);
     devtools_web_view_->SetWebContents(nullptr);
   }
@@ -939,6 +942,8 @@
     }
 
     web_contents_close_handler_->ActiveTabChanged();
+    if (loading_bar_)
+      loading_bar_->SetWebContents(new_contents);
     contents_web_view_->SetWebContents(new_contents);
     SadTabHelper* sad_tab_helper = SadTabHelper::FromWebContents(new_contents);
     if (sad_tab_helper)
@@ -968,6 +973,8 @@
     // freed. This is because the focus manager performs some operations
     // on the selected WebContents when it is removed.
     web_contents_close_handler_->ActiveTabChanged();
+    if (loading_bar_)
+      loading_bar_->SetWebContents(nullptr);
     contents_web_view_->SetWebContents(nullptr);
     infobar_container_->ChangeInfoBarManager(nullptr);
     app_banner_manager_observer_.RemoveAll();
@@ -1777,6 +1784,9 @@
   if (selection.selection_changed())
     toolbar_->InvalidateLayout();
 
+  if (loading_bar_)
+    loading_bar_->SetWebContents(GetActiveWebContents());
+
   if (change.type() != TabStripModelChange::kInserted)
     return;
 
@@ -2697,13 +2707,21 @@
       webui_tab_strip_ = top_container_->AddChildView(
           std::make_unique<WebUITabStripContainerView>(browser_.get(),
                                                        contents_container_));
+      loading_bar_ = top_container_->AddChildView(
+          std::make_unique<TopContainerLoadingBar>());
+      loading_bar_->SetWebContents(GetActiveWebContents());
     }
   } else if (webui_tab_strip_) {
     top_container_->RemoveChildView(webui_tab_strip_);
     delete webui_tab_strip_;
     webui_tab_strip_ = nullptr;
+
+    top_container_->RemoveChildView(loading_bar_);
+    delete loading_bar_;
+    loading_bar_ = nullptr;
   }
   GetBrowserViewLayout()->set_webui_tab_strip(webui_tab_strip_);
+  GetBrowserViewLayout()->set_loading_bar(loading_bar_);
   if (toolbar_)
     toolbar_->UpdateForWebUITabStrip();
 #endif  // BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index 64c8886..4bf8b2c 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -68,6 +68,7 @@
 class TabStripRegionView;
 class ToolbarButtonProvider;
 class ToolbarView;
+class TopContainerLoadingBar;
 class TopContainerView;
 class TopControlsSlideControllerTest;
 class WebContentsCloseHandler;
@@ -753,6 +754,9 @@
   // Separator between top container and contents.
   views::View* contents_separator_ = nullptr;
 
+  // Loading bar (part of top container for / WebUI tab strip).
+  TopContainerLoadingBar* loading_bar_ = nullptr;
+
   // The do-nothing view which controls the z-order of the find bar widget
   // relative to views which paint into layers and views with an associated
   // NativeView.
diff --git a/chrome/browser/ui/views/frame/browser_view_layout.cc b/chrome/browser/ui/views/frame/browser_view_layout.cc
index 8cee9e7..d05dc2d 100644
--- a/chrome/browser/ui/views/frame/browser_view_layout.cc
+++ b/chrome/browser/ui/views/frame/browser_view_layout.cc
@@ -396,9 +396,18 @@
     contents_separator_->SetBounds(vertical_layout_rect_.x(), top,
                                    vertical_layout_rect_.width(),
                                    separator_height);
+    if (loading_bar_) {
+      SetViewVisibility(loading_bar_, true);
+      loading_bar_->SetBounds(vertical_layout_rect_.x(), top - 2,
+                              vertical_layout_rect_.width(),
+                              separator_height + 2);
+      top_container_->ReorderChildView(loading_bar_, -1);
+    }
     top += separator_height;
   } else {
     SetViewVisibility(contents_separator_, false);
+    if (loading_bar_)
+      SetViewVisibility(loading_bar_, false);
   }
 
   return LayoutInfoBar(top);
diff --git a/chrome/browser/ui/views/frame/browser_view_layout.h b/chrome/browser/ui/views/frame/browser_view_layout.h
index 1beb8af..bc6841c 100644
--- a/chrome/browser/ui/views/frame/browser_view_layout.h
+++ b/chrome/browser/ui/views/frame/browser_view_layout.h
@@ -67,6 +67,7 @@
   void set_webui_tab_strip(views::View* webui_tab_strip) {
     webui_tab_strip_ = webui_tab_strip;
   }
+  void set_loading_bar(views::View* loading_bar) { loading_bar_ = loading_bar; }
   void set_bookmark_bar(BookmarkBarView* bookmark_bar) {
     bookmark_bar_ = bookmark_bar;
   }
@@ -154,6 +155,7 @@
   views::View* const contents_separator_;
 
   views::View* webui_tab_strip_ = nullptr;
+  views::View* loading_bar_ = nullptr;
   TabStrip* tab_strip_ = nullptr;
   BookmarkBarView* bookmark_bar_ = nullptr;
   views::View* download_shelf_ = nullptr;
diff --git a/chrome/browser/ui/views/frame/top_container_loading_bar.cc b/chrome/browser/ui/views/frame/top_container_loading_bar.cc
new file mode 100644
index 0000000..a8751e1
--- /dev/null
+++ b/chrome/browser/ui/views/frame/top_container_loading_bar.cc
@@ -0,0 +1,155 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/frame/top_container_loading_bar.h"
+
+#include "chrome/browser/favicon/favicon_utils.h"
+#include "chrome/browser/ui/tab_ui_helper.h"
+#include "ui/gfx/animation/tween.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/color_palette.h"
+
+LoadingBarView::LoadingBarView() {
+  SetPaintToLayer();
+  layer()->SetFillsBoundsOpaquely(false);
+  animation_.SetDuration(base::TimeDelta::FromMilliseconds(300));
+}
+
+double LoadingBarView::GetDisplayedLoadingProgress() const {
+  return gfx::Tween::DoubleValueBetween(
+      gfx::Tween::CalculateValue(gfx::Tween::EASE_OUT,
+                                 animation_.GetCurrentValue()),
+      start_loading_progress_, target_loading_progress_);
+}
+
+void LoadingBarView::OnThemeChanged() {
+  SchedulePaint();
+}
+
+void LoadingBarView::AddedToWidget() {
+  SchedulePaint();
+}
+
+void LoadingBarView::HideImmediately() {
+  is_shown_when_not_animating_ = false;
+  start_loading_progress_ = 0.0;
+  target_loading_progress_ = 0.0;
+  animation_.Stop();
+  // If we were previously drawn we have to redraw as invisible.
+  SchedulePaint();
+}
+
+void LoadingBarView::Show(double loading_progress) {
+  is_shown_when_not_animating_ = true;
+  start_loading_progress_ = loading_progress;
+  target_loading_progress_ = loading_progress;
+  animation_.Stop();
+  SchedulePaint();
+}
+
+void LoadingBarView::FinishLoading() {
+  if (!is_shown_when_not_animating_)
+    return;
+  SetLoadingProgress(1.0);
+  is_shown_when_not_animating_ = false;
+}
+
+void LoadingBarView::SetLoadingProgress(double loading_progress) {
+  if (loading_progress <= target_loading_progress_)
+    return;
+  start_loading_progress_ = GetDisplayedLoadingProgress();
+  target_loading_progress_ = loading_progress;
+  animation_.SetCurrentValue(0.0);
+  animation_.Start();
+}
+
+void LoadingBarView::OnPaint(gfx::Canvas* canvas) {
+  if (is_shown_when_not_animating_ || animation_.is_animating()) {
+    canvas->FillRect(GetLocalBounds(), gfx::kGoogleBlue100);
+    gfx::Rect progress_bounds(GetLocalBounds());
+    progress_bounds.set_width(gfx::Tween::IntValueBetween(
+        GetDisplayedLoadingProgress(), 0, progress_bounds.width()));
+    canvas->FillRect(progress_bounds, gfx::kGoogleBlue500);
+  }
+}
+
+void LoadingBarView::AnimationEnded(const gfx::Animation* animation) {
+  SchedulePaint();
+}
+
+void LoadingBarView::AnimationProgressed(const gfx::Animation* animation) {
+  SchedulePaint();
+}
+
+TopContainerLoadingBar::TopContainerLoadingBar() = default;
+
+void TopContainerLoadingBar::SetWebContents(
+    content::WebContents* web_contents) {
+  Observe(web_contents);
+
+  if (!web_contents) {
+    network_state_ = TabNetworkState::kNone;
+    HideImmediately();
+    return;
+  }
+
+  // TODO(pbos): Consider storing one loading bar per tab and have it run (and
+  // observing) in the background. This would remove the need to reset
+  // loading-bar state during tab transitions as we'd just swap in the visible
+  // object. Currently Show(GetLoadingProgress()) can decrease from what was
+  // previously displayed in that tab.
+
+  // Reset network state to update from a clean slate.
+  network_state_ = TabNetworkState::kNone;
+  UpdateLoadingProgress();
+}
+
+void TopContainerLoadingBar::UpdateLoadingProgress() {
+  DCHECK(web_contents());
+  if (!favicon::ShouldDisplayFavicon(web_contents())) {
+    HideImmediately();
+    return;
+  }
+
+  TabUIHelper* const tab_ui_helper =
+      TabUIHelper::FromWebContents(web_contents());
+  if (tab_ui_helper->ShouldHideThrobber()) {
+    HideImmediately();
+    return;
+  }
+
+  const TabNetworkState old_network_state = network_state_;
+  network_state_ = TabNetworkStateForWebContents(web_contents());
+  if (old_network_state != network_state_) {
+    if (network_state_ == TabNetworkState::kWaiting ||
+        network_state_ == TabNetworkState::kLoading) {
+      // Reset loading state when we go to waiting or loading.
+      Show(GetLoadingProgress());
+    }
+  }
+
+  switch (network_state_) {
+    case TabNetworkState::kLoading:
+      SetLoadingProgress(GetLoadingProgress());
+      break;
+    case TabNetworkState::kError:
+      // TODO(pbos): Add a better error indicator (fade-out red?).
+      HideImmediately();
+      break;
+    case TabNetworkState::kWaiting:
+      break;
+    case TabNetworkState::kNone:
+      FinishLoading();
+      break;
+  }
+}
+
+double TopContainerLoadingBar::GetLoadingProgress() {
+  DCHECK(web_contents());
+  return std::min(web_contents()->GetLoadProgress(), 0.9);
+}
+
+void TopContainerLoadingBar::LoadProgressChanged(double progress) {
+  UpdateLoadingProgress();
+}
diff --git a/chrome/browser/ui/views/frame/top_container_loading_bar.h b/chrome/browser/ui/views/frame/top_container_loading_bar.h
new file mode 100644
index 0000000..79fd2d3
--- /dev/null
+++ b/chrome/browser/ui/views/frame/top_container_loading_bar.h
@@ -0,0 +1,63 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_TOP_CONTAINER_LOADING_BAR_H_
+#define CHROME_BROWSER_UI_VIEWS_FRAME_TOP_CONTAINER_LOADING_BAR_H_
+
+#include "chrome/browser/ui/tabs/tab_network_state.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "ui/gfx/animation/animation_delegate.h"
+#include "ui/gfx/animation/linear_animation.h"
+#include "ui/views/view.h"
+
+class LoadingBarView : public views::View, public gfx::AnimationDelegate {
+ public:
+  LoadingBarView();
+  LoadingBarView(const LoadingBarView&) = delete;
+  LoadingBarView& operator=(const LoadingBarView&) = delete;
+
+  void HideImmediately();
+  void Show(double loading_progress);
+  void FinishLoading();
+
+  void SetLoadingProgress(double loading_progress);
+
+ private:
+  double GetDisplayedLoadingProgress() const;
+
+  // views::View:
+  void OnThemeChanged() override;
+  void AddedToWidget() override;
+  void OnPaint(gfx::Canvas* canvas) override;
+
+  // gfx::AnimationDelegate:
+  void AnimationEnded(const gfx::Animation* animation) override;
+  void AnimationProgressed(const gfx::Animation* animation) override;
+
+  gfx::LinearAnimation animation_{this};
+  bool is_shown_when_not_animating_ = false;
+  double start_loading_progress_ = 0.0;
+  double target_loading_progress_ = 0.0;
+};
+
+class TopContainerLoadingBar : public LoadingBarView,
+                               public content::WebContentsObserver {
+ public:
+  TopContainerLoadingBar();
+  TopContainerLoadingBar(const TopContainerLoadingBar&) = delete;
+  TopContainerLoadingBar& operator=(const TopContainerLoadingBar&) = delete;
+
+  void SetWebContents(content::WebContents* web_contents);
+
+ private:
+  void UpdateLoadingProgress();
+  double GetLoadingProgress();
+
+  // content::WebContentsObserver:
+  void LoadProgressChanged(double progress) override;
+
+  TabNetworkState network_state_ = TabNetworkState::kNone;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_FRAME_TOP_CONTAINER_LOADING_BAR_H_
diff --git a/chrome/browser/ui/views/location_bar/content_setting_image_view.cc b/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
index 8db29526..35218a63 100644
--- a/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
+++ b/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
@@ -70,9 +70,10 @@
 
 ContentSettingImageView::ContentSettingImageView(
     std::unique_ptr<ContentSettingImageModel> image_model,
+    IconLabelBubbleView::Delegate* parent_delegate,
     Delegate* delegate,
     const gfx::FontList& font_list)
-    : IconLabelBubbleView(font_list),
+    : IconLabelBubbleView(font_list, parent_delegate),
       delegate_(delegate),
       content_setting_image_model_(std::move(image_model)),
       bubble_view_(nullptr) {
@@ -176,11 +177,6 @@
   IconLabelBubbleView::OnThemeChanged();
 }
 
-SkColor ContentSettingImageView::GetTextColor() const {
-  return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_TextfieldDefaultColor);
-}
-
 bool ContentSettingImageView::ShouldShowSeparator() const {
   return false;
 }
@@ -215,10 +211,6 @@
   return bubble_view_ != nullptr;
 }
 
-SkColor ContentSettingImageView::GetInkDropBaseColor() const {
-  return delegate_->GetContentSettingInkDropColor();
-}
-
 ContentSettingImageModel::ImageType ContentSettingImageView::GetTypeForTesting()
     const {
   return content_setting_image_model_->image_type();
@@ -242,7 +234,7 @@
   SetImage(content_setting_image_model_
                ->GetIcon(icon_color_ ? icon_color_.value()
                                      : color_utils::DeriveDefaultIconColor(
-                                           GetTextColor()))
+                                           GetForegroundColor()))
                .AsImageSkia());
 }
 
diff --git a/chrome/browser/ui/views/location_bar/content_setting_image_view.h b/chrome/browser/ui/views/location_bar/content_setting_image_view.h
index 3022446..6d4627bb 100644
--- a/chrome/browser/ui/views/location_bar/content_setting_image_view.h
+++ b/chrome/browser/ui/views/location_bar/content_setting_image_view.h
@@ -41,9 +41,6 @@
  public:
   class Delegate {
    public:
-    // Gets the color to use for the ink highlight.
-    virtual SkColor GetContentSettingInkDropColor() const = 0;
-
     // Gets the web contents the ContentSettingImageView is for.
     virtual content::WebContents* GetContentSettingWebContents() = 0;
 
@@ -58,6 +55,7 @@
   };
 
   ContentSettingImageView(std::unique_ptr<ContentSettingImageModel> image_model,
+                          IconLabelBubbleView::Delegate* parent_delegate,
                           Delegate* delegate,
                           const gfx::FontList& font_list);
   ~ContentSettingImageView() override;
@@ -78,11 +76,9 @@
   bool OnMousePressed(const ui::MouseEvent& event) override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
   void OnThemeChanged() override;
-  SkColor GetTextColor() const override;
   bool ShouldShowSeparator() const override;
   bool ShowBubble(const ui::Event& event) override;
   bool IsBubbleShowing() const override;
-  SkColor GetInkDropBaseColor() const override;
   void AnimationEnded(const gfx::Animation* animation) override;
 
   ContentSettingImageModel::ImageType GetTypeForTesting() const;
diff --git a/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc b/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc
index 5955fea..1e25fe7 100644
--- a/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc
+++ b/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc
@@ -17,8 +17,12 @@
 #include "ui/gfx/paint_vector_icon.h"
 
 CookieControlsIconView::CookieControlsIconView(
-    PageActionIconView::Delegate* delegate)
-    : PageActionIconView(nullptr, 0, delegate) {
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(nullptr,
+                         0,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate) {
   SetVisible(false);
 }
 
diff --git a/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.h b/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.h
index 36c2759d..3803f5e 100644
--- a/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.h
+++ b/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.h
@@ -17,7 +17,9 @@
 class CookieControlsIconView : public PageActionIconView,
                                public CookieControlsView {
  public:
-  explicit CookieControlsIconView(PageActionIconView::Delegate* delegate);
+  CookieControlsIconView(
+      IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+      PageActionIconView::Delegate* page_action_icon_delegate);
   ~CookieControlsIconView() override;
 
   // CookieControlsUI:
diff --git a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc
index 32d8e815..510b6d6 100644
--- a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc
@@ -198,7 +198,7 @@
   close_button_ = AddChildView(CreateCloseButton(this, foreground_color));
 
   location_icon_view_ =
-      AddChildView(std::make_unique<LocationIconView>(font_list, this));
+      AddChildView(std::make_unique<LocationIconView>(font_list, this, this));
 
   auto title_origin_view =
       std::make_unique<CustomTabBarTitleOriginView>(background_color_);
@@ -349,6 +349,11 @@
   }
 }
 
+SkColor CustomTabBarView::GetIconLabelBubbleSurroundingForegroundColor() const {
+  return GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_TextfieldDefaultColor);
+}
+
 content::WebContents* CustomTabBarView::GetWebContents() {
   return delegate_->GetWebContents();
 }
@@ -384,11 +389,6 @@
       GetSecurityChipColor(GetLocationBarModel()->GetSecurityLevel()));
 }
 
-SkColor CustomTabBarView::GetLocationIconInkDropColor() const {
-  return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_TextfieldDefaultColor);
-}
-
 const LocationBarModel* CustomTabBarView::GetLocationBarModel() const {
   return delegate_->GetLocationBarModel();
 }
diff --git a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h
index 402df26..7669233 100644
--- a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h
+++ b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h
@@ -35,6 +35,7 @@
                          public TabStripModelObserver,
                          public ui::SimpleMenuModel::Delegate,
                          public views::ContextMenuController,
+                         public IconLabelBubbleView::Delegate,
                          public LocationIconView::Delegate,
                          public views::ButtonListener {
  public:
@@ -60,6 +61,9 @@
   void OnPaintBackground(gfx::Canvas* canvas) override;
   void ChildPreferredSizeChanged(views::View* child) override;
 
+  // IconLabelBubbleView::Delegate:
+  SkColor GetIconLabelBubbleSurroundingForegroundColor() const override;
+
   // LocationIconView::Delegate:
   content::WebContents* GetWebContents() override;
   bool IsEditingOrEmpty() const override;
@@ -71,7 +75,6 @@
   const LocationBarModel* GetLocationBarModel() const override;
   gfx::ImageSkia GetLocationIcon(LocationIconView::Delegate::IconFetchedCallback
                                      on_icon_fetched) const override;
-  SkColor GetLocationIconInkDropColor() const override;
 
   // ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
diff --git a/chrome/browser/ui/views/location_bar/find_bar_icon.cc b/chrome/browser/ui/views/location_bar/find_bar_icon.cc
index 898872a5..588b57cb 100644
--- a/chrome/browser/ui/views/location_bar/find_bar_icon.cc
+++ b/chrome/browser/ui/views/location_bar/find_bar_icon.cc
@@ -12,9 +12,15 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/views/animation/ink_drop.h"
 
-FindBarIcon::FindBarIcon(Browser* browser,
-                         PageActionIconView::Delegate* delegate)
-    : PageActionIconView(nullptr, 0, delegate), browser_(browser) {
+FindBarIcon::FindBarIcon(
+    Browser* browser,
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(nullptr,
+                         0,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate),
+      browser_(browser) {
   DCHECK(browser_);
 }
 
diff --git a/chrome/browser/ui/views/location_bar/find_bar_icon.h b/chrome/browser/ui/views/location_bar/find_bar_icon.h
index d690ce4..96be5e3 100644
--- a/chrome/browser/ui/views/location_bar/find_bar_icon.h
+++ b/chrome/browser/ui/views/location_bar/find_bar_icon.h
@@ -13,7 +13,9 @@
 // The find icon to show when the find bar is visible.
 class FindBarIcon : public PageActionIconView {
  public:
-  FindBarIcon(Browser* browser, PageActionIconView::Delegate* delegate);
+  FindBarIcon(Browser* browser,
+              IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+              PageActionIconView::Delegate* page_action_icon_delegate);
   ~FindBarIcon() override;
 
   void SetActive(bool activate, bool should_animate);
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
index e254c2e5..24f6b8d 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
@@ -21,7 +21,6 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/scoped_canvas.h"
 #include "ui/gfx/skia_util.h"
-#include "ui/native_theme/native_theme.h"
 #include "ui/views/accessibility/ax_virtual_view.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
@@ -53,8 +52,9 @@
 
 }  // namespace
 
-//////////////////////////////////////////////////////////////////
-// SeparatorView class
+SkColor IconLabelBubbleView::Delegate::GetIconLabelBubbleInkDropColor() const {
+  return GetIconLabelBubbleSurroundingForegroundColor();
+}
 
 IconLabelBubbleView::SeparatorView::SeparatorView(IconLabelBubbleView* owner) {
   DCHECK(owner);
@@ -103,20 +103,12 @@
     duration = kIconLabelBubbleFadeInDurationMs;
   }
 
-  if (disable_animation_for_test_) {
-    layer()->SetOpacity(opacity);
-  } else {
-    ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
-    animation.SetTransitionDuration(
-        base::TimeDelta::FromMilliseconds(duration));
-    animation.SetTweenType(gfx::Tween::Type::EASE_IN);
-    layer()->SetOpacity(opacity);
-  }
+  ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
+  animation.SetTransitionDuration(base::TimeDelta::FromMilliseconds(duration));
+  animation.SetTweenType(gfx::Tween::Type::EASE_IN);
+  layer()->SetOpacity(opacity);
 }
 
-//////////////////////////////////////////////////////////////////
-// HighlightPathGenerator class
-
 class IconLabelBubbleView::HighlightPathGenerator
     : public views::HighlightPathGenerator {
  public:
@@ -131,12 +123,13 @@
   DISALLOW_COPY_AND_ASSIGN(HighlightPathGenerator);
 };
 
-//////////////////////////////////////////////////////////////////
-// IconLabelBubbleView class
-
-IconLabelBubbleView::IconLabelBubbleView(const gfx::FontList& font_list)
+IconLabelBubbleView::IconLabelBubbleView(const gfx::FontList& font_list,
+                                         Delegate* delegate)
     : LabelButton(nullptr, base::string16()),
+      delegate_(delegate),
       separator_view_(new SeparatorView(this)) {
+  DCHECK(delegate_);
+
   SetFontList(font_list);
   SetHorizontalAlignment(gfx::ALIGN_LEFT);
 
@@ -196,6 +189,10 @@
   label()->SetFontList(font_list);
 }
 
+SkColor IconLabelBubbleView::GetForegroundColor() const {
+  return delegate_->GetIconLabelBubbleSurroundingForegroundColor();
+}
+
 SkColor IconLabelBubbleView::GetParentBackgroundColor() const {
   return GetNativeTheme()->GetSystemColor(
       ui::NativeTheme::kColorId_TextfieldDefaultBackground);
@@ -310,7 +307,7 @@
   // under certain conditions. We don't want that, so unset the background.
   label()->SetBackground(nullptr);
 
-  SetEnabledTextColors(GetTextColor());
+  SetEnabledTextColors(GetForegroundColor());
   label()->SetBackgroundColor(GetParentBackgroundColor());
   SchedulePaint();
 }
@@ -323,6 +320,10 @@
   return std::move(ink_drop);
 }
 
+SkColor IconLabelBubbleView::GetInkDropBaseColor() const {
+  return delegate_->GetIconLabelBubbleInkDropColor();
+}
+
 bool IconLabelBubbleView::IsTriggerableEvent(const ui::Event& event) {
   if (event.IsMouseEvent())
     return !IsBubbleShowing() && !suppress_button_release_;
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
index 16ccba50..037f22bc 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
@@ -46,31 +46,19 @@
  public:
   static constexpr int kTrailingPaddingPreMd = 2;
 
-  // A view that draws the separator.
-  class SeparatorView : public views::View {
+  class Delegate {
    public:
-    explicit SeparatorView(IconLabelBubbleView* owner);
+    // Returns the foreground color of items around the IconLabelBubbleView,
+    // e.g. nearby text items.  By default, the IconLabelBubbleView will use
+    // this as its foreground color and ink drop base color.
+    virtual SkColor GetIconLabelBubbleSurroundingForegroundColor() const = 0;
 
-    // views::View:
-    void OnPaint(gfx::Canvas* canvas) override;
-
-    // Updates the opacity based on the ink drop's state.
-    void UpdateOpacity();
-
-    void set_disable_animation_for_test(bool disable_animation_for_test) {
-      disable_animation_for_test_ = disable_animation_for_test;
-    }
-
-   private:
-    // Weak.
-    IconLabelBubbleView* owner_;
-
-    bool disable_animation_for_test_ = false;
-
-    DISALLOW_COPY_AND_ASSIGN(SeparatorView);
+    // Returns the base color for ink drops.  If not overridden, this returns
+    // GetIconLabelBubbleSurroundingForegroundColor().
+    virtual SkColor GetIconLabelBubbleInkDropColor() const;
   };
 
-  explicit IconLabelBubbleView(const gfx::FontList& font_list);
+  IconLabelBubbleView(const gfx::FontList& font_list, Delegate* delegate);
   ~IconLabelBubbleView() override;
 
   // views::InkDropObserver:
@@ -91,7 +79,7 @@
   SkColor GetParentBackgroundColor() const;
 
   // Exposed for testing.
-  SeparatorView* separator_view() const { return separator_view_; }
+  views::View* separator_view() const { return separator_view_; }
 
   // Exposed for testing.
   bool is_animating_label() const { return slide_animation_.is_animating(); }
@@ -111,8 +99,8 @@
  protected:
   static constexpr int kOpenTimeMS = 150;
 
-  // Gets the color for displaying text.
-  virtual SkColor GetTextColor() const = 0;
+  // Gets the color for displaying text and/or icons.
+  virtual SkColor GetForegroundColor() const;
 
   // Returns true when the separator should be visible.
   virtual bool ShouldShowSeparator() const;
@@ -138,7 +126,7 @@
   bool OnMousePressed(const ui::MouseEvent& event) override;
   void OnThemeChanged() override;
   std::unique_ptr<views::InkDrop> CreateInkDrop() override;
-  SkColor GetInkDropBaseColor() const override = 0;
+  SkColor GetInkDropBaseColor() const override;
   bool IsTriggerableEvent(const ui::Event& event) override;
   bool ShouldUpdateInkDropOnClickCanceled() const override;
   void NotifyClick(const ui::Event& event) override;
@@ -194,6 +182,24 @@
  private:
   class HighlightPathGenerator;
 
+  // A view that draws the separator.
+  class SeparatorView : public views::View {
+   public:
+    explicit SeparatorView(IconLabelBubbleView* owner);
+
+    // views::View:
+    void OnPaint(gfx::Canvas* canvas) override;
+
+    // Updates the opacity based on the ink drop's state.
+    void UpdateOpacity();
+
+   private:
+    // Weak.
+    IconLabelBubbleView* owner_;
+
+    DISALLOW_COPY_AND_ASSIGN(SeparatorView);
+  };
+
   // Spacing between the image and the label.
   int GetInternalSpacing() const;
 
@@ -229,6 +235,8 @@
   // Sets the border padding around this view.
   void UpdateBorder();
 
+  Delegate* delegate_;
+
   // The contents of the bubble.
   SeparatorView* separator_view_;
 
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
index 2e30b99..a1fce1bd 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
@@ -11,6 +11,7 @@
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "components/strings/grit/components_strings.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/gesture_detection/gesture_configuration.h"
 #include "ui/events/test/event_generator.h"
@@ -47,11 +48,11 @@
     SHRINKING,
   };
 
-  explicit TestIconLabelBubbleView(const gfx::FontList& font_list)
-      : IconLabelBubbleView(font_list), value_(0), is_bubble_showing_(false) {
+  explicit TestIconLabelBubbleView(const gfx::FontList& font_list,
+                                   Delegate* delegate)
+      : IconLabelBubbleView(font_list, delegate) {
     GetImageView()->SetImageSize(gfx::Size(kImageSize, kImageSize));
     SetLabel(base::ASCIIToUTF16("Label"));
-    separator_view()->set_disable_animation_for_test(true);
   }
 
   void SetCurrentAnimationValue(int value) {
@@ -83,9 +84,6 @@
 
  protected:
   // IconLabelBubbleView:
-  SkColor GetTextColor() const override { return kTestColor; }
-  SkColor GetInkDropBaseColor() const override { return kTestColor; }
-
   bool ShouldShowLabel() const override {
     return !IsShrinking() ||
            (width() >
@@ -119,16 +117,28 @@
   }
 
  private:
-  int value_;
-  bool is_bubble_showing_;
+  std::unique_ptr<ui::ScopedAnimationDurationScaleMode> zero_duration_mode_ =
+      std::make_unique<ui::ScopedAnimationDurationScaleMode>(
+          ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
+  int value_ = 0;
+  bool is_bubble_showing_ = false;
   DISALLOW_COPY_AND_ASSIGN(TestIconLabelBubbleView);
 };
 
 }  // namespace
 
-class IconLabelBubbleViewTest : public ChromeViewsTestBase {
+class IconLabelBubbleViewTestBase : public ChromeViewsTestBase,
+                                    public IconLabelBubbleView::Delegate {
+ public:
+  // IconLabelBubbleView::Delegate:
+  SkColor GetIconLabelBubbleSurroundingForegroundColor() const override {
+    return kTestColor;
+  }
+};
+
+class IconLabelBubbleViewTest : public IconLabelBubbleViewTestBase {
  protected:
-  // ChromeViewsTestBase:
+  // IconLabelBubbleViewTestBase:
   void SetUp() override {
     ChromeViewsTestBase::SetUp();
     gfx::FontList font_list;
@@ -136,7 +146,7 @@
     CreateWidget();
     generator_ =
         std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_));
-    view_ = new TestIconLabelBubbleView(font_list);
+    view_ = new TestIconLabelBubbleView(font_list, this);
     view_->SetBoundsRect(gfx::Rect(0, 0, 24, 24));
     widget_->SetContentsView(view_);
 
@@ -417,7 +427,7 @@
 #if defined(OS_CHROMEOS)
 // Verifies IconLabelBubbleView::CalculatePreferredSize() doesn't crash when
 // there is a widget but no compositor.
-using IconLabelBubbleViewCrashTest = ChromeViewsTestBase;
+using IconLabelBubbleViewCrashTest = IconLabelBubbleViewTestBase;
 
 TEST_F(IconLabelBubbleViewCrashTest,
        GetPreferredSizeDoesntCrashWhenNoCompositor) {
@@ -428,7 +438,7 @@
   views::Widget widget;
   widget.Init(std::move(params));
   IconLabelBubbleView* icon_label_bubble_view =
-      new TestIconLabelBubbleView(font_list);
+      new TestIconLabelBubbleView(font_list, this);
   icon_label_bubble_view->SetLabel(base::ASCIIToUTF16("x"));
   widget.GetContentsView()->AddChildView(icon_label_bubble_view);
   aura::Window* widget_native_view = widget.GetNativeView();
diff --git a/chrome/browser/ui/views/location_bar/intent_picker_view.cc b/chrome/browser/ui/views/location_bar/intent_picker_view.cc
index 01f793e..608a886 100644
--- a/chrome/browser/ui/views/location_bar/intent_picker_view.cc
+++ b/chrome/browser/ui/views/location_bar/intent_picker_view.cc
@@ -30,9 +30,15 @@
 class WebContents;
 }
 
-IntentPickerView::IntentPickerView(Browser* browser,
-                                   PageActionIconView::Delegate* delegate)
-    : PageActionIconView(nullptr, 0, delegate), browser_(browser) {}
+IntentPickerView::IntentPickerView(
+    Browser* browser,
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(nullptr,
+                         0,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate),
+      browser_(browser) {}
 
 IntentPickerView::~IntentPickerView() = default;
 
diff --git a/chrome/browser/ui/views/location_bar/intent_picker_view.h b/chrome/browser/ui/views/location_bar/intent_picker_view.h
index bf8ff24cb..edd90cd 100644
--- a/chrome/browser/ui/views/location_bar/intent_picker_view.h
+++ b/chrome/browser/ui/views/location_bar/intent_picker_view.h
@@ -12,7 +12,9 @@
 // The entry point for the intent picker.
 class IntentPickerView : public PageActionIconView {
  public:
-  IntentPickerView(Browser* browser, PageActionIconView::Delegate* delegate);
+  IntentPickerView(Browser* browser,
+                   IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+                   PageActionIconView::Delegate* page_action_icon_delegate);
   ~IntentPickerView() override;
 
   // PageActionIconView:
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index 485678e..c5c1797 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -122,10 +122,6 @@
 #include "ui/views/controls/label.h"
 #include "ui/views/widget/widget.h"
 
-#if defined(USE_AURA) || defined(OS_MACOSX)
-#include "ui/native_theme/native_theme_dark_aura.h"
-#endif
-
 namespace {
 
 int IncrementalMinimumWidth(const views::View* view) {
@@ -174,9 +170,6 @@
 
 LocationBarView::~LocationBarView() {}
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, public:
-
 void LocationBarView::Init() {
   // We need to be in a Widget, otherwise GetNativeTheme() may change and we're
   // not prepared for that.
@@ -190,7 +183,8 @@
   const gfx::FontList& font_list = views::style::GetFont(
       CONTEXT_OMNIBOX_PRIMARY, views::style::STYLE_PRIMARY);
 
-  auto location_icon_view = std::make_unique<LocationIconView>(font_list, this);
+  auto location_icon_view =
+      std::make_unique<LocationIconView>(font_list, this, this);
   location_icon_view->set_drag_controller(this);
   location_icon_view_ = AddChildView(std::move(location_icon_view));
 
@@ -233,7 +227,7 @@
       ContentSettingImageModel::GenerateContentSettingImageModels();
   for (auto& model : models) {
     auto image_view = std::make_unique<ContentSettingImageView>(
-        std::move(model), this, font_list);
+        std::move(model), this, this, font_list);
     image_view->SetIconColor(icon_color);
     image_view->SetVisible(false);
     content_setting_views_.push_back(AddChildView(std::move(image_view)));
@@ -287,6 +281,7 @@
   params.font_list = &font_list;
   params.browser = browser_;
   params.command_updater = command_updater();
+  params.icon_label_bubble_delegate = this;
   params.page_action_icon_delegate = this;
   page_action_icon_container_ =
       AddChildView(std::make_unique<PageActionIconContainerView>(params));
@@ -361,9 +356,6 @@
   omnibox_view_->SelectAll(true);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, public LocationBar implementation:
-
 void LocationBarView::FocusLocation(bool is_user_initiated) {
   omnibox_view_->SetFocus(is_user_initiated);
 }
@@ -376,9 +368,6 @@
   return omnibox_view_;
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, public views::View implementation:
-
 bool LocationBarView::HasFocus() const {
   return omnibox_view_ && omnibox_view_->model()->has_focus();
 }
@@ -672,9 +661,6 @@
       ->ActivateFirstInactiveBubbleForAccessibility();
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, public OmniboxEditController implementation:
-
 void LocationBarView::UpdateWithoutTabRestore() {
   Update(nullptr);
 }
@@ -687,11 +673,9 @@
   return delegate_->GetWebContents();
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, public ContentSettingImageView::Delegate implementation:
-
-SkColor LocationBarView::GetContentSettingInkDropColor() const {
-  return GetLocationIconInkDropColor();
+SkColor LocationBarView::GetIconLabelBubbleSurroundingForegroundColor() const {
+  return GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_TextfieldDefaultColor);
 }
 
 content::WebContents* LocationBarView::GetContentSettingWebContents() {
@@ -703,24 +687,14 @@
   return delegate_->GetContentSettingBubbleModelDelegate();
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, public PageActionIconView::Delegate implementation:
-
-SkColor LocationBarView::GetPageActionInkDropColor() const {
-  return GetLocationIconInkDropColor();
-}
-
 WebContents* LocationBarView::GetWebContentsForPageActionIconView() {
   return GetWebContents();
 }
 
 bool LocationBarView::IsLocationBarUserInputInProgress() const {
-  return omnibox_view() && omnibox_view()->model()->user_input_in_progress();
+  return omnibox_view_ && omnibox_view_->model()->user_input_in_progress();
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, public static methods:
-
 // static
 bool LocationBarView::IsVirtualKeyboardVisible(views::Widget* widget) {
   if (auto* input_method = widget->GetInputMethod()) {
@@ -745,9 +719,6 @@
       0, LocationBarView::GetAvailableTextHeight() - (bubble_padding * 2));
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, private:
-
 int LocationBarView::GetMinimumLeadingWidth() const {
   // If the keyword bubble is showing, the view can collapse completely.
   if (ShouldShowKeywordBubble())
@@ -787,9 +758,7 @@
   if (omnibox_view_->model()->is_caret_visible()) {
     background_color = border_color = GetColor(OmniboxPart::RESULTS_BACKGROUND);
   } else {
-    const SkColor normal = GetOmniboxColor(GetThemeProvider(),
-                                           OmniboxPart::LOCATION_BAR_BACKGROUND,
-                                           OmniboxPartState::NORMAL);
+    const SkColor normal = GetColor(OmniboxPart::LOCATION_BAR_BACKGROUND);
     const SkColor hovered = GetOmniboxColor(
         GetThemeProvider(), OmniboxPart::LOCATION_BAR_BACKGROUND,
         OmniboxPartState::HOVERED);
@@ -895,9 +864,6 @@
   FocusLocation(false);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, private LocationBar implementation:
-
 GURL LocationBarView::GetDestinationURL() const {
   return destination_url();
 }
@@ -958,9 +924,6 @@
   return this;
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, private LocationBarTesting implementation:
-
 bool LocationBarView::TestContentSettingImagePressed(size_t index) {
   if (index >= content_setting_views_.size())
     return false;
@@ -979,9 +942,6 @@
          content_setting_views_[index]->IsBubbleShowing();
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, private views::View implementation:
-
 const char* LocationBarView::GetClassName() const {
   return kViewClassName;
 }
@@ -1019,9 +979,6 @@
                    border_color);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, private views::DragController implementation:
-
 void LocationBarView::WriteDragDataForView(views::View* sender,
                                            const gfx::Point& press_pt,
                                            OSExchangeData* data) {
@@ -1053,8 +1010,6 @@
   return true;
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, private views::AnimationDelegateViews implementation:
 void LocationBarView::AnimationProgressed(const gfx::Animation* animation) {
   DCHECK_EQ(animation, &hover_animation_);
   RefreshBackground();
@@ -1070,9 +1025,6 @@
   AnimationProgressed(animation);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, private OmniboxEditController implementation:
-
 void LocationBarView::OnChanged() {
   location_icon_view_->Update(/*suppress_animations=*/false);
   clear_all_button_->SetVisible(IsLocationBarUserInputInProgress() &&
@@ -1136,16 +1088,10 @@
   }
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, private DropdownBarHostDelegate implementation:
-
 void LocationBarView::FocusAndSelectAll() {
   FocusLocation(true);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, private ui::MaterialDesignControllerObserver implementation:
-
 void LocationBarView::OnTouchUiChanged() {
   const gfx::FontList& font_list = views::style::GetFont(
       CONTEXT_OMNIBOX_PRIMARY, views::style::STYLE_PRIMARY);
@@ -1160,11 +1106,8 @@
   PreferredSizeChanged();
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// LocationBarView, LocationBarIconView::Delegate implementation:
-
 bool LocationBarView::IsEditingOrEmpty() const {
-  return omnibox_view() && omnibox_view()->IsEditingOrEmpty();
+  return omnibox_view_ && omnibox_view_->IsEditingOrEmpty();
 }
 
 void LocationBarView::OnLocationIconPressed(const ui::MouseEvent& event) {
@@ -1214,24 +1157,16 @@
           *helper->GetVisibleSecurityState(),
           base::BindOnce(&LocationBarView::OnPageInfoBubbleClosed,
                          weak_factory_.GetWeakPtr()));
-  bubble->SetHighlightedButton(location_icon_view());
+  bubble->SetHighlightedButton(location_icon_view_);
   bubble->GetWidget()->Show();
   return true;
 }
 
 gfx::ImageSkia LocationBarView::GetLocationIcon(
     LocationIconView::Delegate::IconFetchedCallback on_icon_fetched) const {
-  security_state::SecurityLevel level = security_state::SecurityLevel::NONE;
-  if (!IsEditingOrEmpty())
-    level = GetLocationBarModel()->GetSecurityLevel();
-  return omnibox_view()
-             ? omnibox_view()->GetIcon(
-                   GetLayoutConstant(LOCATION_BAR_ICON_SIZE),
-                   GetSecurityChipColor(level), std::move(on_icon_fetched))
-             : gfx::ImageSkia();
-}
-
-SkColor LocationBarView::GetLocationIconInkDropColor() const {
-  return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_TextfieldDefaultColor);
+  if (!omnibox_view_)
+    return gfx::ImageSkia();
+  return omnibox_view_->GetIcon(GetLayoutConstant(LOCATION_BAR_ICON_SIZE),
+                                location_icon_view_->GetForegroundColor(),
+                                std::move(on_icon_fetched));
 }
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.h b/chrome/browser/ui/views/location_bar/location_bar_view.h
index 00743002..c8c1c29 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.h
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.h
@@ -70,6 +70,7 @@
                         public ChromeOmniboxEditController,
                         public DropdownBarHostDelegate,
                         public views::ButtonListener,
+                        public IconLabelBubbleView::Delegate,
                         public LocationIconView::Delegate,
                         public ContentSettingImageView::Delegate,
                         public PageActionIconView::Delegate,
@@ -186,8 +187,10 @@
   LocationBarModel* GetLocationBarModel() override;
   content::WebContents* GetWebContents() override;
 
+  // IconLabelBubbleView::Delegate:
+  SkColor GetIconLabelBubbleSurroundingForegroundColor() const override;
+
   // ContentSettingImageView::Delegate:
-  SkColor GetContentSettingInkDropColor() const override;
   content::WebContents* GetContentSettingWebContents() override;
   ContentSettingBubbleModelDelegate* GetContentSettingBubbleModelDelegate()
       override;
@@ -213,7 +216,7 @@
   Browser* browser() { return browser_; }
   Profile* profile() { return profile_; }
 
-  // LocationIconView::Delegate
+  // LocationIconView::Delegate:
   bool IsEditingOrEmpty() const override;
   void OnLocationIconPressed(const ui::MouseEvent& event) override;
   void OnLocationIconDragged(const ui::MouseEvent& event) override;
@@ -222,7 +225,6 @@
       security_state::SecurityLevel security_level) const override;
   gfx::ImageSkia GetLocationIcon(LocationIconView::Delegate::IconFetchedCallback
                                      on_icon_fetched) const override;
-  SkColor GetLocationIconInkDropColor() const override;
 
  private:
   FRIEND_TEST_ALL_PREFIXES(SecurityIndicatorTest, CheckIndicatorText);
@@ -306,7 +308,6 @@
                            const gfx::Point& p) override;
 
   // PageActionIconView::Delegate:
-  SkColor GetPageActionInkDropColor() const override;
   content::WebContents* GetWebContentsForPageActionIconView() override;
   bool IsLocationBarUserInputInProgress() const override;
 
diff --git a/chrome/browser/ui/views/location_bar/location_icon_view.cc b/chrome/browser/ui/views/location_bar/location_icon_view.cc
index 222fab0..dc1a34d5 100644
--- a/chrome/browser/ui/views/location_bar/location_icon_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_icon_view.cc
@@ -28,9 +28,13 @@
 using content::WebContents;
 using security_state::SecurityLevel;
 
-LocationIconView::LocationIconView(const gfx::FontList& font_list,
-                                   Delegate* delegate)
-    : IconLabelBubbleView(font_list), delegate_(delegate) {
+LocationIconView::LocationIconView(
+    const gfx::FontList& font_list,
+    IconLabelBubbleView::Delegate* parent_delegate,
+    Delegate* delegate)
+    : IconLabelBubbleView(font_list, parent_delegate), delegate_(delegate) {
+  DCHECK(delegate_);
+
   SetID(VIEW_ID_LOCATION_ICON);
   Update(true);
   SetUpForAnimation();
@@ -45,19 +49,12 @@
   return GetMinimumSizeForPreferredSize(GetPreferredSize());
 }
 
-bool LocationIconView::OnMousePressed(const ui::MouseEvent& event) {
-  delegate_->OnLocationIconPressed(event);
-
-  IconLabelBubbleView::OnMousePressed(event);
-  return true;
-}
-
 bool LocationIconView::OnMouseDragged(const ui::MouseEvent& event) {
   delegate_->OnLocationIconDragged(event);
   return IconLabelBubbleView::OnMouseDragged(event);
 }
 
-SkColor LocationIconView::GetTextColor() const {
+SkColor LocationIconView::GetForegroundColor() const {
   SecurityLevel security_level = SecurityLevel::NONE;
   if (!delegate_->IsEditingOrEmpty())
     security_level = delegate_->GetLocationBarModel()->GetSecurityLevel();
@@ -73,8 +70,16 @@
   return delegate_->ShowPageInfoDialog();
 }
 
-SkColor LocationIconView::GetInkDropBaseColor() const {
-  return delegate_->GetLocationIconInkDropColor();
+bool LocationIconView::IsBubbleShowing() const {
+  return PageInfoBubbleView::GetShownBubbleType() !=
+         PageInfoBubbleView::BUBBLE_NONE;
+}
+
+bool LocationIconView::OnMousePressed(const ui::MouseEvent& event) {
+  delegate_->OnLocationIconPressed(event);
+
+  IconLabelBubbleView::OnMousePressed(event);
+  return true;
 }
 
 void LocationIconView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
@@ -96,11 +101,6 @@
   node_data->role = ax::mojom::Role::kPopUpButton;
 }
 
-bool LocationIconView::IsBubbleShowing() const {
-  return PageInfoBubbleView::GetShownBubbleType() !=
-         PageInfoBubbleView::BUBBLE_NONE;
-}
-
 int LocationIconView::GetMinimumLabelTextWidth() const {
   int width = 0;
 
diff --git a/chrome/browser/ui/views/location_bar/location_icon_view.h b/chrome/browser/ui/views/location_bar/location_icon_view.h
index 30fa313..8c5c4d3 100644
--- a/chrome/browser/ui/views/location_bar/location_icon_view.h
+++ b/chrome/browser/ui/views/location_bar/location_icon_view.h
@@ -56,27 +56,22 @@
     // Gets an icon for the location bar icon chip.
     virtual gfx::ImageSkia GetLocationIcon(
         IconFetchedCallback on_icon_fetched) const = 0;
-
-    // Gets the color to use for icon ink highlights.
-    virtual SkColor GetLocationIconInkDropColor() const = 0;
-
-   protected:
-    virtual ~Delegate() {}
   };
 
-  LocationIconView(const gfx::FontList& font_list, Delegate* delagate);
+  LocationIconView(const gfx::FontList& font_list,
+                   IconLabelBubbleView::Delegate* parent_delegate,
+                   Delegate* delegate);
   ~LocationIconView() override;
 
   // IconLabelBubbleView:
   gfx::Size GetMinimumSize() const override;
-  bool OnMousePressed(const ui::MouseEvent& event) override;
   bool OnMouseDragged(const ui::MouseEvent& event) override;
-  SkColor GetTextColor() const override;
+  SkColor GetForegroundColor() const override;
   bool ShouldShowSeparator() const override;
   bool ShowBubble(const ui::Event& event) override;
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   bool IsBubbleShowing() const override;
-  SkColor GetInkDropBaseColor() const override;
+  bool OnMousePressed(const ui::MouseEvent& event) override;
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
   // Returns what the minimum width for the label text.
   int GetMinimumLabelTextWidth() const;
diff --git a/chrome/browser/ui/views/location_bar/location_icon_view_browsertest.cc b/chrome/browser/ui/views/location_bar/location_icon_view_browsertest.cc
index c807b6f..9422599 100644
--- a/chrome/browser/ui/views/location_bar/location_icon_view_browsertest.cc
+++ b/chrome/browser/ui/views/location_bar/location_icon_view_browsertest.cc
@@ -22,7 +22,8 @@
     BrowserView* browser_view =
         BrowserView::GetBrowserViewForBrowser(browser());
     location_bar_ = browser_view->GetLocationBarView();
-    icon_view_ = std::make_unique<LocationIconView>(font_list, location_bar_);
+    icon_view_ = std::make_unique<LocationIconView>(font_list, location_bar_,
+                                                    location_bar_);
   }
 
   LocationBarView* location_bar() const { return location_bar_; }
diff --git a/chrome/browser/ui/views/location_bar/location_icon_view_unittest.cc b/chrome/browser/ui/views/location_bar/location_icon_view_unittest.cc
index 83c91c66..d528bae 100644
--- a/chrome/browser/ui/views/location_bar/location_icon_view_unittest.cc
+++ b/chrome/browser/ui/views/location_bar/location_icon_view_unittest.cc
@@ -11,38 +11,37 @@
 
 namespace {
 
-class TestLocationIconDelegate : public LocationIconView::Delegate {
+class TestLocationIconDelegate : public IconLabelBubbleView::Delegate,
+                                 public LocationIconView::Delegate {
  public:
   explicit TestLocationIconDelegate(LocationBarModel* location_bar_model)
       : location_bar_model_(location_bar_model) {}
+  virtual ~TestLocationIconDelegate() = default;
 
-  content::WebContents* GetWebContents() override { return nullptr; }
-
-  bool IsEditingOrEmpty() const override { return is_editing_or_empty_; }
-  void set_is_editing_or_empty(bool is_editing_or_empty) {
-    is_editing_or_empty_ = is_editing_or_empty;
+  // IconLabelBubbleView::Delegate:
+  SkColor GetIconLabelBubbleSurroundingForegroundColor() const override {
+    return SK_ColorBLACK;
   }
 
+  // LocationIconView::Delegate:
+  content::WebContents* GetWebContents() override { return nullptr; }
+  bool IsEditingOrEmpty() const override { return is_editing_or_empty_; }
   SkColor GetSecurityChipColor(
       security_state::SecurityLevel security_level) const override {
     return SK_ColorWHITE;
   }
-
   bool ShowPageInfoDialog() override { return false; }
-
-  // Gets the LocationBarModel.
   const LocationBarModel* GetLocationBarModel() const override {
     return location_bar_model_;
   }
-
-  // Gets an icon for the location bar icon chip.
   gfx::ImageSkia GetLocationIcon(
       IconFetchedCallback on_icon_fetched) const override {
     return gfx::ImageSkia();
   }
 
-  // Gets the color to use for icon ink highlights.
-  SkColor GetLocationIconInkDropColor() const override { return SK_ColorBLACK; }
+  void set_is_editing_or_empty(bool is_editing_or_empty) {
+    is_editing_or_empty_ = is_editing_or_empty;
+  }
 
  private:
   LocationBarModel* location_bar_model_;
@@ -64,7 +63,7 @@
     delegate_ =
         std::make_unique<TestLocationIconDelegate>(location_bar_model());
 
-    view_ = new LocationIconView(font_list, delegate());
+    view_ = new LocationIconView(font_list, delegate(), delegate());
     view_->SetBoundsRect(gfx::Rect(0, 0, 24, 24));
     widget_->SetContentsView(view_);
 
diff --git a/chrome/browser/ui/views/location_bar/selected_keyword_view.cc b/chrome/browser/ui/views/location_bar/selected_keyword_view.cc
index 97cb93d..6d501acf 100644
--- a/chrome/browser/ui/views/location_bar/selected_keyword_view.cc
+++ b/chrome/browser/ui/views/location_bar/selected_keyword_view.cc
@@ -22,7 +22,7 @@
 SelectedKeywordView::SelectedKeywordView(LocationBarView* location_bar,
                                          const gfx::FontList& font_list,
                                          Profile* profile)
-    : IconLabelBubbleView(font_list),
+    : IconLabelBubbleView(font_list, location_bar),
       location_bar_(location_bar),
       profile_(profile) {
   full_label_.SetFontList(font_list);
@@ -37,21 +37,17 @@
 void SelectedKeywordView::ResetImage() {
   SetImage(gfx::CreateVectorIcon(vector_icons::kSearchIcon,
                                  GetLayoutConstant(LOCATION_BAR_ICON_SIZE),
-                                 GetTextColor()));
+                                 GetForegroundColor()));
 }
 
 void SelectedKeywordView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
   SetLabelForCurrentWidth();
 }
 
-SkColor SelectedKeywordView::GetTextColor() const {
+SkColor SelectedKeywordView::GetForegroundColor() const {
   return location_bar_->GetColor(OmniboxPart::LOCATION_BAR_SELECTED_KEYWORD);
 }
 
-SkColor SelectedKeywordView::GetInkDropBaseColor() const {
-  return location_bar_->GetLocationIconInkDropColor();
-}
-
 gfx::Size SelectedKeywordView::CalculatePreferredSize() const {
   // Height will be ignored by the LocationBarView.
   return GetSizeForLabelWidth(full_label_.GetPreferredSize().width());
diff --git a/chrome/browser/ui/views/location_bar/selected_keyword_view.h b/chrome/browser/ui/views/location_bar/selected_keyword_view.h
index f4a43343..cf4c773 100644
--- a/chrome/browser/ui/views/location_bar/selected_keyword_view.h
+++ b/chrome/browser/ui/views/location_bar/selected_keyword_view.h
@@ -36,8 +36,7 @@
   // IconLabelBubbleView:
   gfx::Size CalculatePreferredSize() const override;
   gfx::Size GetMinimumSize() const override;
-  SkColor GetTextColor() const override;
-  SkColor GetInkDropBaseColor() const override;
+  SkColor GetForegroundColor() const override;
 
   // The current keyword, or an empty string if no keyword is displayed.
   void SetKeyword(const base::string16& keyword);
diff --git a/chrome/browser/ui/views/location_bar/star_view.cc b/chrome/browser/ui/views/location_bar/star_view.cc
index 1615a97..8559616 100644
--- a/chrome/browser/ui/views/location_bar/star_view.cc
+++ b/chrome/browser/ui/views/location_bar/star_view.cc
@@ -53,8 +53,12 @@
 
 StarView::StarView(CommandUpdater* command_updater,
                    Browser* browser,
-                   PageActionIconView::Delegate* delegate)
-    : PageActionIconView(command_updater, IDC_BOOKMARK_THIS_TAB, delegate),
+                   IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+                   PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(command_updater,
+                         IDC_BOOKMARK_THIS_TAB,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate),
       browser_(browser) {
   DCHECK(browser_);
   extension_observer_.Add(
diff --git a/chrome/browser/ui/views/location_bar/star_view.h b/chrome/browser/ui/views/location_bar/star_view.h
index d5dbf9ae..bb242fe 100644
--- a/chrome/browser/ui/views/location_bar/star_view.h
+++ b/chrome/browser/ui/views/location_bar/star_view.h
@@ -24,7 +24,8 @@
  public:
   StarView(CommandUpdater* command_updater,
            Browser* browser,
-           PageActionIconView::Delegate* delegate);
+           IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+           PageActionIconView::Delegate* page_action_icon_delegate);
   ~StarView() override;
 
   // Shows the BookmarkPromoBubbleView when the BookmarkTracker calls for it.
diff --git a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
index 402a6d6..19ca3a41 100644
--- a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
+++ b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
@@ -121,14 +121,12 @@
 }
 
 void MediaRouterDialogControllerViews::InitializeMediaRouterUI() {
-  ui_ = std::make_unique<MediaRouterViewsUI>();
-  PresentationServiceDelegateImpl* delegate =
-      PresentationServiceDelegateImpl::FromWebContents(initiator());
-  if (!start_presentation_context_) {
-    ui_->InitWithDefaultMediaSource(initiator(), delegate);
-  } else {
+  ui_ = std::make_unique<MediaRouterViewsUI>(initiator());
+  if (start_presentation_context_) {
     ui_->InitWithStartPresentationContext(
-        initiator(), delegate, std::move(start_presentation_context_));
+        std::move(start_presentation_context_));
+  } else {
+    ui_->InitWithDefaultMediaSource();
   }
 }
 
diff --git a/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc b/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
index 08e2b98..88bc450b 100644
--- a/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
+++ b/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
@@ -16,7 +16,6 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
 #include "chrome/browser/ui/media_router/media_router_ui_service.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/toolbar/media_router_action_controller.h"
diff --git a/chrome/browser/ui/views/media_router/media_router_views_ui.cc b/chrome/browser/ui/views/media_router/media_router_views_ui.cc
index 1a8bb26..e68e11b 100644
--- a/chrome/browser/ui/views/media_router/media_router_views_ui.cc
+++ b/chrome/browser/ui/views/media_router/media_router_views_ui.cc
@@ -26,7 +26,6 @@
 #include "chrome/browser/media/router/media_router_factory.h"
 #include "chrome/browser/media/router/media_router_metrics.h"
 #include "chrome/browser/media/router/media_routes_observer.h"
-#include "chrome/browser/media/router/presentation/presentation_service_delegate_impl.h"
 #include "chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h"
 #include "chrome/browser/media/webrtc/desktop_media_picker_controller.h"
 #include "chrome/browser/profiles/profile.h"
@@ -198,7 +197,13 @@
   base::OneShotTimer capture_poll_timer_;
 };
 
-MediaRouterViewsUI::MediaRouterViewsUI() = default;
+MediaRouterViewsUI::MediaRouterViewsUI(content::WebContents* initiator)
+    : presentation_manager_(WebContentsPresentationManager::Get(initiator)),
+      initiator_(initiator) {
+  CHECK(initiator_);
+  if (presentation_manager_)
+    presentation_manager_->AddObserver(this);
+}
 
 MediaRouterViewsUI::~MediaRouterViewsUI() {
   for (CastDialogController::Observer& observer : observers_)
@@ -206,9 +211,9 @@
 
   if (query_result_manager_.get())
     query_result_manager_->RemoveObserver(this);
-  if (presentation_service_delegate_.get())
-    presentation_service_delegate_->RemoveDefaultPresentationRequestObserver(
-        this);
+  if (presentation_manager_)
+    presentation_manager_->RemoveObserver(this);
+
   // If |start_presentation_context_| still exists, then it means presentation
   // route request was never attempted.
   if (start_presentation_context_) {
@@ -264,21 +269,14 @@
   RemoveIssue(issue_id);
 }
 
-void MediaRouterViewsUI::InitWithDefaultMediaSource(
-    content::WebContents* initiator,
-    PresentationServiceDelegateImpl* delegate) {
-  DCHECK(initiator);
-  DCHECK(!presentation_service_delegate_);
+void MediaRouterViewsUI::InitWithDefaultMediaSource() {
   DCHECK(!query_result_manager_);
+  InitCommon();
 
-  InitCommon(initiator);
-  if (delegate) {
-    presentation_service_delegate_ = delegate->GetWeakPtr();
-    presentation_service_delegate_->AddDefaultPresentationRequestObserver(this);
-  }
-
-  if (delegate && delegate->HasDefaultPresentationRequest()) {
-    OnDefaultPresentationChanged(delegate->GetDefaultPresentationRequest());
+  if (presentation_manager_ &&
+      presentation_manager_->HasDefaultPresentationRequest()) {
+    OnDefaultPresentationChanged(
+        &presentation_manager_->GetDefaultPresentationRequest());
   } else {
     // Register for MediaRoute updates without a media source.
     routes_observer_ = std::make_unique<UIMediaRoutesObserver>(
@@ -289,21 +287,16 @@
 }
 
 void MediaRouterViewsUI::InitWithStartPresentationContext(
-    content::WebContents* initiator,
-    PresentationServiceDelegateImpl* delegate,
     std::unique_ptr<StartPresentationContext> context) {
-  DCHECK(initiator);
-  DCHECK(delegate);
   DCHECK(context);
   DCHECK(!start_presentation_context_);
   DCHECK(!query_result_manager_);
 
   start_presentation_context_ = std::move(context);
-  presentation_service_delegate_ = delegate->GetWeakPtr();
 
-  InitCommon(initiator);
+  InitCommon();
   OnDefaultPresentationChanged(
-      start_presentation_context_->presentation_request());
+      &start_presentation_context_->presentation_request());
 }
 
 bool MediaRouterViewsUI::CreateRoute(const MediaSink::Id& sink_id,
@@ -379,7 +372,7 @@
   // the best place to do this, but the Media Router browser service and
   // extension process are shared between normal and incognito, so incognito
   // behaviors around sink availability have to be handled at the UI layer.
-  if (initiator()->GetBrowserContext()->IsOffTheRecord()) {
+  if (initiator_->GetBrowserContext()->IsOffTheRecord()) {
     base::EraseIf(enabled_sinks, [](const MediaSinkWithCastModes& sink) {
       return sink.sink.IsMaybeCloudSink();
     });
@@ -390,7 +383,6 @@
 
 base::string16 MediaRouterViewsUI::GetPresentationRequestSourceName() const {
   GURL gurl = GetFrameURL();
-  CHECK(initiator_);
   // Presentation URLs are only possible on https: and other secure contexts,
   // so we can omit http/https schemes here.
   return gurl.SchemeIs(extensions::kExtensionScheme)
@@ -482,10 +474,7 @@
   // TODO(crbug.com/868186): Close the dialog.
 }
 
-void MediaRouterViewsUI::InitCommon(content::WebContents* initiator) {
-  DCHECK(initiator);
-  initiator_ = initiator;
-
+void MediaRouterViewsUI::InitCommon() {
   GetMediaRouter()->OnUserGesture();
 
   // Create |collator_| before |query_result_manager_| so that |collator_| is
@@ -514,7 +503,7 @@
   query_result_manager_->SetSourcesForCastMode(
       MediaCastMode::LOCAL_FILE, {MediaSource::ForTab(0)}, origin);
 
-  SessionID::id_type tab_id = SessionTabHelper::IdForTab(initiator).id();
+  SessionID::id_type tab_id = SessionTabHelper::IdForTab(initiator_).id();
   if (tab_id != -1) {
     MediaSource mirroring_source(MediaSource::ForTab(tab_id));
     query_result_manager_->SetSourcesForCastMode(MediaCastMode::TAB_MIRROR,
@@ -533,12 +522,17 @@
 }
 
 void MediaRouterViewsUI::OnDefaultPresentationChanged(
-    const content::PresentationRequest& presentation_request) {
+    const content::PresentationRequest* presentation_request) {
+  if (!presentation_request) {
+    OnDefaultPresentationRemoved();
+    return;
+  }
+
   std::vector<MediaSource> sources;
-  for (const auto& url : presentation_request.presentation_urls) {
+  for (const auto& url : presentation_request->presentation_urls) {
     sources.push_back(MediaSource::ForPresentationUrl(url));
   }
-  presentation_request_ = presentation_request;
+  presentation_request_ = *presentation_request;
   query_result_manager_->SetSourcesForCastMode(
       MediaCastMode::PRESENTATION, sources,
       presentation_request_->frame_origin);
@@ -589,8 +583,6 @@
     const MediaSink::Id& sink_id,
     MediaCastMode cast_mode) {
   DCHECK(query_result_manager_);
-  DCHECK(initiator_);
-
   RouteParameters params;
 
   // Note that there is a rarely-encountered bug, where the MediaCastMode to
@@ -636,7 +628,7 @@
   //     The StartPresentationContext will need to be answered with the route
   //     response.
   // (3) Browser-initiated presentation route request. If successful,
-  //     PresentationServiceDelegateImpl will have to be notified. Note that we
+  //     WebContentsPresentationManager will have to be notified. Note that we
   //     treat subsequent route requests from a Presentation API-initiated
   //     dialogs as browser-initiated.
   // TODO(https://crbug.com/868186): Close the Views dialog in case (2).
@@ -654,15 +646,14 @@
       params.route_result_callbacks.push_back(base::BindOnce(
           &MediaRouterViewsUI::HandleCreateSessionRequestRouteResponse,
           weak_factory_.GetWeakPtr()));
-    } else if (presentation_service_delegate_) {
+    } else if (presentation_manager_) {
       params.presentation_callback = base::BindOnce(
-          &PresentationServiceDelegateImpl::OnRouteResponse,
-          presentation_service_delegate_, *presentation_request_);
+          &WebContentsPresentationManager::OnPresentationResponse,
+          presentation_manager_, *presentation_request_);
     }
   }
 
   params.timeout = GetRouteRequestTimeout(cast_mode);
-  CHECK(initiator_);
   params.incognito = initiator_->GetBrowserContext()->IsOffTheRecord();
 
   return base::make_optional(std::move(params));
@@ -948,13 +939,11 @@
 }
 
 MediaRouter* MediaRouterViewsUI::GetMediaRouter() const {
-  CHECK(initiator_);
   return MediaRouterFactory::GetApiForBrowserContext(
       initiator_->GetBrowserContext());
 }
 
 Browser* MediaRouterViewsUI::GetBrowser() {
-  CHECK(initiator_);
   return chrome::FindBrowserWithWebContents(initiator_);
 }
 
diff --git a/chrome/browser/ui/views/media_router/media_router_views_ui.h b/chrome/browser/ui/views/media_router/media_router_views_ui.h
index 80915f7..6060b0a5 100644
--- a/chrome/browser/ui/views/media_router/media_router_views_ui.h
+++ b/chrome/browser/ui/views/media_router/media_router_views_ui.h
@@ -19,7 +19,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/media/router/issues_observer.h"
 #include "chrome/browser/media/router/media_router_dialog_controller.h"
-#include "chrome/browser/media/router/presentation/presentation_service_delegate_impl.h"
+#include "chrome/browser/media/router/presentation/web_contents_presentation_manager.h"
 #include "chrome/browser/ui/media_router/cast_dialog_controller.h"
 #include "chrome/browser/ui/media_router/cast_dialog_model.h"
 #include "chrome/browser/ui/media_router/media_cast_mode.h"
@@ -53,11 +53,10 @@
 class MediaRouterViewsUI
     : public CastDialogController,
       public QueryResultManager::Observer,
-      public PresentationServiceDelegateImpl::
-          DefaultPresentationRequestObserver,
+      public WebContentsPresentationManager::Observer,
       public MediaRouterFileDialog::MediaRouterFileDialogDelegate {
  public:
-  MediaRouterViewsUI();
+  explicit MediaRouterViewsUI(content::WebContents* initiator);
   ~MediaRouterViewsUI() override;
 
   // CastDialogController:
@@ -71,36 +70,22 @@
   void ClearIssue(const Issue::Id& issue_id) override;
 
   // Initializes internal state (e.g. starts listening for MediaSinks) for
-  // targeting the default MediaSource (if any) of the initiator tab that owns
-  // |delegate|, as well as mirroring sources of that tab.
-  // The contents of the UI will change as the default MediaSource changes.
-  // If there is a default MediaSource, then PRESENTATION MediaCastMode will be
-  // added to |cast_modes_|.
-  // Init* methods can only be called once.
-  // |initiator|: Reference to the WebContents that initiated the dialog.
-  //              Must not be null.
-  // |delegate|: PresentationServiceDelegateImpl of the initiator tab.
-  //             Must not be null.
-  // TODO(imcheng): Replace use of impl with an intermediate abstract
-  // interface.
-  void InitWithDefaultMediaSource(content::WebContents* initiator,
-                                  PresentationServiceDelegateImpl* delegate);
+  // targeting the default MediaSource (if any) of |initiator_|, as well as
+  // mirroring sources of that tab. The contents of the UI will change as the
+  // default MediaSource changes. If there is a default MediaSource, then
+  // PRESENTATION MediaCastMode will be added to |cast_modes_|. Init* methods
+  // can only be called once.
+  void InitWithDefaultMediaSource();
 
   // Initializes internal state targeting the presentation specified in
-  // |context|. Also sets up mirroring sources based on |initiator|.
+  // |context|. Also sets up mirroring sources based on |initiator_|.
   // This is different from InitWithDefaultMediaSource() in that it does not
   // listen for default media source changes, as the UI is fixed to the source
-  // in |request|.
+  // in |context|.
   // Init* methods can only be called once.
-  // |initiator|: Reference to the WebContents that initiated the dialog.
-  //              Must not be null.
-  // |delegate|: PresentationServiceDelegateImpl of the initiator tab.
-  //             Must not be null.
   // |context|: Context object for the PresentationRequest. This instance will
   //            take ownership of it. Must not be null.
   void InitWithStartPresentationContext(
-      content::WebContents* initiator,
-      PresentationServiceDelegateImpl* delegate,
       std::unique_ptr<StartPresentationContext> context);
 
   // Requests a route be created from the source mapped to
@@ -225,13 +210,14 @@
   virtual void HandleCreateSessionRequestRouteResponse(
       const RouteRequestResult&);
 
-  // Initializes the dialog with mirroring sources derived from |initiator|.
-  virtual void InitCommon(content::WebContents* initiator);
+  // Initializes the dialog with mirroring sources derived from |initiator_|.
+  virtual void InitCommon();
 
-  // PresentationServiceDelegateImpl::DefaultPresentationObserver
+  // WebContentsPresentationManager::Observer
   void OnDefaultPresentationChanged(
-      const content::PresentationRequest& presentation_request) override;
-  void OnDefaultPresentationRemoved() override;
+      const content::PresentationRequest* presentation_request) override;
+
+  void OnDefaultPresentationRemoved();
 
   // Called to update the dialog with the current list of of enabled sinks.
   void UpdateSinks();
@@ -392,17 +378,12 @@
   // mode, if supported. Otherwise set to nullopt.
   base::Optional<content::PresentationRequest> presentation_request_;
 
-  // It's possible for PresentationServiceDelegateImpl to be destroyed before
-  // this class.
-  // (e.g. if a tab with the UI open is closed, then the tab WebContents will
-  // be destroyed first momentarily before the UI WebContents).
-  // Holding a WeakPtr to PresentationServiceDelegateImpl is the cleanest way to
-  // handle this.
-  // TODO(imcheng): hold a weak ptr to an abstract type instead.
-  base::WeakPtr<PresentationServiceDelegateImpl> presentation_service_delegate_;
+  // |presentation_manager_| notifies |this| whenever there is an update to the
+  // default PresentationRequest or MediaRoutes associated with |initiator_|.
+  base::WeakPtr<WebContentsPresentationManager> presentation_manager_;
 
   // WebContents for the tab for which the Cast dialog is shown.
-  content::WebContents* initiator_ = nullptr;
+  content::WebContents* const initiator_;
 
   // The dialog that handles opening the file dialog and validating and
   // returning the results.
diff --git a/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc b/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
index 10a65852..d1e70ec 100644
--- a/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
+++ b/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/media/router/media_router_factory.h"
 #include "chrome/browser/media/router/media_sinks_observer.h"
 #include "chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h"
 #include "chrome/browser/media/router/test/mock_media_router.h"
@@ -107,19 +108,6 @@
   blink::mojom::PresentationError expected_error_;
 };
 
-// Injects a MediaRouter instance into MediaRouterViewsUI.
-class TestMediaRouterViewsUI : public MediaRouterViewsUI {
- public:
-  explicit TestMediaRouterViewsUI(MediaRouter* router) : router_(router) {}
-  ~TestMediaRouterViewsUI() override = default;
-
-  MediaRouter* GetMediaRouter() const override { return router_; }
-
- private:
-  MediaRouter* router_;
-  DISALLOW_COPY_AND_ASSIGN(TestMediaRouterViewsUI);
-};
-
 class TestWebContentsDisplayObserver : public WebContentsDisplayObserver {
  public:
   explicit TestWebContentsDisplayObserver(const display::Display& display)
@@ -143,16 +131,20 @@
   void SetUp() override {
     ChromeRenderViewHostTestHarness::SetUp();
 
+    SetMediaRouterFactory();
+    mock_router_ = static_cast<MockMediaRouter*>(
+        MediaRouterFactory::GetApiForBrowserContext(GetBrowserContext()));
+
     // Store sink observers so that they can be notified in tests.
-    ON_CALL(mock_router_, RegisterMediaSinksObserver(_))
-        .WillByDefault(Invoke([this](MediaSinksObserver* observer) {
+    ON_CALL(*mock_router_, RegisterMediaSinksObserver(_))
+        .WillByDefault([this](MediaSinksObserver* observer) {
           media_sinks_observers_.push_back(observer);
           return true;
-        }));
+        });
 
     SessionTabHelper::CreateForWebContents(web_contents());
-    ui_ = std::make_unique<TestMediaRouterViewsUI>(&mock_router_);
-    ui_->InitWithDefaultMediaSource(web_contents(), nullptr);
+    ui_ = std::make_unique<MediaRouterViewsUI>(web_contents());
+    ui_->InitWithDefaultMediaSource();
   }
 
   void TearDown() override {
@@ -160,14 +152,19 @@
     ChromeRenderViewHostTestHarness::TearDown();
   }
 
+  virtual void SetMediaRouterFactory() {
+    MediaRouterFactory::GetInstance()->SetTestingFactory(
+        GetBrowserContext(), base::BindRepeating(&MockMediaRouter::Create));
+  }
+
   void CreateMediaRouterUIForURL(const GURL& url) {
     web_contents()->GetController().LoadURL(url, content::Referrer(),
                                             ui::PAGE_TRANSITION_LINK, "");
     content::RenderFrameHostTester::CommitPendingLoad(
         &web_contents()->GetController());
     SessionTabHelper::CreateForWebContents(web_contents());
-    ui_ = std::make_unique<TestMediaRouterViewsUI>(&mock_router_);
-    ui_->InitWithDefaultMediaSource(web_contents(), nullptr);
+    ui_ = std::make_unique<MediaRouterViewsUI>(web_contents());
+    ui_->InitWithDefaultMediaSource();
   }
 
   // These methods are used so that we don't have to friend each test case that
@@ -186,7 +183,7 @@
     MediaSource media_source =
         MediaSource::ForTab(SessionTabHelper::IdForTab(web_contents()).id());
     EXPECT_CALL(
-        mock_router_,
+        *mock_router_,
         CreateRouteInternal(media_source.id(), kSinkId, _, web_contents(), _,
                             base::TimeDelta::FromSeconds(60), is_incognito));
     MediaSink sink(kSinkId, kSinkName, SinkIconType::GENERIC);
@@ -202,7 +199,7 @@
     MediaSink sink(kSinkId, kSinkName, SinkIconType::CAST);
     ui_->OnResultsUpdated({{sink, {cast_mode}}});
     MediaRouteResponseCallback callback;
-    EXPECT_CALL(mock_router_,
+    EXPECT_CALL(*mock_router_,
                 CreateRouteInternal(
                     _, _, _, _, _,
                     base::TimeDelta::FromSeconds(timeout_seconds), false))
@@ -210,7 +207,7 @@
     for (MediaSinksObserver* sinks_observer : media_sinks_observers_)
       sinks_observer->OnSinksUpdated({sink}, std::vector<url::Origin>());
     ui_->StartCasting(kSinkId, cast_mode);
-    Mock::VerifyAndClearExpectations(&mock_router_);
+    Mock::VerifyAndClearExpectations(mock_router_);
 
     EXPECT_CALL(observer, OnModelUpdated(_))
         .WillOnce(WithArg<0>([&](const CastDialogModel& model) {
@@ -238,13 +235,13 @@
                    base::Unretained(request_callbacks.get())));
     StartPresentationContext* context_ptr = context.get();
     ui_->set_start_presentation_context_for_test(std::move(context));
-    ui_->OnDefaultPresentationChanged(context_ptr->presentation_request());
+    ui_->OnDefaultPresentationChanged(&context_ptr->presentation_request());
     return request_callbacks;
   }
 
  protected:
   std::vector<MediaSinksObserver*> media_sinks_observers_;
-  MockMediaRouter mock_router_;
+  MockMediaRouter* mock_router_ = nullptr;
   std::unique_ptr<MediaRouterViewsUI> ui_;
   std::unique_ptr<StartPresentationContext> start_presentation_context_;
   content::PresentationRequest presentation_request_{
@@ -326,8 +323,9 @@
 
   GURL gurl("https://example.com");
   url::Origin origin = url::Origin::Create(gurl);
-  ui_->OnDefaultPresentationChanged(content::PresentationRequest(
-      content::GlobalFrameRoutingId(), {gurl}, origin));
+  content::PresentationRequest presentation_request(
+      content::GlobalFrameRoutingId(), {gurl}, origin);
+  ui_->OnDefaultPresentationChanged(&presentation_request);
 
   // Now that the presentation request has been set, the dialog header contains
   // its origin.
@@ -347,7 +345,7 @@
 }
 
 TEST_F(MediaRouterViewsUITest, StopCasting) {
-  EXPECT_CALL(mock_router_, TerminateRoute(kRouteId));
+  EXPECT_CALL(*mock_router_, TerminateRoute(kRouteId));
   ui_->StopCasting(kRouteId);
 }
 
@@ -430,7 +428,7 @@
                             {sink2, {MediaCastMode::TAB_MIRROR}}});
 
   MockControllerObserver observer(ui_.get());
-  MockIssuesObserver issues_observer(mock_router_.GetIssueManager());
+  MockIssuesObserver issues_observer(mock_router_->GetIssueManager());
   issues_observer.Init();
   const std::string issue_title("Issue 1");
   IssueInfo issue(issue_title, IssueInfo::Action::DISMISS,
@@ -450,7 +448,7 @@
             EXPECT_EQ(model.media_sinks()[1].id, sink2.id());
             EXPECT_EQ(model.media_sinks()[1].issue->info().title, issue_title);
           })));
-  mock_router_.GetIssueManager()->AddIssue(issue);
+  mock_router_->GetIssueManager()->AddIssue(issue);
 
   EXPECT_CALL(observer, OnModelUpdated(_))
       .WillOnce(WithArg<0>(Invoke([&sink2](const CastDialogModel& model) {
@@ -458,7 +456,7 @@
         EXPECT_EQ(model.media_sinks()[1].id, sink2.id());
         EXPECT_FALSE(model.media_sinks()[1].issue.has_value());
       })));
-  mock_router_.GetIssueManager()->ClearIssue(issue_id);
+  mock_router_->GetIssueManager()->ClearIssue(issue_id);
 }
 
 TEST_F(MediaRouterViewsUITest, ShowDomainForHangouts) {
@@ -514,7 +512,7 @@
   content::PresentationRequest presentation_request(
       {0, 0}, {GURL("https://presentationurl.com")},
       url::Origin::Create(GURL("https://frameurl.fakeurl")));
-  ui_->OnDefaultPresentationChanged(presentation_request);
+  ui_->OnDefaultPresentationChanged(&presentation_request);
   StartCastingAndExpectTimeout(
       MediaCastMode::PRESENTATION,
       l10n_util::GetStringFUTF8(IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_TIMEOUT,
@@ -535,7 +533,7 @@
   EXPECT_CALL(*file_dialog_ptr, GetLastSelectedFileUrl())
       .WillOnce(Return(GURL(file_url)));
   content::WebContents* location_file_opened = nullptr;
-  EXPECT_CALL(mock_router_, CreateRouteInternal(_, _, _, _, _, _, _))
+  EXPECT_CALL(*mock_router_, CreateRouteInternal(_, _, _, _, _, _, _))
       .WillOnce(SaveArgWithMove<3>(&location_file_opened));
   ui_->CreateRoute(kSinkId, MediaCastMode::LOCAL_FILE);
 
@@ -695,6 +693,13 @@
 
 class MediaRouterViewsUIIncognitoTest : public MediaRouterViewsUITest {
  protected:
+  void SetMediaRouterFactory() override {
+    // We must set the factory on the non-incognito browser context.
+    MediaRouterFactory::GetInstance()->SetTestingFactory(
+        MediaRouterViewsUITest::GetBrowserContext(),
+        base::BindRepeating(&MockMediaRouter::Create));
+  }
+
   content::BrowserContext* GetBrowserContext() override {
     return static_cast<Profile*>(MediaRouterViewsUITest::GetBrowserContext())
         ->GetOffTheRecordProfile();
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc b/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
index 836be4d..72654fdf 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
@@ -22,8 +22,12 @@
     base::FEATURE_DISABLED_BY_DEFAULT};
 
 NativeFileSystemAccessIconView::NativeFileSystemAccessIconView(
-    Delegate* delegate)
-    : PageActionIconView(nullptr, 0, delegate) {
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(nullptr,
+                         0,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate) {
   SetVisible(false);
 }
 
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.h b/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.h
index e291e9b..69ba98f5 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.h
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.h
@@ -12,7 +12,9 @@
 // access to files or directories.
 class NativeFileSystemAccessIconView : public PageActionIconView {
  public:
-  explicit NativeFileSystemAccessIconView(Delegate* delegate);
+  NativeFileSystemAccessIconView(
+      IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+      PageActionIconView::Delegate* page_action_icon_delegate);
 
   // PageActionIconView:
   views::BubbleDialogDelegateView* GetBubble() const override;
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_controller.cc b/chrome/browser/ui/views/page_action/page_action_icon_controller.cc
index 34da019..5036000e 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_controller.cc
+++ b/chrome/browser/ui/views/page_action/page_action_icon_controller.cc
@@ -36,6 +36,7 @@
                                     PageActionIconContainer* icon_container) {
   DCHECK(icon_container);
   DCHECK(!icon_container_);
+  DCHECK(params.icon_label_bubble_delegate);
   DCHECK(params.page_action_icon_delegate);
 
   icon_container_ = icon_container;
@@ -45,12 +46,13 @@
       case PageActionIconType::kBookmarkStar:
         bookmark_star_icon_ =
             new StarView(params.command_updater, params.browser,
+                         params.icon_label_bubble_delegate,
                          params.page_action_icon_delegate);
         page_action_icons_.push_back(bookmark_star_icon_);
         break;
       case PageActionIconType::kClickToCall:
         click_to_call_icon_ = new SharingIconView(
-            params.page_action_icon_delegate,
+            params.icon_label_bubble_delegate, params.page_action_icon_delegate,
             base::BindRepeating([](content::WebContents* contents) {
               return static_cast<SharingUiController*>(
                   ClickToCallUiController::GetOrCreateFromWebContents(
@@ -61,66 +63,77 @@
         break;
       case PageActionIconType::kCookieControls:
         cookie_controls_icon_ =
-            new CookieControlsIconView(params.page_action_icon_delegate);
+            new CookieControlsIconView(params.icon_label_bubble_delegate,
+                                       params.page_action_icon_delegate);
         page_action_icons_.push_back(cookie_controls_icon_);
         break;
       case PageActionIconType::kFind:
         find_icon_ =
-            new FindBarIcon(params.browser, params.page_action_icon_delegate);
+            new FindBarIcon(params.browser, params.icon_label_bubble_delegate,
+                            params.page_action_icon_delegate);
         page_action_icons_.push_back(find_icon_);
         break;
       case PageActionIconType::kIntentPicker:
         intent_picker_icon_ = new IntentPickerView(
-            params.browser, params.page_action_icon_delegate);
+            params.browser, params.icon_label_bubble_delegate,
+            params.page_action_icon_delegate);
         page_action_icons_.push_back(intent_picker_icon_);
         break;
       case PageActionIconType::kLocalCardMigration:
         local_card_migration_icon_ = new autofill::LocalCardMigrationIconView(
-            params.command_updater, params.page_action_icon_delegate);
+            params.command_updater, params.icon_label_bubble_delegate,
+            params.page_action_icon_delegate);
         page_action_icons_.push_back(local_card_migration_icon_);
         break;
       case PageActionIconType::kManagePasswords:
         DCHECK(params.command_updater);
         manage_passwords_icon_ = new ManagePasswordsIconViews(
-            params.command_updater, params.page_action_icon_delegate);
+            params.command_updater, params.icon_label_bubble_delegate,
+            params.page_action_icon_delegate);
         page_action_icons_.push_back(manage_passwords_icon_);
         break;
       case PageActionIconType::kNativeFileSystemAccess:
         native_file_system_access_icon_ = new NativeFileSystemAccessIconView(
+            params.icon_label_bubble_delegate,
             params.page_action_icon_delegate);
         page_action_icons_.push_back(native_file_system_access_icon_);
         break;
       case PageActionIconType::kPwaInstall:
         DCHECK(params.command_updater);
         pwa_install_icon_ = new PwaInstallView(
-            params.command_updater, params.page_action_icon_delegate);
+            params.command_updater, params.icon_label_bubble_delegate,
+            params.page_action_icon_delegate);
         page_action_icons_.push_back(pwa_install_icon_);
         break;
       case PageActionIconType::kQRCodeGenerator:
         qrcode_generator_icon_view_ =
             new qrcode_generator::QRCodeGeneratorIconView(
-                params.command_updater, params.page_action_icon_delegate);
+                params.command_updater, params.icon_label_bubble_delegate,
+                params.page_action_icon_delegate);
         page_action_icons_.push_back(qrcode_generator_icon_view_);
         break;
       case PageActionIconType::kReaderMode:
         DCHECK(params.command_updater);
         reader_mode_icon_ = new ReaderModeIconView(
-            params.command_updater, params.page_action_icon_delegate);
+            params.command_updater, params.icon_label_bubble_delegate,
+            params.page_action_icon_delegate);
         page_action_icons_.push_back(reader_mode_icon_);
         break;
       case PageActionIconType::kSaveCard:
         save_card_icon_ = new autofill::SaveCardIconView(
-            params.command_updater, params.page_action_icon_delegate);
+            params.command_updater, params.icon_label_bubble_delegate,
+            params.page_action_icon_delegate);
         page_action_icons_.push_back(save_card_icon_);
         break;
       case PageActionIconType::kSendTabToSelf:
         send_tab_to_self_icon_ = new send_tab_to_self::SendTabToSelfIconView(
-            params.command_updater, params.page_action_icon_delegate);
+            params.command_updater, params.icon_label_bubble_delegate,
+            params.page_action_icon_delegate);
         page_action_icons_.push_back(send_tab_to_self_icon_);
         break;
       case PageActionIconType::kSharedClipboard:
         shared_clipboard_icon_ = new SharingIconView(
-            params.page_action_icon_delegate,
+            params.icon_label_bubble_delegate, params.page_action_icon_delegate,
             base::BindRepeating([](content::WebContents* contents) {
               return static_cast<SharingUiController*>(
                   SharedClipboardUiController::GetOrCreateFromWebContents(
@@ -132,11 +145,13 @@
       case PageActionIconType::kTranslate:
         DCHECK(params.command_updater);
         translate_icon_ = new TranslateIconView(
-            params.command_updater, params.page_action_icon_delegate);
+            params.command_updater, params.icon_label_bubble_delegate,
+            params.page_action_icon_delegate);
         page_action_icons_.push_back(translate_icon_);
         break;
       case PageActionIconType::kZoom:
-        zoom_icon_ = new ZoomView(params.page_action_icon_delegate);
+        zoom_icon_ = new ZoomView(params.icon_label_bubble_delegate,
+                                  params.page_action_icon_delegate);
         page_action_icons_.push_back(zoom_icon_);
         break;
     }
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_params.h b/chrome/browser/ui/views/page_action/page_action_icon_params.h
index 9bbe561f..f60ff98 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_params.h
+++ b/chrome/browser/ui/views/page_action/page_action_icon_params.h
@@ -40,6 +40,7 @@
   int between_icon_spacing = 0;
   Browser* browser = nullptr;
   CommandUpdater* command_updater = nullptr;
+  IconLabelBubbleView::Delegate* icon_label_bubble_delegate = nullptr;
   PageActionIconView::Delegate* page_action_icon_delegate = nullptr;
   views::ButtonObserver* button_observer = nullptr;
   views::ViewObserver* view_observer = nullptr;
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_view.cc b/chrome/browser/ui/views/page_action/page_action_icon_view.cc
index 4f8e4531..ccefb1d6f 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_view.cc
+++ b/chrome/browser/ui/views/page_action/page_action_icon_view.cc
@@ -46,11 +46,13 @@
   return nullptr;
 }
 
-PageActionIconView::PageActionIconView(CommandUpdater* command_updater,
-                                       int command_id,
-                                       PageActionIconView::Delegate* delegate,
-                                       const gfx::FontList& font_list)
-    : IconLabelBubbleView(font_list),
+PageActionIconView::PageActionIconView(
+    CommandUpdater* command_updater,
+    int command_id,
+    IconLabelBubbleView::Delegate* parent_delegate,
+    PageActionIconView::Delegate* delegate,
+    const gfx::FontList& font_list)
+    : IconLabelBubbleView(font_list, parent_delegate),
       command_updater_(command_updater),
       delegate_(delegate),
       command_id_(command_id) {
@@ -88,11 +90,6 @@
   OnExecuting(EXECUTE_SOURCE_MOUSE);
 }
 
-SkColor PageActionIconView::GetTextColor() const {
-  return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_TextfieldDefaultColor);
-}
-
 void PageActionIconView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   node_data->role = ax::mojom::Role::kButton;
   node_data->SetName(GetTextForTooltipAndAccessibleName());
@@ -115,10 +112,6 @@
   UpdateIconImage();
 }
 
-SkColor PageActionIconView::GetInkDropBaseColor() const {
-  return delegate_->GetPageActionInkDropColor();
-}
-
 bool PageActionIconView::ShouldShowSeparator() const {
   return false;
 }
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_view.h b/chrome/browser/ui/views/page_action/page_action_icon_view.h
index a63a276..42c3a4d 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_view.h
+++ b/chrome/browser/ui/views/page_action/page_action_icon_view.h
@@ -40,9 +40,6 @@
  public:
   class Delegate {
    public:
-    // Gets the color to use for the ink highlight.
-    virtual SkColor GetPageActionInkDropColor() const = 0;
-
     // Gets the opacity to use for the ink highlight.
     virtual float GetPageActionInkDropVisibleOpacity() const;
 
@@ -96,6 +93,7 @@
 
   PageActionIconView(CommandUpdater* command_updater,
                      int command_id,
+                     IconLabelBubbleView::Delegate* parent_delegate,
                      Delegate* delegate,
                      const gfx::FontList& = gfx::FontList());
 
@@ -116,13 +114,11 @@
   virtual void OnPressed(bool activated) {}
 
   // views::IconLabelBubbleView:
-  SkColor GetTextColor() const override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   base::string16 GetTooltipText(const gfx::Point& p) const override;
   void ViewHierarchyChanged(
       const views::ViewHierarchyChangedDetails& details) override;
   void OnThemeChanged() override;
-  SkColor GetInkDropBaseColor() const override;
   bool ShouldShowSeparator() const final;
   void NotifyClick(const ui::Event& event) override;
   bool IsTriggerableEvent(const ui::Event& event) override;
diff --git a/chrome/browser/ui/views/page_action/pwa_install_view.cc b/chrome/browser/ui/views/page_action/pwa_install_view.cc
index c794434..069001a4 100644
--- a/chrome/browser/ui/views/page_action/pwa_install_view.cc
+++ b/chrome/browser/ui/views/page_action/pwa_install_view.cc
@@ -17,9 +17,14 @@
 #include "components/omnibox/browser/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 
-PwaInstallView::PwaInstallView(CommandUpdater* command_updater,
-                               PageActionIconView::Delegate* delegate)
-    : PageActionIconView(nullptr, 0, delegate) {
+PwaInstallView::PwaInstallView(
+    CommandUpdater* command_updater,
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(nullptr,
+                         0,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate) {
   SetVisible(false);
   SetLabel(l10n_util::GetStringUTF16(IDS_OMNIBOX_PWA_INSTALL_ICON_LABEL));
   SetUpForInOutAnimation();
diff --git a/chrome/browser/ui/views/page_action/pwa_install_view.h b/chrome/browser/ui/views/page_action/pwa_install_view.h
index 5f15e81..6d634a5 100644
--- a/chrome/browser/ui/views/page_action/pwa_install_view.h
+++ b/chrome/browser/ui/views/page_action/pwa_install_view.h
@@ -12,8 +12,10 @@
 // installability checks and can be installed.
 class PwaInstallView : public PageActionIconView {
  public:
-  explicit PwaInstallView(CommandUpdater* command_updater,
-                          PageActionIconView::Delegate* delegate);
+  explicit PwaInstallView(
+      CommandUpdater* command_updater,
+      IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+      PageActionIconView::Delegate* page_action_icon_delegate);
   ~PwaInstallView() override;
 
  protected:
diff --git a/chrome/browser/ui/views/page_action/zoom_view.cc b/chrome/browser/ui/views/page_action/zoom_view.cc
index 359bb6b..78a9e20 100644
--- a/chrome/browser/ui/views/page_action/zoom_view.cc
+++ b/chrome/browser/ui/views/page_action/zoom_view.cc
@@ -16,8 +16,13 @@
 #include "ui/events/event.h"
 #include "ui/gfx/geometry/size.h"
 
-ZoomView::ZoomView(PageActionIconView::Delegate* delegate)
-    : PageActionIconView(nullptr, 0, delegate), icon_(&kZoomMinusIcon) {
+ZoomView::ZoomView(IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+                   PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(nullptr,
+                         0,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate),
+      icon_(&kZoomMinusIcon) {
   SetVisible(false);
 }
 
diff --git a/chrome/browser/ui/views/page_action/zoom_view.h b/chrome/browser/ui/views/page_action/zoom_view.h
index 8ef782c3..632b284 100644
--- a/chrome/browser/ui/views/page_action/zoom_view.h
+++ b/chrome/browser/ui/views/page_action/zoom_view.h
@@ -15,7 +15,8 @@
   // WebContents. Because the current WebContents changes as the user switches
   // tabs, a LocationBarView::Delegate is supplied to queried for the current
   // WebContents when needed.
-  explicit ZoomView(PageActionIconView::Delegate* delegate);
+  ZoomView(IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+           PageActionIconView::Delegate* page_action_icon_delegate);
   ~ZoomView() override;
 
   // Updates the image and its tooltip appropriately, hiding or showing the icon
diff --git a/chrome/browser/ui/views/passwords/manage_passwords_icon_views.cc b/chrome/browser/ui/views/passwords/manage_passwords_icon_views.cc
index 99b7dcf..5422a6bc 100644
--- a/chrome/browser/ui/views/passwords/manage_passwords_icon_views.cc
+++ b/chrome/browser/ui/views/passwords/manage_passwords_icon_views.cc
@@ -18,10 +18,12 @@
 
 ManagePasswordsIconViews::ManagePasswordsIconViews(
     CommandUpdater* updater,
-    PageActionIconView::Delegate* delegate)
-    : PageActionIconView(updater, IDC_MANAGE_PASSWORDS_FOR_PAGE, delegate) {
-  DCHECK(delegate);
-}
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(updater,
+                         IDC_MANAGE_PASSWORDS_FOR_PAGE,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate) {}
 
 ManagePasswordsIconViews::~ManagePasswordsIconViews() = default;
 
diff --git a/chrome/browser/ui/views/passwords/manage_passwords_icon_views.h b/chrome/browser/ui/views/passwords/manage_passwords_icon_views.h
index b30b5cb..835e1613 100644
--- a/chrome/browser/ui/views/passwords/manage_passwords_icon_views.h
+++ b/chrome/browser/ui/views/passwords/manage_passwords_icon_views.h
@@ -20,8 +20,10 @@
  public:
   static const char kClassName[];
 
-  ManagePasswordsIconViews(CommandUpdater* updater,
-                           PageActionIconView::Delegate* delegate);
+  ManagePasswordsIconViews(
+      CommandUpdater* updater,
+      IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+      PageActionIconView::Delegate* page_action_icon_delegate);
   ~ManagePasswordsIconViews() override;
 
   // ManagePasswordsIconView:
diff --git a/chrome/browser/ui/views/qrcode_generator/qrcode_generator_icon_view.cc b/chrome/browser/ui/views/qrcode_generator/qrcode_generator_icon_view.cc
index 65e4192..894ef83 100644
--- a/chrome/browser/ui/views/qrcode_generator/qrcode_generator_icon_view.cc
+++ b/chrome/browser/ui/views/qrcode_generator/qrcode_generator_icon_view.cc
@@ -18,8 +18,12 @@
 
 QRCodeGeneratorIconView::QRCodeGeneratorIconView(
     CommandUpdater* command_updater,
-    PageActionIconView::Delegate* delegate)
-    : PageActionIconView(command_updater, IDC_QRCODE_GENERATOR, delegate) {
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(command_updater,
+                         IDC_QRCODE_GENERATOR,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate) {
   SetVisible(false);
   SetLabel(l10n_util::GetStringUTF16(IDS_OMNIBOX_QRCODE_GENERATOR_ICON_LABEL));
 }
@@ -63,7 +67,7 @@
   return kQrcodeGeneratorIcon;
 }
 
-SkColor QRCodeGeneratorIconView::GetTextColor() const {
+SkColor QRCodeGeneratorIconView::GetForegroundColor() const {
   return GetOmniboxColor(GetThemeProvider(),
                          OmniboxPart::LOCATION_BAR_TEXT_DEFAULT);
 }
diff --git a/chrome/browser/ui/views/qrcode_generator/qrcode_generator_icon_view.h b/chrome/browser/ui/views/qrcode_generator/qrcode_generator_icon_view.h
index 788dccb..07106ed 100644
--- a/chrome/browser/ui/views/qrcode_generator/qrcode_generator_icon_view.h
+++ b/chrome/browser/ui/views/qrcode_generator/qrcode_generator_icon_view.h
@@ -16,14 +16,16 @@
 // can generate a QR code for the current page or a selected image.
 class QRCodeGeneratorIconView : public PageActionIconView {
  public:
-  QRCodeGeneratorIconView(CommandUpdater* command_updater,
-                          PageActionIconView::Delegate* delegate);
+  QRCodeGeneratorIconView(
+      CommandUpdater* command_updater,
+      IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+      PageActionIconView::Delegate* page_action_icon_delegate);
   ~QRCodeGeneratorIconView() override;
 
   // PageActionIconView:
   views::BubbleDialogDelegateView* GetBubble() const override;
   void UpdateImpl() override;
-  SkColor GetTextColor() const override;
+  SkColor GetForegroundColor() const override;
   base::string16 GetTextForTooltipAndAccessibleName() const override;
 
  protected:
diff --git a/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.cc b/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.cc
index 9a7c2eed..222b201 100644
--- a/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.cc
+++ b/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.cc
@@ -14,9 +14,14 @@
 
 using dom_distiller::url_utils::IsDistilledPage;
 
-ReaderModeIconView::ReaderModeIconView(CommandUpdater* command_updater,
-                                       PageActionIconView::Delegate* delegate)
-    : PageActionIconView(command_updater, IDC_DISTILL_PAGE, delegate) {}
+ReaderModeIconView::ReaderModeIconView(
+    CommandUpdater* command_updater,
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(command_updater,
+                         IDC_DISTILL_PAGE,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate) {}
 
 void ReaderModeIconView::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
diff --git a/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.h b/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.h
index 7ce196e..f4361dd6 100644
--- a/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.h
+++ b/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.h
@@ -26,7 +26,8 @@
                            public content::WebContentsObserver {
  public:
   ReaderModeIconView(CommandUpdater* command_updater,
-                     PageActionIconView::Delegate* delegate);
+                     IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+                     PageActionIconView::Delegate* page_action_icon_delegate);
   ~ReaderModeIconView() override = default;
 
  protected:
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.cc b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.cc
index da5e5dd..5fdf6d1 100644
--- a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.cc
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.cc
@@ -20,8 +20,12 @@
 
 SendTabToSelfIconView::SendTabToSelfIconView(
     CommandUpdater* command_updater,
-    PageActionIconView::Delegate* delegate)
-    : PageActionIconView(command_updater, IDC_SEND_TAB_TO_SELF, delegate) {
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(command_updater,
+                         IDC_SEND_TAB_TO_SELF,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate) {
   SetVisible(false);
   SetLabel(l10n_util::GetStringUTF16(IDS_OMNIBOX_ICON_SEND_TAB_TO_SELF));
   SetUpForInOutAnimation();
@@ -81,7 +85,7 @@
   return kSendTabToSelfIcon;
 }
 
-SkColor SendTabToSelfIconView::GetTextColor() const {
+SkColor SendTabToSelfIconView::GetForegroundColor() const {
   return GetOmniboxColor(GetThemeProvider(),
                          OmniboxPart::LOCATION_BAR_TEXT_DEFAULT);
 }
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.h b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.h
index 725560e..319bfea 100644
--- a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.h
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.h
@@ -18,14 +18,16 @@
 // choose to share the url to a target device.
 class SendTabToSelfIconView : public PageActionIconView {
  public:
-  SendTabToSelfIconView(CommandUpdater* command_updater,
-                        PageActionIconView::Delegate* delegate);
+  SendTabToSelfIconView(
+      CommandUpdater* command_updater,
+      IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+      PageActionIconView::Delegate* page_action_icon_delegate);
   ~SendTabToSelfIconView() override;
 
   // PageActionIconView:
   views::BubbleDialogDelegateView* GetBubble() const override;
   void UpdateImpl() override;
-  SkColor GetTextColor() const override;
+  SkColor GetForegroundColor() const override;
   base::string16 GetTextForTooltipAndAccessibleName() const override;
 
   // gfx::AnimationDelegate:
diff --git a/chrome/browser/ui/views/sharing/sharing_icon_view.cc b/chrome/browser/ui/views/sharing/sharing_icon_view.cc
index cf0dca0..00fe3f1 100644
--- a/chrome/browser/ui/views/sharing/sharing_icon_view.cc
+++ b/chrome/browser/ui/views/sharing/sharing_icon_view.cc
@@ -17,12 +17,15 @@
 constexpr double kAnimationTextFullLengthShownProgressState = 0.5;
 }  // namespace
 
-SharingIconView::SharingIconView(PageActionIconView::Delegate* delegate,
-                                 GetControllerCallback get_controller_callback,
-                                 GetBubbleCallback get_bubble_callback)
+SharingIconView::SharingIconView(
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate,
+    GetControllerCallback get_controller_callback,
+    GetBubbleCallback get_bubble_callback)
     : PageActionIconView(/*command_updater=*/nullptr,
                          /*command_id=*/0,
-                         delegate),
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate),
       get_controller_callback_(std::move(get_controller_callback)),
       get_bubble_callback_(std::move(get_bubble_callback)) {
   SetVisible(false);
diff --git a/chrome/browser/ui/views/sharing/sharing_icon_view.h b/chrome/browser/ui/views/sharing/sharing_icon_view.h
index 3cf860b..7bcff96 100644
--- a/chrome/browser/ui/views/sharing/sharing_icon_view.h
+++ b/chrome/browser/ui/views/sharing/sharing_icon_view.h
@@ -18,9 +18,11 @@
   using GetBubbleCallback =
       base::RepeatingCallback<views::BubbleDialogDelegateView*(SharingDialog*)>;
 
-  explicit SharingIconView(PageActionIconView::Delegate* delegate,
-                           GetControllerCallback get_controller,
-                           GetBubbleCallback get_bubble);
+  explicit SharingIconView(
+      IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+      PageActionIconView::Delegate* page_action_icon_delegate,
+      GetControllerCallback get_controller,
+      GetBubbleCallback get_bubble);
   ~SharingIconView() override;
 
   void StartLoadingAnimation();
diff --git a/chrome/browser/ui/views/toolbar/browser_action_test_util_views.h b/chrome/browser/ui/views/toolbar/browser_action_test_util_views.h
deleted file mode 100644
index f708bc2..0000000
--- a/chrome/browser/ui/views/toolbar/browser_action_test_util_views.h
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_TOOLBAR_BROWSER_ACTION_TEST_UTIL_VIEWS_H_
-#define CHROME_BROWSER_UI_VIEWS_TOOLBAR_BROWSER_ACTION_TEST_UTIL_VIEWS_H_
-
-#include <memory>
-
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
-
-class BrowserActionsContainer;
-
-class BrowserActionTestUtilViews : public BrowserActionTestUtil {
- public:
-  BrowserActionTestUtilViews(const BrowserActionTestUtilViews&) = delete;
-  BrowserActionTestUtilViews& operator=(const BrowserActionTestUtilViews&) =
-      delete;
-  ~BrowserActionTestUtilViews() override;
-
-  // BrowserActionTestUtil:
-  int NumberOfBrowserActions() override;
-  int VisibleBrowserActions() override;
-  void InspectPopup(int index) override;
-  bool HasIcon(int index) override;
-  gfx::Image GetIcon(int index) override;
-  void Press(int index) override;
-  std::string GetExtensionId(int index) override;
-  std::string GetTooltip(int index) override;
-  gfx::NativeView GetPopupNativeView() override;
-  bool HasPopup() override;
-  gfx::Size GetPopupSize() override;
-  bool HidePopup() override;
-  bool ActionButtonWantsToRun(size_t index) override;
-  void SetWidth(int width) override;
-  ToolbarActionsBar* GetToolbarActionsBar() override;
-  ExtensionsContainer* GetExtensionsContainer() override;
-  std::unique_ptr<BrowserActionTestUtil> CreateOverflowBar(
-      Browser* browser) override;
-  gfx::Size GetMinPopupSize() override;
-  gfx::Size GetMaxPopupSize() override;
-  gfx::Size GetToolbarActionSize() override;
-  bool CanBeResized() override;
-
- private:
-  friend class BrowserActionTestUtil;
-
-  class TestToolbarActionsBarHelper;
-
-  // Constructs a version of BrowserActionTestUtilViews that does not own the
-  // BrowserActionsContainer it tests.
-  explicit BrowserActionTestUtilViews(
-      BrowserActionsContainer* browser_actions_container);
-  // Constructs a version of BrowserActionTestUtilViews given a |test_helper|
-  // responsible for owning the BrowserActionsContainer.
-  explicit BrowserActionTestUtilViews(
-      std::unique_ptr<TestToolbarActionsBarHelper> test_helper);
-
-  std::unique_ptr<TestToolbarActionsBarHelper> test_helper_;
-
-  // The associated BrowserActionsContainer. Not owned.
-  BrowserActionsContainer* const browser_actions_container_;
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_BROWSER_ACTION_TEST_UTIL_VIEWS_H_
diff --git a/chrome/browser/ui/views/toolbar/browser_actions_container_browsertest.cc b/chrome/browser/ui/views/toolbar/browser_actions_container_browsertest.cc
index 1ef68e8..d6153f9 100644
--- a/chrome/browser/ui/views/toolbar/browser_actions_container_browsertest.cc
+++ b/chrome/browser/ui/views/toolbar/browser_actions_container_browsertest.cc
@@ -9,7 +9,7 @@
 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
 #include "chrome/browser/extensions/extension_context_menu_model.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 #include "chrome/browser/ui/toolbar/browser_actions_bar_browsertest.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
diff --git a/chrome/browser/ui/views/toolbar/browser_action_test_util_views_aura.cc b/chrome/browser/ui/views/toolbar/extension_action_test_helper_aura.cc
similarity index 93%
rename from chrome/browser/ui/views/toolbar/browser_action_test_util_views_aura.cc
rename to chrome/browser/ui/views/toolbar/extension_action_test_helper_aura.cc
index 6a4f6e8e..98cfbc9 100644
--- a/chrome/browser/ui/views/toolbar/browser_action_test_util_views_aura.cc
+++ b/chrome/browser/ui/views/toolbar/extension_action_test_helper_aura.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 
 #include "base/logging.h"
 #include "base/run_loop.h"
@@ -40,7 +40,7 @@
 
 }  // namespace
 
-bool BrowserActionTestUtil::WaitForPopup() {
+bool ExtensionActionTestHelper::WaitForPopup() {
   // The popup starts out active but invisible, so all we need to really do is
   // look for visibility.
   aura::Window* native_view = GetPopupNativeView();
diff --git a/chrome/browser/ui/views/toolbar/browser_action_test_util_views_mac.mm b/chrome/browser/ui/views/toolbar/extension_action_test_helper_mac.mm
similarity index 86%
rename from chrome/browser/ui/views/toolbar/browser_action_test_util_views_mac.mm
rename to chrome/browser/ui/views/toolbar/extension_action_test_helper_mac.mm
index 002419d..adfc337 100644
--- a/chrome/browser/ui/views/toolbar/browser_action_test_util_views_mac.mm
+++ b/chrome/browser/ui/views/toolbar/extension_action_test_helper_mac.mm
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 
 #include <AppKit/AppKit.h>
 
 #import "base/mac/scoped_nsobject.h"
 #import "ui/base/test/windowed_nsnotification_observer.h"
 
-bool BrowserActionTestUtil::WaitForPopup() {
+bool ExtensionActionTestHelper::WaitForPopup() {
   NSWindow* window = [GetPopupNativeView().GetNativeNSView() window];
   if (!window)
     return false;
diff --git a/chrome/browser/ui/views/toolbar/browser_action_test_util_views.cc b/chrome/browser/ui/views/toolbar/extension_action_test_helper_views.cc
similarity index 71%
rename from chrome/browser/ui/views/toolbar/browser_action_test_util_views.cc
rename to chrome/browser/ui/views/toolbar/extension_action_test_helper_views.cc
index eaae10b6..ab65a49 100644
--- a/chrome/browser/ui/views/toolbar/browser_action_test_util_views.cc
+++ b/chrome/browser/ui/views/toolbar/extension_action_test_helper_views.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/extensions/browser_action_test_util.h"
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
 
 #include <stddef.h>
 
@@ -16,8 +16,8 @@
 #include "chrome/browser/ui/views/extensions/extension_popup.h"
 #include "chrome/browser/ui/views/extensions/extensions_menu_test_util.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/browser/ui/views/toolbar/browser_action_test_util_views.h"
 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
+#include "chrome/browser/ui/views/toolbar/extension_action_test_helper_views.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_action_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "ui/gfx/geometry/rect.h"
@@ -29,7 +29,7 @@
 // A helper class that owns an instance of a BrowserActionsContainer; this is
 // used when testing without an associated browser window, or if this is for
 // the overflow version of the bar.
-class BrowserActionTestUtilViews::TestToolbarActionsBarHelper
+class ExtensionActionTestHelperViews::TestToolbarActionsBarHelper
     : public BrowserActionsContainer::Delegate {
  public:
   TestToolbarActionsBarHelper(Browser* browser,
@@ -71,117 +71,117 @@
   views::ResizeAwareParentView container_parent_;
 };
 
-BrowserActionTestUtilViews::~BrowserActionTestUtilViews() = default;
+ExtensionActionTestHelperViews::~ExtensionActionTestHelperViews() = default;
 
-int BrowserActionTestUtilViews::NumberOfBrowserActions() {
+int ExtensionActionTestHelperViews::NumberOfBrowserActions() {
   return browser_actions_container_->num_toolbar_actions();
 }
 
-int BrowserActionTestUtilViews::VisibleBrowserActions() {
+int ExtensionActionTestHelperViews::VisibleBrowserActions() {
   return browser_actions_container_->VisibleBrowserActions();
 }
 
-void BrowserActionTestUtilViews::InspectPopup(int index) {
+void ExtensionActionTestHelperViews::InspectPopup(int index) {
   ToolbarActionView* view =
       browser_actions_container_->GetToolbarActionViewAt(index);
-  static_cast<ExtensionActionViewController*>(view->view_controller())->
-      InspectPopup();
+  static_cast<ExtensionActionViewController*>(view->view_controller())
+      ->InspectPopup();
 }
 
-bool BrowserActionTestUtilViews::HasIcon(int index) {
+bool ExtensionActionTestHelperViews::HasIcon(int index) {
   return !browser_actions_container_->GetToolbarActionViewAt(index)
               ->GetImage(views::Button::STATE_NORMAL)
               .isNull();
 }
 
-gfx::Image BrowserActionTestUtilViews::GetIcon(int index) {
+gfx::Image ExtensionActionTestHelperViews::GetIcon(int index) {
   gfx::ImageSkia icon =
       browser_actions_container_->GetToolbarActionViewAt(index)
           ->GetIconForTest();
   return gfx::Image(icon);
 }
 
-void BrowserActionTestUtilViews::Press(int index) {
+void ExtensionActionTestHelperViews::Press(int index) {
   browser_actions_container_->GetToolbarActionViewAt(index)
       ->view_controller()
       ->ExecuteAction(true);
 }
 
-std::string BrowserActionTestUtilViews::GetExtensionId(int index) {
+std::string ExtensionActionTestHelperViews::GetExtensionId(int index) {
   return browser_actions_container_->GetToolbarActionViewAt(index)
       ->view_controller()
       ->GetId();
 }
 
-std::string BrowserActionTestUtilViews::GetTooltip(int index) {
+std::string ExtensionActionTestHelperViews::GetTooltip(int index) {
   base::string16 tooltip =
       browser_actions_container_->GetToolbarActionViewAt(index)->GetTooltipText(
           gfx::Point());
   return base::UTF16ToUTF8(tooltip);
 }
 
-gfx::NativeView BrowserActionTestUtilViews::GetPopupNativeView() {
+gfx::NativeView ExtensionActionTestHelperViews::GetPopupNativeView() {
   ToolbarActionViewController* popup_owner =
       GetToolbarActionsBar()->popup_owner();
   return popup_owner ? popup_owner->GetPopupNativeView() : nullptr;
 }
 
-bool BrowserActionTestUtilViews::HasPopup() {
+bool ExtensionActionTestHelperViews::HasPopup() {
   return GetPopupNativeView() != nullptr;
 }
 
-gfx::Size BrowserActionTestUtilViews::GetPopupSize() {
+gfx::Size ExtensionActionTestHelperViews::GetPopupSize() {
   gfx::NativeView popup = GetPopupNativeView();
   views::Widget* widget = views::Widget::GetWidgetForNativeView(popup);
   return widget->GetWindowBoundsInScreen().size();
 }
 
-bool BrowserActionTestUtilViews::HidePopup() {
+bool ExtensionActionTestHelperViews::HidePopup() {
   GetToolbarActionsBar()->HideActivePopup();
   return !HasPopup();
 }
 
-bool BrowserActionTestUtilViews::ActionButtonWantsToRun(size_t index) {
+bool ExtensionActionTestHelperViews::ActionButtonWantsToRun(size_t index) {
   return browser_actions_container_->GetToolbarActionViewAt(index)
       ->wants_to_run_for_testing();
 }
 
-void BrowserActionTestUtilViews::SetWidth(int width) {
+void ExtensionActionTestHelperViews::SetWidth(int width) {
   browser_actions_container_->SetSize(
       gfx::Size(width, browser_actions_container_->height()));
 }
 
-ToolbarActionsBar* BrowserActionTestUtilViews::GetToolbarActionsBar() {
+ToolbarActionsBar* ExtensionActionTestHelperViews::GetToolbarActionsBar() {
   return browser_actions_container_->toolbar_actions_bar();
 }
 
-ExtensionsContainer* BrowserActionTestUtilViews::GetExtensionsContainer() {
+ExtensionsContainer* ExtensionActionTestHelperViews::GetExtensionsContainer() {
   return GetToolbarActionsBar();
 }
 
-std::unique_ptr<BrowserActionTestUtil>
-BrowserActionTestUtilViews::CreateOverflowBar(Browser* browser) {
+std::unique_ptr<ExtensionActionTestHelper>
+ExtensionActionTestHelperViews::CreateOverflowBar(Browser* browser) {
   CHECK(!GetToolbarActionsBar()->in_overflow_mode())
       << "Only a main bar can create an overflow bar!";
 
-  return base::WrapUnique(new BrowserActionTestUtilViews(
+  return base::WrapUnique(new ExtensionActionTestHelperViews(
       std::make_unique<TestToolbarActionsBarHelper>(
           browser, browser_actions_container_)));
 }
 
-gfx::Size BrowserActionTestUtilViews::GetMinPopupSize() {
+gfx::Size ExtensionActionTestHelperViews::GetMinPopupSize() {
   return gfx::Size(ExtensionPopup::kMinWidth, ExtensionPopup::kMinHeight);
 }
 
-gfx::Size BrowserActionTestUtilViews::GetMaxPopupSize() {
+gfx::Size ExtensionActionTestHelperViews::GetMaxPopupSize() {
   return gfx::Size(ExtensionPopup::kMaxWidth, ExtensionPopup::kMaxHeight);
 }
 
-gfx::Size BrowserActionTestUtilViews::GetToolbarActionSize() {
+gfx::Size ExtensionActionTestHelperViews::GetToolbarActionSize() {
   return GetToolbarActionsBar()->GetViewSize();
 }
 
-bool BrowserActionTestUtilViews::CanBeResized() {
+bool ExtensionActionTestHelperViews::CanBeResized() {
   // The container can only be resized if we can start a drag for the view.
   DCHECK_LE(1u, browser_actions_container_->num_toolbar_actions());
   ToolbarActionView* action_view =
@@ -191,38 +191,40 @@
                                                          point);
 }
 
-BrowserActionTestUtilViews::BrowserActionTestUtilViews(
+ExtensionActionTestHelperViews::ExtensionActionTestHelperViews(
     BrowserActionsContainer* browser_actions_container)
     : browser_actions_container_(browser_actions_container) {}
 
-BrowserActionTestUtilViews::BrowserActionTestUtilViews(
+ExtensionActionTestHelperViews::ExtensionActionTestHelperViews(
     std::unique_ptr<TestToolbarActionsBarHelper> test_helper)
     : test_helper_(std::move(test_helper)),
       browser_actions_container_(test_helper_->browser_actions_container()) {}
 
 // static
-std::unique_ptr<BrowserActionTestUtil> BrowserActionTestUtil::Create(
+std::unique_ptr<ExtensionActionTestHelper> ExtensionActionTestHelper::Create(
     Browser* browser,
     bool is_real_window) {
   // If the ExtensionsMenu is enabled, then use a separate implementation of
-  // the BrowserActionTestUtil.
+  // the ExtensionActionTestHelper.
   if (base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu))
     return std::make_unique<ExtensionsMenuTestUtil>(browser, is_real_window);
 
-  std::unique_ptr<BrowserActionTestUtil> browser_action_test_util;
+  std::unique_ptr<ExtensionActionTestHelper> browser_action_test_util;
 
   if (is_real_window) {
-    browser_action_test_util = base::WrapUnique(new BrowserActionTestUtilViews(
-        BrowserView::GetBrowserViewForBrowser(browser)
-            ->toolbar()
-            ->browser_actions()));
+    browser_action_test_util =
+        base::WrapUnique(new ExtensionActionTestHelperViews(
+            BrowserView::GetBrowserViewForBrowser(browser)
+                ->toolbar()
+                ->browser_actions()));
   } else {
     // This is the main bar.
     BrowserActionsContainer* main_bar = nullptr;
-    browser_action_test_util = base::WrapUnique(new BrowserActionTestUtilViews(
-        std::make_unique<
-            BrowserActionTestUtilViews::TestToolbarActionsBarHelper>(
-            browser, main_bar)));
+    browser_action_test_util =
+        base::WrapUnique(new ExtensionActionTestHelperViews(
+            std::make_unique<
+                ExtensionActionTestHelperViews::TestToolbarActionsBarHelper>(
+                browser, main_bar)));
   }
 
   return browser_action_test_util;
diff --git a/chrome/browser/ui/views/toolbar/extension_action_test_helper_views.h b/chrome/browser/ui/views/toolbar/extension_action_test_helper_views.h
new file mode 100644
index 0000000..6a819194
--- /dev/null
+++ b/chrome/browser/ui/views/toolbar/extension_action_test_helper_views.h
@@ -0,0 +1,66 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_TOOLBAR_EXTENSION_ACTION_TEST_HELPER_VIEWS_H_
+#define CHROME_BROWSER_UI_VIEWS_TOOLBAR_EXTENSION_ACTION_TEST_HELPER_VIEWS_H_
+
+#include <memory>
+
+#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
+
+class BrowserActionsContainer;
+
+class ExtensionActionTestHelperViews : public ExtensionActionTestHelper {
+ public:
+  ExtensionActionTestHelperViews(const ExtensionActionTestHelperViews&) =
+      delete;
+  ExtensionActionTestHelperViews& operator=(
+      const ExtensionActionTestHelperViews&) = delete;
+  ~ExtensionActionTestHelperViews() override;
+
+  // ExtensionActionTestHelper:
+  int NumberOfBrowserActions() override;
+  int VisibleBrowserActions() override;
+  void InspectPopup(int index) override;
+  bool HasIcon(int index) override;
+  gfx::Image GetIcon(int index) override;
+  void Press(int index) override;
+  std::string GetExtensionId(int index) override;
+  std::string GetTooltip(int index) override;
+  gfx::NativeView GetPopupNativeView() override;
+  bool HasPopup() override;
+  gfx::Size GetPopupSize() override;
+  bool HidePopup() override;
+  bool ActionButtonWantsToRun(size_t index) override;
+  void SetWidth(int width) override;
+  ToolbarActionsBar* GetToolbarActionsBar() override;
+  ExtensionsContainer* GetExtensionsContainer() override;
+  std::unique_ptr<ExtensionActionTestHelper> CreateOverflowBar(
+      Browser* browser) override;
+  gfx::Size GetMinPopupSize() override;
+  gfx::Size GetMaxPopupSize() override;
+  gfx::Size GetToolbarActionSize() override;
+  bool CanBeResized() override;
+
+ private:
+  friend class ExtensionActionTestHelper;
+
+  class TestToolbarActionsBarHelper;
+
+  // Constructs a version of ExtensionActionTestHelperViews that does not own
+  // the BrowserActionsContainer it tests.
+  explicit ExtensionActionTestHelperViews(
+      BrowserActionsContainer* browser_actions_container);
+  // Constructs a version of ExtensionActionTestHelperViews given a
+  // |test_helper| responsible for owning the BrowserActionsContainer.
+  explicit ExtensionActionTestHelperViews(
+      std::unique_ptr<TestToolbarActionsBarHelper> test_helper);
+
+  std::unique_ptr<TestToolbarActionsBarHelper> test_helper_;
+
+  // The associated BrowserActionsContainer. Not owned.
+  BrowserActionsContainer* const browser_actions_container_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_EXTENSION_ACTION_TEST_HELPER_VIEWS_H_
diff --git a/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.cc b/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.cc
index 59696cfd..6dfc124 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.cc
@@ -42,6 +42,7 @@
   };
   params.browser = browser_;
   params.command_updater = browser_->command_controller();
+  params.icon_label_bubble_delegate = this;
   params.page_action_icon_delegate = this;
   params.button_observer = this;
   params.view_observer = this;
@@ -64,7 +65,15 @@
   avatar_->UpdateIcon();
 }
 
-SkColor ToolbarAccountIconContainerView::GetPageActionInkDropColor() const {
+SkColor
+ToolbarAccountIconContainerView::GetIconLabelBubbleSurroundingForegroundColor()
+    const {
+  return GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_TextfieldDefaultColor);
+}
+
+SkColor ToolbarAccountIconContainerView::GetIconLabelBubbleInkDropColor()
+    const {
   return GetToolbarInkDropBaseColor(this);
 }
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.h b/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.h
index bf1f112..85ccf27d 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.h
@@ -17,6 +17,7 @@
 // A container view for user-account-related PageActionIconViews and the profile
 // avatar icon.
 class ToolbarAccountIconContainerView : public ToolbarIconContainerView,
+                                        public IconLabelBubbleView::Delegate,
                                         public PageActionIconView::Delegate {
  public:
   explicit ToolbarAccountIconContainerView(Browser* browser);
@@ -29,8 +30,11 @@
   // ToolbarIconContainerView:
   void UpdateAllIcons() override;
 
+  // IconLabelBubbleView::Delegate:
+  SkColor GetIconLabelBubbleSurroundingForegroundColor() const override;
+  SkColor GetIconLabelBubbleInkDropColor() const override;
+
   // PageActionIconView::Delegate:
-  SkColor GetPageActionInkDropColor() const override;
   float GetPageActionInkDropVisibleOpacity() const override;
   content::WebContents* GetWebContentsForPageActionIconView() override;
   std::unique_ptr<views::Border> CreatePageActionIconBorder() const override;
diff --git a/chrome/browser/ui/views/translate/translate_icon_view.cc b/chrome/browser/ui/views/translate/translate_icon_view.cc
index 4dc88dfe..9c94a701 100644
--- a/chrome/browser/ui/views/translate/translate_icon_view.cc
+++ b/chrome/browser/ui/views/translate/translate_icon_view.cc
@@ -18,10 +18,14 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 
-TranslateIconView::TranslateIconView(CommandUpdater* command_updater,
-                                     PageActionIconView::Delegate* delegate)
-    : PageActionIconView(command_updater, IDC_TRANSLATE_PAGE, delegate) {
-  DCHECK(delegate);
+TranslateIconView::TranslateIconView(
+    CommandUpdater* command_updater,
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    PageActionIconView::Delegate* page_action_icon_delegate)
+    : PageActionIconView(command_updater,
+                         IDC_TRANSLATE_PAGE,
+                         icon_label_bubble_delegate,
+                         page_action_icon_delegate) {
   SetID(VIEW_ID_TRANSLATE_BUTTON);
 }
 
diff --git a/chrome/browser/ui/views/translate/translate_icon_view.h b/chrome/browser/ui/views/translate/translate_icon_view.h
index f2bd30fc..541f8a3a 100644
--- a/chrome/browser/ui/views/translate/translate_icon_view.h
+++ b/chrome/browser/ui/views/translate/translate_icon_view.h
@@ -15,7 +15,8 @@
 class TranslateIconView : public PageActionIconView {
  public:
   TranslateIconView(CommandUpdater* command_updater,
-                    PageActionIconView::Delegate* delegate);
+                    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+                    PageActionIconView::Delegate* page_action_icon_delegate);
   ~TranslateIconView() override;
 
   // PageActionIconView:
diff --git a/chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.cc b/chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.cc
index d34d16a..6460aa6 100644
--- a/chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.cc
@@ -173,8 +173,9 @@
 
 class WebAppFrameToolbarView::ContentSettingsContainer : public views::View {
  public:
-  explicit ContentSettingsContainer(
-      ContentSettingImageView::Delegate* delegate);
+  ContentSettingsContainer(
+      IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+      ContentSettingImageView::Delegate* content_setting_image_delegate);
   ~ContentSettingsContainer() override = default;
 
   void UpdateContentSettingViewsVisibility() {
@@ -224,7 +225,8 @@
 };
 
 WebAppFrameToolbarView::ContentSettingsContainer::ContentSettingsContainer(
-    ContentSettingImageView::Delegate* delegate) {
+    IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
+    ContentSettingImageView::Delegate* content_setting_image_delegate) {
   views::BoxLayout& layout =
       *SetLayoutManager(std::make_unique<views::BoxLayout>(
           views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
@@ -237,7 +239,8 @@
       ContentSettingImageModel::GenerateContentSettingImageModels();
   for (auto& model : models) {
     auto image_view = std::make_unique<ContentSettingImageView>(
-        std::move(model), delegate,
+        std::move(model), icon_label_bubble_delegate,
+        content_setting_image_delegate,
         views::CustomFrameView::GetWindowTitleFontList());
     // Padding around content setting icons.
     constexpr auto kContentSettingIconInteriorPadding = gfx::Insets(4);
@@ -367,6 +370,7 @@
 class WebAppFrameToolbarView::ToolbarButtonContainer
     : public views::View,
       public BrowserActionsContainer::Delegate,
+      public IconLabelBubbleView::Delegate,
       public ContentSettingImageView::Delegate,
       public ImmersiveModeController::Observer,
       public PageActionIconView::Delegate,
@@ -468,8 +472,16 @@
                                                      main_bar);
   }
 
+  // IconLabelBubbleView::Delegate:
+  SkColor GetIconLabelBubbleSurroundingForegroundColor() const override {
+    return GetNativeTheme()->GetSystemColor(
+        ui::NativeTheme::kColorId_TextfieldDefaultColor);
+  }
+  SkColor GetIconLabelBubbleInkDropColor() const override {
+    return icon_color_;
+  }
+
   // ContentSettingImageView::Delegate:
-  SkColor GetContentSettingInkDropColor() const override { return icon_color_; }
   content::WebContents* GetContentSettingWebContents() override {
     return browser_view_->GetActiveWebContents();
   }
@@ -493,7 +505,6 @@
   }
 
   // PageActionIconView::Delegate:
-  SkColor GetPageActionInkDropColor() const override { return icon_color_; }
   content::WebContents* GetWebContentsForPageActionIconView() override {
     return browser_view_->GetActiveWebContents();
   }
@@ -548,7 +559,7 @@
 
   if (app_controller->HasTitlebarContentSettings()) {
     content_settings_container_ =
-        AddChildView(std::make_unique<ContentSettingsContainer>(this));
+        AddChildView(std::make_unique<ContentSettingsContainer>(this, this));
     views::SetHitTestComponent(content_settings_container_,
                                static_cast<int>(HTCLIENT));
   }
@@ -569,6 +580,7 @@
       HorizontalPaddingBetweenPageActionsAndAppMenuButtons();
   params.browser = browser_view_->browser();
   params.command_updater = browser_view_->browser()->command_controller();
+  params.icon_label_bubble_delegate = this;
   params.page_action_icon_delegate = this;
   page_action_icon_container_view_ =
       AddChildView(std::make_unique<PageActionIconContainerView>(params));
@@ -863,12 +875,8 @@
       ->get_content_setting_views();
 }
 
-SkColor WebAppFrameToolbarView::GetCaptionColor() const {
-  return paint_as_active_ ? active_color_ : inactive_color_;
-}
-
 void WebAppFrameToolbarView::UpdateChildrenColor() {
-  SkColor icon_color = GetCaptionColor();
+  const SkColor icon_color = paint_as_active_ ? active_color_ : inactive_color_;
   if (left_container_)
     left_container_->SetIconColor(icon_color);
   right_container_->SetIconColor(icon_color);
diff --git a/chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.h b/chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.h
index 4b4cca4..a0e56e53 100644
--- a/chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.h
+++ b/chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.h
@@ -111,7 +111,6 @@
   const std::vector<ContentSettingImageView*>&
   GetContentSettingViewsForTesting() const;
 
-  SkColor GetCaptionColor() const;
   void UpdateChildrenColor();
 
   // The containing browser view.
diff --git a/chrome/browser/ui/webui/settings/about_handler.cc b/chrome/browser/ui/webui/settings/about_handler.cc
index 70d59dd..e741359 100644
--- a/chrome/browser/ui/webui/settings/about_handler.cc
+++ b/chrome/browser/ui/webui/settings/about_handler.cc
@@ -22,6 +22,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/system/sys_info.h"
 #include "base/task/post_task.h"
 #include "base/time/time.h"
 #include "base/values.h"
diff --git a/chrome/browser/ui/window_sizer/window_sizer_ash_uitest.cc b/chrome/browser/ui/window_sizer/window_sizer_ash_uitest.cc
index e3de038..c8f8af5 100644
--- a/chrome/browser/ui/window_sizer/window_sizer_ash_uitest.cc
+++ b/chrome/browser/ui/window_sizer/window_sizer_ash_uitest.cc
@@ -89,7 +89,8 @@
   DISALLOW_COPY_AND_ASSIGN(WindowSizerTest);
 };
 
-IN_PROC_BROWSER_TEST_F(WindowSizerTest, OpenBrowserUsingShelfItem) {
+// TODO(crbug.com/1038342): Test is flaky.
+IN_PROC_BROWSER_TEST_F(WindowSizerTest, DISABLED_OpenBrowserUsingShelfItem) {
   // Don't shutdown when closing the last browser window.
   ScopedKeepAlive test_keep_alive(KeepAliveOrigin::BROWSER_PROCESS_CHROMEOS,
                                   KeepAliveRestartOption::DISABLED);
@@ -133,7 +134,8 @@
   EXPECT_EQ(root_windows[0], ash::Shell::GetRootWindowForNewWindows());
 }
 
-IN_PROC_BROWSER_TEST_F(WindowSizerTest, OpenBrowserUsingContextMenu) {
+// TODO(crbug.com/1038342): Test is flaky.
+IN_PROC_BROWSER_TEST_F(WindowSizerTest, DISABLED_OpenBrowserUsingContextMenu) {
   // Don't shutdown when closing the last browser window.
   ScopedKeepAlive test_keep_alive(KeepAliveOrigin::BROWSER_PROCESS_CHROMEOS,
                                   KeepAliveRestartOption::DISABLED);
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc b/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc
index 068acda..5c7cd804 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc
@@ -457,7 +457,8 @@
   ASSERT_EQ(S_OK, SetGlobalFlagForTesting(L"enable_ad_association", 0));
 }
 
-TEST_P(GcpCredentialProviderWithGaiaUsersTest, ReauthCredentialTest) {
+// TODO(crbug.com/1038339): Test is failing consistently.
+TEST_P(GcpCredentialProviderWithGaiaUsersTest, DISABLED_ReauthCredentialTest) {
   const bool has_token_handle = std::get<0>(GetParam());
   const bool valid_token_handle = std::get<1>(GetParam());
   const bool has_internet = std::get<2>(GetParam());
@@ -551,7 +552,13 @@
   ASSERT_EQ(S_OK, SetGlobalFlagForTesting(L"enable_ad_association", 1));
 }
 
-TEST_P(GcpCredentialProviderWithADUsersTest, ReauthCredentialTest) {
+// TODO(crbug.com/1038351): Test fails on Windows.
+#if defined(OS_WIN)
+#define MAYBE_ReauthCredentialTest DISABLED_ReauthCredentialTest
+#else
+#define MAYBE_ReauthCredentialTest ReauthCredentialTest
+#endif
+TEST_P(GcpCredentialProviderWithADUsersTest, MAYBE_ReauthCredentialTest) {
   const bool has_user_id = std::get<0>(GetParam());
   const bool valid_token_handle = std::get<1>(GetParam());
   const bool is_ad_user = std::get<2>(GetParam());
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index b59c01e..3bb7b98 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1012,6 +1012,7 @@
       "../browser/net/chrome_network_service_browsertest.cc",
       "../browser/net/chrome_network_service_restart_browsertest.cc",
       "../browser/net/cookie_policy_browsertest.cc",
+      "../browser/net/dns_over_https_browsertest.cc",
       "../browser/net/dns_probe_browsertest.cc",
       "../browser/net/errorpage_browsertest.cc",
       "../browser/net/ftp_browsertest.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java
index 21bbc55..6988d18 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java
@@ -9,19 +9,16 @@
 
 import androidx.annotation.WorkerThread;
 
-import org.chromium.base.ContextUtils;
 import org.chromium.chrome.browser.signin.IdentityServicesProvider;
 import org.chromium.chrome.browser.signin.SigninHelper;
 import org.chromium.components.signin.AccountIdProvider;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.ChromeSigninController;
-import org.chromium.components.signin.identitymanager.ProfileOAuth2TokenServiceDelegate;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
 
 /**
@@ -139,11 +136,6 @@
     public static void resetSigninState() {
         // Clear cached signed account name and accounts list.
         ChromeSigninController.get().setSignedInAccountName(null);
-        ContextUtils.getAppSharedPreferences()
-                .edit()
-                .putStringSet(
-                        ProfileOAuth2TokenServiceDelegate.STORED_ACCOUNTS_KEY, new HashSet<>())
-                .apply();
     }
 
     private SigninTestUtil() {}
diff --git a/chrome/test/base/in_process_browser_test.cc b/chrome/test/base/in_process_browser_test.cc
index 9ccda16..19d33b3 100644
--- a/chrome/test/base/in_process_browser_test.cc
+++ b/chrome/test/base/in_process_browser_test.cc
@@ -164,14 +164,6 @@
 }
 #endif
 
-std::unique_ptr<storage::QuotaSettings>
-InProcessBrowserTest::CreateQuotaSettings() {
-  // By default use hardcoded quota settings to have a consistent testing
-  // environment.
-  const int kQuota = 5 * 1024 * 1024;
-  return std::make_unique<storage::QuotaSettings>(kQuota * 5, kQuota, 0, 0);
-}
-
 void InProcessBrowserTest::Initialize() {
   CreateTestServer(GetChromeTestDataDir());
   base::FilePath src_dir;
@@ -289,10 +281,6 @@
   ash::ShellTestApi::SetTabletControllerUseScreenshotForTest(false);
 #endif  // defined(OS_CHROMEOS)
 
-  quota_settings_ = CreateQuotaSettings();
-  ChromeContentBrowserClient::SetDefaultQuotaSettingsForTesting(
-      quota_settings_.get());
-
   // Redirect the default download directory to a temporary directory.
   ASSERT_TRUE(default_download_dir_.CreateUniqueTempDir());
   CHECK(base::PathService::Override(chrome::DIR_DEFAULT_DOWNLOADS,
@@ -323,7 +311,6 @@
 #if defined(OS_MACOSX) || defined(OS_LINUX)
   OSCryptMocker::TearDown();
 #endif
-  ChromeContentBrowserClient::SetDefaultQuotaSettingsForTesting(nullptr);
 
 #if defined(OS_CHROMEOS)
   chromeos::device_sync::DeviceSyncImpl::Factory::SetInstanceForTesting(
diff --git a/chrome/test/base/in_process_browser_test.h b/chrome/test/base/in_process_browser_test.h
index 0cc16a2..8bd4494 100644
--- a/chrome/test/base/in_process_browser_test.h
+++ b/chrome/test/base/in_process_browser_test.h
@@ -17,7 +17,6 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_base.h"
-#include "storage/browser/quota/quota_settings.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/page_transition_types.h"
 
@@ -264,8 +263,6 @@
     open_about_blank_on_browser_launch_ = value;
   }
 
-  virtual std::unique_ptr<storage::QuotaSettings> CreateQuotaSettings();
-
  private:
   void Initialize();
 
@@ -293,11 +290,6 @@
   // True if the about:blank tab should be opened when the browser is launched.
   bool open_about_blank_on_browser_launch_ = true;
 
-  // We use fake quota settings by default to have a consistent testing
-  // environment.  These can be overridden by subclasses via the
-  // CreateQuotaSettings() method.
-  std::unique_ptr<storage::QuotaSettings> quota_settings_;
-
   // Use a default download directory to make sure downloads don't end up in the
   // system default location.
   base::ScopedTempDir default_download_dir_;
diff --git a/chrome/test/chromedriver/BUILD.gn b/chrome/test/chromedriver/BUILD.gn
index cc839593..4993ce8 100644
--- a/chrome/test/chromedriver/BUILD.gn
+++ b/chrome/test/chromedriver/BUILD.gn
@@ -344,6 +344,7 @@
   pydeps_file = "test/run_py_tests.pydeps"
   data = [
     "//chrome/test/data/chromedriver/",
+    "//chrome/test/chromedriver/js/",
     "//testing/scripts/run_chromedriver_tests.py",
     "//testing/scripts/common.py",
     "//testing/xvfb.py",
diff --git a/chrome/test/chromedriver/js/test.js b/chrome/test/chromedriver/js/test.js
index 2ec380d..bd3741d 100644
--- a/chrome/test/chromedriver/js/test.js
+++ b/chrome/test/chromedriver/js/test.js
@@ -43,7 +43,12 @@
     }
   };
 
-  test(runner);
+  try {
+    test(runner);
+  } catch (error) {
+    window.CDCJStestRunStatus = "FAIL: " + error.stack;
+    throw error;
+  }
   if (shouldContinue)
     onPass();
 }
@@ -52,6 +57,7 @@
  * Runs all tests and reports the results via the console.
  */
 function runTests() {
+  window.CDCJStestRunStatus;
   var tests = [];
   for (var i in window) {
     if (i.indexOf('test') == 0)
@@ -62,6 +68,7 @@
   var testNo = 0;
   function runNextTest() {
     if (testNo >= tests.length) {
+      window.CDCJStestRunStatus = "PASS"
       console.log('All tests passed');
       return;
     }
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index 11b979e..09bcdad 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -78,6 +78,10 @@
     'ChromeDriverTest.testAlertOnNewWindow',
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2532
     'ChromeDriverPageLoadTimeoutTest.testRefreshWithPageLoadTimeout',
+    # testFocus is failing
+    'JavaScriptTests.testFocus',
+    # https://bugs.chromium.org/p/chromium/issues/detail?id=1038366
+    'JavaScriptTests.testAllJS',
 ]
 
 
@@ -4201,6 +4205,46 @@
       self.CreateDriver('http://[::1]:' +
                                  str(chromedriver_server.GetPort()))
 
+class JavaScriptTests(ChromeDriverBaseTestWithWebServer):
+  def GetFileUrl(self, filename):
+    return 'file://' + self.js_root + filename
+
+  def setUp(self):
+    self._driver = self.CreateDriver()
+    self.js_root = os.path.dirname(os.path.realpath(__file__)) + '/../js/'
+
+  def checkTestResult(self):
+    def getStatus():
+      return self._driver.ExecuteScript('return window.CDCJStestRunStatus')
+
+    self.WaitForCondition(getStatus)
+    self.assertEquals('PASS', getStatus())
+
+  def testAllJS(self):
+    self._driver.Load(self.GetFileUrl('call_function_test.html'))
+    self.checkTestResult()
+
+    self._driver.Load(self.GetFileUrl('dispatch_touch_event_test.html'))
+    self.checkTestResult()
+
+    self._driver.Load(self.GetFileUrl('execute_async_script_test.html'))
+    self.checkTestResult()
+
+    self._driver.Load(self.GetFileUrl('execute_script_test.html'))
+    self.checkTestResult()
+
+    self._driver.Load(self.GetFileUrl('get_element_location_test.html'))
+    self.checkTestResult()
+
+    self._driver.Load(self.GetFileUrl('get_element_region_test.html'))
+    self.checkTestResult()
+
+    self._driver.Load(self.GetFileUrl('is_option_element_toggleable_test.html'))
+    self.checkTestResult()
+
+  def testFocus(self):
+    self._driver.Load(self.GetFileUrl('focus_test.html'))
+    self.checkTestResult()
 
 # 'Z' in the beginning is to make test executed in the end of suite.
 class ZChromeStartRetryCountTest(unittest.TestCase):
diff --git a/chrome/test/chromedriver/test/test_expectations b/chrome/test/chromedriver/test/test_expectations
index 36fd705..e6257d9 100644
--- a/chrome/test/chromedriver/test/test_expectations
+++ b/chrome/test/chromedriver/test/test_expectations
@@ -25,6 +25,7 @@
     'BasicMouseInterfaceTest.testMoveMouseByOffsetOverAndOutOfAnElement',
     'BasicMouseInterfaceTest.testMovingMouseToRelativeElementOffset',
     'BasicMouseInterfaceTest.testMovingMouseToRelativeZeroElementOffset',
+    'ChromeOptionsFunctionalTest.canAddExtensionFromFile',
     'CombinedInputActionsTest.testClickAfterMoveToAnElementWithAnOffsetShouldUseLastMousePosition',
     'ContentEditableTest.appendsTextToEndOfContentEditableWithMultipleTextNodes',
     'ContentEditableTest.testShouldAppendToTinyMCE',
@@ -35,6 +36,7 @@
     'PageLoadingTest.testEagerStrategyShouldNotWaitForResources',
     'PageLoadingTest.testEagerStrategyShouldNotWaitForResourcesOnRefresh',
     'PageLoadingTest.testEagerStrategyShouldWaitForDocumentToBeLoaded',
+    'TextHandlingTest.canHandleTextTransformProperty',
     'WindowSwitchingTest.canOpenANewWindow',
     'LocationContextTest.testShouldSetAndGetLatitude',
     'LocationContextTest.testShouldSetAndGetLongitude',
diff --git a/chrome/test/data/prerender/prerender_new_entry.html b/chrome/test/data/prerender/prerender_new_entry.html
deleted file mode 100644
index 1a20122..0000000
--- a/chrome/test/data/prerender/prerender_new_entry.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<html>
-  <!--
-  This test navigates to prerender_page.html after the onload event to create a
-  new navigation entry.
-  -->
-  <head>
-    <title>Prerender navigate after load</title>
-    <script>
-      window.onload = function() {
-        setTimeout(function() {
-          location.href = "prerender_page.html";
-        });
-      };
-    </script>
-  </head>
-  <body></body>
-</html>
diff --git a/chromecast/browser/cast_content_browser_client.cc b/chromecast/browser/cast_content_browser_client.cc
index ca955d2..ff12c5a 100644
--- a/chromecast/browser/cast_content_browser_client.cc
+++ b/chromecast/browser/cast_content_browser_client.cc
@@ -550,15 +550,6 @@
   return new CastQuotaPermissionContext();
 }
 
-void CastContentBrowserClient::GetQuotaSettings(
-    content::BrowserContext* context,
-    content::StoragePartition* partition,
-    storage::OptionalQuotaSettingsCallback callback) {
-  storage::GetNominalDynamicSettings(
-      partition->GetPath(), context->IsOffTheRecord(),
-      storage::GetDefaultDeviceInfoHelper(), std::move(callback));
-}
-
 void CastContentBrowserClient::AllowCertificateError(
     content::WebContents* web_contents,
     int cert_error,
diff --git a/chromecast/browser/cast_content_browser_client.h b/chromecast/browser/cast_content_browser_client.h
index 7bd355a..4297593 100644
--- a/chromecast/browser/cast_content_browser_client.h
+++ b/chromecast/browser/cast_content_browser_client.h
@@ -168,10 +168,6 @@
   std::string GetApplicationLocale() override;
   scoped_refptr<content::QuotaPermissionContext> CreateQuotaPermissionContext()
       override;
-  void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      storage::OptionalQuotaSettingsCallback callback) override;
   void AllowCertificateError(
       content::WebContents* web_contents,
       int cert_error,
diff --git a/chromeos/profiles/airmont.afdo.newest.txt b/chromeos/profiles/airmont.afdo.newest.txt
index c1d35aa..7f6e565 100644
--- a/chromeos/profiles/airmont.afdo.newest.txt
+++ b/chromeos/profiles/airmont.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-airmont-81-3987.0-1577100150-benchmark-81.0.4009.0-r1-redacted.afdo.xz
\ No newline at end of file
+chromeos-chrome-amd64-airmont-81-3987.18-1577702544-benchmark-81.0.4011.0-r1-redacted.afdo.xz
\ No newline at end of file
diff --git a/chromeos/profiles/broadwell.afdo.newest.txt b/chromeos/profiles/broadwell.afdo.newest.txt
index cc729739..b527432 100644
--- a/chromeos/profiles/broadwell.afdo.newest.txt
+++ b/chromeos/profiles/broadwell.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-broadwell-81-3945.86-1577097374-benchmark-81.0.4009.0-r1-redacted.afdo.xz
\ No newline at end of file
+chromeos-chrome-amd64-broadwell-81-3987.18-1577704324-benchmark-81.0.4011.0-r1-redacted.afdo.xz
\ No newline at end of file
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt
index 7e384c3d..ac20ebf 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-81-3987.0-1577099071-benchmark-81.0.4005.0-r1.orderfile.xz
\ No newline at end of file
+chromeos-chrome-orderfile-field-81-3987.0-1577099071-benchmark-81.0.4009.0-r1.orderfile.xz
\ No newline at end of file
diff --git a/chromeos/profiles/silvermont.afdo.newest.txt b/chromeos/profiles/silvermont.afdo.newest.txt
index 4bee3e6..9cb6b04 100644
--- a/chromeos/profiles/silvermont.afdo.newest.txt
+++ b/chromeos/profiles/silvermont.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-silvermont-81-3987.0-1577099071-benchmark-81.0.4009.0-r1-redacted.afdo.xz
\ No newline at end of file
+chromeos-chrome-amd64-silvermont-81-3987.18-1577705382-benchmark-81.0.4011.0-r1-redacted.afdo.xz
\ No newline at end of file
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats.cc
index be304c1..10082e3 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats.cc
@@ -333,13 +333,13 @@
 
   data_usage_reporting_enabled_.Init(
       prefs::kDataUsageReportingEnabled, pref_service_,
-      base::Bind(
+      base::BindRepeating(
           &DataReductionProxyCompressionStats::OnDataUsageReportingPrefChanged,
           weak_factory_.GetWeakPtr()));
 
   if (data_usage_reporting_enabled_.GetValue()) {
     current_data_usage_load_status_ = LOADING;
-    service_->LoadCurrentDataUsageBucket(base::Bind(
+    service_->LoadCurrentDataUsageBucket(base::BindRepeating(
         &DataReductionProxyCompressionStats::OnCurrentDataUsageLoaded,
         weak_factory_.GetWeakPtr()));
   }
@@ -522,8 +522,9 @@
 }
 
 void DataReductionProxyCompressionStats::GetHistoricalDataUsage(
-    const HistoricalDataUsageCallback& get_data_usage_callback) {
-  GetHistoricalDataUsageImpl(get_data_usage_callback, base::Time::Now());
+    HistoricalDataUsageCallback get_data_usage_callback) {
+  GetHistoricalDataUsageImpl(std::move(get_data_usage_callback),
+                             base::Time::Now());
 }
 
 void DataReductionProxyCompressionStats::DeleteBrowsingHistory(
@@ -751,7 +752,7 @@
 }
 
 void DataReductionProxyCompressionStats::GetHistoricalDataUsageImpl(
-    const HistoricalDataUsageCallback& get_data_usage_callback,
+    HistoricalDataUsageCallback get_data_usage_callback,
     const base::Time& now) {
 #if !defined(OS_ANDROID)
   if (current_data_usage_load_status_ != LOADED) {
@@ -759,8 +760,8 @@
     // extension can retry after a slight delay.
     // This use case is unlikely to occur in practice since current data usage
     // should have sufficient time to load before user tries to view data usage.
-    get_data_usage_callback.Run(
-        std::make_unique<std::vector<DataUsageBucket>>());
+    std::move(get_data_usage_callback)
+        .Run(std::make_unique<std::vector<DataUsageBucket>>());
     return;
   }
 #endif
@@ -779,14 +780,14 @@
     service_->StoreCurrentDataUsageBucket(std::move(data_usage_bucket));
   }
 
-  service_->LoadHistoricalDataUsage(get_data_usage_callback);
+  service_->LoadHistoricalDataUsage(std::move(get_data_usage_callback));
 }
 
 void DataReductionProxyCompressionStats::OnDataUsageReportingPrefChanged() {
   if (data_usage_reporting_enabled_.GetValue()) {
     if (current_data_usage_load_status_ == NOT_LOADED) {
       current_data_usage_load_status_ = LOADING;
-      service_->LoadCurrentDataUsageBucket(base::Bind(
+      service_->LoadCurrentDataUsageBucket(base::BindOnce(
           &DataReductionProxyCompressionStats::OnCurrentDataUsageLoaded,
           weak_factory_.GetWeakPtr()));
     }
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats.h
index 7e05e74d..708c9db 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats.h
@@ -135,7 +135,7 @@
   // in-memory stats could be initialized from storage. Data usage is sorted
   // chronologically with the last entry corresponding to |base::Time::Now()|.
   void GetHistoricalDataUsage(
-      const HistoricalDataUsageCallback& get_data_usage_callback);
+      HistoricalDataUsageCallback get_data_usage_callback);
 
   // Deletes browsing history from storage and memory for the given time
   // range. Currently, this method deletes all data usage for the given range.
@@ -242,7 +242,7 @@
   // Actual implementation of |GetHistoricalDataUsage|. This helper method
   // explicitly passes |base::Time::Now()| to make testing easier.
   void GetHistoricalDataUsageImpl(
-      const HistoricalDataUsageCallback& get_data_usage_callback,
+      HistoricalDataUsageCallback get_data_usage_callback,
       const base::Time& now);
 
   // Called when |prefs::kDataUsageReportingEnabled| pref values changes.
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats_unittest.cc
index 850e7edc..c8accb3 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats_unittest.cc
@@ -302,15 +302,15 @@
                                             original_size, time);
   }
 
-  void GetHistoricalDataUsage(
-      const HistoricalDataUsageCallback& onLoadDataUsage,
-      const base::Time& now) {
-    compression_stats_->GetHistoricalDataUsageImpl(onLoadDataUsage, now);
+  void GetHistoricalDataUsage(HistoricalDataUsageCallback on_load_data_usage,
+                              const base::Time& now) {
+    compression_stats_->GetHistoricalDataUsageImpl(
+        std::move(on_load_data_usage), now);
   }
 
-  void LoadHistoricalDataUsage(
-      const HistoricalDataUsageCallback& onLoadDataUsage) {
-    compression_stats_->service_->LoadHistoricalDataUsage(onLoadDataUsage);
+  void LoadHistoricalDataUsage(HistoricalDataUsageCallback on_load_data_usage) {
+    compression_stats_->service_->LoadHistoricalDataUsage(
+        std::move(on_load_data_usage));
   }
 
   void DeleteHistoricalDataUsage() {
@@ -700,8 +700,8 @@
 
   DataUsageLoadVerifier verifier(std::move(expected_data_usage));
 
-  GetHistoricalDataUsage(base::Bind(&DataUsageLoadVerifier::OnLoadDataUsage,
-                                    base::Unretained(&verifier)),
+  GetHistoricalDataUsage(base::BindOnce(&DataUsageLoadVerifier::OnLoadDataUsage,
+                                        base::Unretained(&verifier)),
                          now);
   base::RunLoop().RunUntilIdle();
 }
@@ -722,15 +722,15 @@
       std::make_unique<std::vector<data_reduction_proxy::DataUsageBucket>>(
           kNumExpectedBuckets);
   DataUsageLoadVerifier verifier1(std::move(expected_data_usage1));
-  LoadHistoricalDataUsage(base::Bind(&DataUsageLoadVerifier::OnLoadDataUsage,
-                                     base::Unretained(&verifier1)));
+  LoadHistoricalDataUsage(base::BindOnce(
+      &DataUsageLoadVerifier::OnLoadDataUsage, base::Unretained(&verifier1)));
 
   // Public API must return an empty array.
   auto expected_data_usage2 =
       std::make_unique<std::vector<data_reduction_proxy::DataUsageBucket>>();
   DataUsageLoadVerifier verifier2(std::move(expected_data_usage2));
-  GetHistoricalDataUsage(base::Bind(&DataUsageLoadVerifier::OnLoadDataUsage,
-                                    base::Unretained(&verifier2)),
+  GetHistoricalDataUsage(base::BindOnce(&DataUsageLoadVerifier::OnLoadDataUsage,
+                                        base::Unretained(&verifier2)),
                          now);
 #else
   // For Android don't delete data usage.
@@ -747,8 +747,8 @@
 
   DataUsageLoadVerifier verifier(std::move(expected_data_usage));
 
-  GetHistoricalDataUsage(base::Bind(&DataUsageLoadVerifier::OnLoadDataUsage,
-                                    base::Unretained(&verifier)),
+  GetHistoricalDataUsage(base::BindOnce(&DataUsageLoadVerifier::OnLoadDataUsage,
+                                        base::Unretained(&verifier)),
                          now);
 #endif
 
@@ -787,8 +787,8 @@
 
   DataUsageLoadVerifier verifier(std::move(expected_data_usage));
 
-  GetHistoricalDataUsage(base::Bind(&DataUsageLoadVerifier::OnLoadDataUsage,
-                                    base::Unretained(&verifier)),
+  GetHistoricalDataUsage(base::BindOnce(&DataUsageLoadVerifier::OnLoadDataUsage,
+                                        base::Unretained(&verifier)),
                          now);
   base::RunLoop().RunUntilIdle();
 }
@@ -825,8 +825,8 @@
 
   DataUsageLoadVerifier verifier(std::move(expected_data_usage));
 
-  GetHistoricalDataUsage(base::Bind(&DataUsageLoadVerifier::OnLoadDataUsage,
-                                    base::Unretained(&verifier)),
+  GetHistoricalDataUsage(base::BindOnce(&DataUsageLoadVerifier::OnLoadDataUsage,
+                                        base::Unretained(&verifier)),
                          now);
   base::RunLoop().RunUntilIdle();
 }
@@ -856,8 +856,8 @@
 
   DataUsageLoadVerifier verifier(std::move(expected_data_usage));
 
-  GetHistoricalDataUsage(base::Bind(&DataUsageLoadVerifier::OnLoadDataUsage,
-                                    base::Unretained(&verifier)),
+  GetHistoricalDataUsage(base::BindOnce(&DataUsageLoadVerifier::OnLoadDataUsage,
+                                        base::Unretained(&verifier)),
                          now);
   base::RunLoop().RunUntilIdle();
 }
@@ -881,8 +881,8 @@
           kNumExpectedBuckets);
   DataUsageLoadVerifier verifier(std::move(expected_data_usage));
 
-  GetHistoricalDataUsage(base::Bind(&DataUsageLoadVerifier::OnLoadDataUsage,
-                                    base::Unretained(&verifier)),
+  GetHistoricalDataUsage(base::BindOnce(&DataUsageLoadVerifier::OnLoadDataUsage,
+                                        base::Unretained(&verifier)),
                          now);
   base::RunLoop().RunUntilIdle();
 }
@@ -918,8 +918,8 @@
   site_usage->set_original_size(1100);
   DataUsageLoadVerifier verifier1(std::move(expected_data_usage));
 
-  LoadHistoricalDataUsage(base::Bind(&DataUsageLoadVerifier::OnLoadDataUsage,
-                                     base::Unretained(&verifier1)));
+  LoadHistoricalDataUsage(base::BindOnce(
+      &DataUsageLoadVerifier::OnLoadDataUsage, base::Unretained(&verifier1)));
   base::RunLoop().RunUntilIdle();
 
   // This should delete in-storage usage as well.
@@ -930,8 +930,8 @@
       std::make_unique<std::vector<data_reduction_proxy::DataUsageBucket>>(
           kNumExpectedBuckets);
   DataUsageLoadVerifier verifier2(std::move(expected_data_usage));
-  LoadHistoricalDataUsage(base::Bind(&DataUsageLoadVerifier::OnLoadDataUsage,
-                                     base::Unretained(&verifier2)));
+  LoadHistoricalDataUsage(base::BindOnce(
+      &DataUsageLoadVerifier::OnLoadDataUsage, base::Unretained(&verifier2)));
   base::RunLoop().RunUntilIdle();
 }
 
@@ -965,8 +965,8 @@
           kNumExpectedBuckets);
   DataUsageLoadVerifier verifier(std::move(expected_data_usage));
 
-  GetHistoricalDataUsage(base::Bind(&DataUsageLoadVerifier::OnLoadDataUsage,
-                                    base::Unretained(&verifier)),
+  GetHistoricalDataUsage(base::BindOnce(&DataUsageLoadVerifier::OnLoadDataUsage,
+                                        base::Unretained(&verifier)),
                          now);
   base::RunLoop().RunUntilIdle();
 
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
index 13eac80..72789a5f 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
@@ -347,9 +347,9 @@
     // It is safe to use base::Unretained here, since it gets executed
     // synchronously on the IO thread, and |this| outlives
     // |secure_proxy_checker_|.
-    SecureProxyCheck(
-        base::Bind(&DataReductionProxyConfig::HandleSecureProxyCheckResponse,
-                   base::Unretained(this)));
+    SecureProxyCheck(base::BindRepeating(
+        &DataReductionProxyConfig::HandleSecureProxyCheckResponse,
+        base::Unretained(this)));
   }
   network_properties_manager_->ResetWarmupURLFetchMetrics();
   FetchWarmupProbeURL();
@@ -601,9 +601,9 @@
     // It is safe to use base::Unretained here, since it gets executed
     // synchronously on the IO thread, and |this| outlives
     // |secure_proxy_checker_|.
-    SecureProxyCheck(
-        base::Bind(&DataReductionProxyConfig::HandleSecureProxyCheckResponse,
-                   base::Unretained(this)));
+    SecureProxyCheck(base::BindRepeating(
+        &DataReductionProxyConfig::HandleSecureProxyCheckResponse,
+        base::Unretained(this)));
   }
 }
 
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.h
index 3a034f3..fc9507d5 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.h
@@ -44,7 +44,7 @@
 class DataReductionProxyMutableConfigValues;
 class DataReductionProxyRequestOptions;
 
-typedef base::Callback<void(const std::string&)> ConfigStorer;
+using ConfigStorer = base::RepeatingCallback<void(const std::string&)>;
 
 // Retrieves the default net::BackoffEntry::Policy for the Data Reduction Proxy
 // configuration service client.
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.cc
index 98d7f48e..164a249 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.cc
@@ -321,7 +321,7 @@
 }
 
 void DataReductionProxyService::LoadHistoricalDataUsage(
-    const HistoricalDataUsageCallback& load_data_usage_callback) {
+    HistoricalDataUsageCallback load_data_usage_callback) {
   std::unique_ptr<std::vector<DataUsageBucket>> data_usage(
       new std::vector<DataUsageBucket>());
   std::vector<DataUsageBucket>* data_usage_ptr = data_usage.get();
@@ -330,11 +330,12 @@
       base::BindOnce(&DBDataOwner::LoadHistoricalDataUsage,
                      db_data_owner_->GetWeakPtr(),
                      base::Unretained(data_usage_ptr)),
-      base::BindOnce(load_data_usage_callback, std::move(data_usage)));
+      base::BindOnce(std::move(load_data_usage_callback),
+                     std::move(data_usage)));
 }
 
 void DataReductionProxyService::LoadCurrentDataUsageBucket(
-    const LoadCurrentDataUsageCallback& load_current_data_usage_callback) {
+    LoadCurrentDataUsageCallback load_current_data_usage_callback) {
   std::unique_ptr<DataUsageBucket> bucket(new DataUsageBucket());
   DataUsageBucket* bucket_ptr = bucket.get();
   db_task_runner_->PostTaskAndReply(
@@ -342,7 +343,8 @@
       base::BindOnce(&DBDataOwner::LoadCurrentDataUsageBucket,
                      db_data_owner_->GetWeakPtr(),
                      base::Unretained(bucket_ptr)),
-      base::BindOnce(load_current_data_usage_callback, std::move(bucket)));
+      base::BindOnce(std::move(load_current_data_usage_callback),
+                     std::move(bucket)));
 }
 
 void DataReductionProxyService::StoreCurrentDataUsageBucket(
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h
index 73c96e7f..b59b72d 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h
@@ -119,9 +119,9 @@
   virtual void SetProxyPrefs(bool enabled, bool at_startup);
 
   void LoadHistoricalDataUsage(
-      const HistoricalDataUsageCallback& load_data_usage_callback);
+      HistoricalDataUsageCallback load_data_usage_callback);
   void LoadCurrentDataUsageBucket(
-      const LoadCurrentDataUsageCallback& load_current_data_usage_callback);
+      LoadCurrentDataUsageCallback load_current_data_usage_callback);
   void StoreCurrentDataUsageBucket(std::unique_ptr<DataUsageBucket> current);
   void DeleteHistoricalDataUsage();
   void DeleteBrowsingHistory(const base::Time& start, const base::Time& end);
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h
index 6eb2b74c..a2bcc08 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h
@@ -80,7 +80,7 @@
 class DataReductionProxySettings {
  public:
   using SyntheticFieldTrialRegistrationCallback =
-      base::Callback<bool(base::StringPiece, base::StringPiece)>;
+      base::RepeatingCallback<bool(base::StringPiece, base::StringPiece)>;
 
   explicit DataReductionProxySettings(bool is_off_the_record_profile);
   virtual ~DataReductionProxySettings();
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings_test_utils.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings_test_utils.cc
index dde964e..2190ece7 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings_test_utils.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings_test_utils.cc
@@ -126,7 +126,7 @@
   settings_->InitDataReductionProxySettings(
       test_context_->pref_service(),
       std::move(settings_->data_reduction_proxy_service_));
-  settings_->SetCallbackToRegisterSyntheticFieldTrial(base::Bind(
+  settings_->SetCallbackToRegisterSyntheticFieldTrial(base::BindRepeating(
       &DataReductionProxySettingsTestBase::OnSyntheticFieldTrialRegistration,
       base::Unretained(this)));
 
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.cc
index 485cbdf..008f0f9 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.cc
@@ -468,8 +468,8 @@
     config_client.reset(new DataReductionProxyConfigServiceClient(
         GetBackoffPolicy(), request_options.get(), raw_mutable_config,
         config.get(), service.get(), test_network_connection_tracker,
-        base::Bind(&TestConfigStorer::StoreSerializedConfig,
-                   base::Unretained(config_storer.get()))));
+        base::BindRepeating(&TestConfigStorer::StoreSerializedConfig,
+                            base::Unretained(config_storer.get()))));
   }
 
   service->SetDependenciesForTesting(
diff --git a/components/data_reduction_proxy/core/browser/db_data_owner.h b/components/data_reduction_proxy/core/browser/db_data_owner.h
index 6acb002..9a63addf 100644
--- a/components/data_reduction_proxy/core/browser/db_data_owner.h
+++ b/components/data_reduction_proxy/core/browser/db_data_owner.h
@@ -19,12 +19,12 @@
 class DataUsageStore;
 
 // Callback for loading the historical data usage.
-typedef base::Callback<void(std::unique_ptr<std::vector<DataUsageBucket>>)>
-    HistoricalDataUsageCallback;
+using HistoricalDataUsageCallback =
+    base::OnceCallback<void(std::unique_ptr<std::vector<DataUsageBucket>>)>;
 
 // Callback for loading data usage for the current bucket.
-typedef base::Callback<void(std::unique_ptr<DataUsageBucket>)>
-    LoadCurrentDataUsageCallback;
+using LoadCurrentDataUsageCallback =
+    base::OnceCallback<void(std::unique_ptr<DataUsageBucket>)>;
 
 // Contains and initializes all Data Reduction Proxy objects that have a
 // lifetime based on the DB task runner.
diff --git a/components/optimization_guide/optimization_guide_features.cc b/components/optimization_guide/optimization_guide_features.cc
index ff7c4fe..adc0434 100644
--- a/components/optimization_guide/optimization_guide_features.cc
+++ b/components/optimization_guide/optimization_guide_features.cc
@@ -193,6 +193,17 @@
       "max_store_duration_for_host_model_features_in_days", 7));
 }
 
+size_t MaxHostsForOptimizationGuideServiceModelsFetch() {
+  return GetFieldTrialParamByFeatureAsInt(
+      kOptimizationTargetPrediction,
+      "max_hosts_for_optimization_guide_service_models_fetch", 30);
+}
+
+size_t MaxHostModelFeaturesCacheSize() {
+  return GetFieldTrialParamByFeatureAsInt(
+      kOptimizationTargetPrediction, "max_host_model_features_cache_size", 100);
+}
+
 bool IsOptimizationTargetPredictionEnabled() {
   return base::FeatureList::IsEnabled(kOptimizationTargetPrediction);
 }
diff --git a/components/optimization_guide/optimization_guide_features.h b/components/optimization_guide/optimization_guide_features.h
index 05aff07..1ce8a9ed 100644
--- a/components/optimization_guide/optimization_guide_features.h
+++ b/components/optimization_guide/optimization_guide_features.h
@@ -99,6 +99,14 @@
 // to be used and remain in the OptimizationGuideStore.
 base::TimeDelta StoredHostModelFeaturesFreshnessDuration();
 
+// The maximum number of hosts allowed to be requested by the client to the
+// remote Optimzation Guide Service for use by prediction models.
+size_t MaxHostsForOptimizationGuideServiceModelsFetch();
+
+// The maximum number of hosts allowed to be maintained in a least-recently-used
+// cache by the prediction manager.
+size_t MaxHostModelFeaturesCacheSize();
+
 // Returns true if the optimization target decision for |optimization_target|
 // should not be propagated to the caller in an effort to fully understand the
 // statistics for the served model and not taint the resulting data.
diff --git a/components/optimization_guide/optimization_guide_store.cc b/components/optimization_guide/optimization_guide_store.cc
index 0a24bea..7bfc4758 100644
--- a/components/optimization_guide/optimization_guide_store.cc
+++ b/components/optimization_guide/optimization_guide_store.cc
@@ -88,6 +88,12 @@
   return base::StartsWith(key, key_prefix, base::CompareCase::SENSITIVE);
 }
 
+// Returns true if |key| is in |keys_to_remove|.
+bool ExpiredKeyFilter(const base::flat_set<std::string>& keys_to_remove,
+                      const std::string& key) {
+  return keys_to_remove.find(key) != keys_to_remove.end();
+}
+
 }  // namespace
 
 OptimizationGuideStore::OptimizationGuideStore(
@@ -258,40 +264,50 @@
 void OptimizationGuideStore::PurgeExpiredFetchedHints() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (!IsAvailable()) {
+  if (!IsAvailable())
     return;
-  }
 
   // Load all the fetched hints to check their expiry times.
   database_->LoadKeysAndEntriesWithFilter(
       base::BindRepeating(&DatabasePrefixFilter,
                           GetFetchedHintEntryKeyPrefix()),
-      base::BindOnce(&OptimizationGuideStore::OnLoadFetchedHintsToPurgeExpired,
+      base::BindOnce(&OptimizationGuideStore::OnLoadEntriesToPurgeExpired,
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
-void OptimizationGuideStore::OnLoadFetchedHintsToPurgeExpired(
-    bool success,
-    std::unique_ptr<EntryMap> fetched_entries) {
+void OptimizationGuideStore::PurgeExpiredHostModelFeatures() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (!success) {
+  if (!IsAvailable())
     return;
-  }
 
-  auto keys_to_remove = std::make_unique<EntryKeySet>();
+  // Load all the host model features to check their expiry times.
+  database_->LoadKeysAndEntriesWithFilter(
+      base::BindRepeating(&DatabasePrefixFilter,
+                          GetHostModelFeaturesEntryKeyPrefix()),
+      base::BindOnce(&OptimizationGuideStore::OnLoadEntriesToPurgeExpired,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void OptimizationGuideStore::OnLoadEntriesToPurgeExpired(
+    bool success,
+    std::unique_ptr<EntryMap> entries) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!success)
+    return;
+
+  EntryKeySet expired_keys_to_remove;
   int64_t now_since_epoch =
       base::Time::Now().ToDeltaSinceWindowsEpoch().InSeconds();
 
-  for (const auto& entry : *fetched_entries) {
-    if (entry.second.expiry_time_secs() <= now_since_epoch) {
-      keys_to_remove->insert(entry.first);
+  for (const auto& entry : *entries) {
+    if (entry.second.has_expiry_time_secs() &&
+        entry.second.expiry_time_secs() <= now_since_epoch) {
+      expired_keys_to_remove.insert(entry.first);
     }
   }
 
-  // TODO(mcrouse): Record the number of hints that will be expired from the
-  // store.
-
   data_update_in_flight_ = true;
   entry_keys_.reset();
 
@@ -299,11 +315,7 @@
 
   database_->UpdateEntriesWithRemoveFilter(
       std::move(empty_entries),
-      base::BindRepeating(
-          [](EntryKeySet* keys_to_remove, const std::string& key) {
-            return keys_to_remove->find(key) != keys_to_remove->end();
-          },
-          keys_to_remove.get()),
+      base::BindRepeating(&ExpiredKeyFilter, std::move(expired_keys_to_remove)),
       base::BindOnce(&OptimizationGuideStore::OnUpdateStore,
                      weak_ptr_factory_.GetWeakPtr(), base::DoNothing::Once()));
 }
diff --git a/components/optimization_guide/optimization_guide_store.h b/components/optimization_guide/optimization_guide_store.h
index 9526708..7a82002 100644
--- a/components/optimization_guide/optimization_guide_store.h
+++ b/components/optimization_guide/optimization_guide_store.h
@@ -170,6 +170,11 @@
   // removed.
   void PurgeExpiredFetchedHints();
 
+  // Removes all host model features that have expired from the store.
+  // |entry_keys_| is updated after the expired host model features are
+  // removed.
+  void PurgeExpiredHostModelFeatures();
+
   // Creates and returns a StoreUpdateData object for Prediction Models. This
   // object is used to collect a batch of prediction models in a format that is
   // usable to update the store on a background thread. This is always created
@@ -344,11 +349,10 @@
       EntryKey* out_entry_key,
       const EntryKeyPrefix& entry_key_prefix) const;
 
-  // Callback that identifies any expired hints from |fetched_entries| and
+  // Callback that identifies any expired |entries| and
   // asynchronously removes them from the store.
-  void OnLoadFetchedHintsToPurgeExpired(
-      bool success,
-      std::unique_ptr<EntryMap> fetched_entries);
+  void OnLoadEntriesToPurgeExpired(bool success,
+                                   std::unique_ptr<EntryMap> entries);
 
   // Callback that runs after the database finishes being initialized. If
   // |purge_existing_data| is true, then unconditionally purges the database;
diff --git a/components/optimization_guide/optimization_guide_store_unittest.cc b/components/optimization_guide/optimization_guide_store_unittest.cc
index 1f99630..c468415 100644
--- a/components/optimization_guide/optimization_guide_store_unittest.cc
+++ b/components/optimization_guide/optimization_guide_store_unittest.cc
@@ -314,7 +314,18 @@
   void PurgeExpiredFetchedHints() {
     guide_store()->PurgeExpiredFetchedHints();
 
-    // OnFetchedHintsLoadedToMaybePurge
+    // OnLoadExpiredEntriesToPurge
+    db()->LoadCallback(true);
+    // OnUpdateStore
+    db()->UpdateCallback(true);
+    // OnLoadEntryKeys callback
+    db()->LoadCallback(true);
+  }
+
+  void PurgeExpiredHostModelFeatures() {
+    guide_store()->PurgeExpiredHostModelFeatures();
+
+    // OnLoadExpiredEntriesToPurge
     db()->LoadCallback(true);
     // OnUpdateStore
     db()->UpdateCallback(true);
@@ -2019,4 +2030,41 @@
   }
 }
 
+TEST_F(OptimizationGuideStoreTest, PurgeExpiredHostModelFeatures) {
+  base::HistogramTester histogram_tester;
+  size_t update_host_model_features_count = 5;
+  MetadataSchemaState schema_state = MetadataSchemaState::kValid;
+  base::Time update_time = base::Time().Now();
+  SeedInitialData(schema_state, 0, base::Time().Now());
+  CreateDatabase();
+  InitializeStore(schema_state);
+
+  std::unique_ptr<StoreUpdateData> update_data =
+      guide_store()->CreateUpdateDataForHostModelFeatures(
+          update_time, update_time -
+                           optimization_guide::features::
+                               StoredHostModelFeaturesFreshnessDuration());
+  ASSERT_TRUE(update_data);
+  SeedHostModelFeaturesUpdateData(update_data.get(),
+                                  update_host_model_features_count);
+  UpdateHostModelFeatures(std::move(update_data));
+
+  for (size_t i = 0; i < update_host_model_features_count; ++i) {
+    std::string host_suffix = GetHostSuffix(i);
+    OptimizationGuideStore::EntryKey entry_key;
+    EXPECT_TRUE(
+        guide_store()->FindHostModelFeaturesEntryKey(host_suffix, &entry_key));
+  }
+
+  // Remove expired host model features from the opt. guide store.
+  PurgeExpiredHostModelFeatures();
+
+  for (size_t i = 0; i < update_host_model_features_count; ++i) {
+    std::string host_suffix = GetHostSuffix(i);
+    OptimizationGuideStore::EntryKey entry_key;
+    EXPECT_FALSE(
+        guide_store()->FindHostModelFeaturesEntryKey(host_suffix, &entry_key));
+  }
+}
+
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/proto/hints.proto b/components/optimization_guide/proto/hints.proto
index f1fff87..5b641d9 100644
--- a/components/optimization_guide/proto/hints.proto
+++ b/components/optimization_guide/proto/hints.proto
@@ -27,12 +27,21 @@
   optional MatchedHintInfo matched_hint = 2;
 }
 
+// Information about a URL to request hints for.
+message UrlInfo {
+  // The URL that the client is requesting information for.
+  optional string url = 1;
+}
+
 // Request to return a set of hints that guide what optimizations to perform
 // on those hosts.
 message GetHintsRequest {
   // Information about the set of hosts to retrieve hints for.
   repeated HostInfo hosts = 1;
 
+  // Information about the set of URLs to retrieve hints for.
+  repeated UrlInfo urls = 4;
+
   // The set of optimization types that the requesting client can support
   // and perform.
   //
@@ -124,6 +133,11 @@
   // Example: A host suffix of example.com would match pages with host
   // sports.example.com, but not fooexample.com.
   HOST_SUFFIX = 1;
+  // The full URL to match.
+  //
+  // This will be an exact match of a page load URL, including query params and
+  // fragments.
+  FULL_URL = 2;
 }
 
 message Optimization {
@@ -206,6 +220,8 @@
   // It is expected that this version be sent along with subsequent requests
   // for hosts that match this hint.
   optional string version = 5;
+  // The maximum duration for which the hint should be used for.
+  optional Duration max_cache_duration = 6;
 }
 
 // Configuration and data for a Bloom filter.
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc
index d487731..3573d3b3 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.cc
@@ -239,23 +239,7 @@
 
 std::vector<CoreAccountId>
 ProfileOAuth2TokenServiceDelegateAndroid::GetAccounts() const {
-  std::vector<std::string> accounts;
-  JNIEnv* env = AttachCurrentThread();
-
-  // TODO(crbug.com/1028580) Pass |j_accounts| as a list of
-  // org.chromium.components.signin.identitymanager.CoreAccountId and convert it
-  // to CoreAccountId using ConvertFromJavaCoreAccountId.
-  ScopedJavaLocalRef<jobjectArray> j_accounts =
-      signin::Java_ProfileOAuth2TokenServiceDelegate_getAccounts(env);
-  ;
-  // TODO(fgorski): We may decide to filter out some of the accounts.
-  base::android::AppendJavaStringArrayToStringVector(env, j_accounts,
-                                                     &accounts);
-  std::vector<CoreAccountId> account_ids;
-  for (auto& account : accounts)
-    account_ids.push_back(CoreAccountId::FromString(account));
-
-  return account_ids;
+  return accounts_;
 }
 
 std::vector<std::string>
@@ -301,11 +285,7 @@
 
 void ProfileOAuth2TokenServiceDelegateAndroid::SetAccounts(
     const std::vector<CoreAccountId>& accounts) {
-  JNIEnv* env = AttachCurrentThread();
-  ScopedJavaLocalRef<jobjectArray> java_accounts(
-      base::android::ToJavaArrayOfStrings(env, ToStringList(accounts)));
-  signin::Java_ProfileOAuth2TokenServiceDelegate_setAccounts(env,
-                                                             java_accounts);
+  accounts_ = accounts;
 }
 
 std::unique_ptr<OAuth2AccessTokenFetcher>
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.h b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.h
index 7766266..f8755a56 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.h
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_android.h
@@ -130,11 +130,14 @@
   std::vector<CoreAccountId> GetSystemAccounts();
   // As |GetAccounts| but with only validated account IDs.
   std::vector<CoreAccountId> GetValidAccounts();
-  // Set accounts using Java's Oauth2TokenService.setAccounts.
+  // Set accounts that have been advertised by OnRefreshTokenAvailable.
   virtual void SetAccounts(const std::vector<CoreAccountId>& accounts);
 
   base::android::ScopedJavaGlobalRef<jobject> java_ref_;
 
+  // Accounts that have been advertised by OnRefreshTokenAvailable.
+  std::vector<CoreAccountId> accounts_;
+
   // Maps account_id to the last error for that account.
   std::map<CoreAccountId, GoogleServiceAuthError> errors_;
 
diff --git a/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/ProfileOAuth2TokenServiceDelegate.java b/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/ProfileOAuth2TokenServiceDelegate.java
index 8da601e..b5ecc669 100644
--- a/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/ProfileOAuth2TokenServiceDelegate.java
+++ b/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/ProfileOAuth2TokenServiceDelegate.java
@@ -11,7 +11,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.StrictModeContext;
 import org.chromium.base.ThreadUtils;
@@ -23,10 +22,7 @@
 import org.chromium.components.signin.AuthException;
 import org.chromium.net.NetworkChangeNotifier;
 
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -42,8 +38,6 @@
         implements AccountTrackerService.OnSystemAccountsSeededListener {
     private static final String TAG = "OAuth2TokenService";
 
-    public static final String STORED_ACCOUNTS_KEY = "google.services.stored_accounts";
-
     /**
      * A simple callback for getAccessToken.
      */
@@ -129,18 +123,6 @@
     }
 
     /**
-     * Called by native to list the accounts Id with OAuth2 refresh tokens.
-     * This can differ from getSystemAccountNames as the user add/remove accounts
-     * from the OS. updateAccountList should be called to keep these two
-     * in sync.
-     */
-    @CalledByNative
-    @VisibleForTesting
-    static String[] getAccounts() {
-        return getStoredAccounts();
-    }
-
-    /**
      * Called by native to retrieve OAuth2 tokens.
      * @param username The native username (email address).
      * @param scope The scope to get an auth token for (without Android-style 'oauth2:' prefix).
@@ -327,26 +309,6 @@
                 mNativeProfileOAuth2TokenServiceDelegate, accountId);
     }
 
-    private static String[] getStoredAccounts() {
-        Set<String> accounts =
-                ContextUtils.getAppSharedPreferences().getStringSet(STORED_ACCOUNTS_KEY, null);
-        return accounts == null ? new String[] {} : accounts.toArray(new String[0]);
-    }
-
-    /**
-     * Called by native to save the account IDs that have associated OAuth2 refresh tokens.
-     * This is called during updateAccountList to sync with getSystemAccountNames.
-     * @param accounts IDs to save.
-     */
-    @CalledByNative
-    private static void setAccounts(String[] accounts) {
-        Set<String> set = new HashSet<>(Arrays.asList(accounts));
-        ContextUtils.getAppSharedPreferences()
-                .edit()
-                .putStringSet(STORED_ACCOUNTS_KEY, set)
-                .apply();
-    }
-
     private interface AuthTask<T> {
         T run() throws AuthException;
         void onSuccess(T result);
diff --git a/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc b/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc
index 57bf523..1c17395 100644
--- a/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc
+++ b/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc
@@ -113,10 +113,13 @@
   std::vector<GrBackendSemaphore> begin_semaphores;
   SkSurfaceProps surface_props{0, kUnknown_SkPixelGeometry};
 
+  // Buffer queue is internal to GPU proc and handles texture initialization,
+  // so allow uncleared access.
   // TODO(vasilyt): Props and MSAA
   scoped_write_access_ = skia_representation_->BeginScopedWriteAccess(
       0 /* final_msaa_count */, surface_props, &begin_semaphores,
-      &end_semaphores_);
+      &end_semaphores_,
+      gpu::SharedImageRepresentation::AllowUnclearedAccess::kYes);
   DCHECK(scoped_write_access_);
   if (!begin_semaphores.empty()) {
     scoped_write_access_->surface()->wait(begin_semaphores.size(),
@@ -137,13 +140,18 @@
       SkSurface::BackendSurfaceAccess::kNoAccess, flush_info);
   scoped_write_access_.reset();
   end_semaphores_.clear();
+
+  // SkiaRenderer always draws the full frame.
+  skia_representation_->SetCleared();
 }
 
 void SkiaOutputDeviceBufferQueue::Image::BeginPresent() {
   DCHECK(!scoped_write_access_);
   DCHECK(!scoped_read_access_);
   scoped_read_access_ = gl_representation_->BeginScopedAccess(
-      GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
+      GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM,
+      gpu::SharedImageRepresentation::AllowUnclearedAccess::kNo);
+  DCHECK(scoped_read_access_);
 }
 
 void SkiaOutputDeviceBufferQueue::Image::EndPresent() {
diff --git a/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc b/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc
index 19ab3df..adf4e82e 100644
--- a/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc
+++ b/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc
@@ -251,7 +251,13 @@
     EXPECT_EQ(images.size(), (size_t)CountBuffers());
   }
 
-  Image* GetCurrentImage() { return output_device_->GetCurrentImage(); }
+  Image* GetCurrentImage() {
+    // Call Begin/EndPaint to ensusre the image is initialized before use.
+    output_device_->BeginPaint();
+    GrBackendSemaphore semaphore;
+    output_device_->EndPaint(semaphore);
+    return output_device_->GetCurrentImage();
+  }
 
   void SwapBuffers() {
     auto present_callback =
diff --git a/content/browser/indexed_db/indexed_db_browsertest.cc b/content/browser/indexed_db/indexed_db_browsertest.cc
index 995f59d..da293d9 100644
--- a/content/browser/indexed_db/indexed_db_browsertest.cc
+++ b/content/browser/indexed_db/indexed_db_browsertest.cc
@@ -60,6 +60,7 @@
 #include "storage/browser/blob/blob_storage_context.h"
 #include "storage/browser/database/database_util.h"
 #include "storage/browser/quota/quota_manager.h"
+#include "storage/browser/quota/quota_settings.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -84,6 +85,12 @@
   void SetUp() override {
     GetTestClassFactory()->Reset();
     IndexedDBClassFactory::SetIndexedDBClassFactoryGetter(GetIDBClassFactory);
+
+    // Some tests need more space than the default used for browser tests.
+    static storage::QuotaSettings quota_settings =
+        storage::GetHardCodedSettings(100 * 1024 * 1024);
+    StoragePartition::SetDefaultQuotaSettingsForTesting(&quota_settings);
+
     ContentBrowserTest::SetUp();
   }
 
@@ -92,6 +99,12 @@
     ContentBrowserTest::TearDown();
   }
 
+  bool UseProductionQuotaSettings() override {
+    // So that the browser test harness doesn't call
+    // SetDefaultQuotaSettingsForTesting and overwrite the settings above.
+    return true;
+  }
+
   void FailOperation(FailClass failure_class,
                      FailMethod failure_method,
                      int fail_on_instance_num,
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index f4430f1..6978dcbf 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -87,6 +87,7 @@
 #include "storage/browser/blob/blob_storage_context.h"
 #include "storage/browser/database/database_tracker.h"
 #include "storage/browser/quota/quota_manager.h"
+#include "storage/browser/quota/quota_settings.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 
 #if defined(OS_ANDROID)
@@ -106,6 +107,8 @@
 
 namespace {
 
+const storage::QuotaSettings* g_test_quota_settings;
+
 // A callback to create a URLLoaderFactory that is used in tests.
 StoragePartitionImpl::CreateNetworkFactoryCallback&
 GetCreateURLLoaderFactoryCallback() {
@@ -2284,8 +2287,15 @@
 
 void StoragePartitionImpl::GetQuotaSettings(
     storage::OptionalQuotaSettingsCallback callback) {
-  GetContentClient()->browser()->GetQuotaSettings(browser_context_, this,
-                                                  std::move(callback));
+  if (g_test_quota_settings) {
+    // For debugging tests harness can inject settings.
+    std::move(callback).Run(*g_test_quota_settings);
+    return;
+  }
+
+  storage::GetNominalDynamicSettings(
+      GetPath(), browser_context_->IsOffTheRecord(),
+      storage::GetDefaultDeviceInfoHelper(), std::move(callback));
 }
 
 void StoragePartitionImpl::InitNetworkContext() {
@@ -2370,4 +2380,9 @@
   origin_policy_manager_for_browser_process_.reset();
 }
 
+void StoragePartition::SetDefaultQuotaSettingsForTesting(
+    const storage::QuotaSettings* settings) {
+  g_test_quota_settings = settings;
+}
+
 }  // namespace content
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 6b11e44..dc800f6 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -395,16 +395,6 @@
   return nullptr;
 }
 
-void ContentBrowserClient::GetQuotaSettings(
-    BrowserContext* context,
-    StoragePartition* partition,
-    storage::OptionalQuotaSettingsCallback callback) {
-  DCHECK(context);
-
-  // By default, no quota is provided, embedders should override.
-  std::move(callback).Run(storage::GetNoQuotaSettings());
-}
-
 GeneratedCodeCacheSettings ContentBrowserClient::GetGeneratedCodeCacheSettings(
     BrowserContext* context) {
   // By default, code cache is disabled, embedders should override.
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 272fa03d..c10ea616 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -157,7 +157,6 @@
 
 namespace storage {
 class FileSystemBackend;
-struct QuotaSettings;
 }  // namespace storage
 
 namespace content {
@@ -751,15 +750,6 @@
   // Create and return a new quota permission context.
   virtual scoped_refptr<QuotaPermissionContext> CreateQuotaPermissionContext();
 
-  // Allows the embedder to provide settings that determine the amount
-  // of disk space that may be used by content facing storage apis like
-  // IndexedDatabase and ServiceWorker::CacheStorage and others.
-  virtual void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      base::OnceCallback<void(base::Optional<storage::QuotaSettings>)>
-          callback);
-
   // Allows the embedder to provide settings that determine if generated code
   // can be cached and the amount of disk space used for caching generated code.
   virtual GeneratedCodeCacheSettings GetGeneratedCodeCacheSettings(
diff --git a/content/public/browser/storage_partition.h b/content/public/browser/storage_partition.h
index da224f4..0e68e34 100644
--- a/content/public/browser/storage_partition.h
+++ b/content/public/browser/storage_partition.h
@@ -42,6 +42,7 @@
 namespace storage {
 class QuotaManager;
 class SpecialStoragePolicy;
+struct QuotaSettings;
 }
 
 namespace storage {
@@ -264,6 +265,11 @@
   // Wait until code cache's shutdown is complete. For test use only.
   virtual void WaitForCodeCacheShutdownForTesting() = 0;
 
+  // The value pointed to by |settings| should remain valid until the
+  // the function is called again with a new value or a nullptr.
+  static void SetDefaultQuotaSettingsForTesting(
+      const storage::QuotaSettings* settings);
+
  protected:
   virtual ~StoragePartition() {}
 };
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index fc255f5b..6f027f5 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -38,6 +38,7 @@
 #include "content/browser/scheduler/browser_task_executor.h"
 #include "content/browser/startup_data_impl.h"
 #include "content/browser/startup_helper.h"
+#include "content/browser/storage_partition_impl.h"
 #include "content/browser/tracing/memory_instrumentation_util.h"
 #include "content/browser/tracing/tracing_controller_impl.h"
 #include "content/public/app/content_main.h"
@@ -217,6 +218,16 @@
 void BrowserTestBase::SetUp() {
   set_up_called_ = true;
 
+  if (!UseProductionQuotaSettings()) {
+    // By default use hardcoded quota settings to have a consistent testing
+    // environment.
+    const int kQuota = 5 * 1024 * 1024;
+    quota_settings_ =
+        std::make_unique<storage::QuotaSettings>(kQuota * 5, kQuota, 0, 0);
+    StoragePartitionImpl::SetDefaultQuotaSettingsForTesting(
+        quota_settings_.get());
+  }
+
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
 
   // Features that depend on external factors (e.g. memory pressure monitor) can
@@ -322,7 +333,8 @@
   // not affect the results.
   command_line->AppendSwitchASCII(switches::kForceDisplayColorProfile, "srgb");
 
-  test_host_resolver_ = std::make_unique<TestHostResolver>();
+  if (!allow_network_access_to_host_resolutions_)
+    test_host_resolver_ = std::make_unique<TestHostResolver>();
 
   ContentBrowserSanityChecker scoped_enable_sanity_checks;
 
@@ -514,12 +526,18 @@
   ui::test::EventGeneratorDelegate::SetFactoryFunction(
       ui::test::EventGeneratorDelegate::FactoryFunction());
 #endif
+
+  StoragePartitionImpl::SetDefaultQuotaSettingsForTesting(nullptr);
 }
 
 bool BrowserTestBase::AllowFileAccessFromFiles() {
   return true;
 }
 
+bool BrowserTestBase::UseProductionQuotaSettings() {
+  return false;
+}
+
 void BrowserTestBase::SimulateNetworkServiceCrash() {
   CHECK(!IsInProcessNetworkService())
       << "Can't crash the network service if it's running in-process!";
@@ -734,6 +752,7 @@
     return;
 
   initialized_network_process_ = true;
+
   host_resolver()->DisableModifications();
 
   // Send the host resolver rules to the network service if it's in use. No need
@@ -741,6 +760,18 @@
   if (!IsOutOfProcessNetworkService())
     return;
 
+  mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
+  content::GetNetworkService()->BindTestInterface(
+      network_service_test.BindNewPipeAndPassReceiver());
+
+  // Do not set up host resolver rules if we allow the test to access
+  // the network.
+  if (allow_network_access_to_host_resolutions_) {
+    mojo::ScopedAllowSyncCallForTesting allow_sync_call;
+    network_service_test->SetAllowNetworkAccessToHostResolutions();
+    return;
+  }
+
   net::RuleBasedHostResolverProc::RuleList rules = host_resolver()->GetRules();
   std::vector<network::mojom::RulePtr> mojo_rules;
   for (const auto& rule : rules) {
@@ -792,10 +823,6 @@
   if (mojo_rules.empty())
     return;
 
-  mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
-  content::GetNetworkService()->BindTestInterface(
-      network_service_test.BindNewPipeAndPassReceiver());
-
   // Send the DNS rules to network service process. Android needs the RunLoop
   // to dispatch a Java callback that makes network process to enter native
   // code.
diff --git a/content/public/test/browser_test_base.h b/content/public/test/browser_test_base.h
index 36a06126..4ee8ea0 100644
--- a/content/public/test/browser_test_base.h
+++ b/content/public/test/browser_test_base.h
@@ -16,6 +16,7 @@
 #include "content/public/test/test_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/spawned_test_server/spawned_test_server.h"
+#include "storage/browser/quota/quota_settings.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -58,6 +59,11 @@
   // Override this to disallow accesses to be production-compatible.
   virtual bool AllowFileAccessFromFiles();
 
+  // By default browser tests use hardcoded quota settings for consistency,
+  // instead of dynamically based on available disk space. Tests can override
+  // this if they want to use the production path.
+  virtual bool UseProductionQuotaSettings();
+
   // Crash the Network Service process. Should only be called when
   // out-of-process Network Service is enabled. Re-applies any added host
   // resolver rules, though network tasks started before the call returns may
@@ -105,6 +111,12 @@
   // Sets expected browser exit code, in case it's different than 0 (success).
   void set_expected_exit_code(int code) { expected_exit_code_ = code; }
 
+  // Sets flag to allow host resolutions to reach the network. Must be called
+  // before Setup() to take effect.
+  void set_allow_network_access_to_host_resolutions() {
+    allow_network_access_to_host_resolutions_ = true;
+  }
+
   const net::SpawnedTestServer* spawned_test_server() const {
     return spawned_test_server_.get();
   }
@@ -201,10 +213,14 @@
   // not run and report a false positive result.
   bool set_up_called_;
 
+  std::unique_ptr<storage::QuotaSettings> quota_settings_;
+
   std::unique_ptr<NoRendererCrashesAssertion> no_renderer_crashes_assertion_;
 
   bool initialized_network_process_ = false;
 
+  bool allow_network_access_to_host_resolutions_ = false;
+
 #if defined(OS_POSIX)
   bool handle_sigterm_;
 #endif
diff --git a/content/public/test/network_service_test_helper.cc b/content/public/test/network_service_test_helper.cc
index 642ea508..96a42fd 100644
--- a/content/public/test/network_service_test_helper.cc
+++ b/content/public/test/network_service_test_helper.cc
@@ -72,7 +72,8 @@
       public base::MessageLoopCurrent::DestructionObserver {
  public:
   NetworkServiceTestImpl()
-      : memory_pressure_listener_(
+      : test_host_resolver_(new TestHostResolver()),
+        memory_pressure_listener_(
             base::DoNothing(),
             base::BindRepeating(&NetworkServiceTestHelper::
                                     NetworkServiceTestImpl::OnMemoryPressure,
@@ -102,7 +103,10 @@
   // network::mojom::NetworkServiceTest:
   void AddRules(std::vector<network::mojom::RulePtr> rules,
                 AddRulesCallback callback) override {
-    auto* host_resolver = test_host_resolver_.host_resolver();
+    // test_host_resolver_ may be empty if
+    // SetAllowNetworkAccessToHostResolutions was invoked.
+    DCHECK(test_host_resolver_);
+    auto* host_resolver = test_host_resolver_->host_resolver();
     for (const auto& rule : rules) {
       switch (rule->resolver_type) {
         case network::mojom::ResolverType::kResolverTypeFail:
@@ -198,6 +202,12 @@
     std::move(callback).Run();
   }
 
+  void SetAllowNetworkAccessToHostResolutions(
+      SetAllowNetworkAccessToHostResolutionsCallback callback) override {
+    test_host_resolver_.reset();
+    std::move(callback).Run();
+  }
+
   void CrashOnResolveHost(const std::string& host) override {
     network::HostResolver::SetResolveHostCallbackForTesting(
         base::BindRepeating(CrashResolveHost, host));
@@ -261,7 +271,7 @@
 
   bool registered_as_destruction_observer_ = false;
   mojo::ReceiverSet<network::mojom::NetworkServiceTest> receivers_;
-  TestHostResolver test_host_resolver_;
+  std::unique_ptr<TestHostResolver> test_host_resolver_;
   std::unique_ptr<net::MockCertVerifier> mock_cert_verifier_;
   std::unique_ptr<net::ScopedTransportSecurityStateSource>
       transport_security_state_source_;
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc
index 1621fe0..bd58e47 100644
--- a/content/shell/browser/shell_content_browser_client.cc
+++ b/content/shell/browser/shell_content_browser_client.cc
@@ -53,7 +53,6 @@
 #include "services/network/public/mojom/network_service.mojom.h"
 #include "services/service_manager/public/cpp/manifest.h"
 #include "services/service_manager/public/cpp/manifest_builder.h"
-#include "storage/browser/quota/quota_settings.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
 #include "ui/base/ui_base_features.h"
@@ -301,13 +300,6 @@
   return new ShellQuotaPermissionContext();
 }
 
-void ShellContentBrowserClient::GetQuotaSettings(
-    BrowserContext* context,
-    StoragePartition* partition,
-    storage::OptionalQuotaSettingsCallback callback) {
-  std::move(callback).Run(storage::GetHardCodedSettings(100 * 1024 * 1024));
-}
-
 GeneratedCodeCacheSettings
 ShellContentBrowserClient::GetGeneratedCodeCacheSettings(
     content::BrowserContext* context) {
diff --git a/content/shell/browser/shell_content_browser_client.h b/content/shell/browser/shell_content_browser_client.h
index 0bbbf81..1fec69a 100644
--- a/content/shell/browser/shell_content_browser_client.h
+++ b/content/shell/browser/shell_content_browser_client.h
@@ -14,7 +14,6 @@
 #include "build/build_config.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/shell/browser/shell_speech_recognition_manager_delegate.h"
-#include "storage/browser/quota/quota_settings.h"
 
 namespace content {
 
@@ -49,10 +48,6 @@
       WebContents* web_contents) override;
   scoped_refptr<content::QuotaPermissionContext> CreateQuotaPermissionContext()
       override;
-  void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      storage::OptionalQuotaSettingsCallback callback) override;
   GeneratedCodeCacheSettings GetGeneratedCodeCacheSettings(
       content::BrowserContext* context) override;
   base::OnceClosure SelectClientCertificate(
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 5908c91..700996b 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
@@ -44,6 +44,7 @@
 #include "gpu/config/gpu_switches.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "services/service_manager/public/cpp/binder_map.h"
+#include "storage/browser/quota/quota_settings.h"
 #include "url/origin.h"
 
 namespace content {
@@ -97,6 +98,12 @@
   DCHECK(!g_web_test_browser_client);
 
   g_web_test_browser_client = this;
+
+  // The 1GB limit is intended to give a large headroom to tests that need to
+  // build up a large data set and issue many concurrent reads or writes.
+  static storage::QuotaSettings quota_settings(
+      storage::GetHardCodedSettings(1024 * 1024 * 1024));
+  StoragePartition::SetDefaultQuotaSettingsForTesting(&quota_settings);
 }
 
 WebTestContentBrowserClient::~WebTestContentBrowserClient() {
@@ -240,15 +247,6 @@
   return browser_main_parts;
 }
 
-void WebTestContentBrowserClient::GetQuotaSettings(
-    BrowserContext* context,
-    StoragePartition* partition,
-    storage::OptionalQuotaSettingsCallback callback) {
-  // The 1GB limit is intended to give a large headroom to tests that need to
-  // build up a large data set and issue many concurrent reads or writes.
-  std::move(callback).Run(storage::GetHardCodedSettings(1024 * 1024 * 1024));
-}
-
 std::unique_ptr<OverlayWindow>
 WebTestContentBrowserClient::CreateWindowForPictureInPicture(
     PictureInPictureWindowController* controller) {
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 63252a35..0cd293f 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
@@ -50,10 +50,6 @@
                                       int child_process_id) override;
   std::unique_ptr<BrowserMainParts> CreateBrowserMainParts(
       const MainFunctionParams& parameters) override;
-  void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      storage::OptionalQuotaSettingsCallback callback) override;
   std::vector<url::Origin> GetOriginsRequiringDedicatedProcess() override;
   std::unique_ptr<OverlayWindow> CreateWindowForPictureInPicture(
       PictureInPictureWindowController* controller) override;
diff --git a/content/test/test_content_browser_client.cc b/content/test/test_content_browser_client.cc
index fc70a57d..6d0e1be 100644
--- a/content/test/test_content_browser_client.cc
+++ b/content/test/test_content_browser_client.cc
@@ -7,7 +7,6 @@
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "content/public/browser/browser_context.h"
-#include "storage/browser/quota/quota_settings.h"
 
 #if defined(OS_ANDROID)
 #include "content/shell/android/shell_descriptors.h"
@@ -38,13 +37,6 @@
   return GeneratedCodeCacheSettings(true, 0, context->GetPath());
 }
 
-void TestContentBrowserClient::GetQuotaSettings(
-    BrowserContext* context,
-    StoragePartition* partition,
-    storage::OptionalQuotaSettingsCallback callback) {
-  std::move(callback).Run(storage::GetHardCodedSettings(100 * 1024 * 1024));
-}
-
 std::string TestContentBrowserClient::GetUserAgent() {
   return std::string("TestContentClient");
 }
diff --git a/content/test/test_content_browser_client.h b/content/test/test_content_browser_client.h
index 18df270..78e2aeb 100644
--- a/content/test/test_content_browser_client.h
+++ b/content/test/test_content_browser_client.h
@@ -12,7 +12,6 @@
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "content/public/browser/content_browser_client.h"
-#include "storage/browser/quota/quota_settings.h"
 
 namespace content {
 
@@ -24,10 +23,6 @@
   base::FilePath GetDefaultDownloadDirectory() override;
   GeneratedCodeCacheSettings GetGeneratedCodeCacheSettings(
       content::BrowserContext* context) override;
-  void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      storage::OptionalQuotaSettingsCallback callback) override;
   std::string GetUserAgent() override;
 #if defined(OS_ANDROID)
   void GetAdditionalMappedFilesForChildProcess(
diff --git a/device/fido/bio/enrollment_handler.cc b/device/fido/bio/enrollment_handler.cc
index 5466e99..7ed63cc 100644
--- a/device/fido/bio/enrollment_handler.cc
+++ b/device/fido/bio/enrollment_handler.cc
@@ -163,29 +163,10 @@
 void BioEnrollmentHandler::OnHavePIN(std::string pin) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(state_, State::kWaitingForPIN);
-  state_ = State::kGettingEphemeralKey;
-  authenticator_->GetEphemeralKey(
-      base::BindOnce(&BioEnrollmentHandler::OnHaveEphemeralKey,
-                     weak_factory_.GetWeakPtr(), std::move(pin)));
-}
-
-void BioEnrollmentHandler::OnHaveEphemeralKey(
-    std::string pin,
-    CtapDeviceResponseCode status,
-    base::Optional<pin::KeyAgreementResponse> response) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_EQ(State::kGettingEphemeralKey, state_);
-
-  if (status != CtapDeviceResponseCode::kSuccess) {
-    Finish(BioEnrollmentStatus::kAuthenticatorResponseInvalid);
-    return;
-  }
-
   state_ = State::kGettingPINToken;
   authenticator_->GetPINToken(
-      std::move(pin), *response,
-      base::BindOnce(&BioEnrollmentHandler::OnHavePINToken,
-                     weak_factory_.GetWeakPtr()));
+      std::move(pin), base::BindOnce(&BioEnrollmentHandler::OnHavePINToken,
+                                     weak_factory_.GetWeakPtr()));
 }
 
 void BioEnrollmentHandler::OnHavePINToken(
diff --git a/device/fido/bio/enrollment_handler.h b/device/fido/bio/enrollment_handler.h
index 73f570c..d69d392 100644
--- a/device/fido/bio/enrollment_handler.h
+++ b/device/fido/bio/enrollment_handler.h
@@ -85,7 +85,6 @@
     kWaitingForTouch,
     kGettingRetries,
     kWaitingForPIN,
-    kGettingEphemeralKey,
     kGettingPINToken,
     kReady,
     kEnrolling,
@@ -105,9 +104,6 @@
   void OnRetriesResponse(CtapDeviceResponseCode,
                          base::Optional<pin::RetriesResponse>);
   void OnHavePIN(std::string pin);
-  void OnHaveEphemeralKey(std::string,
-                          CtapDeviceResponseCode,
-                          base::Optional<pin::KeyAgreementResponse>);
   void OnHavePINToken(CtapDeviceResponseCode,
                       base::Optional<pin::TokenResponse>);
   void OnEnrollResponse(SampleCallback,
diff --git a/device/fido/credential_management_handler.cc b/device/fido/credential_management_handler.cc
index e49d31f3..d5cb44f 100644
--- a/device/fido/credential_management_handler.cc
+++ b/device/fido/credential_management_handler.cc
@@ -115,29 +115,9 @@
     return;
   }
 
-  state_ = State::kGettingEphemeralKey;
-  authenticator_->GetEphemeralKey(
-      base::BindOnce(&CredentialManagementHandler::OnHaveEphemeralKey,
-                     weak_factory_.GetWeakPtr(), std::move(pin)));
-}
-
-void CredentialManagementHandler::OnHaveEphemeralKey(
-    std::string pin,
-    CtapDeviceResponseCode status,
-    base::Optional<pin::KeyAgreementResponse> response) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_EQ(State::kGettingEphemeralKey, state_);
-
-  if (status != CtapDeviceResponseCode::kSuccess) {
-    state_ = State::kFinished;
-    std::move(finished_callback_)
-        .Run(CredentialManagementStatus::kAuthenticatorResponseInvalid);
-    return;
-  }
-
   state_ = State::kGettingPINToken;
   authenticator_->GetPINToken(
-      std::move(pin), *response,
+      std::move(pin),
       base::BindOnce(&CredentialManagementHandler::OnHavePINToken,
                      weak_factory_.GetWeakPtr()));
 }
diff --git a/device/fido/credential_management_handler.h b/device/fido/credential_management_handler.h
index b6528d12..757f399 100644
--- a/device/fido/credential_management_handler.h
+++ b/device/fido/credential_management_handler.h
@@ -90,7 +90,6 @@
     kWaitingForTouch,
     kGettingRetries,
     kWaitingForPIN,
-    kGettingEphemeralKey,
     kGettingPINToken,
     kReady,
     kGettingMetadata,
@@ -108,9 +107,6 @@
   void OnRetriesResponse(CtapDeviceResponseCode status,
                          base::Optional<pin::RetriesResponse> response);
   void OnHavePIN(std::string pin);
-  void OnHaveEphemeralKey(std::string pin,
-                          CtapDeviceResponseCode status,
-                          base::Optional<pin::KeyAgreementResponse> response);
   void OnHavePINToken(CtapDeviceResponseCode status,
                       base::Optional<pin::TokenResponse> response);
   void OnCredentialsMetadata(
diff --git a/device/fido/fido_authenticator.cc b/device/fido/fido_authenticator.cc
index 11f8d3b..2d6208e 100644
--- a/device/fido/fido_authenticator.cc
+++ b/device/fido/fido_authenticator.cc
@@ -9,7 +9,6 @@
 #include "base/callback.h"
 #include "base/logging.h"
 #include "device/fido/fido_constants.h"
-#include "device/fido/pin.h"
 
 namespace device {
 
@@ -25,27 +24,19 @@
   NOTREACHED();
 }
 
-void FidoAuthenticator::GetEphemeralKey(
-    FidoAuthenticator::GetEphemeralKeyCallback callback) {
-  NOTREACHED();
-}
-
 void FidoAuthenticator::GetPINToken(
     std::string pin,
-    const pin::KeyAgreementResponse& peer_key,
     FidoAuthenticator::GetPINTokenCallback callback) {
   NOTREACHED();
 }
 
 void FidoAuthenticator::SetPIN(const std::string& pin,
-                               const pin::KeyAgreementResponse& peer_key,
                                FidoAuthenticator::SetPINCallback callback) {
   NOTREACHED();
 }
 
 void FidoAuthenticator::ChangePIN(const std::string& old_pin,
                                   const std::string& new_pin,
-                                  pin::KeyAgreementResponse& peer_key,
                                   SetPINCallback callback) {
   NOTREACHED();
 }
diff --git a/device/fido/fido_authenticator.h b/device/fido/fido_authenticator.h
index 8216874..96de5c5 100644
--- a/device/fido/fido_authenticator.h
+++ b/device/fido/fido_authenticator.h
@@ -21,7 +21,6 @@
 #include "device/fido/credential_management.h"
 #include "device/fido/fido_request_handler_base.h"
 #include "device/fido/fido_transport_protocol.h"
-#include "device/fido/pin.h"
 
 namespace device {
 
@@ -30,7 +29,6 @@
 
 namespace pin {
 struct RetriesResponse;
-struct KeyAgreementResponse;
 struct EmptyResponse;
 class TokenResponse;
 }  // namespace pin
@@ -49,9 +47,6 @@
   using GetRetriesCallback =
       base::OnceCallback<void(CtapDeviceResponseCode,
                               base::Optional<pin::RetriesResponse>)>;
-  using GetEphemeralKeyCallback =
-      base::OnceCallback<void(CtapDeviceResponseCode,
-                              base::Optional<pin::KeyAgreementResponse>)>;
   using GetPINTokenCallback =
       base::OnceCallback<void(CtapDeviceResponseCode,
                               base::Optional<pin::TokenResponse>)>;
@@ -94,30 +89,21 @@
   // authenticator locks. It is only valid to call this method if |Options|
   // indicates that the authenticator supports PINs.
   virtual void GetRetries(GetRetriesCallback callback);
-  // GetEphemeralKey fetches an ephemeral P-256 key from the authenticator for
-  // use in protecting transmitted PINs. It is only valid to call this method if
-  // |Options| indicates that the authenticator supports PINs.
-  virtual void GetEphemeralKey(GetEphemeralKeyCallback callback);
   // GetPINToken uses the given PIN to request a PIN-token from an
   // authenticator. It is only valid to call this method if |Options| indicates
   // that the authenticator supports PINs.
-  virtual void GetPINToken(std::string pin,
-                           const pin::KeyAgreementResponse& peer_key,
-                           GetPINTokenCallback callback);
+  virtual void GetPINToken(std::string pin, GetPINTokenCallback callback);
   // SetPIN sets a new PIN on a device that does not currently have one. The
   // length of |pin| must respect |pin::kMinLength| and |pin::kMaxLength|. It is
   // only valid to call this method if |Options| indicates that the
   // authenticator supports PINs.
-  virtual void SetPIN(const std::string& pin,
-                      const pin::KeyAgreementResponse& peer_key,
-                      SetPINCallback callback);
+  virtual void SetPIN(const std::string& pin, SetPINCallback callback);
   // ChangePIN alters the PIN on a device that already has a PIN set. The
   // length of |pin| must respect |pin::kMinLength| and |pin::kMaxLength|. It is
   // only valid to call this method if |Options| indicates that the
   // authenticator supports PINs.
   virtual void ChangePIN(const std::string& old_pin,
                          const std::string& new_pin,
-                         pin::KeyAgreementResponse& peer_key,
                          SetPINCallback callback);
 
   // MakeCredentialPINDisposition enumerates the possible interactions between
diff --git a/device/fido/fido_device_authenticator.cc b/device/fido/fido_device_authenticator.cc
index 3ca8dd6..ea7ac3127 100644
--- a/device/fido/fido_device_authenticator.cc
+++ b/device/fido/fido_device_authenticator.cc
@@ -120,13 +120,26 @@
 
 void FidoDeviceAuthenticator::GetPINToken(
     std::string pin,
-    const pin::KeyAgreementResponse& peer_key,
     GetPINTokenCallback callback) {
   DCHECK(Options());
   DCHECK(Options()->client_pin_availability !=
          AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported);
 
-  pin::TokenRequest request(pin, peer_key);
+  GetEphemeralKey(base::BindOnce(
+      &FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken,
+      weak_factory_.GetWeakPtr(), std::move(pin), std::move(callback)));
+}
+
+void FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken(
+    std::string pin,
+    GetPINTokenCallback callback,
+    CtapDeviceResponseCode status,
+    base::Optional<pin::KeyAgreementResponse> key) {
+  if (status != CtapDeviceResponseCode::kSuccess) {
+    std::move(callback).Run(status, base::nullopt);
+    return;
+  }
+  pin::TokenRequest request(pin, *key);
   std::array<uint8_t, 32> shared_key = request.shared_key();
   RunOperation<pin::TokenRequest, pin::TokenResponse>(
       std::move(request), std::move(callback),
@@ -134,27 +147,57 @@
 }
 
 void FidoDeviceAuthenticator::SetPIN(const std::string& pin,
-                                     const pin::KeyAgreementResponse& peer_key,
                                      SetPINCallback callback) {
   DCHECK(Options());
   DCHECK(Options()->client_pin_availability !=
          AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported);
 
+  GetEphemeralKey(base::BindOnce(
+      &FidoDeviceAuthenticator::OnHaveEphemeralKeyForSetPIN,
+      weak_factory_.GetWeakPtr(), std::move(pin), std::move(callback)));
+}
+
+void FidoDeviceAuthenticator::OnHaveEphemeralKeyForSetPIN(
+    std::string pin,
+    SetPINCallback callback,
+    CtapDeviceResponseCode status,
+    base::Optional<pin::KeyAgreementResponse> key) {
+  if (status != CtapDeviceResponseCode::kSuccess) {
+    std::move(callback).Run(status, base::nullopt);
+    return;
+  }
+
   RunOperation<pin::SetRequest, pin::EmptyResponse>(
-      pin::SetRequest(pin, peer_key), std::move(callback),
+      pin::SetRequest(pin, *key), std::move(callback),
       base::BindOnce(&pin::EmptyResponse::Parse));
 }
 
 void FidoDeviceAuthenticator::ChangePIN(const std::string& old_pin,
                                         const std::string& new_pin,
-                                        pin::KeyAgreementResponse& peer_key,
                                         SetPINCallback callback) {
   DCHECK(Options());
   DCHECK(Options()->client_pin_availability !=
          AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported);
 
+  GetEphemeralKey(
+      base::BindOnce(&FidoDeviceAuthenticator::OnHaveEphemeralKeyForChangePIN,
+                     weak_factory_.GetWeakPtr(), std::move(old_pin),
+                     std::move(new_pin), std::move(callback)));
+}
+
+void FidoDeviceAuthenticator::OnHaveEphemeralKeyForChangePIN(
+    std::string old_pin,
+    std::string new_pin,
+    SetPINCallback callback,
+    CtapDeviceResponseCode status,
+    base::Optional<pin::KeyAgreementResponse> key) {
+  if (status != CtapDeviceResponseCode::kSuccess) {
+    std::move(callback).Run(status, base::nullopt);
+    return;
+  }
+
   RunOperation<pin::ChangeRequest, pin::EmptyResponse>(
-      pin::ChangeRequest(old_pin, new_pin, peer_key), std::move(callback),
+      pin::ChangeRequest(old_pin, new_pin, *key), std::move(callback),
       base::BindOnce(&pin::EmptyResponse::Parse));
 }
 
diff --git a/device/fido/fido_device_authenticator.h b/device/fido/fido_device_authenticator.h
index 0f0c2b7..d554188 100644
--- a/device/fido/fido_device_authenticator.h
+++ b/device/fido/fido_device_authenticator.h
@@ -50,16 +50,12 @@
   void GetNextAssertion(GetAssertionCallback callback) override;
   void GetTouch(base::OnceCallback<void()> callback) override;
   void GetRetries(GetRetriesCallback callback) override;
-  void GetEphemeralKey(GetEphemeralKeyCallback callback) override;
   void GetPINToken(std::string pin,
-                   const pin::KeyAgreementResponse& peer_key,
                    GetPINTokenCallback callback) override;
   void SetPIN(const std::string& pin,
-              const pin::KeyAgreementResponse& peer_key,
               SetPINCallback callback) override;
   void ChangePIN(const std::string& old_pin,
                  const std::string& new_pin,
-                 pin::KeyAgreementResponse& peer_key,
                  SetPINCallback callback) override;
   MakeCredentialPINDisposition WillNeedPINToMakeCredential(
       const CtapMakeCredentialRequest& request,
@@ -125,7 +121,27 @@
       base::Optional<std::vector<uint8_t>> response_data);
 
  private:
+  using GetEphemeralKeyCallback =
+      base::OnceCallback<void(CtapDeviceResponseCode,
+                              base::Optional<pin::KeyAgreementResponse>)>;
   void InitializeAuthenticatorDone(base::OnceClosure callback);
+  void GetEphemeralKey(GetEphemeralKeyCallback callback);
+  void OnHaveEphemeralKeyForGetPINToken(
+      std::string pin,
+      GetPINTokenCallback callback,
+      CtapDeviceResponseCode status,
+      base::Optional<pin::KeyAgreementResponse> key);
+  void OnHaveEphemeralKeyForSetPIN(
+      std::string pin,
+      SetPINCallback callback,
+      CtapDeviceResponseCode status,
+      base::Optional<pin::KeyAgreementResponse> key);
+  void OnHaveEphemeralKeyForChangePIN(
+      std::string old_pin,
+      std::string new_pin,
+      SetPINCallback callback,
+      CtapDeviceResponseCode status,
+      base::Optional<pin::KeyAgreementResponse> key);
 
   template <typename... Args>
   void TaskClearProxy(base::OnceCallback<void(Args...)> callback, Args... args);
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index f515d7e1..10e72a05 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -559,30 +559,9 @@
     return;
   }
 
-  state_ = State::kGetEphemeralKey;
-  authenticator_->GetEphemeralKey(
-      base::BindOnce(&GetAssertionRequestHandler::OnHaveEphemeralKey,
-                     weak_factory_.GetWeakPtr(), std::move(pin)));
-}
-
-void GetAssertionRequestHandler::OnHaveEphemeralKey(
-    std::string pin,
-    CtapDeviceResponseCode status,
-    base::Optional<pin::KeyAgreementResponse> response) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
-  DCHECK_EQ(State::kGetEphemeralKey, state_);
-
-  if (status != CtapDeviceResponseCode::kSuccess) {
-    state_ = State::kFinished;
-    std::move(completion_callback_)
-        .Run(GetAssertionStatus::kAuthenticatorResponseInvalid, base::nullopt,
-             nullptr);
-    return;
-  }
-
   state_ = State::kRequestWithPIN;
   authenticator_->GetPINToken(
-      std::move(pin), *response,
+      std::move(pin),
       base::BindOnce(&GetAssertionRequestHandler::OnHavePINToken,
                      weak_factory_.GetWeakPtr()));
 }
diff --git a/device/fido/get_assertion_request_handler.h b/device/fido/get_assertion_request_handler.h
index 78121eb5..f02b7c5 100644
--- a/device/fido/get_assertion_request_handler.h
+++ b/device/fido/get_assertion_request_handler.h
@@ -25,7 +25,6 @@
 class FidoDiscoveryFactory;
 
 namespace pin {
-struct KeyAgreementResponse;
 struct RetriesResponse;
 class TokenResponse;
 }  // namespace pin
@@ -69,7 +68,6 @@
     kWaitingForSecondTouch,
     kGettingRetries,
     kWaitingForPIN,
-    kGetEphemeralKey,
     kRequestWithPIN,
     kReadingMultipleResponses,
     kFinished,
@@ -95,9 +93,6 @@
   void OnRetriesResponse(CtapDeviceResponseCode status,
                          base::Optional<pin::RetriesResponse> response);
   void OnHavePIN(std::string pin);
-  void OnHaveEphemeralKey(std::string pin,
-                          CtapDeviceResponseCode status,
-                          base::Optional<pin::KeyAgreementResponse> response);
   void OnHavePINToken(CtapDeviceResponseCode status,
                       base::Optional<pin::TokenResponse> response);
 
diff --git a/device/fido/make_credential_request_handler.cc b/device/fido/make_credential_request_handler.cc
index a3d41f54..84c810d 100644
--- a/device/fido/make_credential_request_handler.cc
+++ b/device/fido/make_credential_request_handler.cc
@@ -489,15 +489,19 @@
   }
 
   if (state_ == State::kWaitingForPIN) {
-    state_ = State::kGetEphemeralKey;
-  } else {
-    DCHECK_EQ(state_, State::kWaitingForNewPIN);
-    state_ = State::kGetEphemeralKeyForNewPIN;
+    state_ = State::kRequestWithPIN;
+    authenticator_->GetPINToken(
+        std::move(pin),
+        base::BindOnce(&MakeCredentialRequestHandler::OnHavePINToken,
+                       weak_factory_.GetWeakPtr()));
+    return;
   }
 
-  authenticator_->GetEphemeralKey(
-      base::BindOnce(&MakeCredentialRequestHandler::OnHaveEphemeralKey,
-                     weak_factory_.GetWeakPtr(), std::move(pin)));
+  DCHECK_EQ(state_, State::kWaitingForNewPIN);
+  state_ = State::kSettingPIN;
+  authenticator_->SetPIN(
+      pin, base::BindOnce(&MakeCredentialRequestHandler::OnHaveSetPIN,
+                          weak_factory_.GetWeakPtr(), pin));
 }
 
 void MakeCredentialRequestHandler::OnRetriesResponse(
@@ -525,41 +529,8 @@
                      weak_factory_.GetWeakPtr()));
 }
 
-void MakeCredentialRequestHandler::OnHaveEphemeralKey(
-    std::string pin,
-    CtapDeviceResponseCode status,
-    base::Optional<pin::KeyAgreementResponse> response) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
-  DCHECK(state_ == State::kGetEphemeralKey ||
-         state_ == State::kGetEphemeralKeyForNewPIN);
-
-  if (status != CtapDeviceResponseCode::kSuccess) {
-    state_ = State::kFinished;
-    std::move(completion_callback_)
-        .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid, base::nullopt,
-             nullptr);
-    return;
-  }
-
-  if (state_ == State::kGetEphemeralKey) {
-    state_ = State::kRequestWithPIN;
-    authenticator_->GetPINToken(
-        std::move(pin), *response,
-        base::BindOnce(&MakeCredentialRequestHandler::OnHavePINToken,
-                       weak_factory_.GetWeakPtr()));
-  } else {
-    DCHECK_EQ(state_, State::kGetEphemeralKeyForNewPIN);
-    state_ = State::kSettingPIN;
-    authenticator_->SetPIN(
-        pin, *response,
-        base::BindOnce(&MakeCredentialRequestHandler::OnHaveSetPIN,
-                       weak_factory_.GetWeakPtr(), pin, *response));
-  }
-}
-
 void MakeCredentialRequestHandler::OnHaveSetPIN(
     std::string pin,
-    pin::KeyAgreementResponse key_agreement,
     CtapDeviceResponseCode status,
     base::Optional<pin::EmptyResponse> response) {
   DCHECK_EQ(state_, State::kSettingPIN);
@@ -576,7 +547,7 @@
   // get a PIN token.
   state_ = State::kRequestWithPIN;
   authenticator_->GetPINToken(
-      std::move(pin), key_agreement,
+      std::move(pin),
       base::BindOnce(&MakeCredentialRequestHandler::OnHavePINToken,
                      weak_factory_.GetWeakPtr()));
 }
diff --git a/device/fido/make_credential_request_handler.h b/device/fido/make_credential_request_handler.h
index 1970a007..3938f04 100644
--- a/device/fido/make_credential_request_handler.h
+++ b/device/fido/make_credential_request_handler.h
@@ -28,7 +28,6 @@
 
 namespace pin {
 struct EmptyResponse;
-struct KeyAgreementResponse;
 struct RetriesResponse;
 class TokenResponse;
 }  // namespace pin
@@ -76,8 +75,6 @@
     kGettingRetries,
     kWaitingForPIN,
     kWaitingForNewPIN,
-    kGetEphemeralKey,
-    kGetEphemeralKeyForNewPIN,
     kSettingPIN,
     kRequestWithPIN,
     kFinished,
@@ -97,11 +94,7 @@
   void OnHavePIN(std::string pin);
   void OnRetriesResponse(CtapDeviceResponseCode status,
                          base::Optional<pin::RetriesResponse> response);
-  void OnHaveEphemeralKey(std::string pin,
-                          CtapDeviceResponseCode status,
-                          base::Optional<pin::KeyAgreementResponse> response);
   void OnHaveSetPIN(std::string pin,
-                    pin::KeyAgreementResponse key_agreement,
                     CtapDeviceResponseCode status,
                     base::Optional<pin::EmptyResponse> response);
   void OnHavePINToken(CtapDeviceResponseCode status,
diff --git a/device/fido/set_pin_request_handler.cc b/device/fido/set_pin_request_handler.cc
index 149074e..bfd663d 100644
--- a/device/fido/set_pin_request_handler.cc
+++ b/device/fido/set_pin_request_handler.cc
@@ -44,10 +44,18 @@
     return;
   }
 
-  state_ = State::kGetEphemeralKey;
-  authenticator_->GetEphemeralKey(base::BindOnce(
-      &SetPINRequestHandler::OnHaveEphemeralKey, weak_factory_.GetWeakPtr(),
-      std::move(old_pin), std::move(new_pin)));
+  state_ = State::kSettingPIN;
+
+  if (old_pin.empty()) {
+    authenticator_->SetPIN(
+        new_pin, base::BindOnce(&SetPINRequestHandler::OnSetPINComplete,
+                                weak_factory_.GetWeakPtr()));
+  } else {
+    authenticator_->ChangePIN(
+        old_pin, new_pin,
+        base::BindOnce(&SetPINRequestHandler::OnSetPINComplete,
+                       weak_factory_.GetWeakPtr()));
+  }
 }
 
 void SetPINRequestHandler::DispatchRequest(FidoAuthenticator* authenticator) {
@@ -120,35 +128,6 @@
   std::move(get_pin_callback_).Run(response->retries);
 }
 
-void SetPINRequestHandler::OnHaveEphemeralKey(
-    std::string old_pin,
-    std::string new_pin,
-    CtapDeviceResponseCode status,
-    base::Optional<pin::KeyAgreementResponse> response) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
-  DCHECK_EQ(state_, State::kGetEphemeralKey);
-
-  if (status != CtapDeviceResponseCode::kSuccess) {
-    state_ = State::kFinished;
-    finished_callback_.Run(status);
-    return;
-  }
-
-  state_ = State::kSettingPIN;
-
-  if (old_pin.empty()) {
-    authenticator_->SetPIN(
-        new_pin, *response,
-        base::BindOnce(&SetPINRequestHandler::OnSetPINComplete,
-                       weak_factory_.GetWeakPtr()));
-  } else {
-    authenticator_->ChangePIN(
-        old_pin, new_pin, *response,
-        base::BindOnce(&SetPINRequestHandler::OnSetPINComplete,
-                       weak_factory_.GetWeakPtr()));
-  }
-}
-
 void SetPINRequestHandler::OnSetPINComplete(
     CtapDeviceResponseCode status,
     base::Optional<pin::EmptyResponse> response) {
diff --git a/device/fido/set_pin_request_handler.h b/device/fido/set_pin_request_handler.h
index c78dfb0..0c13e60 100644
--- a/device/fido/set_pin_request_handler.h
+++ b/device/fido/set_pin_request_handler.h
@@ -24,7 +24,6 @@
 
 namespace pin {
 struct RetriesResponse;
-struct KeyAgreementResponse;
 struct EmptyResponse;
 }  // namespace pin
 
@@ -74,7 +73,6 @@
     kWaitingForTouch,
     kGettingRetries,
     kWaitingForPIN,
-    kGetEphemeralKey,
     kSettingPIN,
     kFinished,
   };
@@ -89,11 +87,6 @@
   void OnRetriesResponse(CtapDeviceResponseCode status,
                          base::Optional<pin::RetriesResponse> response);
 
-  void OnHaveEphemeralKey(std::string old_pin,
-                          std::string new_pin,
-                          CtapDeviceResponseCode status,
-                          base::Optional<pin::KeyAgreementResponse> response);
-
   void OnSetPINComplete(CtapDeviceResponseCode status,
                         base::Optional<pin::EmptyResponse> response);
 
diff --git a/extensions/browser/extension_registrar.cc b/extensions/browser/extension_registrar.cc
index cd41cfd..55a5cba 100644
--- a/extensions/browser/extension_registrar.cc
+++ b/extensions/browser/extension_registrar.cc
@@ -434,13 +434,17 @@
       base::Bind(&ExtensionRegistrar::OnExtensionRegisteredWithRequestContexts,
                  weak_factory_.GetWeakPtr(), WrapRefCounted(extension)));
 
-  renderer_helper_->OnExtensionLoaded(*extension);
-
+  // Activate the extension before calling
+  // RendererStartupHelper::OnExtensionLoaded() below, so that we have
+  // activation information ready while we send ExtensionMsg_Load IPC.
+  //
   // TODO(lazyboy): We should move all logic that is required to start up an
   // extension to a separate class, instead of calling adhoc methods like
   // service worker ones below.
   ActivateTaskQueueForExtension(browser_context_, extension);
 
+  renderer_helper_->OnExtensionLoaded(*extension);
+
   // Tell subsystems that use the ExtensionRegistryObserver::OnExtensionLoaded
   // about the new extension.
   //
diff --git a/extensions/browser/extension_service_worker_message_filter.cc b/extensions/browser/extension_service_worker_message_filter.cc
index 33f883da..cb246ae3 100644
--- a/extensions/browser/extension_service_worker_message_filter.cc
+++ b/extensions/browser/extension_service_worker_message_filter.cc
@@ -146,6 +146,7 @@
 
 void ExtensionServiceWorkerMessageFilter::OnDidStartServiceWorkerContext(
     const ExtensionId& extension_id,
+    int activation_sequence,
     const GURL& service_worker_scope,
     int64_t service_worker_version_id,
     int thread_id) {
@@ -161,12 +162,13 @@
 
   ServiceWorkerTaskQueue::Get(browser_context_)
       ->DidStartServiceWorkerContext(render_process_id_, extension_id,
-                                     service_worker_scope,
+                                     activation_sequence, service_worker_scope,
                                      service_worker_version_id, thread_id);
 }
 
 void ExtensionServiceWorkerMessageFilter::OnDidStopServiceWorkerContext(
     const ExtensionId& extension_id,
+    int activation_sequence,
     const GURL& service_worker_scope,
     int64_t service_worker_version_id,
     int thread_id) {
@@ -182,7 +184,7 @@
 
   ServiceWorkerTaskQueue::Get(browser_context_)
       ->DidStopServiceWorkerContext(render_process_id_, extension_id,
-                                    service_worker_scope,
+                                    activation_sequence, service_worker_scope,
                                     service_worker_version_id, thread_id);
 }
 
diff --git a/extensions/browser/extension_service_worker_message_filter.h b/extensions/browser/extension_service_worker_message_filter.h
index 049d5771..07a72ab 100644
--- a/extensions/browser/extension_service_worker_message_filter.h
+++ b/extensions/browser/extension_service_worker_message_filter.h
@@ -56,10 +56,12 @@
                                            int64_t service_worker_version_id,
                                            int thread_id);
   void OnDidStartServiceWorkerContext(const ExtensionId& extension_id,
+                                      int activation_sequence,
                                       const GURL& service_worker_scope,
                                       int64_t service_worker_version_id,
                                       int thread_id);
   void OnDidStopServiceWorkerContext(const ExtensionId& extension_id,
+                                     int activation_sequence,
                                      const GURL& service_worker_scope,
                                      int64_t service_worker_version_id,
                                      int thread_id);
diff --git a/extensions/browser/renderer_startup_helper.cc b/extensions/browser/renderer_startup_helper.cc
index ff445f5..0c05644 100644
--- a/extensions/browser/renderer_startup_helper.cc
+++ b/extensions/browser/renderer_startup_helper.cc
@@ -23,12 +23,14 @@
 #include "extensions/browser/extension_util.h"
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
+#include "extensions/browser/service_worker_task_queue.h"
 #include "extensions/common/cors_util.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/extensions_client.h"
 #include "extensions/common/features/feature_channel.h"
 #include "extensions/common/features/feature_session_type.h"
+#include "extensions/common/manifest_handlers/background_info.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "ui/base/webui/web_ui_util.h"
 #include "url/origin.h"
@@ -56,6 +58,17 @@
          util::IsIncognitoEnabled(extension.id(), browser_context);
 }
 
+// Returns the current ActivationSequence of |extension| if the extension is
+// Service Worker-based, otherwise returns base::nullopt.
+base::Optional<int> GetWorkerActivationSequence(BrowserContext* browser_context,
+                                                const Extension& extension) {
+  if (BackgroundInfo::IsServiceWorkerBased(&extension)) {
+    return ServiceWorkerTaskQueue::Get(browser_context)
+        ->GetCurrentSequence(extension.id());
+  }
+  return base::nullopt;
+}
+
 }  // namespace
 
 RendererStartupHelper::RendererStartupHelper(BrowserContext* browser_context)
@@ -153,8 +166,9 @@
     // I am not sure this is possible to know this here, at such a low
     // level of the stack. Perhaps site isolation can help.
     bool include_tab_permissions = true;
-    loaded_extensions.push_back(
-        ExtensionMsg_Loaded_Params(ext.get(), include_tab_permissions));
+    loaded_extensions.push_back(ExtensionMsg_Loaded_Params(
+        ext.get(), include_tab_permissions,
+        GetWorkerActivationSequence(renderer_context, *ext)));
     extension_process_map_[ext->id()].insert(process);
   }
 
@@ -248,7 +262,8 @@
   // Uninitialized renderers will be informed of the extension load during the
   // first batch of messages.
   std::vector<ExtensionMsg_Loaded_Params> params;
-  params.emplace_back(&extension, false /* no tab permissions */);
+  params.emplace_back(&extension, false /* no tab permissions */,
+                      GetWorkerActivationSequence(browser_context_, extension));
 
   for (content::RenderProcessHost* process : initialized_processes_) {
     if (!IsExtensionVisibleToContext(extension, process->GetBrowserContext()))
diff --git a/extensions/browser/service_worker/worker_id.cc b/extensions/browser/service_worker/worker_id.cc
index bdfa21d..7e66ff0 100644
--- a/extensions/browser/service_worker/worker_id.cc
+++ b/extensions/browser/service_worker/worker_id.cc
@@ -24,4 +24,8 @@
          version_id == other.version_id && thread_id == other.thread_id;
 }
 
+bool WorkerId::operator!=(const WorkerId& other) const {
+  return !this->operator==(other);
+}
+
 }  // namespace extensions
diff --git a/extensions/browser/service_worker/worker_id.h b/extensions/browser/service_worker/worker_id.h
index 21f91c6..5ce79cd 100644
--- a/extensions/browser/service_worker/worker_id.h
+++ b/extensions/browser/service_worker/worker_id.h
@@ -20,6 +20,7 @@
 
   bool operator<(const WorkerId& other) const;
   bool operator==(const WorkerId& other) const;
+  bool operator!=(const WorkerId& other) const;
 };
 
 }  // namespace extensions
diff --git a/extensions/browser/service_worker_task_queue.cc b/extensions/browser/service_worker_task_queue.cc
index ec55c41..18cac2d 100644
--- a/extensions/browser/service_worker_task_queue.cc
+++ b/extensions/browser/service_worker_task_queue.cc
@@ -41,6 +41,36 @@
 
 ServiceWorkerTaskQueue::TestObserver* g_test_observer = nullptr;
 
+// ServiceWorkerRegistration state of an activated extension.
+enum class RegistrationState {
+  // Not registered.
+  kNotRegistered,
+  // Registration is inflight.
+  kPending,
+  // Registration is complete.
+  kRegistered,
+};
+
+// Browser process worker state of an activated extension.
+enum class BrowserState {
+  // Initial state, not started.
+  kInitial,
+  // Worker is in the process of starting from the browser process.
+  kStarting,
+  // Worker has completed starting (i.e. has seen DidStartWorkerForScope).
+  kStarted,
+};
+
+// Render process worker state of an activated extension.
+enum class RendererState {
+  // Initial state, neither started nor stopped.
+  kInitial,
+  // Worker thread has started.
+  kStarted,
+  // Worker thread has not started or has been stopped.
+  kStopped,
+};
+
 }  // namespace
 
 ServiceWorkerTaskQueue::ServiceWorkerTaskQueue(BrowserContext* browser_context)
@@ -109,19 +139,44 @@
                      context_id, task_queue_weak));
 }
 
-// The current state of a worker.
-struct ServiceWorkerTaskQueue::WorkerState {
-  // Whether or not worker has completed starting (DidStartWorkerForScope).
-  bool browser_ready = false;
-
-  // Whether or not worker is ready in the renderer
-  // (DidStartServiceWorkerContext).
-  bool renderer_ready = false;
-
-  // If |browser_ready| = true, this is the ActivationSequence of the worker.
-  base::Optional<ActivationSequence> sequence;
-
+// The current worker related state of an activated extension.
+class ServiceWorkerTaskQueue::WorkerState {
+ public:
   WorkerState() = default;
+
+  WorkerState(const WorkerState&) = delete;
+  WorkerState& operator=(const WorkerState&) = delete;
+
+  void SetWorkerId(const WorkerId& worker_id, ProcessManager* process_manager) {
+    if (worker_id_ && *worker_id_ != worker_id) {
+      // Sanity check that the old worker is gone.
+      DCHECK(!process_manager->HasServiceWorker(*worker_id_));
+      // Clear stale renderer state if there's any.
+      renderer_state_ = RendererState::kInitial;
+    }
+    worker_id_ = worker_id;
+  }
+
+  bool ready() const {
+    return registration_state_ == RegistrationState::kRegistered &&
+           browser_state_ == BrowserState::kStarted &&
+           renderer_state_ == RendererState::kStarted && worker_id_.has_value();
+  }
+  bool has_pending_tasks() const { return !pending_tasks_.empty(); }
+
+ private:
+  friend class ServiceWorkerTaskQueue;
+
+  RegistrationState registration_state_ = RegistrationState::kNotRegistered;
+  BrowserState browser_state_ = BrowserState::kInitial;
+  RendererState renderer_state_ = RendererState::kInitial;
+
+  // Pending tasks that will be run once the worker becomes ready.
+  std::vector<PendingTask> pending_tasks_;
+
+  // Contains the worker's WorkerId associated with this WorkerState, once we
+  // have discovered info about the worker.
+  base::Optional<WorkerId> worker_id_;
 };
 
 void ServiceWorkerTaskQueue::DidStartWorkerForScope(
@@ -136,11 +191,12 @@
     // Extension run with |sequence| was already deactivated.
     // TODO(lazyboy): Add a DCHECK that the worker in question is actually
     // shutting down soon.
-    DCHECK(!base::Contains(pending_tasks_, context_id));
+    DCHECK(!GetWorkerState(context_id));
     return;
   }
 
-  const LazyContextId& lazy_context_id = context_id.first;
+  WorkerState* worker_state = GetWorkerState(context_id);
+  DCHECK(worker_state);
   const WorkerId worker_id = {extension_id, process_id, version_id, thread_id};
 
   // Note: If the worker has already stopped on worker thread
@@ -151,15 +207,12 @@
   // renderer before we execute tasks in the browser process. This will also
   // avoid holding the worker in |worker_state_map_| until deactivation as noted
   // above.
-  WorkerState* worker_state =
-      GetOrCreateWorkerState(WorkerKey(lazy_context_id, worker_id));
-  DCHECK(worker_state);
-  DCHECK(!worker_state->browser_ready) << "Worker was already loaded";
-  worker_state->browser_ready = true;
-  worker_state->sequence = sequence;
+  DCHECK_NE(BrowserState::kStarted, worker_state->browser_state_)
+      << "Worker was already loaded";
+  worker_state->SetWorkerId(worker_id, ProcessManager::Get(browser_context_));
+  worker_state->browser_state_ = BrowserState::kStarted;
 
-  RunPendingTasksIfWorkerReady(lazy_context_id, version_id, process_id,
-                               thread_id);
+  RunPendingTasksIfWorkerReady(context_id);
 }
 
 void ServiceWorkerTaskQueue::DidStartWorkerFail(
@@ -168,9 +221,10 @@
   if (!IsCurrentSequence(context_id.first.extension_id(), context_id.second)) {
     // This can happen is when the registration got unregistered right before we
     // tried to start it. See crbug.com/999027 for details.
-    DCHECK(!base::Contains(pending_tasks_, context_id));
+    DCHECK(!GetWorkerState(context_id));
     return;
   }
+
   // TODO(lazyboy): Handle failure cases.
   DCHECK(false) << "DidStartWorkerFail: " << context_id.first.extension_id();
 }
@@ -189,50 +243,70 @@
 void ServiceWorkerTaskQueue::DidStartServiceWorkerContext(
     int render_process_id,
     const ExtensionId& extension_id,
+    int activation_sequence,
     const GURL& service_worker_scope,
     int64_t service_worker_version_id,
     int thread_id) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  LazyContextId context_id(browser_context_, extension_id,
-                           service_worker_scope);
+  if (!IsCurrentSequence(extension_id, activation_sequence))
+    return;
+
+  SequencedContextId context_id(
+      LazyContextId(browser_context_, extension_id, service_worker_scope),
+      activation_sequence);
   const WorkerId worker_id = {extension_id, render_process_id,
                               service_worker_version_id, thread_id};
-  WorkerState* worker_state =
-      GetOrCreateWorkerState(WorkerKey(context_id, worker_id));
-  DCHECK(!worker_state->renderer_ready) << "Worker already started";
-  worker_state->renderer_ready = true;
+  WorkerState* worker_state = GetWorkerState(context_id);
+  DCHECK(worker_state);
+  // If |worker_state| had a worker running previously, for which we didn't
+  // see DidStopServiceWorkerContext notification (typically happens on render
+  // process shutdown), then we'd preserve stale state in |renderer_state_|.
+  //
+  // This isn't a problem because the next browser process readiness
+  // (DidStartWorkerForScope) or the next renderer process readiness
+  // (DidStartServiceWorkerContext) will clear the state, whichever happens
+  // first.
+  //
+  // TODO(lazyboy): Update the renderer state in RenderProcessExited() and
+  // uncomment the following DCHECK:
+  // DCHECK_NE(RendererState::kStarted, worker_state->renderer_state_)
+  //    << "Worker already started";
+  worker_state->SetWorkerId(worker_id, ProcessManager::Get(browser_context_));
+  worker_state->renderer_state_ = RendererState::kStarted;
 
-  RunPendingTasksIfWorkerReady(context_id, service_worker_version_id,
-                               render_process_id, thread_id);
+  RunPendingTasksIfWorkerReady(context_id);
 }
 
 void ServiceWorkerTaskQueue::DidStopServiceWorkerContext(
     int render_process_id,
     const ExtensionId& extension_id,
+    int activation_sequence,
     const GURL& service_worker_scope,
     int64_t service_worker_version_id,
     int thread_id) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (!IsCurrentSequence(extension_id, activation_sequence))
+    return;
+
   const WorkerId worker_id = {extension_id, render_process_id,
                               service_worker_version_id, thread_id};
   ProcessManager::Get(browser_context_)->UnregisterServiceWorker(worker_id);
-  LazyContextId context_id(browser_context_, extension_id,
-                           service_worker_scope);
+  SequencedContextId context_id(
+      LazyContextId(browser_context_, extension_id, service_worker_scope),
+      activation_sequence);
 
-  WorkerKey worker_key(context_id, worker_id);
-  WorkerState* worker_state = GetWorkerState(worker_key);
-  if (!worker_state) {
+  WorkerState* worker_state = GetWorkerState(context_id);
+  DCHECK(worker_state);
+
+  if (worker_state->worker_id_ != worker_id) {
     // We can see DidStopServiceWorkerContext right after DidInitialize and
     // without DidStartServiceWorkerContext.
     return;
   }
 
-  // Clean up both the renderer and browser readiness states.
-  // One caveat is that although this is renderer notification, we also clear
-  // the browser readiness state, this is because a worker can be
-  // |browser_ready| and was waiting for DidStartServiceWorkerContext, but
-  // instead received DidStopServiceWorkerContext.
-  worker_state_map_.erase(worker_key);
+  DCHECK_NE(RendererState::kStopped, worker_state->renderer_state_);
+  worker_state->renderer_state_ = RendererState::kStopped;
+  worker_state->worker_id_ = base::nullopt;
 }
 
 // static
@@ -259,11 +333,13 @@
   DCHECK(sequence) << "Trying to add pending task to an inactive extension: "
                    << lazy_context_id.extension_id();
   const SequencedContextId context_id(lazy_context_id, *sequence);
-  auto& tasks = pending_tasks_[context_id];
+  WorkerState* worker_state = GetWorkerState(context_id);
+  DCHECK(worker_state);
+  auto& tasks = worker_state->pending_tasks_;
   bool needs_start_worker = tasks.empty();
   tasks.push_back(std::move(task));
 
-  if (pending_registrations_.count(context_id) > 0) {
+  if (worker_state->registration_state_ != RegistrationState::kRegistered) {
     // If the worker hasn't finished registration, wait for it to complete.
     // DidRegisterServiceWorker will Start worker to run |task| later.
     return;
@@ -281,6 +357,11 @@
   const ExtensionId extension_id = extension->id();
   ActivationSequence current_sequence = ++next_activation_sequence_;
   activation_sequences_[extension_id] = current_sequence;
+  SequencedContextId context_id(
+      LazyContextId(browser_context_, extension_id, extension->url()),
+      current_sequence);
+  DCHECK(!base::Contains(worker_state_map_, context_id));
+  WorkerState& worker_state = worker_state_map_[context_id];
 
   // Note: version.IsValid() = false implies we didn't have any prefs stored.
   base::Version version = RetrieveRegisteredServiceWorkerVersion(extension_id);
@@ -292,15 +373,13 @@
   }
 
   if (service_worker_already_registered) {
+    worker_state.registration_state_ = RegistrationState::kRegistered;
     // TODO(https://crbug.com/901101): We should kick off an async check to see
     // if the registration is *actually* there and re-register if necessary.
     return;
   }
 
-  SequencedContextId context_id(
-      LazyContextId(browser_context_, extension_id, extension->url()),
-      current_sequence);
-  pending_registrations_.insert(context_id);
+  worker_state.registration_state_ = RegistrationState::kPending;
   GURL script_url = extension->GetResourceURL(
       BackgroundInfo::GetBackgroundServiceWorkerScript(extension));
   blink::mojom::ServiceWorkerRegistrationOptions option;
@@ -329,14 +408,11 @@
   SequencedContextId context_id(
       LazyContextId(browser_context_, extension_id, extension->url()),
       *sequence);
-  ClearPendingTasks(context_id);
-
-  // Clear loaded worker if it was waiting for start.
-  // Note that we don't clear the entire state here as we expect the renderer to
-  // stop shortly after this and its notification will clear the state.
-  ClearBrowserReadyForWorkers(
-      LazyContextId(browser_context_, extension_id, extension->url()),
-      *sequence);
+  WorkerState* worker_state = GetWorkerState(context_id);
+  DCHECK(worker_state);
+  // TODO(lazyboy): Run orphaned tasks with nullptr ContextInfo.
+  worker_state->pending_tasks_.clear();
+  worker_state_map_.erase(context_id);
 
   util::GetStoragePartitionForExtensionId(extension->id(), browser_context_)
       ->GetServiceWorkerContext()
@@ -354,6 +430,9 @@
   if (lazy_context_id.browser_context() != browser_context_)
     return;
 
+  WorkerState* worker_state = GetWorkerState(context_id);
+  DCHECK_NE(BrowserState::kStarted, worker_state->browser_state_);
+
   content::StoragePartition* partition =
       util::GetStoragePartitionForExtensionId(
           lazy_context_id.extension_id(), lazy_context_id.browser_context());
@@ -377,35 +456,30 @@
 void ServiceWorkerTaskQueue::DidRegisterServiceWorker(
     const SequencedContextId& context_id,
     bool success) {
-  pending_registrations_.erase(context_id);
   ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);
   const ExtensionId& extension_id = context_id.first.extension_id();
   DCHECK(registry);
   const Extension* extension =
       registry->enabled_extensions().GetByID(extension_id);
   if (!extension) {
-    // DeactivateExtension must have cleared |pending_tasks_| already.
-    DCHECK(!base::Contains(pending_tasks_, context_id));
     return;
   }
+  if (!IsCurrentSequence(extension_id, context_id.second))
+    return;
+
+  WorkerState* worker_state = GetWorkerState(context_id);
+  DCHECK(worker_state);
 
   if (!success) {
-    if (!IsCurrentSequence(extension_id, context_id.second)) {
-      // DeactivateExtension must have cleared |pending_tasks_| already.
-      DCHECK(!base::Contains(pending_tasks_, context_id));
-      return;
-    }
     // TODO(lazyboy): Handle failure case thoroughly.
     DCHECK(false) << "Failed to register Service Worker";
     return;
   }
 
+  worker_state->registration_state_ = RegistrationState::kRegistered;
   SetRegisteredServiceWorkerInfo(extension->id(), extension->version());
 
-  auto pending_tasks_iter = pending_tasks_.find(context_id);
-  const bool has_pending_tasks = pending_tasks_iter != pending_tasks_.end() &&
-                                 pending_tasks_iter->second.size() > 0u;
-  if (has_pending_tasks) {
+  if (worker_state->has_pending_tasks()) {
     // TODO(lazyboy): If worker for |context_id| is already running, consider
     // not calling StartWorker. This isn't straightforward as service worker's
     // internal state is mostly on the core thread.
@@ -467,45 +541,35 @@
 }
 
 void ServiceWorkerTaskQueue::RunPendingTasksIfWorkerReady(
-    const LazyContextId& context_id,
-    int64_t version_id,
-    int process_id,
-    int thread_id) {
-  WorkerState* worker_state = GetWorkerState(WorkerKey(
-      context_id,
-      {context_id.extension_id(), process_id, version_id, thread_id}));
+    const SequencedContextId& context_id) {
+  WorkerState* worker_state = GetWorkerState(context_id);
   DCHECK(worker_state);
-  if (!worker_state->browser_ready || !worker_state->renderer_ready) {
+  if (!worker_state->ready()) {
     // Worker isn't ready yet, wait for next event and run the tasks then.
     return;
   }
-  base::Optional<int> sequence = worker_state->sequence;
-  DCHECK(sequence.has_value());
 
   // Running |pending_tasks_[context_id]| marks the completion of
   // DidStartWorkerForScope, clean up |browser_ready| state of the worker so
   // that new tasks can be queued up.
-  worker_state->browser_ready = false;
+  worker_state->browser_state_ = BrowserState::kInitial;
 
-  auto iter = pending_tasks_.find(SequencedContextId(context_id, *sequence));
-  DCHECK(iter != pending_tasks_.end()) << "Worker ready, but no tasks to run!";
-  std::vector<PendingTask> tasks = std::move(iter->second);
-  pending_tasks_.erase(iter);
+  DCHECK(worker_state->has_pending_tasks())
+      << "Worker ready, but no tasks to run!";
+  std::vector<PendingTask> tasks;
+  std::swap(worker_state->pending_tasks_, tasks);
+  DCHECK(worker_state->worker_id_);
+  const auto& worker_id = *worker_state->worker_id_;
   for (auto& task : tasks) {
     auto context_info = std::make_unique<LazyContextTaskQueue::ContextInfo>(
-        context_id.extension_id(),
-        content::RenderProcessHost::FromID(process_id), version_id, thread_id,
-        context_id.service_worker_scope());
+        context_id.first.extension_id(),
+        content::RenderProcessHost::FromID(worker_id.render_process_id),
+        worker_id.version_id, worker_id.thread_id,
+        context_id.first.service_worker_scope());
     std::move(task).Run(std::move(context_info));
   }
 }
 
-void ServiceWorkerTaskQueue::ClearPendingTasks(
-    const SequencedContextId& context_id) {
-  // TODO(lazyboy): Run orphaned tasks with nullptr ContextInfo.
-  pending_tasks_.erase(context_id);
-}
-
 bool ServiceWorkerTaskQueue::IsCurrentSequence(
     const ExtensionId& extension_id,
     ActivationSequence sequence) const {
@@ -522,44 +586,11 @@
   return iter->second;
 }
 
-ServiceWorkerTaskQueue::WorkerState*
-ServiceWorkerTaskQueue::GetOrCreateWorkerState(const WorkerKey& worker_key) {
-  auto iter = worker_state_map_.find(worker_key);
-  if (iter == worker_state_map_.end())
-    iter = worker_state_map_.emplace(worker_key, WorkerState()).first;
-  return &(iter->second);
-}
-
 ServiceWorkerTaskQueue::WorkerState* ServiceWorkerTaskQueue::GetWorkerState(
-    const WorkerKey& worker_key) {
-  auto iter = worker_state_map_.find(worker_key);
-  if (iter == worker_state_map_.end())
-    return nullptr;
-  return &(iter->second);
-}
-
-void ServiceWorkerTaskQueue::ClearBrowserReadyForWorkers(
-    const LazyContextId& context_id,
-    ActivationSequence sequence) {
-  // TODO(lazyboy): We could use |worker_state_map_|.lower_bound() to avoid
-  // iterating over all workers. Note that it would require creating artificial
-  // WorkerKey with |context_id|.
-  for (auto iter = worker_state_map_.begin();
-       iter != worker_state_map_.end();) {
-    if (iter->first.first != context_id || iter->second.sequence != sequence) {
-      ++iter;
-      continue;
-    }
-
-    iter->second.browser_ready = false;
-    iter->second.sequence = base::nullopt;
-
-    // Clean up stray entries if renderer readiness was also gone.
-    if (!iter->second.renderer_ready)
-      iter = worker_state_map_.erase(iter);
-    else
-      ++iter;
-  }
+    const SequencedContextId& context_id) {
+  auto worker_iter = worker_state_map_.find(context_id);
+  return worker_iter == worker_state_map_.end() ? nullptr
+                                                : &worker_iter->second;
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/service_worker_task_queue.h b/extensions/browser/service_worker_task_queue.h
index 2e5e215b..76b2285 100644
--- a/extensions/browser/service_worker_task_queue.h
+++ b/extensions/browser/service_worker_task_queue.h
@@ -72,6 +72,12 @@
 class ServiceWorkerTaskQueue : public KeyedService,
                                public LazyContextTaskQueue {
  public:
+  // Unique identifier for an extension's activation->deactivation span.
+  // TODO(lazyboy): Move this under extensions/common/ for consistency, so that
+  // renderer process can use this instead of using "int" directly. We'd also
+  // want StrongAlias for this.
+  using ActivationSequence = int;
+
   explicit ServiceWorkerTaskQueue(content::BrowserContext* browser_context);
   ~ServiceWorkerTaskQueue() override;
 
@@ -102,16 +108,24 @@
   // has completed executing.
   void DidStartServiceWorkerContext(int render_process_id,
                                     const ExtensionId& extension_id,
+                                    int activation_sequence,
                                     const GURL& service_worker_scope,
                                     int64_t service_worker_version_id,
                                     int thread_id);
   // Called once an extension Service Worker was destroyed.
   void DidStopServiceWorkerContext(int render_process_id,
                                    const ExtensionId& extension_id,
+                                   int activation_sequence,
                                    const GURL& service_worker_scope,
                                    int64_t service_worker_version_id,
                                    int thread_id);
 
+  // Returns the current ActivationSequence for an extension, if the extension
+  // is currently activated. Returns base::nullopt if the extension isn't
+  // activated.
+  base::Optional<ActivationSequence> GetCurrentSequence(
+      const ExtensionId& extension_id) const;
+
   class TestObserver {
    public:
     TestObserver();
@@ -130,14 +144,9 @@
   static void SetObserverForTest(TestObserver* observer);
 
  private:
-  // Unique identifier for an extension's activation->deactivation span.
-  using ActivationSequence = int;
   using SequencedContextId = std::pair<LazyContextId, ActivationSequence>;
 
-  // Key used to identify a WorkerState within the worker container.
-  using WorkerKey = std::pair<LazyContextId, WorkerId>;
-
-  struct WorkerState;
+  class WorkerState;
 
   static void DidStartWorkerForScopeOnCoreThread(
       const SequencedContextId& context_id,
@@ -184,40 +193,19 @@
   // If the worker with |context_id| has seen worker start
   // (DidStartWorkerForScope) and load (DidStartServiceWorkerContext) then runs
   // all pending tasks for that worker.
-  void RunPendingTasksIfWorkerReady(const LazyContextId& context_id,
-                                    int64_t version_id,
-                                    int process_id,
-                                    int thread_id);
-
-  void ClearPendingTasks(const SequencedContextId& context_id);
+  void RunPendingTasksIfWorkerReady(const SequencedContextId& context_id);
 
   // Returns true if |sequence| is the current activation sequence for
   // |extension_id|.
   bool IsCurrentSequence(const ExtensionId& extension_id,
                          ActivationSequence sequence) const;
 
-  // Returns the current ActivationSequence for an extension, if the extension
-  // is currently activated. Returns base::nullopt if the extension isn't
-  // activated.
-  base::Optional<ActivationSequence> GetCurrentSequence(
-      const ExtensionId& extension_id) const;
-
-  WorkerState* GetOrCreateWorkerState(const WorkerKey& worker_key);
-  WorkerState* GetWorkerState(const WorkerKey& worker_key);
-  void ClearBrowserReadyForWorkers(const LazyContextId& context_id,
-                                   ActivationSequence sequence);
+  WorkerState* GetWorkerState(const SequencedContextId& context_id);
 
   ActivationSequence next_activation_sequence_ = 0;
 
-  // Set of extension ids that hasn't completed Service Worker registration.
-  std::set<SequencedContextId> pending_registrations_;
-
-  // The state of each workers we know about.
-  std::map<WorkerKey, WorkerState> worker_state_map_;
-
-  // Pending tasks for a |LazyContextId| with an ActivationSequence.
-  // These tasks will be run once the corresponding worker becomes ready.
-  std::map<SequencedContextId, std::vector<PendingTask>> pending_tasks_;
+  // The state of worker of each activated extension.
+  std::map<SequencedContextId, WorkerState> worker_state_map_;
 
   content::BrowserContext* const browser_context_ = nullptr;
 
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index c44279a..c83c6e6 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -161,11 +161,6 @@
     "channel": "beta",
     "location": "unpacked"
   },
-  "declarativeNetRequest.setActionCountAsBadgeText": {
-    "dependencies": ["permission:declarativeNetRequest"],
-    "contexts": ["blessed_extension"],
-    "channel": "trunk"
-  },
   "declarativeWebRequest": {
     "dependencies": ["permission:declarativeWebRequest"],
     "contexts": ["blessed_extension"]
diff --git a/extensions/common/api/declarative_net_request.idl b/extensions/common/api/declarative_net_request.idl
index 1036e39..541bc63 100644
--- a/extensions/common/api/declarative_net_request.idl
+++ b/extensions/common/api/declarative_net_request.idl
@@ -384,9 +384,6 @@
     // Sets whether to automatically badge extension's icon to the matched
     // action count for a tab. This preference is persisted across sessions and
     // is false by default.
-    // TODO(crbug.com/973211): Add documentation once implementation is
-    // complete.
-    [nodoc]
     static void setActionCountAsBadgeText(boolean enable);
   };
 
diff --git a/extensions/common/extension_messages.cc b/extensions/common/extension_messages.cc
index 1edd698..9f8d1b04 100644
--- a/extensions/common/extension_messages.cc
+++ b/extensions/common/extension_messages.cc
@@ -61,7 +61,8 @@
 
 ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params(
     const Extension* extension,
-    bool include_tab_permissions)
+    bool include_tab_permissions,
+    base::Optional<int> worker_activation_sequence)
     : manifest(static_cast<base::DictionaryValue&&>(
           extension->manifest()->value()->Clone())),
       location(extension->location()),
@@ -76,6 +77,7 @@
       uses_default_policy_blocked_allowed_hosts(
           extension->permissions_data()->UsesDefaultPolicyHostRestrictions()),
       id(extension->id()),
+      worker_activation_sequence(worker_activation_sequence),
       creation_flags(extension->creation_flags()) {
   if (include_tab_permissions) {
     for (const auto& pair :
@@ -327,6 +329,7 @@
   WriteParam(m, p.policy_blocked_hosts);
   WriteParam(m, p.policy_allowed_hosts);
   WriteParam(m, p.uses_default_policy_blocked_allowed_hosts);
+  WriteParam(m, p.worker_activation_sequence);
 }
 
 bool ParamTraits<ExtensionMsg_Loaded_Params>::Read(const base::Pickle* m,
@@ -341,7 +344,8 @@
          ReadParam(m, iter, &p->tab_specific_permissions) &&
          ReadParam(m, iter, &p->policy_blocked_hosts) &&
          ReadParam(m, iter, &p->policy_allowed_hosts) &&
-         ReadParam(m, iter, &p->uses_default_policy_blocked_allowed_hosts);
+         ReadParam(m, iter, &p->uses_default_policy_blocked_allowed_hosts) &&
+         ReadParam(m, iter, &p->worker_activation_sequence);
 }
 
 void ParamTraits<ExtensionMsg_Loaded_Params>::Log(const param_type& p,
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index dfd0f3b..f73c4452 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -356,7 +356,8 @@
   ExtensionMsg_Loaded_Params();
   ~ExtensionMsg_Loaded_Params();
   ExtensionMsg_Loaded_Params(const extensions::Extension* extension,
-                             bool include_tab_permissions);
+                             bool include_tab_permissions,
+                             base::Optional<int> worker_activation_sequence);
 
   ExtensionMsg_Loaded_Params(ExtensionMsg_Loaded_Params&& other);
   ExtensionMsg_Loaded_Params& operator=(ExtensionMsg_Loaded_Params&& other);
@@ -391,6 +392,10 @@
   // We keep this separate so that it can be used in logging.
   std::string id;
 
+  // If this extension is Service Worker based, then this contains the
+  // activation sequence of the extension.
+  base::Optional<int> worker_activation_sequence;
+
   // Send creation flags so extension is initialized identically.
   int creation_flags;
 
@@ -1060,16 +1065,18 @@
 //     straightforward as it changes SW IPC ordering with respect of rest of
 //     Chrome.
 // See https://crbug.com/879015#c4 for details.
-IPC_MESSAGE_CONTROL4(ExtensionHostMsg_DidStartServiceWorkerContext,
+IPC_MESSAGE_CONTROL5(ExtensionHostMsg_DidStartServiceWorkerContext,
                      std::string /* extension_id */,
+                     int /* activation_sequence */,
                      GURL /* service_worker_scope */,
                      int64_t /* service_worker_version_id */,
                      int /* worker_thread_id */)
 
 // Tells the browser that an extension service worker context has been
 // destroyed.
-IPC_MESSAGE_CONTROL4(ExtensionHostMsg_DidStopServiceWorkerContext,
+IPC_MESSAGE_CONTROL5(ExtensionHostMsg_DidStopServiceWorkerContext,
                      std::string /* extension_id */,
+                     int /* activation_sequence */,
                      GURL /* service_worker_scope */,
                      int64_t /* service_worker_version_id */,
                      int /* worker_thread_id */)
diff --git a/extensions/common/extension_messages_unittest.cc b/extensions/common/extension_messages_unittest.cc
index 208436d..f99d59e6 100644
--- a/extensions/common/extension_messages_unittest.cc
+++ b/extensions/common/extension_messages_unittest.cc
@@ -81,7 +81,7 @@
   extension->permissions_data()->SetPolicyHostRestrictions(
       runtime_blocked_hosts, runtime_allowed_hosts);
 
-  ExtensionMsg_Loaded_Params params_in(extension.get(), true);
+  ExtensionMsg_Loaded_Params params_in(extension.get(), true, base::nullopt);
   EXPECT_EQ(extension->id(), params_in.id);
 
   {
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index 82e432e..6410d9c 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -451,8 +451,11 @@
     std::unique_ptr<IPCMessageSender> ipc_sender =
         IPCMessageSender::CreateWorkerThreadIPCMessageSender(
             worker_dispatcher, service_worker_version_id);
+    int worker_activation_sequence =
+        *RendererExtensionRegistry::Get()->GetWorkerActivationSequence(
+            extension->id());
     worker_dispatcher->AddWorkerData(
-        service_worker_version_id, context,
+        service_worker_version_id, worker_activation_sequence, context,
         CreateBindingsSystem(std::move(ipc_sender)));
     worker_thread_util::SetWorkerContextProxy(context_proxy);
 
@@ -986,6 +989,10 @@
       // consider making this a release CHECK.
       NOTREACHED();
     }
+    if (param.worker_activation_sequence) {
+      extension_registry->SetWorkerActivationSequence(
+          extension, *param.worker_activation_sequence);
+    }
     if (param.uses_default_policy_blocked_allowed_hosts) {
       extension->permissions_data()->SetUsesDefaultHostRestrictions();
     } else {
diff --git a/extensions/renderer/renderer_extension_registry.cc b/extensions/renderer/renderer_extension_registry.cc
index 4fb14dd..93936af5 100644
--- a/extensions/renderer/renderer_extension_registry.cc
+++ b/extensions/renderer/renderer_extension_registry.cc
@@ -7,6 +7,7 @@
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "content/public/renderer/render_thread.h"
+#include "extensions/common/manifest_handlers/background_info.h"
 
 namespace extensions {
 
@@ -102,4 +103,24 @@
   return extensions_.ExtensionBindingsAllowed(url);
 }
 
+void RendererExtensionRegistry::SetWorkerActivationSequence(
+    const scoped_refptr<const Extension>& extension,
+    int worker_activation_sequence) {
+  DCHECK(content::RenderThread::Get());
+  DCHECK(Contains(extension->id()));
+  DCHECK(BackgroundInfo::IsServiceWorkerBased(extension.get()));
+
+  base::AutoLock lock(lock_);
+  worker_activation_sequences_[extension->id()] = worker_activation_sequence;
+}
+
+base::Optional<int> RendererExtensionRegistry::GetWorkerActivationSequence(
+    const ExtensionId& extension_id) const {
+  base::AutoLock lock(lock_);
+  auto iter = worker_activation_sequences_.find(extension_id);
+  if (iter == worker_activation_sequences_.end())
+    return base::nullopt;
+  return iter->second;
+}
+
 }  // namespace extensions
diff --git a/extensions/renderer/renderer_extension_registry.h b/extensions/renderer/renderer_extension_registry.h
index 97c24bb..a62d26c 100644
--- a/extensions/renderer/renderer_extension_registry.h
+++ b/extensions/renderer/renderer_extension_registry.h
@@ -10,7 +10,9 @@
 #include <string>
 
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/synchronization/lock.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/extension_set.h"
 
 class GURL;
@@ -49,9 +51,23 @@
   ExtensionIdSet GetIDs() const;
   bool ExtensionBindingsAllowed(const GURL& url) const;
 
+  // ActivationSequence related methods.
+  //
+  // Sets ActivationSequence for a Service Worker based |extension|.
+  void SetWorkerActivationSequence(
+      const scoped_refptr<const Extension>& extension,
+      int worker_activation_sequence);
+  // Returns the current activation sequence for worker based extension with
+  // |extension_id|. Returns base::nullopt otherwise.
+  base::Optional<int> GetWorkerActivationSequence(
+      const ExtensionId& extension_id) const;
+
  private:
   ExtensionSet extensions_;
 
+  // Maps extension id to ActivationSequence, for worker based extensions.
+  std::map<ExtensionId, int> worker_activation_sequences_;
+
   mutable base::Lock lock_;
 
   DISALLOW_COPY_AND_ASSIGN(RendererExtensionRegistry);
diff --git a/extensions/renderer/service_worker_data.cc b/extensions/renderer/service_worker_data.cc
index 3c96f09..1f95bd2 100644
--- a/extensions/renderer/service_worker_data.cc
+++ b/extensions/renderer/service_worker_data.cc
@@ -10,9 +10,11 @@
 
 ServiceWorkerData::ServiceWorkerData(
     int64_t service_worker_version_id,
+    int activation_sequence,
     ScriptContext* context,
     std::unique_ptr<NativeExtensionBindingsSystem> bindings_system)
     : service_worker_version_id_(service_worker_version_id),
+      activation_sequence_(activation_sequence),
       context_(context),
       v8_schema_registry_(new V8SchemaRegistry),
       bindings_system_(std::move(bindings_system)) {}
diff --git a/extensions/renderer/service_worker_data.h b/extensions/renderer/service_worker_data.h
index 1ee28d1..5cd152a 100644
--- a/extensions/renderer/service_worker_data.h
+++ b/extensions/renderer/service_worker_data.h
@@ -20,6 +20,7 @@
  public:
   ServiceWorkerData(
       int64_t service_worker_version_id,
+      int activation_sequence,
       ScriptContext* context,
       std::unique_ptr<NativeExtensionBindingsSystem> bindings_system);
   ~ServiceWorkerData();
@@ -31,10 +32,12 @@
   int64_t service_worker_version_id() const {
     return service_worker_version_id_;
   }
+  int activation_sequence() const { return activation_sequence_; }
   ScriptContext* context() const { return context_; }
 
  private:
   const int64_t service_worker_version_id_;
+  const int activation_sequence_;
   ScriptContext* const context_ = nullptr;
 
   std::unique_ptr<V8SchemaRegistry> v8_schema_registry_;
diff --git a/extensions/renderer/worker_thread_dispatcher.cc b/extensions/renderer/worker_thread_dispatcher.cc
index efbcdc3..9906e300 100644
--- a/extensions/renderer/worker_thread_dispatcher.cc
+++ b/extensions/renderer/worker_thread_dispatcher.cc
@@ -264,12 +264,14 @@
 
 void WorkerThreadDispatcher::AddWorkerData(
     int64_t service_worker_version_id,
+    int activation_sequence,
     ScriptContext* script_context,
     std::unique_ptr<NativeExtensionBindingsSystem> bindings_system) {
   ServiceWorkerData* data = g_data_tls.Pointer()->Get();
   if (!data) {
-    ServiceWorkerData* new_data = new ServiceWorkerData(
-        service_worker_version_id, script_context, std::move(bindings_system));
+    ServiceWorkerData* new_data =
+        new ServiceWorkerData(service_worker_version_id, activation_sequence,
+                              script_context, std::move(bindings_system));
     g_data_tls.Pointer()->Set(new_data);
   }
 
@@ -300,8 +302,8 @@
   const int thread_id = content::WorkerThread::GetCurrentId();
   DCHECK_NE(thread_id, kMainThreadId);
   Send(new ExtensionHostMsg_DidStartServiceWorkerContext(
-      data->context()->GetExtensionID(), service_worker_scope,
-      service_worker_version_id, thread_id));
+      data->context()->GetExtensionID(), data->activation_sequence(),
+      service_worker_scope, service_worker_version_id, thread_id));
 }
 
 void WorkerThreadDispatcher::DidStopContext(const GURL& service_worker_scope,
@@ -311,8 +313,8 @@
   DCHECK_NE(thread_id, kMainThreadId);
   DCHECK_EQ(service_worker_version_id, data->service_worker_version_id());
   Send(new ExtensionHostMsg_DidStopServiceWorkerContext(
-      data->context()->GetExtensionID(), service_worker_scope,
-      service_worker_version_id, thread_id));
+      data->context()->GetExtensionID(), data->activation_sequence(),
+      service_worker_scope, service_worker_version_id, thread_id));
 }
 
 void WorkerThreadDispatcher::RemoveWorkerData(
diff --git a/extensions/renderer/worker_thread_dispatcher.h b/extensions/renderer/worker_thread_dispatcher.h
index dc341bb6..f13874ef 100644
--- a/extensions/renderer/worker_thread_dispatcher.h
+++ b/extensions/renderer/worker_thread_dispatcher.h
@@ -63,6 +63,7 @@
 
   void AddWorkerData(
       int64_t service_worker_version_id,
+      int activation_sequence,
       ScriptContext* script_context,
       std::unique_ptr<NativeExtensionBindingsSystem> bindings_system);
   void RemoveWorkerData(int64_t service_worker_version_id);
diff --git a/extensions/shell/browser/shell_content_browser_client.cc b/extensions/shell/browser/shell_content_browser_client.cc
index e411dab..182dc53b 100644
--- a/extensions/shell/browser/shell_content_browser_client.cc
+++ b/extensions/shell/browser/shell_content_browser_client.cc
@@ -48,7 +48,6 @@
 #include "extensions/shell/browser/shell_navigation_ui_data.h"
 #include "extensions/shell/browser/shell_speech_recognition_manager_delegate.h"
 #include "extensions/shell/common/version.h"  // Generated file.
-#include "storage/browser/quota/quota_settings.h"
 #include "url/gurl.h"
 
 #if BUILDFLAG(ENABLE_NACL)
@@ -132,15 +131,6 @@
   return true;
 }
 
-void ShellContentBrowserClient::GetQuotaSettings(
-    content::BrowserContext* context,
-    content::StoragePartition* partition,
-    storage::OptionalQuotaSettingsCallback callback) {
-  storage::GetNominalDynamicSettings(
-      partition->GetPath(), context->IsOffTheRecord(),
-      storage::GetDefaultDeviceInfoHelper(), std::move(callback));
-}
-
 bool ShellContentBrowserClient::IsHandledURL(const GURL& url) {
   if (!url.is_valid())
     return false;
diff --git a/extensions/shell/browser/shell_content_browser_client.h b/extensions/shell/browser/shell_content_browser_client.h
index c41b63d..ba5e219 100644
--- a/extensions/shell/browser/shell_content_browser_client.h
+++ b/extensions/shell/browser/shell_content_browser_client.h
@@ -13,7 +13,6 @@
 #include "content/public/browser/web_contents.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "storage/browser/quota/quota_settings.h"
 
 class GURL;
 
@@ -49,10 +48,6 @@
   void RenderProcessWillLaunch(content::RenderProcessHost* host) override;
   bool ShouldUseProcessPerSite(content::BrowserContext* browser_context,
                                const GURL& effective_url) override;
-  void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      storage::OptionalQuotaSettingsCallback callback) override;
   bool IsHandledURL(const GURL& url) override;
   void SiteInstanceGotProcess(content::SiteInstance* site_instance) override;
   void SiteInstanceDeleting(content::SiteInstance* site_instance) override;
diff --git a/gpu/BUILD.gn b/gpu/BUILD.gn
index 04bf17c..acc467ab 100644
--- a/gpu/BUILD.gn
+++ b/gpu/BUILD.gn
@@ -295,6 +295,7 @@
     "command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc",
     "command_buffer/service/shared_image_factory_unittest.cc",
     "command_buffer/service/shared_image_manager_unittest.cc",
+    "command_buffer/service/shared_image_representation_unittest.cc",
     "command_buffer/service/shared_image_test_utils.cc",
     "command_buffer/service/shared_image_test_utils.h",
     "command_buffer/tests/compressed_texture_test.cc",
diff --git a/gpu/GLES2/extensions/CHROMIUM/CHROMIUM_shared_image.txt b/gpu/GLES2/extensions/CHROMIUM/CHROMIUM_shared_image.txt
index def75e9..d540307 100644
--- a/gpu/GLES2/extensions/CHROMIUM/CHROMIUM_shared_image.txt
+++ b/gpu/GLES2/extensions/CHROMIUM/CHROMIUM_shared_image.txt
@@ -81,6 +81,10 @@
 
     mode - the access mode with which to begin access.
 
+    This function indicates that the calling context will access the SharedImage
+    bound to <texture> until glEndSharedImageAccessDirectCHROMIUM is called, or
+    the calling context deletes <texture>.
+
     INVALID_OPERATION is generated if the texture id indicated is not
     backed by a shared image.
 
diff --git a/gpu/command_buffer/service/external_vk_image_dawn_representation.cc b/gpu/command_buffer/service/external_vk_image_dawn_representation.cc
index f7f0427..be4d292 100644
--- a/gpu/command_buffer/service/external_vk_image_dawn_representation.cc
+++ b/gpu/command_buffer/service/external_vk_image_dawn_representation.cc
@@ -93,7 +93,7 @@
     // the result.
     // TODO(cwallez@chromium.org): This is incorrect and allows reading
     // uninitialized data. When !IsCleared we should tell dawn_native to
-    // consider the texture lazy-cleared.
+    // consider the texture lazy-cleared. crbug.com/1036080
     SetCleared();
   }
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
index a876c06a..00965e3d 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
@@ -391,7 +391,10 @@
 bool PassthroughResources::SharedImageData::BeginAccess(GLenum mode,
                                                         gl::GLApi* api) {
   DCHECK(!is_being_accessed());
-  scoped_access_ = representation_->BeginScopedAccess(mode);
+  // When importing a texture for use in passthrough cmd decoder, always allow
+  // uncleared access. We ensure the texture is cleared below.
+  scoped_access_ = representation_->BeginScopedAccess(
+      mode, SharedImageRepresentation::AllowUnclearedAccess::kYes);
   if (!scoped_access_) {
     return false;
   }
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_unittest_textures.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_unittest_textures.cc
index 9069424..f2fffe2c 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_unittest_textures.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_unittest_textures.cc
@@ -166,17 +166,10 @@
     EXPECT_EQ(GL_NO_ERROR, GetGLError());
     cmds::EndSharedImageAccessDirectCHROMIUM readwrite_end_cmd;
     readwrite_end_cmd.Init(client_id);
-    // EXPECT_EQ(error::kNoError, ExecuteCmd(readwrite_end_cmd));
-    // EXPECT_EQ(GL_NO_ERROR, GetGLError());
-  }
-
-  for (int i = 20; i > 10; --i) {
-    cmds::EndSharedImageAccessDirectCHROMIUM readwrite_end_cmd;
-    readwrite_end_cmd.Init(kNewClientId + i);
     EXPECT_EQ(error::kNoError, ExecuteCmd(readwrite_end_cmd));
     EXPECT_EQ(GL_NO_ERROR, GetGLError());
-    DoDeleteTexture(kNewClientId + i);
-    fprintf(stderr, "EEEE DoDeleteTexture() i=%d\n", i);
+
+    DoDeleteTexture(client_id);
   }
 
   // Cleanup
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index 6ab2f00..c333429c 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -1796,16 +1796,19 @@
 
   std::unique_ptr<SharedImageRepresentationGLTexturePassthrough::ScopedAccess>
       source_access = source_shared_image->BeginScopedAccess(
-          GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
+          GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM,
+          SharedImageRepresentation::AllowUnclearedAccess::kNo);
   if (!source_access) {
     LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glCopySubTexture",
                        "unable to access source for read");
     return;
   }
 
+  // Allow uncleared access, as we manually handle clear tracking.
   std::unique_ptr<SharedImageRepresentationGLTexturePassthrough::ScopedAccess>
       dest_access = dest_shared_image->BeginScopedAccess(
-          GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
+          GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
+          SharedImageRepresentation::AllowUnclearedAccess::kYes);
   if (!dest_access) {
     LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glCopySubTexture",
                        "unable to access destination for write");
@@ -1872,7 +1875,8 @@
 
   std::unique_ptr<SharedImageRepresentationGLTexture::ScopedAccess>
       source_access = source_shared_image->BeginScopedAccess(
-          GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
+          GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM,
+          SharedImageRepresentation::AllowUnclearedAccess::kNo);
   if (!source_access) {
     LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glCopySubTexture",
                        "unable to access source for read");
@@ -1891,9 +1895,11 @@
     return;
   }
 
+  // Allow uncleared access, as we manually handle clear tracking.
   std::unique_ptr<SharedImageRepresentationGLTexture::ScopedAccess>
       dest_access = dest_shared_image->BeginScopedAccess(
-          GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
+          GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
+          SharedImageRepresentation::AllowUnclearedAccess::kYes);
   if (!dest_access) {
     LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glCopySubTexture",
                        "unable to access destination for write");
@@ -2116,9 +2122,11 @@
   std::vector<GrBackendSemaphore> begin_semaphores;
   std::vector<GrBackendSemaphore> end_semaphores;
 
+  // Allow uncleared access, as we manually handle clear tracking.
   std::unique_ptr<SharedImageRepresentationSkia::ScopedWriteAccess>
       dest_scoped_access = dest_shared_image->BeginScopedWriteAccess(
-          &begin_semaphores, &end_semaphores);
+          &begin_semaphores, &end_semaphores,
+          SharedImageRepresentation::AllowUnclearedAccess::kYes);
   if (!dest_scoped_access) {
     LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glCopySubTexture",
                        "Dest shared image is not writable");
@@ -2336,8 +2344,11 @@
   std::vector<GrBackendSemaphore> begin_semaphores;
   DCHECK(end_semaphores_.empty());
   DCHECK(!scoped_shared_image_write_);
+  // Allow uncleared access, as raster specifically handles uncleared images by
+  // clearing them before writing.
   scoped_shared_image_write_ = shared_image_->BeginScopedWriteAccess(
-      final_msaa_count, surface_props, &begin_semaphores, &end_semaphores_);
+      final_msaa_count, surface_props, &begin_semaphores, &end_semaphores_,
+      SharedImageRepresentation::AllowUnclearedAccess::kYes);
   if (!scoped_shared_image_write_) {
     LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glBeginRasterCHROMIUM",
                        "failed to create surface");
diff --git a/gpu/command_buffer/service/raster_decoder_unittest.cc b/gpu/command_buffer/service/raster_decoder_unittest.cc
index 425b7f6..ac90def 100644
--- a/gpu/command_buffer/service/raster_decoder_unittest.cc
+++ b/gpu/command_buffer/service/raster_decoder_unittest.cc
@@ -297,6 +297,11 @@
   init.extensions.push_back("GL_EXT_texture_rg");
   InitDecoder(init);
 
+  // Recreate |client_texture_mailbox_| as a cleared mailbox.
+  client_texture_mailbox_ = CreateFakeTexture(
+      kServiceTextureId, viz::ResourceFormat::RGBA_8888, /*width=*/2,
+      /*height=*/2, /*cleared=*/true);
+
   // Create dest texture.
   gpu::Mailbox dest_texture_mailbox =
       CreateFakeTexture(kNewServiceId, viz::ResourceFormat::RED_8,
diff --git a/gpu/command_buffer/service/shared_image_backing_egl_image.cc b/gpu/command_buffer/service/shared_image_backing_egl_image.cc
index 71803a0c..6f93bfe 100644
--- a/gpu/command_buffer/service/shared_image_backing_egl_image.cc
+++ b/gpu/command_buffer/service/shared_image_backing_egl_image.cc
@@ -59,11 +59,6 @@
       egl_backing()->EndRead(this, std::move(egl_fence));
     } else if (mode_ == RepresentationAccessMode::kWrite) {
       egl_backing()->EndWrite(std::move(egl_fence));
-
-      if (texture_) {
-        if (texture_->IsLevelCleared(texture_->target(), 0))
-          backing()->SetCleared();
-      }
     } else {
       NOTREACHED();
     }
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
index 183dcdd..68becc3 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
@@ -234,11 +234,6 @@
       ahb_backing()->EndRead(this, std::move(sync_fd));
     } else if (mode_ == RepresentationAccessMode::kWrite) {
       ahb_backing()->EndWrite(std::move(sync_fd));
-
-      if (texture_) {
-        if (texture_->IsLevelCleared(texture_->target(), 0))
-          backing()->SetCleared();
-      }
     }
 
     mode_ = RepresentationAccessMode::kNone;
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer_unittest.cc
index 6e7db40..f34ebf15 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer_unittest.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer_unittest.cc
@@ -127,7 +127,8 @@
   std::unique_ptr<SharedImageRepresentationSkia::ScopedWriteAccess>
       scoped_write_access;
   scoped_write_access = skia_representation->BeginScopedWriteAccess(
-      &begin_semaphores, &end_semaphores);
+      &begin_semaphores, &end_semaphores,
+      SharedImageRepresentation::AllowUnclearedAccess::kYes);
   EXPECT_TRUE(scoped_write_access);
   auto* surface = scoped_write_access->surface();
   EXPECT_TRUE(surface);
@@ -325,7 +326,8 @@
   std::unique_ptr<SharedImageRepresentationSkia::ScopedWriteAccess>
       scoped_write_access;
   scoped_write_access = skia_representation->BeginScopedWriteAccess(
-      &begin_semaphores, &end_semaphores);
+      &begin_semaphores, &end_semaphores,
+      SharedImageRepresentation::AllowUnclearedAccess::kYes);
   EXPECT_TRUE(scoped_write_access);
   EXPECT_EQ(0u, begin_semaphores.size());
   EXPECT_EQ(0u, end_semaphores.size());
@@ -337,7 +339,8 @@
   std::unique_ptr<SharedImageRepresentationSkia::ScopedWriteAccess>
       scoped_write_access2;
   scoped_write_access2 = skia_representation2->BeginScopedWriteAccess(
-      &begin_semaphores2, &end_semaphores2);
+      &begin_semaphores2, &end_semaphores2,
+      SharedImageRepresentation::AllowUnclearedAccess::kYes);
   EXPECT_FALSE(scoped_write_access);
   EXPECT_EQ(0u, begin_semaphores2.size());
   EXPECT_EQ(0u, end_semaphores2.size());
@@ -419,7 +422,8 @@
   std::unique_ptr<SharedImageRepresentationSkia::ScopedWriteAccess>
       scoped_write_access;
   scoped_write_access = skia_representation2->BeginScopedWriteAccess(
-      &begin_semaphores2, &end_semaphores2);
+      &begin_semaphores2, &end_semaphores2,
+      SharedImageRepresentation::AllowUnclearedAccess::kYes);
   EXPECT_FALSE(scoped_write_access);
   EXPECT_EQ(0u, begin_semaphores2.size());
   EXPECT_EQ(0u, end_semaphores2.size());
@@ -446,7 +450,8 @@
   std::unique_ptr<SharedImageRepresentationSkia::ScopedWriteAccess>
       scoped_write_access;
   scoped_write_access = skia_representation->BeginScopedWriteAccess(
-      &begin_semaphores, &end_semaphores);
+      &begin_semaphores, &end_semaphores,
+      SharedImageRepresentation::AllowUnclearedAccess::kYes);
   EXPECT_TRUE(scoped_write_access);
   EXPECT_EQ(0u, begin_semaphores.size());
   EXPECT_EQ(0u, end_semaphores.size());
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc b/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
index 9308011d..4b55763 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
@@ -460,7 +460,7 @@
     // the result.
     // TODO(cwallez@chromium.org): This is incorrect and allows reading
     // uninitialized data. When !IsCleared we should tell dawn_native to
-    // consider the texture lazy-cleared.
+    // consider the texture lazy-cleared. crbug.com/1036080
     SetCleared();
   } else {
     d3d_image_backing->EndAccessD3D12();
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc
index 475d0261..2c5bb23 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc
@@ -514,7 +514,8 @@
 
   std::unique_ptr<SharedImageRepresentationGLTexturePassthrough::ScopedAccess>
       scoped_access = gl_representation->BeginScopedAccess(
-          GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
+          GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
+          SharedImageRepresentation::AllowUnclearedAccess::kYes);
   EXPECT_TRUE(scoped_access);
 
   // Create an FBO.
@@ -584,7 +585,8 @@
     ASSERT_TRUE(dawn_representation);
 
     auto scoped_access = dawn_representation->BeginScopedAccess(
-        WGPUTextureUsage_OutputAttachment);
+        WGPUTextureUsage_OutputAttachment,
+        SharedImageRepresentation::AllowUnclearedAccess::kNo);
     ASSERT_TRUE(scoped_access);
 
     wgpu::Texture texture = wgpu::Texture::Acquire(scoped_access->texture());
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc
index e9a32cc..beab91e 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture_unittest.cc
@@ -270,7 +270,8 @@
   std::unique_ptr<SharedImageRepresentationSkia::ScopedWriteAccess>
       scoped_write_access;
   scoped_write_access = skia_representation->BeginScopedWriteAccess(
-      &begin_semaphores, &end_semaphores);
+      &begin_semaphores, &end_semaphores,
+      SharedImageRepresentation::AllowUnclearedAccess::kYes);
   auto* surface = scoped_write_access->surface();
   EXPECT_TRUE(surface);
   EXPECT_EQ(size.width(), surface->width());
@@ -381,7 +382,8 @@
   std::unique_ptr<SharedImageRepresentationSkia::ScopedWriteAccess>
       scoped_write_access;
   scoped_write_access = skia_representation->BeginScopedWriteAccess(
-      &begin_semaphores, &end_semaphores);
+      &begin_semaphores, &end_semaphores,
+      SharedImageRepresentation::AllowUnclearedAccess::kYes);
   auto* surface = scoped_write_access->surface();
   EXPECT_TRUE(surface);
   EXPECT_EQ(size.width(), surface->width());
@@ -884,7 +886,8 @@
   // Begin writing to the underlying texture of the backing via ScopedAccess.
   std::unique_ptr<SharedImageRepresentationGLTexture::ScopedAccess>
       writer_scoped_access = gl_representation->BeginScopedAccess(
-          GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
+          GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM,
+          SharedImageRepresentation::AllowUnclearedAccess::kNo);
 
   DCHECK(writer_scoped_access);
 
@@ -903,6 +906,8 @@
   // Set the clear color to green.
   api->glClearColorFn(0.0f, 1.0f, 0.0f, 1.0f);
   api->glClearFn(GL_COLOR_BUFFER_BIT);
+  gl_representation->GetTexture()->SetLevelCleared(
+      gl_representation->GetTexture()->target(), 0, true);
 
   // End writing.
   writer_scoped_access.reset();
@@ -991,7 +996,8 @@
   std::unique_ptr<SharedImageRepresentationSkia::ScopedWriteAccess>
       scoped_write_access;
   scoped_write_access = skia_representation->BeginScopedWriteAccess(
-      &begin_semaphores, &end_semaphores);
+      &begin_semaphores, &end_semaphores,
+      SharedImageRepresentation::AllowUnclearedAccess::kNo);
   auto* surface = scoped_write_access->surface();
   EXPECT_TRUE(surface);
   EXPECT_EQ(size_.width(), surface->width());
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm b/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm
index 31afb13..b460c93 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm
+++ b/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm
@@ -304,7 +304,7 @@
       // the result.
       // TODO(cwallez@chromium.org): This is incorrect and allows reading
       // uninitialized data. When !IsCleared we should tell dawn_native to
-      // consider the texture lazy-cleared.
+      // consider the texture lazy-cleared. crbug.com/1036080
       SetCleared();
     }
 
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc
index 46cb3d7f..cc58235 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc
@@ -143,7 +143,8 @@
       scoped_write_access;
 
   scoped_write_access = skia_representation->BeginScopedWriteAccess(
-      &begin_semaphores, &end_semaphores);
+      &begin_semaphores, &end_semaphores,
+      SharedImageRepresentation::AllowUnclearedAccess::kYes);
   auto* surface = scoped_write_access->surface();
   EXPECT_TRUE(surface);
   EXPECT_EQ(size.width(), surface->width());
@@ -189,27 +190,36 @@
                                      memory_type_tracker_.get());
 
   // Create a SharedImageRepresentationGLTexture.
-  auto gl_representation =
-      shared_image_representation_factory_->ProduceGLTexture(mailbox);
-  EXPECT_TRUE(gl_representation);
-  EXPECT_EQ(expected_target, gl_representation->GetTexture()->target());
+  {
+    auto gl_representation =
+        shared_image_representation_factory_->ProduceGLTexture(mailbox);
+    EXPECT_TRUE(gl_representation);
+    EXPECT_EQ(expected_target, gl_representation->GetTexture()->target());
 
-  // Create an FBO.
-  GLuint fbo = 0;
-  gl::GLApi* api = gl::g_current_gl_context;
-  api->glGenFramebuffersEXTFn(1, &fbo);
-  api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo);
+    // Access the SharedImageRepresentationGLTexutre
+    auto scoped_write_access = gl_representation->BeginScopedAccess(
+        GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
+        SharedImageRepresentation::AllowUnclearedAccess::kYes);
 
-  // Attach the texture to FBO.
-  api->glFramebufferTexture2DEXTFn(
-      GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
-      gl_representation->GetTexture()->target(),
-      gl_representation->GetTexture()->service_id(), 0);
+    // Create an FBO.
+    GLuint fbo = 0;
+    gl::GLApi* api = gl::g_current_gl_context;
+    api->glGenFramebuffersEXTFn(1, &fbo);
+    api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo);
 
-  // Set the clear color to green.
-  api->glClearColorFn(0.0f, 1.0f, 0.0f, 1.0f);
-  api->glClearFn(GL_COLOR_BUFFER_BIT);
-  gl_representation.reset();
+    // Attach the texture to FBO.
+    api->glFramebufferTexture2DEXTFn(
+        GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+        gl_representation->GetTexture()->target(),
+        gl_representation->GetTexture()->service_id(), 0);
+
+    // Set the clear color to green.
+    api->glClearColorFn(0.0f, 1.0f, 0.0f, 1.0f);
+    api->glClearFn(GL_COLOR_BUFFER_BIT);
+
+    gl_representation->GetTexture()->SetLevelCleared(
+        gl_representation->GetTexture()->target(), 0, true);
+  }
 
   // Next create a SharedImageRepresentationSkia to read back the texture data.
   auto skia_representation = shared_image_representation_factory_->ProduceSkia(
@@ -353,7 +363,8 @@
   // Clear the shared image to green using Dawn.
   {
     auto scoped_access = dawn_representation->BeginScopedAccess(
-        WGPUTextureUsage_OutputAttachment);
+        WGPUTextureUsage_OutputAttachment,
+        SharedImageRepresentation::AllowUnclearedAccess::kYes);
     ASSERT_TRUE(scoped_access);
     wgpu::Texture texture = wgpu::Texture::Acquire(scoped_access->texture());
 
diff --git a/gpu/command_buffer/service/shared_image_representation.cc b/gpu/command_buffer/service/shared_image_representation.cc
index 7234e590..5a42baba 100644
--- a/gpu/command_buffer/service/shared_image_representation.cc
+++ b/gpu/command_buffer/service/shared_image_representation.cc
@@ -4,6 +4,7 @@
 
 #include "gpu/command_buffer/service/shared_image_representation.h"
 
+#include "gpu/command_buffer/service/texture_manager.h"
 #include "third_party/skia/include/core/SkPromiseImageTexture.h"
 
 namespace gpu {
@@ -22,7 +23,14 @@
 }
 
 std::unique_ptr<SharedImageRepresentationGLTexture::ScopedAccess>
-SharedImageRepresentationGLTextureBase::BeginScopedAccess(GLenum mode) {
+SharedImageRepresentationGLTextureBase::BeginScopedAccess(
+    GLenum mode,
+    AllowUnclearedAccess allow_uncleared) {
+  if (allow_uncleared != AllowUnclearedAccess::kYes && !IsCleared()) {
+    LOG(ERROR) << "Attempt to access an uninitialized ShardImage";
+    return nullptr;
+  }
+
   if (!BeginAccess(mode))
     return nullptr;
 
@@ -44,6 +52,15 @@
   return false;
 }
 
+void SharedImageRepresentationGLTexture::UpdateClearedStateOnEndAccess() {
+  auto* texture = GetTexture();
+  // Operations on the gles2::Texture may have cleared or uncleared it. Make
+  // sure this state is reflected back in the SharedImage.
+  gfx::Rect cleared_rect = texture->GetLevelClearedRect(texture->target(), 0);
+  if (cleared_rect != ClearedRect())
+    SetClearedRect(cleared_rect);
+}
+
 SharedImageRepresentationSkia::ScopedWriteAccess::ScopedWriteAccess(
     util::PassKey<SharedImageRepresentationSkia> /* pass_key */,
     SharedImageRepresentationSkia* representation,
@@ -59,7 +76,13 @@
     int final_msaa_count,
     const SkSurfaceProps& surface_props,
     std::vector<GrBackendSemaphore>* begin_semaphores,
-    std::vector<GrBackendSemaphore>* end_semaphores) {
+    std::vector<GrBackendSemaphore>* end_semaphores,
+    AllowUnclearedAccess allow_uncleared) {
+  if (allow_uncleared != AllowUnclearedAccess::kYes && !IsCleared()) {
+    LOG(ERROR) << "Attempt to write to an uninitialized ShardImage";
+    return nullptr;
+  }
+
   sk_sp<SkSurface> surface = BeginWriteAccess(final_msaa_count, surface_props,
                                               begin_semaphores, end_semaphores);
   if (!surface)
@@ -72,11 +95,12 @@
 std::unique_ptr<SharedImageRepresentationSkia::ScopedWriteAccess>
 SharedImageRepresentationSkia::BeginScopedWriteAccess(
     std::vector<GrBackendSemaphore>* begin_semaphores,
-    std::vector<GrBackendSemaphore>* end_semaphores) {
+    std::vector<GrBackendSemaphore>* end_semaphores,
+    AllowUnclearedAccess allow_uncleared) {
   return BeginScopedWriteAccess(
       0 /* final_msaa_count */,
       SkSurfaceProps(0 /* flags */, kUnknown_SkPixelGeometry), begin_semaphores,
-      end_semaphores);
+      end_semaphores, allow_uncleared);
 }
 
 SharedImageRepresentationSkia::ScopedReadAccess::ScopedReadAccess(
@@ -94,6 +118,11 @@
 SharedImageRepresentationSkia::BeginScopedReadAccess(
     std::vector<GrBackendSemaphore>* begin_semaphores,
     std::vector<GrBackendSemaphore>* end_semaphores) {
+  if (!IsCleared()) {
+    LOG(ERROR) << "Attempt to read from an uninitialized ShardImage";
+    return nullptr;
+  }
+
   sk_sp<SkPromiseImageTexture> promise_image_texture =
       BeginReadAccess(begin_semaphores, end_semaphores);
   if (!promise_image_texture)
@@ -106,6 +135,11 @@
 
 std::unique_ptr<SharedImageRepresentationOverlay::ScopedReadAccess>
 SharedImageRepresentationOverlay::BeginScopedReadAccess(bool needs_gl_image) {
+  if (!IsCleared()) {
+    LOG(ERROR) << "Attempt to read from an uninitialized ShardImage";
+    return nullptr;
+  }
+
   BeginReadAccess();
   return std::make_unique<ScopedReadAccess>(
       util::PassKey<SharedImageRepresentationOverlay>(), this,
@@ -123,7 +157,14 @@
 }
 
 std::unique_ptr<SharedImageRepresentationDawn::ScopedAccess>
-SharedImageRepresentationDawn::BeginScopedAccess(WGPUTextureUsage usage) {
+SharedImageRepresentationDawn::BeginScopedAccess(
+    WGPUTextureUsage usage,
+    AllowUnclearedAccess allow_uncleared) {
+  if (allow_uncleared != AllowUnclearedAccess::kYes && !IsCleared()) {
+    LOG(ERROR) << "Attempt to access an uninitialized ShardImage";
+    return nullptr;
+  }
+
   WGPUTexture texture = BeginAccess(usage);
   if (!texture)
     return nullptr;
diff --git a/gpu/command_buffer/service/shared_image_representation.h b/gpu/command_buffer/service/shared_image_representation.h
index 96699f1..da832e8 100644
--- a/gpu/command_buffer/service/shared_image_representation.h
+++ b/gpu/command_buffer/service/shared_image_representation.h
@@ -44,6 +44,9 @@
 // api.
 class GPU_GLES2_EXPORT SharedImageRepresentation {
  public:
+  // Used by derived classes.
+  enum class AllowUnclearedAccess { kYes, kNo };
+
   SharedImageRepresentation(SharedImageManager* manager,
                             SharedImageBacking* backing,
                             MemoryTypeTracker* tracker);
@@ -106,7 +109,10 @@
     ScopedAccess(util::PassKey<SharedImageRepresentationGLTextureBase> pass_key,
                  SharedImageRepresentationGLTextureBase* representation)
         : representation_(representation) {}
-    ~ScopedAccess() { representation_->EndAccess(); }
+    ~ScopedAccess() {
+      representation_->UpdateClearedStateOnEndAccess();
+      representation_->EndAccess();
+    }
 
    private:
     SharedImageRepresentationGLTextureBase* representation_ = nullptr;
@@ -119,11 +125,16 @@
                                          MemoryTypeTracker* tracker)
       : SharedImageRepresentation(manager, backing, tracker) {}
 
-  std::unique_ptr<ScopedAccess> BeginScopedAccess(GLenum mode);
+  std::unique_ptr<ScopedAccess> BeginScopedAccess(
+      GLenum mode,
+      AllowUnclearedAccess allow_uncleared);
 
  protected:
   friend class SharedImageRepresentationSkiaGL;
 
+  // Can be overridden to handle clear state tracking when GL access ends.
+  virtual void UpdateClearedStateOnEndAccess() {}
+
   // TODO(ericrk): Make these pure virtual and ensure real implementations
   // exist.
   virtual bool BeginAccess(GLenum mode);
@@ -140,6 +151,9 @@
 
   // TODO(ericrk): Move this to the ScopedAccess object. crbug.com/1003686
   virtual gles2::Texture* GetTexture() = 0;
+
+ protected:
+  void UpdateClearedStateOnEndAccess() override;
 };
 
 class GPU_GLES2_EXPORT SharedImageRepresentationGLTexturePassthrough
@@ -203,10 +217,13 @@
       int final_msaa_count,
       const SkSurfaceProps& surface_props,
       std::vector<GrBackendSemaphore>* begin_semaphores,
-      std::vector<GrBackendSemaphore>* end_semaphores);
+      std::vector<GrBackendSemaphore>* end_semaphores,
+      AllowUnclearedAccess allow_uncleared);
+
   std::unique_ptr<ScopedWriteAccess> BeginScopedWriteAccess(
       std::vector<GrBackendSemaphore>* begin_semaphores,
-      std::vector<GrBackendSemaphore>* end_semaphores);
+      std::vector<GrBackendSemaphore>* end_semaphores,
+      AllowUnclearedAccess allow_uncleared);
 
   // Note: See BeginReadAccess below for a description of the semaphore
   // parameters.
@@ -271,7 +288,9 @@
   // Calls BeginAccess and returns a ScopedAccess object which will EndAccess
   // when it goes out of scope. The Representation must outlive the returned
   // ScopedAccess.
-  std::unique_ptr<ScopedAccess> BeginScopedAccess(WGPUTextureUsage usage);
+  std::unique_ptr<ScopedAccess> BeginScopedAccess(
+      WGPUTextureUsage usage,
+      AllowUnclearedAccess allow_uncleared);
 
  private:
   // This can return null in case of a Dawn validation error, for example if
diff --git a/gpu/command_buffer/service/shared_image_representation_dawn_ozone.cc b/gpu/command_buffer/service/shared_image_representation_dawn_ozone.cc
index 1b7d95f..df4a9d4 100644
--- a/gpu/command_buffer/service/shared_image_representation_dawn_ozone.cc
+++ b/gpu/command_buffer/service/shared_image_representation_dawn_ozone.cc
@@ -90,7 +90,7 @@
     // the result.
     // TODO(cwallez@chromium.org): This is incorrect and allows reading
     // uninitialized data. When !IsCleared we should tell dawn_native to
-    // consider the texture lazy-cleared.
+    // consider the texture lazy-cleared. crbug.com/1036080
     SetCleared();
   } else {
     close(fd);
diff --git a/gpu/command_buffer/service/shared_image_representation_unittest.cc b/gpu/command_buffer/service/shared_image_representation_unittest.cc
new file mode 100644
index 0000000..81c6055
--- /dev/null
+++ b/gpu/command_buffer/service/shared_image_representation_unittest.cc
@@ -0,0 +1,235 @@
+// Copyright 2018 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 "gpu/command_buffer/service/shared_image_representation.h"
+
+#include "components/viz/common/resources/resource_format_utils.h"
+#include "gpu/command_buffer/common/mailbox.h"
+#include "gpu/command_buffer/common/shared_image_usage.h"
+#include "gpu/command_buffer/service/shared_context_state.h"
+#include "gpu/command_buffer/service/shared_image_backing.h"
+#include "gpu/command_buffer/service/shared_image_representation.h"
+#include "gpu/command_buffer/service/test_shared_image_backing.h"
+#include "gpu/command_buffer/service/texture_manager.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkPromiseImageTexture.h"
+#include "third_party/skia/include/gpu/GrBackendSurface.h"
+
+namespace gpu {
+
+class SharedImageRepresentationTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    tracker_ = std::make_unique<MemoryTypeTracker>(nullptr);
+    mailbox_ = Mailbox::GenerateForSharedImage();
+    auto format = viz::ResourceFormat::RGBA_8888;
+    gfx::Size size(256, 256);
+    auto color_space = gfx::ColorSpace::CreateSRGB();
+    uint32_t usage = SHARED_IMAGE_USAGE_GLES2;
+
+    auto backing = std::make_unique<TestSharedImageBacking>(
+        mailbox_, format, size, color_space, usage, 0 /* estimated_size */);
+    factory_ref_ = manager_.Register(std::move(backing), tracker_.get());
+  }
+
+ protected:
+  gpu::Mailbox mailbox_;
+  SharedImageManager manager_;
+  std::unique_ptr<MemoryTypeTracker> tracker_;
+  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref_;
+};
+
+TEST_F(SharedImageRepresentationTest, GLTextureClearing) {
+  auto representation = manager_.ProduceGLTexture(mailbox_, tracker_.get());
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We should not be able to begin access when |allow_uncleared| is false.
+  {
+    auto scoped_access = representation->BeginScopedAccess(
+        GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
+        SharedImageRepresentation::AllowUnclearedAccess::kNo);
+    EXPECT_FALSE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // Begin/End access should not modify clear status on its own.
+  {
+    auto scoped_access = representation->BeginScopedAccess(
+        GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
+        SharedImageRepresentation::AllowUnclearedAccess::kYes);
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // Clearing underlying GL texture should clear the SharedImage.
+  {
+    auto scoped_access = representation->BeginScopedAccess(
+        GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
+        SharedImageRepresentation::AllowUnclearedAccess::kYes);
+    ASSERT_TRUE(scoped_access);
+    representation->GetTexture()->SetLevelCleared(GL_TEXTURE_2D, 0,
+                                                  true /* cleared */);
+  }
+  EXPECT_TRUE(representation->IsCleared());
+
+  // We can now begin accdess with |allow_uncleared| == false.
+  {
+    auto scoped_access = representation->BeginScopedAccess(
+        GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
+        SharedImageRepresentation::AllowUnclearedAccess::kNo);
+    EXPECT_TRUE(scoped_access);
+  }
+}
+
+TEST_F(SharedImageRepresentationTest, GLTexturePassthroughClearing) {
+  auto representation =
+      manager_.ProduceGLTexturePassthrough(mailbox_, tracker_.get());
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We should not be able to begin access when |allow_uncleared| is false.
+  {
+    auto scoped_access = representation->BeginScopedAccess(
+        GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
+        SharedImageRepresentation::AllowUnclearedAccess::kNo);
+    EXPECT_FALSE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // Begin/End access will not clear the representation on its own.
+  {
+    auto scoped_access = representation->BeginScopedAccess(
+        GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
+        SharedImageRepresentation::AllowUnclearedAccess::kYes);
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // Clear the SharedImage.
+  representation->SetCleared();
+  EXPECT_TRUE(representation->IsCleared());
+
+  // We can now begin accdess with |allow_uncleared| == false.
+  {
+    auto scoped_access = representation->BeginScopedAccess(
+        GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
+        SharedImageRepresentation::AllowUnclearedAccess::kNo);
+    EXPECT_TRUE(scoped_access);
+  }
+}
+
+TEST_F(SharedImageRepresentationTest, SkiaClearing) {
+  auto representation = manager_.ProduceSkia(mailbox_, tracker_.get(), nullptr);
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We should not be able to begin read access.
+  {
+    auto scoped_access =
+        representation->BeginScopedReadAccess(nullptr, nullptr);
+    EXPECT_FALSE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We should not be able to begin write access when |allow_uncleared| is
+  // false.
+  {
+    auto scoped_access = representation->BeginScopedWriteAccess(
+        nullptr, nullptr, SharedImageRepresentation::AllowUnclearedAccess::kNo);
+    EXPECT_FALSE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We can begin write access when |allow_uncleared| is true.
+  {
+    auto scoped_access = representation->BeginScopedWriteAccess(
+        nullptr, nullptr,
+        SharedImageRepresentation::AllowUnclearedAccess::kYes);
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // Clear the SharedImage.
+  representation->SetCleared();
+  EXPECT_TRUE(representation->IsCleared());
+
+  // We can now begin read access.
+  {
+    auto scoped_access =
+        representation->BeginScopedReadAccess(nullptr, nullptr);
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_TRUE(representation->IsCleared());
+
+  // We can also begin write access with |allow_uncleared| == false.
+  {
+    auto scoped_access = representation->BeginScopedWriteAccess(
+        nullptr, nullptr, SharedImageRepresentation::AllowUnclearedAccess::kNo);
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_TRUE(representation->IsCleared());
+}
+
+TEST_F(SharedImageRepresentationTest, DawnClearing) {
+  auto representation =
+      manager_.ProduceDawn(mailbox_, tracker_.get(), nullptr /* device */);
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We should not be able to begin access with |allow_uncleared| == false.
+  {
+    auto scoped_access = representation->BeginScopedAccess(
+        WGPUTextureUsage_None,
+        SharedImageRepresentation::AllowUnclearedAccess::kNo);
+    EXPECT_FALSE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We can begin access when |allow_uncleared| is true.
+  {
+    auto scoped_access = representation->BeginScopedAccess(
+        WGPUTextureUsage_None,
+        SharedImageRepresentation::AllowUnclearedAccess::kYes);
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // Clear the SharedImage.
+  representation->SetCleared();
+  EXPECT_TRUE(representation->IsCleared());
+
+  // We can also begin access with |allow_uncleared| == false.
+  {
+    auto scoped_access = representation->BeginScopedAccess(
+        WGPUTextureUsage_None,
+        SharedImageRepresentation::AllowUnclearedAccess::kNo);
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_TRUE(representation->IsCleared());
+}
+
+TEST_F(SharedImageRepresentationTest, OverlayClearing) {
+  auto representation = manager_.ProduceOverlay(mailbox_, tracker_.get());
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We should not be able to begin read ccess.
+  {
+    auto scoped_access =
+        representation->BeginScopedReadAccess(false /* needs_gl_image */);
+    EXPECT_FALSE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // Clear the SharedImage.
+  representation->SetCleared();
+  EXPECT_TRUE(representation->IsCleared());
+
+  // We can now begin read access.
+  {
+    auto scoped_access =
+        representation->BeginScopedReadAccess(false /* needs_gl_image */);
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_TRUE(representation->IsCleared());
+}
+
+}  // namespace gpu
diff --git a/gpu/command_buffer/service/test_shared_image_backing.cc b/gpu/command_buffer/service/test_shared_image_backing.cc
index 62fac229..3427f341a 100644
--- a/gpu/command_buffer/service/test_shared_image_backing.cc
+++ b/gpu/command_buffer/service/test_shared_image_backing.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "gpu/command_buffer/service/test_shared_image_backing.h"
+#include "build/build_config.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "gpu/command_buffer/service/shared_context_state.h"
 #include "third_party/skia/include/core/SkPromiseImageTexture.h"
@@ -55,6 +56,75 @@
   const scoped_refptr<gles2::TexturePassthrough> texture_;
 };
 
+class TestSharedImageRepresentationSkia : public SharedImageRepresentationSkia {
+ public:
+  TestSharedImageRepresentationSkia(SharedImageManager* manager,
+                                    SharedImageBacking* backing,
+                                    MemoryTypeTracker* tracker)
+      : SharedImageRepresentationSkia(manager, backing, tracker) {}
+
+ protected:
+  sk_sp<SkSurface> BeginWriteAccess(
+      int final_msaa_count,
+      const SkSurfaceProps& surface_props,
+      std::vector<GrBackendSemaphore>* begin_semaphores,
+      std::vector<GrBackendSemaphore>* end_semaphores) override {
+    if (!static_cast<TestSharedImageBacking*>(backing())->can_access()) {
+      return nullptr;
+    }
+    return SkSurface::MakeRasterN32Premul(size().width(), size().height());
+  }
+  void EndWriteAccess(sk_sp<SkSurface> surface) override {}
+  sk_sp<SkPromiseImageTexture> BeginReadAccess(
+      std::vector<GrBackendSemaphore>* begin_semaphores,
+      std::vector<GrBackendSemaphore>* end_semaphores) override {
+    if (!static_cast<TestSharedImageBacking*>(backing())->can_access()) {
+      return nullptr;
+    }
+    GrBackendTexture backend_tex(size().width(), size().height(),
+                                 GrMipMapped::kNo, GrMockTextureInfo());
+    return SkPromiseImageTexture::Make(backend_tex);
+  }
+  void EndReadAccess() override {}
+};
+
+class TestSharedImageRepresentationDawn : public SharedImageRepresentationDawn {
+ public:
+  TestSharedImageRepresentationDawn(SharedImageManager* manager,
+                                    SharedImageBacking* backing,
+                                    MemoryTypeTracker* tracker)
+      : SharedImageRepresentationDawn(manager, backing, tracker) {}
+
+  WGPUTexture BeginAccess(WGPUTextureUsage usage) override {
+    if (!static_cast<TestSharedImageBacking*>(backing())->can_access()) {
+      return nullptr;
+    }
+
+    // Return a dummy value.
+    return reinterpret_cast<WGPUTexture>(203);
+  }
+
+  void EndAccess() override {}
+};
+
+class TestSharedImageRepresentationOverlay
+    : public SharedImageRepresentationOverlay {
+ public:
+  TestSharedImageRepresentationOverlay(SharedImageManager* manager,
+                                       SharedImageBacking* backing,
+                                       MemoryTypeTracker* tracker)
+      : SharedImageRepresentationOverlay(manager, backing, tracker) {}
+
+  void BeginReadAccess() override {}
+  void EndReadAccess() override {}
+  gl::GLImage* GetGLImage() override { return nullptr; }
+
+#if defined(OS_ANDROID)
+  void NotifyOverlayPromotion(bool promotion,
+                              const gfx::Rect& bounds) override {}
+#endif
+};
+
 }  // namespace
 
 TestSharedImageBacking::TestSharedImageBacking(
@@ -146,4 +216,28 @@
       manager, this, tracker, texture_passthrough_);
 }
 
+std::unique_ptr<SharedImageRepresentationSkia>
+TestSharedImageBacking::ProduceSkia(
+    SharedImageManager* manager,
+    MemoryTypeTracker* tracker,
+    scoped_refptr<SharedContextState> context_state) {
+  return std::make_unique<TestSharedImageRepresentationSkia>(manager, this,
+                                                             tracker);
+}
+
+std::unique_ptr<SharedImageRepresentationDawn>
+TestSharedImageBacking::ProduceDawn(SharedImageManager* manager,
+                                    MemoryTypeTracker* tracker,
+                                    WGPUDevice device) {
+  return std::make_unique<TestSharedImageRepresentationDawn>(manager, this,
+                                                             tracker);
+}
+
+std::unique_ptr<SharedImageRepresentationOverlay>
+TestSharedImageBacking::ProduceOverlay(SharedImageManager* manager,
+                                       MemoryTypeTracker* tracker) {
+  return std::make_unique<TestSharedImageRepresentationOverlay>(manager, this,
+                                                                tracker);
+}
+
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/test_shared_image_backing.h b/gpu/command_buffer/service/test_shared_image_backing.h
index 93540b8..14d3654 100644
--- a/gpu/command_buffer/service/test_shared_image_backing.h
+++ b/gpu/command_buffer/service/test_shared_image_backing.h
@@ -52,6 +52,20 @@
   ProduceGLTexturePassthrough(SharedImageManager* manager,
                               MemoryTypeTracker* tracker) override;
 
+  // ProduceSkia/Dawn/Overlay all create dummy representations that
+  // don't link up to a real texture.
+  std::unique_ptr<SharedImageRepresentationSkia> ProduceSkia(
+      SharedImageManager* manager,
+      MemoryTypeTracker* tracker,
+      scoped_refptr<SharedContextState> context_state) override;
+  std::unique_ptr<SharedImageRepresentationDawn> ProduceDawn(
+      SharedImageManager* manager,
+      MemoryTypeTracker* tracker,
+      WGPUDevice device) override;
+  std::unique_ptr<SharedImageRepresentationOverlay> ProduceOverlay(
+      SharedImageManager* manager,
+      MemoryTypeTracker* tracker) override;
+
  private:
   const GLuint service_id_ = 0;
   gles2::Texture* texture_ = nullptr;
diff --git a/gpu/command_buffer/service/texture_manager.cc b/gpu/command_buffer/service/texture_manager.cc
index 4ca4a0a..b889041 100644
--- a/gpu/command_buffer/service/texture_manager.cc
+++ b/gpu/command_buffer/service/texture_manager.cc
@@ -2106,15 +2106,27 @@
 }
 
 bool TextureRef::BeginAccessSharedImage(GLenum mode) {
-  shared_image_scoped_access_ = shared_image_->BeginScopedAccess(mode);
+  // When accessing through TextureManager, we are using legacy GL logic which
+  // tracks clearning internally. Always allow access to uncleared
+  // SharedImages.
+  shared_image_scoped_access_ = shared_image_->BeginScopedAccess(
+      mode, SharedImageRepresentation::AllowUnclearedAccess::kYes);
   if (!shared_image_scoped_access_) {
     return false;
   }
+  // After beginning access, the returned gles2::Texture's cleared status
+  // should match the SharedImage's.
+  DCHECK_EQ(shared_image_->ClearedRect(),
+            texture_->GetLevelClearedRect(texture_->target(), 0));
   return true;
 }
 
 void TextureRef::EndAccessSharedImage() {
   shared_image_scoped_access_.reset();
+  // After ending access, the SharedImages cleared rect should be synchronized
+  // with |texture_|'s.
+  DCHECK_EQ(shared_image_->ClearedRect(),
+            texture_->GetLevelClearedRect(texture_->target(), 0));
 }
 
 void TextureRef::ForceContextLost() {
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index adb00ed1..343088a3 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -836,8 +836,12 @@
     return error::kInvalidArguments;
   }
 
+  // TODO(cwallez@chromium.org): Handle texture clearing. We should either
+  // pre-clear textures, or implement a way to detect whether DAWN has cleared
+  // a texture. crbug.com/1036080
   std::unique_ptr<SharedImageRepresentationDawn::ScopedAccess>
-      shared_image_access = shared_image->BeginScopedAccess(wgpu_usage);
+      shared_image_access = shared_image->BeginScopedAccess(
+          wgpu_usage, SharedImageRepresentation::AllowUnclearedAccess::kYes);
   if (!shared_image_access) {
     DLOG(ERROR) << "AssociateMailbox: Couldn't begin shared image access";
     return error::kInvalidArguments;
diff --git a/gpu/command_buffer/service/wrapped_sk_image.cc b/gpu/command_buffer/service/wrapped_sk_image.cc
index 528b8f9..d8c46b23f 100644
--- a/gpu/command_buffer/service/wrapped_sk_image.cc
+++ b/gpu/command_buffer/service/wrapped_sk_image.cc
@@ -157,6 +157,7 @@
       if (!image_)
         return false;
 
+      SetCleared();
       OnWriteSucceeded();
     } else {
       // Initializing to bright green makes it obvious if the pixels are not
diff --git a/headless/lib/browser/headless_content_browser_client.cc b/headless/lib/browser/headless_content_browser_client.cc
index 0713b65b..aac5672 100644
--- a/headless/lib/browser/headless_content_browser_client.cc
+++ b/headless/lib/browser/headless_content_browser_client.cc
@@ -34,7 +34,6 @@
 #include "net/ssl/client_cert_identity.h"
 #include "printing/buildflags/buildflags.h"
 #include "services/service_manager/sandbox/switches.h"
-#include "storage/browser/quota/quota_settings.h"
 #include "ui/base/ui_base_switches.h"
 #include "ui/gfx/switches.h"
 
@@ -149,15 +148,6 @@
   return new HeadlessQuotaPermissionContext();
 }
 
-void HeadlessContentBrowserClient::GetQuotaSettings(
-    content::BrowserContext* context,
-    content::StoragePartition* partition,
-    ::storage::OptionalQuotaSettingsCallback callback) {
-  ::storage::GetNominalDynamicSettings(
-      partition->GetPath(), context->IsOffTheRecord(),
-      ::storage::GetDefaultDeviceInfoHelper(), std::move(callback));
-}
-
 content::GeneratedCodeCacheSettings
 HeadlessContentBrowserClient::GetGeneratedCodeCacheSettings(
     content::BrowserContext* context) {
diff --git a/headless/lib/browser/headless_content_browser_client.h b/headless/lib/browser/headless_content_browser_client.h
index 82b45cb..51576d29 100644
--- a/headless/lib/browser/headless_content_browser_client.h
+++ b/headless/lib/browser/headless_content_browser_client.h
@@ -9,7 +9,6 @@
 
 #include "content/public/browser/content_browser_client.h"
 #include "headless/public/headless_browser.h"
-#include "storage/browser/quota/quota_settings.h"
 
 namespace headless {
 
@@ -28,10 +27,6 @@
   content::DevToolsManagerDelegate* GetDevToolsManagerDelegate() override;
   scoped_refptr<content::QuotaPermissionContext> CreateQuotaPermissionContext()
       override;
-  void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      ::storage::OptionalQuotaSettingsCallback callback) override;
   content::GeneratedCodeCacheSettings GetGeneratedCodeCacheSettings(
       content::BrowserContext* context) override;
 #if defined(OS_POSIX) && !defined(OS_MACOSX)
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn b/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn
index 0c129f5..535e9f31 100644
--- a/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn
+++ b/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn
@@ -4,6 +4,8 @@
 
 source_set("infobar_banner") {
   sources = [
+    "infobar_banner_overlay_responses.cc",
+    "infobar_banner_overlay_responses.h",
     "save_password_infobar_banner_overlay.h",
     "save_password_infobar_banner_overlay.mm",
   ]
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.cc b/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.cc
new file mode 100644
index 0000000..75603b2
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.cc
@@ -0,0 +1,31 @@
+// Copyright 2019 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 "ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h"
+
+#pragma mark - InfobarBannerMainActionResponse
+
+OVERLAY_USER_DATA_SETUP_IMPL(InfobarBannerMainActionResponse);
+
+InfobarBannerMainActionResponse::InfobarBannerMainActionResponse() = default;
+
+InfobarBannerMainActionResponse::~InfobarBannerMainActionResponse() = default;
+
+#pragma mark - InfobarBannerShowModalResponse
+
+OVERLAY_USER_DATA_SETUP_IMPL(InfobarBannerShowModalResponse);
+
+InfobarBannerShowModalResponse::InfobarBannerShowModalResponse() = default;
+
+InfobarBannerShowModalResponse::~InfobarBannerShowModalResponse() = default;
+
+#pragma mark - InfobarBannerCompletionResponse
+
+OVERLAY_USER_DATA_SETUP_IMPL(InfobarBannerCompletionResponse);
+
+InfobarBannerCompletionResponse::InfobarBannerCompletionResponse(
+    bool user_initiated)
+    : user_initiated_(user_initiated) {}
+
+InfobarBannerCompletionResponse::~InfobarBannerCompletionResponse() = default;
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h b/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h
new file mode 100644
index 0000000..92f60c9
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h
@@ -0,0 +1,52 @@
+// Copyright 2019 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 IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_RESPONSES_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_RESPONSES_H_
+
+#include "ios/chrome/browser/overlays/public/overlay_user_data.h"
+
+// Response info used to create dispatched OverlayResponses that trigger the
+// infobar's main action.
+class InfobarBannerMainActionResponse
+    : public OverlayUserData<InfobarBannerMainActionResponse> {
+ public:
+  ~InfobarBannerMainActionResponse() override;
+
+ private:
+  OVERLAY_USER_DATA_SETUP(InfobarBannerMainActionResponse);
+  InfobarBannerMainActionResponse();
+};
+
+// Response info used to create dispatched OverlayResponses that trigger the
+// presentation of the infobar's modal.
+class InfobarBannerShowModalResponse
+    : public OverlayUserData<InfobarBannerShowModalResponse> {
+ public:
+  ~InfobarBannerShowModalResponse() override;
+
+ private:
+  OVERLAY_USER_DATA_SETUP(InfobarBannerShowModalResponse);
+  InfobarBannerShowModalResponse();
+};
+
+// Response info used to create completion OverlayResponses for an infobar
+// banner OverlayRequest.  Executed when the banner is dismissed by the user or
+// the request is cancelled.
+class InfobarBannerCompletionResponse
+    : public OverlayUserData<InfobarBannerCompletionResponse> {
+ public:
+  ~InfobarBannerCompletionResponse() override;
+
+  // Whether the banner dismissal was user-initiated.
+  bool user_initiated() const { return user_initiated_; }
+
+ private:
+  OVERLAY_USER_DATA_SETUP(InfobarBannerCompletionResponse);
+  explicit InfobarBannerCompletionResponse(bool user_initiated);
+
+  bool user_initiated_ = false;
+};
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_RESPONSES_H_
diff --git a/ios/chrome/browser/ui/infobars/banners/infobar_banner_consumer.h b/ios/chrome/browser/ui/infobars/banners/infobar_banner_consumer.h
index 6042ec1..601ad67 100644
--- a/ios/chrome/browser/ui/infobars/banners/infobar_banner_consumer.h
+++ b/ios/chrome/browser/ui/infobars/banners/infobar_banner_consumer.h
@@ -5,7 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_INFOBARS_BANNERS_INFOBAR_BANNER_CONSUMER_H_
 #define IOS_CHROME_BROWSER_UI_INFOBARS_BANNERS_INFOBAR_BANNER_CONSUMER_H_
 
-#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
 
 @protocol InfobarBannerConsumer <NSObject>
 
diff --git a/ios/chrome/browser/ui/infobars/banners/infobar_banner_delegate.h b/ios/chrome/browser/ui/infobars/banners/infobar_banner_delegate.h
index 33891e9..bddedbc 100644
--- a/ios/chrome/browser/ui/infobars/banners/infobar_banner_delegate.h
+++ b/ios/chrome/browser/ui/infobars/banners/infobar_banner_delegate.h
@@ -5,7 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_INFOBARS_BANNERS_INFOBAR_BANNER_DELEGATE_H_
 #define IOS_CHROME_BROWSER_UI_INFOBARS_BANNERS_INFOBAR_BANNER_DELEGATE_H_
 
-#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
 
 // Delegate to handle InfobarBanner actions.
 @protocol InfobarBannerDelegate
diff --git a/ios/chrome/browser/ui/infobars/banners/test/BUILD.gn b/ios/chrome/browser/ui/infobars/banners/test/BUILD.gn
new file mode 100644
index 0000000..dd71e84
--- /dev/null
+++ b/ios/chrome/browser/ui/infobars/banners/test/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2019 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("test") {
+  testonly = true
+  sources = [
+    "fake_infobar_banner_consumer.h",
+    "fake_infobar_banner_consumer.mm",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    "//base",
+    "//ios/chrome/browser/ui/infobars/banners",
+  ]
+}
diff --git a/ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.h b/ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.h
new file mode 100644
index 0000000..2c4421e
--- /dev/null
+++ b/ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.h
@@ -0,0 +1,21 @@
+// Copyright 2019 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 IOS_CHROME_BROWSER_UI_INFOBARS_BANNERS_TEST_FAKE_INFOBAR_BANNER_CONSUMER_H_
+#define IOS_CHROME_BROWSER_UI_INFOBARS_BANNERS_TEST_FAKE_INFOBAR_BANNER_CONSUMER_H_
+
+#import "ios/chrome/browser/ui/infobars/banners/infobar_banner_consumer.h"
+
+// Fake InfobarBannerConsumer used in tests.
+@interface FakeInfobarBannerConsumer : NSObject <InfobarBannerConsumer>
+// Redefine InfobarBannerConsumer properties as readwrite.
+@property(nonatomic, copy) NSString* bannerAccessibilityLabel;
+@property(nonatomic, copy) NSString* buttonText;
+@property(nonatomic, strong) UIImage* iconImage;
+@property(nonatomic, assign) BOOL presentsModal;
+@property(nonatomic, copy) NSString* titleText;
+@property(nonatomic, copy) NSString* subtitleText;
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_INFOBARS_BANNERS_TEST_FAKE_INFOBAR_BANNER_CONSUMER_H_
diff --git a/ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.mm b/ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.mm
new file mode 100644
index 0000000..4fe97c0b
--- /dev/null
+++ b/ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.mm
@@ -0,0 +1,12 @@
+// Copyright 2019 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.
+
+#import "ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation FakeInfobarBannerConsumer
+@end
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm
index a71eea54..f1b1a28 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm
@@ -233,13 +233,8 @@
       assertWithMatcher:grey_nil()];
 }
 
-// TODO(crbug.com/1037651): Enable for EG2.
-#if defined(CHROME_EARL_GREY_2)
-#define MAYBE_testCloseNTPWhenSwitching DISABLED_testCloseNTPWhenSwitching
-#else
-#define MAYBE_testCloseNTPWhenSwitching testCloseNTPWhenSwitching
-#endif
-- (void)MAYBE_testCloseNTPWhenSwitching {
+// TODO(crbug.com/1037651): Test fails.
+- (void)DISABLED_testCloseNTPWhenSwitching {
   // Open the first page.
   GURL URL1 = self.testServer->GetURL(kPage1URL);
   [ChromeEarlGrey loadURL:URL1];
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/BUILD.gn b/ios/chrome/browser/ui/overlays/infobar_banner/BUILD.gn
index 6611898..f2656eb7 100644
--- a/ios/chrome/browser/ui/overlays/infobar_banner/BUILD.gn
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/BUILD.gn
@@ -14,3 +14,52 @@
 
   deps = []
 }
+
+source_set("common") {
+  sources = [
+    "infobar_banner_overlay_coordinator.h",
+    "infobar_banner_overlay_coordinator.mm",
+    "infobar_banner_overlay_mediator+consumer_support.h",
+    "infobar_banner_overlay_mediator.h",
+    "infobar_banner_overlay_mediator.mm",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    "//base",
+    "//components/infobars/core",
+    "//ios/chrome/browser/overlays",
+    "//ios/chrome/browser/overlays/public/common/infobars",
+    "//ios/chrome/browser/overlays/public/infobar_banner",
+    "//ios/chrome/browser/ui/infobars/banners",
+    "//ios/chrome/browser/ui/infobars/banners:public",
+    "//ios/chrome/browser/ui/overlays:coordinators",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "infobar_banner_overlay_mediator_unittest.mm",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    ":common",
+    "//components/infobars/core",
+    "//ios/chrome/browser/infobars/test",
+    "//ios/chrome/browser/overlays",
+    "//ios/chrome/browser/overlays/public/common/infobars",
+    "//ios/chrome/browser/overlays/public/infobar_banner",
+    "//ios/chrome/browser/ui/elements",
+    "//ios/chrome/browser/ui/infobars/banners",
+    "//ios/chrome/browser/ui/infobars/banners:public",
+    "//ios/chrome/browser/ui/infobars/banners/test",
+    "//ios/chrome/browser/ui/overlays/test",
+    "//testing/gtest",
+    "//third_party/ocmock",
+    "//ui/base",
+  ]
+}
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_coordinator.h b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_coordinator.h
new file mode 100644
index 0000000..3e3add8
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_coordinator.h
@@ -0,0 +1,14 @@
+// Copyright 2019 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 IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_COORDINATOR_H_
+
+#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator.h"
+
+// A coordinator that displays infobar banner UI using OverlayPresenter.
+@interface InfobarBannerOverlayCoordinator : OverlayRequestCoordinator
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_coordinator.mm b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_coordinator.mm
new file mode 100644
index 0000000..007400de
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_coordinator.mm
@@ -0,0 +1,136 @@
+// Copyright 2019 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.
+
+#import "ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_coordinator.h"
+
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/no_destructor.h"
+#include "ios/chrome/browser/overlays/public/aggregate_overlay_request_support.h"
+#import "ios/chrome/browser/overlays/public/common/infobars/infobar_overlay_request_config.h"
+#import "ios/chrome/browser/overlays/public/overlay_request.h"
+#import "ios/chrome/browser/overlays/public/overlay_response.h"
+#import "ios/chrome/browser/ui/infobars/banners/infobar_banner_accessibility_util.h"
+#import "ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.h"
+#import "ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.h"
+#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator+subclassing.h"
+#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator_delegate.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+// Creates a vector containing the OverlayRequestSupports for each
+// OverlayRequestMediator class in |mediator_classes|
+const std::vector<const OverlayRequestSupport*> GetSupportsForMediatorClasses(
+    NSArray<Class>* mediator_classes) {
+  std::vector<const OverlayRequestSupport*> supports(mediator_classes.count);
+  for (NSUInteger index = 0; index < mediator_classes.count; ++index) {
+    supports[index] = [mediator_classes[index] requestSupport];
+  }
+  return supports;
+}
+}  // namespace
+
+@interface InfobarBannerOverlayCoordinator ()
+// The list of supported mediator classes.
+@property(class, nonatomic, readonly) NSArray<Class>* supportedMediatorClasses;
+// The banner view being managed by this coordinator.
+@property(nonatomic) InfobarBannerViewController* bannerViewController;
+@end
+
+@implementation InfobarBannerOverlayCoordinator
+
+#pragma mark - Accessors
+
++ (NSArray<Class>*)supportedMediatorClasses {
+  // TODO(crbug.com/1030357): Add more mediator classes here.
+  return @[];
+}
+
++ (const OverlayRequestSupport*)requestSupport {
+  static base::NoDestructor<AggregateOverlayRequestSupport> kSupport(
+      GetSupportsForMediatorClasses(self.supportedMediatorClasses));
+  return kSupport.get();
+}
+
+#pragma mark - OverlayRequestCoordinator
+
+- (void)startAnimated:(BOOL)animated {
+  if (self.started || !self.request)
+    return;
+  // Create the mediator and use it aas the delegate for the banner view.
+  InfobarOverlayRequestConfig* config =
+      self.request->GetConfig<InfobarOverlayRequestConfig>();
+  InfobarBannerOverlayMediator* mediator = [self newMediator];
+  self.bannerViewController = [[InfobarBannerViewController alloc]
+      initWithDelegate:mediator
+         presentsModal:config->has_badge()
+                  type:config->infobar_type()];
+  mediator.consumer = self.bannerViewController;
+  self.mediator = mediator;
+  // Present the banner.
+  // TODO(crbug.com/1030357): Use custom presentation.
+  self.bannerViewController.modalPresentationStyle =
+      UIModalPresentationOverCurrentContext;
+  self.bannerViewController.modalTransitionStyle =
+      UIModalTransitionStyleCrossDissolve;
+  [self.baseViewController presentViewController:self.viewController
+                                        animated:animated
+                                      completion:^{
+                                        [self finishPresentation];
+                                      }];
+  self.started = YES;
+}
+
+- (void)stopAnimated:(BOOL)animated {
+  if (!self.started)
+    return;
+  [self.baseViewController dismissViewControllerAnimated:animated
+                                              completion:^{
+                                                [self finishDismissal];
+                                              }];
+  self.started = NO;
+}
+
+- (UIViewController*)viewController {
+  return self.bannerViewController;
+}
+
+#pragma mark - Private
+
+// Called when the presentation of the banner UI is completed.
+- (void)finishPresentation {
+  // Notify the presentation context that the presentation has finished.  This
+  // is necessary to synchronize OverlayPresenter scheduling logic with the UI
+  // layer.
+  self.delegate->OverlayUIDidFinishPresentation(self.request);
+  UpdateBannerAccessibilityForPresentation(self.baseViewController,
+                                           self.viewController.view);
+}
+
+// Called when the dismissal of the banner UI is finished.
+- (void)finishDismissal {
+  self.bannerViewController = nil;
+  self.mediator = nil;
+  // Notify the presentation context that the dismissal has finished.  This
+  // is necessary to synchronize OverlayPresenter scheduling logic with the UI
+  // layer.
+  self.delegate->OverlayUIDidFinishDismissal(self.request);
+  UpdateBannerAccessibilityForDismissal(self.baseViewController);
+}
+
+// Creates a mediator instance from the supported mediator class list that
+// supports the coordinator's request.
+- (InfobarBannerOverlayMediator*)newMediator {
+  for (Class mediatorClass in [self class].supportedMediatorClasses) {
+    if (mediatorClass.requestSupport->IsRequestSupported(self.request))
+      return [[mediatorClass alloc] initWithRequest:self.request];
+  }
+  NOTREACHED() << "None of the supported mediator classes support request.";
+  return nil;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator+consumer_support.h b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator+consumer_support.h
new file mode 100644
index 0000000..6561216
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator+consumer_support.h
@@ -0,0 +1,20 @@
+// Copyright 2019 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 IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_MEDIATOR_CONSUMER_SUPPORT_H_
+#define IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_MEDIATOR_CONSUMER_SUPPORT_H_
+
+#import "ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.h"
+
+// Category used by the InfobarBannerOverlayMediator superclass in order to
+// configure its consumer.
+@interface InfobarBannerOverlayMediator (ConsumerSupport)
+
+// Sets up the banner consumer using the configuration information from the
+// mediator's OverlayRequest.  Subclasses must implement.
+- (void)configureConsumer;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_MEDIATOR_CONSUMER_SUPPORT_H_
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.h b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.h
new file mode 100644
index 0000000..db2d895
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.h
@@ -0,0 +1,23 @@
+// Copyright 2019 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 IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_MEDIATOR_H_
+
+#import "ios/chrome/browser/ui/infobars/banners/infobar_banner_delegate.h"
+#import "ios/chrome/browser/ui/overlays/overlay_request_mediator.h"
+
+@protocol InfobarBannerConsumer;
+
+// Mediator superclass for configuring InfobarBannerConsumers.
+@interface InfobarBannerOverlayMediator
+    : OverlayRequestMediator <InfobarBannerDelegate>
+
+// The consumer to be updated by this mediator.  Setting to a new value updates
+// the new consumer.
+@property(nonatomic, weak) id<InfobarBannerConsumer> consumer;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.mm b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.mm
new file mode 100644
index 0000000..fe4090f2
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.mm
@@ -0,0 +1,116 @@
+// Copyright 2019 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.
+
+#import "ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.h"
+
+#import <UIKit/UIKit.h>
+
+#include "base/logging.h"
+#import "base/strings/sys_string_conversions.h"
+#import "ios/chrome/browser/overlays/public/common/infobars/infobar_banner_overlay_request_config.h"
+#import "ios/chrome/browser/overlays/public/common/infobars/infobar_overlay_request_config.h"
+#include "ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h"
+#include "ios/chrome/browser/overlays/public/overlay_callback_manager.h"
+#include "ios/chrome/browser/overlays/public/overlay_request.h"
+#include "ios/chrome/browser/overlays/public/overlay_response.h"
+#import "ios/chrome/browser/ui/infobars/banners/infobar_banner_consumer.h"
+#import "ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator+consumer_support.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface InfobarBannerOverlayMediator ()
+// The banner config from the request.
+@property(nonatomic, readonly) InfobarBannerOverlayRequestConfig* config;
+@end
+
+@implementation InfobarBannerOverlayMediator
+
+- (instancetype)initWithRequest:(OverlayRequest*)request {
+  if (self = [super initWithRequest:request]) {
+    DCHECK(request->GetConfig<InfobarBannerOverlayRequestConfig>());
+  }
+  return self;
+}
+
+#pragma mark - Accessors
+
+- (void)setConsumer:(id<InfobarBannerConsumer>)consumer {
+  if (_consumer == consumer)
+    return;
+  _consumer = consumer;
+  if (_consumer)
+    [self configureConsumer];
+}
+
+- (InfobarBannerOverlayRequestConfig*)config {
+  return self.request
+             ? self.request->GetConfig<InfobarBannerOverlayRequestConfig>()
+             : nullptr;
+}
+
+#pragma mark - InfobarBannerDelegate
+
+- (void)bannerInfobarButtonWasPressed:(UIButton*)sender {
+  // Notify the model layer to perform the infobar's main action before
+  // dismissing the banner.
+  [self dispatchResponseAndStopOverlay:OverlayResponse::CreateWithInfo<
+                                           InfobarBannerMainActionResponse>()];
+}
+
+- (void)dismissInfobarBannerForUserInteraction:(BOOL)userInitiated {
+  if (self.request) {
+    // Add a completion response notifying the requesting code of whether the
+    // dismissal was user-initiated.  Provided to the request's completion
+    // callbacks that are executed when the UI is finished being dismissed.
+    self.request->GetCallbackManager()->SetCompletionResponse(
+        OverlayResponse::CreateWithInfo<InfobarBannerCompletionResponse>(
+            userInitiated));
+  }
+  [self.delegate stopOverlayForMediator:self];
+}
+
+- (void)presentInfobarModalFromBanner {
+  // Notify the model layer to show the infobar modal before dismissing the
+  // banner.
+  [self dispatchResponseAndStopOverlay:OverlayResponse::CreateWithInfo<
+                                           InfobarBannerShowModalResponse>()];
+}
+
+- (void)infobarBannerWasDismissed {
+  // Only needed in legacy implementation.  Dismissal completion cleanup occurs
+  // in InfobarBannerOverlayCoordinator.
+}
+
+#pragma mark - Private
+
+// Dispatches |response| through the OverlayRequest, then stops the overlay UI.
+- (void)dispatchResponseAndStopOverlay:
+    (std::unique_ptr<OverlayResponse>)response {
+  if (self.request)
+    self.request->GetCallbackManager()->DispatchResponse(std::move(response));
+  [self.delegate stopOverlayForMediator:self];
+}
+
+@end
+
+@implementation InfobarBannerOverlayMediator (ConsumerSupport)
+
+- (void)configureConsumer {
+  // TODO(crbug.com/1030357): Add NOTREACHED() here to enforce that subclasses
+  // implement.
+  InfobarBannerOverlayRequestConfig* config = self.config;
+  if (!config)
+    return;
+  [self.consumer
+      setBannerAccessibilityLabel:config->banner_accessibility_label()];
+  [self.consumer setButtonText:config->button_text()];
+  [self.consumer setIconImage:[UIImage imageNamed:config->icon_image_name()]];
+  [self.consumer setPresentsModal:config->presents_modal()];
+  [self.consumer setTitleText:config->title_text()];
+  [self.consumer setSubtitleText:config->subtitle_text()];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator_unittest.mm b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator_unittest.mm
new file mode 100644
index 0000000..4791ddc
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator_unittest.mm
@@ -0,0 +1,135 @@
+// Copyright 2019 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.
+
+#import "ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.h"
+
+#import "base/bind.h"
+#include "base/strings/sys_string_conversions.h"
+#import "ios/chrome/browser/overlays/public/common/infobars/infobar_banner_overlay_request_config.h"
+#include "ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h"
+#include "ios/chrome/browser/overlays/public/overlay_callback_manager.h"
+#include "ios/chrome/browser/overlays/public/overlay_request.h"
+#include "ios/chrome/browser/overlays/public/overlay_response.h"
+#import "ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.h"
+#import "testing/gtest_mac.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#import "third_party/ocmock/gtest_support.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+NSString* const kBannerAccessibilityLabel = @"a11y label";
+NSString* const kButtonText = @"button text";
+NSString* const kIconImageName = @"";
+const bool kPresentsModal(false);
+NSString* const kTitleText = @"title text";
+NSString* const kSubtitleText = @"subtitle text";
+}
+
+// Test fixture for InfobarBannerOverlayMediator.
+class InfobarBannerOverlayMediatorTest : public PlatformTest {
+ public:
+  InfobarBannerOverlayMediatorTest()
+      : request_(
+            OverlayRequest::CreateWithConfig<InfobarBannerOverlayRequestConfig>(
+                kBannerAccessibilityLabel,
+                kButtonText,
+                kIconImageName,
+                kPresentsModal,
+                kTitleText,
+                kSubtitleText)),
+        delegate_(
+            OCMStrictProtocolMock(@protocol(OverlayRequestMediatorDelegate))),
+        mediator_([[InfobarBannerOverlayMediator alloc]
+            initWithRequest:request_.get()]) {
+    mediator_.delegate = delegate_;
+  }
+  ~InfobarBannerOverlayMediatorTest() override {
+    EXPECT_OCMOCK_VERIFY(delegate_);
+  }
+
+ protected:
+  std::unique_ptr<OverlayRequest> request_;
+  id<OverlayRequestMediatorDelegate> delegate_ = nil;
+  InfobarBannerOverlayMediator* mediator_ = nil;
+};
+
+// Tests that an InfobarBannerOverlayMediator correctly sets up its consumer.
+TEST_F(InfobarBannerOverlayMediatorTest, SetUpConsumer) {
+  FakeInfobarBannerConsumer* consumer =
+      [[FakeInfobarBannerConsumer alloc] init];
+  mediator_.consumer = consumer;
+  EXPECT_NSEQ(kBannerAccessibilityLabel, consumer.bannerAccessibilityLabel);
+  EXPECT_NSEQ(kButtonText, consumer.buttonText);
+  EXPECT_NSEQ(nil, consumer.iconImage);
+  EXPECT_EQ(kPresentsModal, consumer.presentsModal);
+  EXPECT_NSEQ(kTitleText, consumer.titleText);
+  EXPECT_NSEQ(kSubtitleText, consumer.subtitleText);
+}
+
+// Tests that an InfobarBannerOverlayMediator correctly dispatches a response
+// for confirm button taps before stopping itself.
+TEST_F(InfobarBannerOverlayMediatorTest, ConfirmButtonTapped) {
+  __block bool confirm_button_tapped = false;
+  void (^confirm_button_tapped_callback)(OverlayResponse* response) =
+      ^(OverlayResponse* response) {
+        confirm_button_tapped = true;
+      };
+  request_->GetCallbackManager()
+      ->AddDispatchCallback<InfobarBannerMainActionResponse>(
+          base::BindRepeating(confirm_button_tapped_callback));
+  ASSERT_FALSE(confirm_button_tapped);
+
+  // Notify the mediator of the button tap via its InfobarBannerDelegate
+  // implementation and verify that the confirm button callback was executed and
+  // that the mediator's delegate was instructed to stop.
+  OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
+  [mediator_ bannerInfobarButtonWasPressed:nil];
+  EXPECT_TRUE(confirm_button_tapped);
+}
+
+// Tests that an InfobarBannerOverlayMediator correctly dispatches a response
+// for modal button taps before stopping itself.
+TEST_F(InfobarBannerOverlayMediatorTest, ModalButtonTapped) {
+  __block bool modal_button_tapped = false;
+  void (^modal_button_tapped_callback)(OverlayResponse* response) =
+      ^(OverlayResponse* response) {
+        modal_button_tapped = true;
+      };
+  request_->GetCallbackManager()
+      ->AddDispatchCallback<InfobarBannerShowModalResponse>(
+          base::BindRepeating(modal_button_tapped_callback));
+  ASSERT_FALSE(modal_button_tapped);
+
+  // Notify the mediator of the button tap via its InfobarBannerDelegate
+  // implementation and verify that the modal button callback was executed and
+  // that the mediator's delegate was instructed to stop.
+  OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
+  [mediator_ presentInfobarModalFromBanner];
+  EXPECT_TRUE(modal_button_tapped);
+}
+
+// Tests that an InfobarBannerOverlayMediator correctly sets the completion
+// response for user-initiated dismissals triggered by the banner UI.
+TEST_F(InfobarBannerOverlayMediatorTest, UserInitiatedDismissal) {
+  __block bool user_initiated = false;
+  void (^completion_callback)(OverlayResponse* response) =
+      ^(OverlayResponse* response) {
+        user_initiated = true;
+      };
+  request_->GetCallbackManager()->AddCompletionCallback(
+      base::BindOnce(completion_callback));
+  ASSERT_FALSE(user_initiated);
+
+  // Notify the mediator of the dismissal via its InfobarBannerDelegate
+  // implementation and verify that the completion callback was executed with
+  // the correct info and that the mediator's delegate was instructed to stop.
+  OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
+  [mediator_ dismissInfobarBannerForUserInteraction:YES];
+  request_ = nullptr;
+  EXPECT_TRUE(user_initiated);
+}
diff --git a/ios/chrome/browser/ui/settings/google_services/BUILD.gn b/ios/chrome/browser/ui/settings/google_services/BUILD.gn
index 5aeff8c..e5102dfc 100644
--- a/ios/chrome/browser/ui/settings/google_services/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/google_services/BUILD.gn
@@ -34,6 +34,7 @@
     "manage_sync_settings_view_controller_model_delegate.h",
   ]
   deps = [
+    ":constants",
     "resources:google_services_enterprise",
     "resources:google_services_sync_error",
     "//base",
@@ -78,9 +79,6 @@
     "//ios/public/provider/chrome/browser/signin",
     "//ui/base",
   ]
-  public_deps = [
-    ":constants",
-  ]
   allow_circular_includes_from =
       [ "//ios/chrome/browser/ui/signin_interaction" ]
 }
@@ -109,7 +107,7 @@
     "google_services_settings_egtest.mm",
   ]
   deps = [
-    ":google_services",
+    ":constants",
     ":test_support",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/tabs",
diff --git a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.h b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.h
index 936281a..0e40217 100644
--- a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.h
+++ b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.h
@@ -5,7 +5,6 @@
 #ifndef IOS_CHROME_BROWSER_UI_SETTINGS_GOOGLE_SERVICES_ACCOUNTS_TABLE_VIEW_CONTROLLER_H_
 #define IOS_CHROME_BROWSER_UI_SETTINGS_GOOGLE_SERVICES_ACCOUNTS_TABLE_VIEW_CONTROLLER_H_
 
-#import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller_constants.h"
 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
 #import "ios/chrome/browser/ui/settings/settings_root_table_view_controller.h"
 
diff --git a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
index 5da7c23..c63feb30 100644
--- a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
@@ -28,6 +28,7 @@
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
 #import "ios/chrome/browser/ui/icons/chrome_icon.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_text_item.h"
+#import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller_constants.h"
 #import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h"
 #import "ios/chrome/browser/ui/signin_interaction/signin_interaction_coordinator.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h"
diff --git a/ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_coordinator.h b/ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_coordinator.h
index 0925ba8..20cc76da 100644
--- a/ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_coordinator.h
+++ b/ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_coordinator.h
@@ -7,7 +7,6 @@
 
 #import "base/ios/block_types.h"
 #import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
-#import "ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_constants.h"
 
 @class AdvancedSigninSettingsCoordinator;
 @protocol ApplicationCommands;
diff --git a/ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_coordinator.mm b/ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_coordinator.mm
index cf90c6f..28cd527 100644
--- a/ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_coordinator.mm
@@ -20,6 +20,7 @@
 #include "ios/chrome/browser/sync/sync_setup_service_factory.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
 #import "ios/chrome/browser/ui/icons/chrome_icon.h"
+#import "ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_constants.h"
 #import "ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_navigation_controller.h"
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.h"
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_mode.h"
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.h b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.h
index abce854..5b52197 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.h
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.h
@@ -7,7 +7,6 @@
 
 #import <UIKit/UIKit.h>
 
-#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_constants.h"
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_consumer.h"
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_mode.h"
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_service_delegate.h"
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
index 36bc700..ebf6cd9b 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
@@ -26,6 +26,7 @@
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h"
 #import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_command_handler.h"
+#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_constants.h"
 #import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h"
 #import "ios/chrome/browser/ui/settings/utils/observable_boolean.h"
 #import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.h b/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.h
index 13a68dd..95cb9d6 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.h
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.h
@@ -7,7 +7,6 @@
 
 #import "ios/chrome/browser/ui/settings/settings_root_table_view_controller.h"
 
-#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_constants.h"
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_consumer.h"
 
 @class GoogleServicesSettingsViewController;
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.mm b/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.mm
index 0711791..f534e29 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.mm
@@ -7,6 +7,7 @@
 #include "base/mac/foundation_util.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_cell.h"
 #import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
+#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_constants.h"
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_service_delegate.h"
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller_model_delegate.h"
 #include "ios/chrome/grit/ios_strings.h"
diff --git a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.h b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.h
index 1a8347bd..fe34aa6e 100644
--- a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.h
+++ b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.h
@@ -7,7 +7,6 @@
 
 #import <UIKit/UIKit.h>
 
-#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_constants.h"
 #import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_service_delegate.h"
 #import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_view_controller_model_delegate.h"
 
diff --git a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.mm b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.mm
index 4721d2f..12c0095 100644
--- a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.mm
@@ -16,6 +16,7 @@
 #import "ios/chrome/browser/ui/list_model/list_model.h"
 #import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
 #import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_command_handler.h"
+#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_constants.h"
 #import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_consumer.h"
 #import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
diff --git a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_table_view_controller.mm
index 918570d..34e0aab 100644
--- a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_table_view_controller.mm
@@ -7,6 +7,7 @@
 #include "base/mac/foundation_util.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_cell.h"
 #import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
+#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_constants.h"
 #import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_service_delegate.h"
 #import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_view_controller_model_delegate.h"
 #include "ios/chrome/grit/ios_strings.h"
diff --git a/ios/chrome/browser/ui/settings/settings_egtest.mm b/ios/chrome/browser/ui/settings/settings_egtest.mm
index 1decdb1..1ef8c11 100644
--- a/ios/chrome/browser/ui/settings/settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/settings_egtest.mm
@@ -351,14 +351,10 @@
 // Tests that clearing the cookies through the UI does clear all of them. Use a
 // local server to navigate to a page that sets then tests a cookie, and then
 // clears the cookie and tests it is not set.
-#if defined(CHROME_EARL_GREY_1)
-#define MAYBE_testClearCookies testClearCookies
-#elif defined(CHROME_EARL_GREY_2)
-#define MAYBE_testClearCookies DISABLED_testClearCookies
-#endif
 // TODO(crbug.com/1036133): [ChromeEarlGrey cookies] does not work correctly in
 // this test.
-- (void)MAYBE_testClearCookies {
+// TODO(crbug.com/1038398): This test crashes flakily.
+- (void)DISABLED_testClearCookies {
   // Creates a map of canned responses and set up the test HTML server.
   std::map<GURL, std::pair<std::string, std::string>> response;
 
diff --git a/ios/chrome/browser/ui/signin_interaction/BUILD.gn b/ios/chrome/browser/ui/signin_interaction/BUILD.gn
index c4300e9..48a65ff 100644
--- a/ios/chrome/browser/ui/signin_interaction/BUILD.gn
+++ b/ios/chrome/browser/ui/signin_interaction/BUILD.gn
@@ -97,7 +97,7 @@
     "//ios/chrome/browser/ui/content_suggestions:content_suggestions_constant",
     "//ios/chrome/browser/ui/recent_tabs:recent_tabs_ui_constants",
     "//ios/chrome/browser/ui/settings",
-    "//ios/chrome/browser/ui/settings/google_services",
+    "//ios/chrome/browser/ui/settings/google_services:constants",
     "//ios/chrome/browser/ui/table_view/cells",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/web_state_list",
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index d31098b4..f8bfa17 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -266,6 +266,7 @@
     "//ios/chrome/browser/ui/open_in:unit_tests",
     "//ios/chrome/browser/ui/overlays:unit_tests",
     "//ios/chrome/browser/ui/overlays/common/alerts:unit_tests",
+    "//ios/chrome/browser/ui/overlays/infobar_banner:unit_tests",
     "//ios/chrome/browser/ui/overlays/web_content_area/app_launcher:unit_tests",
     "//ios/chrome/browser/ui/overlays/web_content_area/http_auth_dialogs:unit_tests",
     "//ios/chrome/browser/ui/overlays/web_content_area/java_script_dialogs:unit_tests",
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index 67de4f91..45e14d40 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -262,6 +262,7 @@
     "//ios/chrome/browser/ui/safe_mode:test_support",
     "//ios/chrome/browser/ui/settings:constants",
     "//ios/chrome/browser/ui/settings:settings",
+    "//ios/chrome/browser/ui/settings:settings_root",
     "//ios/chrome/browser/ui/settings:settings_root_constants",
     "//ios/chrome/browser/ui/settings/autofill",
     "//ios/chrome/browser/ui/settings/autofill:constants",
@@ -269,7 +270,7 @@
     "//ios/chrome/browser/ui/settings/cells",
     "//ios/chrome/browser/ui/settings/clear_browsing_data",
     "//ios/chrome/browser/ui/settings/credit_card_scanner",
-    "//ios/chrome/browser/ui/settings/google_services",
+    "//ios/chrome/browser/ui/settings/google_services:constants",
     "//ios/chrome/browser/ui/settings/password:eg_test_support",
     "//ios/chrome/browser/ui/settings/password:password_constants",
     "//ios/chrome/browser/ui/settings/sync",
@@ -410,6 +411,7 @@
     "//ios/chrome/browser/ui/settings:constants",
     "//ios/chrome/browser/ui/settings:eg_app_support+eg2",
     "//ios/chrome/browser/ui/settings:settings",
+    "//ios/chrome/browser/ui/settings:settings_root",
     "//ios/chrome/browser/ui/settings:settings_root_constants",
     "//ios/chrome/browser/ui/settings/autofill",
     "//ios/chrome/browser/ui/settings/autofill:constants",
@@ -417,8 +419,7 @@
     "//ios/chrome/browser/ui/settings/cells",
     "//ios/chrome/browser/ui/settings/clear_browsing_data",
     "//ios/chrome/browser/ui/settings/credit_card_scanner",
-    "//ios/chrome/browser/ui/settings/google_services",
-    "//ios/chrome/browser/ui/settings/google_services:eg_app_support+eg2",
+    "//ios/chrome/browser/ui/settings/google_services:constants",
     "//ios/chrome/browser/ui/settings/password:eg_app_support+eg2",
     "//ios/chrome/browser/ui/settings/password:password_constants",
     "//ios/chrome/browser/ui/settings/sync",
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
index 94e8f89e..8b79eea 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
@@ -42,12 +42,13 @@
 #import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_collection_view_controller.h"
 #import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_ui_constants.h"
 #import "ios/chrome/browser/ui/settings/credit_card_scanner/credit_card_scanner_view_controller.h"
-#import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.h"
-#import "ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_coordinator.h"
-#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.h"
+#import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller_constants.h"
+#import "ios/chrome/browser/ui/settings/google_services/advanced_signin_settings_constants.h"
+#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_constants.h"
 #import "ios/chrome/browser/ui/settings/import_data_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/password/passwords_table_view_constants.h"
 #import "ios/chrome/browser/ui/settings/privacy_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
 #import "ios/chrome/browser/ui/settings/settings_root_table_constants.h"
 #import "ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h"
 #import "ios/chrome/browser/ui/tab_grid/grid/grid_constants.h"
diff --git a/net/dns/dns_util.cc b/net/dns/dns_util.cc
index dd010fbf..20a2128 100644
--- a/net/dns/dns_util.cc
+++ b/net/dns/dns_util.cc
@@ -477,6 +477,20 @@
     return entries[0]->provider;
 }
 
+std::map<std::string, std::string> GetDohServerTemplatesListForTesting() {
+  const std::vector<DohUpgradeEntry>& upgradable_servers = GetDohUpgradeList();
+  std::map<std::string, std::string> server_templates;
+  for (const auto& upgrade_entry : upgradable_servers) {
+    auto return_val = server_templates.insert(
+        std::make_pair(upgrade_entry.provider,
+                       upgrade_entry.dns_over_https_config.server_template));
+    // Check that the new element was inserted. The map's key is the DoH
+    // provider name which should be unique.
+    DCHECK(return_val.second);
+  }
+  return server_templates;
+}
+
 std::string SecureDnsModeToString(
     const DnsConfig::SecureDnsMode secure_dns_mode) {
   switch (secure_dns_mode) {
diff --git a/net/dns/dns_util.h b/net/dns/dns_util.h
index f5c7c44..18fbddc 100644
--- a/net/dns/dns_util.h
+++ b/net/dns/dns_util.h
@@ -141,6 +141,11 @@
 NET_EXPORT_PRIVATE std::string SecureDnsModeToString(
     const DnsConfig::SecureDnsMode secure_dns_mode);
 
+// Returns a map of DoH provider names and server templates
+// from the auto-upgrade list for testing.
+NET_EXPORT_PRIVATE std::map<std::string, std::string>
+GetDohServerTemplatesListForTesting();
+
 }  // namespace net
 
 #endif  // NET_DNS_DNS_UTIL_H_
diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc
index 5d9a913..795386a 100644
--- a/net/dns/host_resolver_manager.cc
+++ b/net/dns/host_resolver_manager.cc
@@ -600,7 +600,9 @@
     results_ = std::move(results);
   }
 
-  void set_error_info(int error) { error_info_ = ResolveErrorInfo(error); }
+  void set_error_info(int error, bool is_secure_network_error) {
+    error_info_ = ResolveErrorInfo(error, is_secure_network_error);
+  }
 
   void set_stale_info(HostCache::EntryStaleness stale_info) {
     // Should only be called at most once and before request is marked
@@ -634,9 +636,10 @@
   }
 
   // Cleans up Job assignment, marks request completed, and calls the completion
-  // callback.
-  void OnJobCompleted(Job* job, int error) {
-    set_error_info(error);
+  // callback. |is_secure_network_error| indicates whether |error| came from a
+  // secure DNS lookup.
+  void OnJobCompleted(Job* job, int error, bool is_secure_network_error) {
+    set_error_info(error, is_secure_network_error);
 
     DCHECK_EQ(job_, job);
     job_ = nullptr;
@@ -2591,7 +2594,9 @@
         req->set_results(
             results.CopyWithDefaultPort(req->request_host().port()));
       }
-      req->OnJobCompleted(this, results.error());
+      req->OnJobCompleted(
+          this, results.error(),
+          secure && results.error() != OK /* is_secure_network_error */);
 
       // Check if the resolver was destroyed as a result of running the
       // callback. If it was, we could continue, but we choose to bail.
@@ -2985,7 +2990,8 @@
       request->set_stale_info(std::move(stale_info).value());
     RecordTotalTime(request->parameters().is_speculative, true /* from_cache */,
                     effective_secure_dns_mode, base::TimeDelta());
-    request->set_error_info(results.error());
+    request->set_error_info(results.error(),
+                            false /* is_secure_network_error */);
     return results.error();
   }
 
diff --git a/net/dns/host_resolver_manager_unittest.cc b/net/dns/host_resolver_manager_unittest.cc
index 7d465e2..73a6844 100644
--- a/net/dns/host_resolver_manager_unittest.cc
+++ b/net/dns/host_resolver_manager_unittest.cc
@@ -5300,6 +5300,8 @@
       HostPortPair("automatic", 80), NetworkIsolationKey(), NetLogWithSource(),
       base::nullopt, request_context_.get(), host_cache_.get()));
   ASSERT_THAT(response_secure.result_error(), IsOk());
+  EXPECT_FALSE(
+      response_secure.request()->GetResolveErrorInfo().is_secure_network_error);
   EXPECT_THAT(
       response_secure.request()->GetAddressResults().value().endpoints(),
       testing::UnorderedElementsAre(CreateExpected("127.0.0.1", 80),
@@ -5318,6 +5320,9 @@
       NetLogWithSource(), base::nullopt, request_context_.get(),
       host_cache_.get()));
   ASSERT_THAT(response_insecure.result_error(), IsOk());
+  EXPECT_FALSE(response_insecure.request()
+                   ->GetResolveErrorInfo()
+                   .is_secure_network_error);
   EXPECT_THAT(
       response_insecure.request()->GetAddressResults().value().endpoints(),
       testing::UnorderedElementsAre(CreateExpected("127.0.0.1", 80),
@@ -5360,6 +5365,9 @@
       NetLogWithSource(), base::nullopt, request_context_.get(),
       host_cache_.get()));
   EXPECT_THAT(response_secure_cached.result_error(), IsOk());
+  EXPECT_FALSE(response_secure_cached.request()
+                   ->GetResolveErrorInfo()
+                   .is_secure_network_error);
   EXPECT_THAT(
       response_secure_cached.request()->GetAddressResults().value().endpoints(),
       testing::ElementsAre(kExpectedSecureIP));
@@ -5387,6 +5395,9 @@
       NetLogWithSource(), base::nullopt, request_context_.get(),
       host_cache_.get()));
   EXPECT_THAT(response_insecure_cached.result_error(), IsOk());
+  EXPECT_FALSE(response_insecure_cached.request()
+                   ->GetResolveErrorInfo()
+                   .is_secure_network_error);
   EXPECT_THAT(response_insecure_cached.request()
                   ->GetAddressResults()
                   .value()
@@ -5473,6 +5484,9 @@
       HostPortPair("automatic", 80), NetworkIsolationKey(), NetLogWithSource(),
       base::nullopt, request_context_.get(), host_cache_.get()));
   ASSERT_THAT(response_automatic.result_error(), IsOk());
+  EXPECT_FALSE(response_automatic.request()
+                   ->GetResolveErrorInfo()
+                   .is_secure_network_error);
   EXPECT_THAT(
       response_automatic.request()->GetAddressResults().value().endpoints(),
       testing::UnorderedElementsAre(CreateExpected("127.0.0.1", 80),
@@ -5505,6 +5519,8 @@
       HostPortPair("secure", 80), NetworkIsolationKey(), NetLogWithSource(),
       base::nullopt, request_context_.get(), host_cache_.get()));
   ASSERT_THAT(response_secure.result_error(), IsError(ERR_NAME_NOT_RESOLVED));
+  EXPECT_FALSE(
+      response_secure.request()->GetResolveErrorInfo().is_secure_network_error);
 
   HostCache::Key secure_key = HostCache::Key(
       "secure", DnsQueryType::UNSPECIFIED, 0 /* host_resolver_flags */,
@@ -5546,6 +5562,8 @@
       NetLogWithSource(), stale_allowed_parameters, request_context_.get(),
       host_cache_.get()));
   EXPECT_THAT(response_stale.result_error(), IsOk());
+  EXPECT_FALSE(
+      response_stale.request()->GetResolveErrorInfo().is_secure_network_error);
   EXPECT_THAT(response_stale.request()->GetAddressResults().value().endpoints(),
               testing::ElementsAre(kExpectedStaleIP));
   EXPECT_TRUE(response_stale.request()->GetStaleInfo()->is_stale());
@@ -5652,6 +5670,9 @@
       host_cache_.get()));
   proc_->SignalMultiple(1u);
   ASSERT_THAT(response_insecure.result_error(), IsOk());
+  EXPECT_FALSE(response_insecure.request()
+                   ->GetResolveErrorInfo()
+                   .is_secure_network_error);
   EXPECT_THAT(
       response_insecure.request()->GetAddressResults().value().endpoints(),
       testing::ElementsAre(CreateExpected("192.168.1.100", 80)));
@@ -5675,6 +5696,9 @@
       NetLogWithSource(), base::nullopt, request_context_.get(),
       host_cache_.get()));
   EXPECT_THAT(response_insecure_cached.result_error(), IsOk());
+  EXPECT_FALSE(response_insecure_cached.request()
+                   ->GetResolveErrorInfo()
+                   .is_secure_network_error);
   EXPECT_THAT(response_insecure_cached.request()
                   ->GetAddressResults()
                   .value()
@@ -5696,6 +5720,8 @@
       HostPortPair("secure", 80), NetworkIsolationKey(), NetLogWithSource(),
       base::nullopt, request_context_.get(), host_cache_.get()));
   ASSERT_THAT(response_secure.result_error(), IsOk());
+  EXPECT_FALSE(
+      response_secure.request()->GetResolveErrorInfo().is_secure_network_error);
   HostCache::Key secure_key = HostCache::Key(
       "secure", DnsQueryType::UNSPECIFIED, 0 /* host_resolver_flags */,
       HostResolverSource::ANY, NetworkIsolationKey());
@@ -5707,6 +5733,9 @@
       HostPortPair("ok", 80), NetworkIsolationKey(), NetLogWithSource(),
       base::nullopt, request_context_.get(), host_cache_.get()));
   ASSERT_THAT(response_insecure.result_error(), IsError(ERR_NAME_NOT_RESOLVED));
+  EXPECT_TRUE(response_insecure.request()
+                  ->GetResolveErrorInfo()
+                  .is_secure_network_error);
   HostCache::Key insecure_key = HostCache::Key(
       "ok", DnsQueryType::UNSPECIFIED, 0 /* host_resolver_flags */,
       HostResolverSource::ANY, NetworkIsolationKey());
@@ -5719,6 +5748,8 @@
       base::nullopt, request_context_.get(), host_cache_.get()));
   proc_->SignalMultiple(1u);
   EXPECT_THAT(response_proc.result_error(), IsError(ERR_NAME_NOT_RESOLVED));
+  EXPECT_TRUE(
+      response_proc.request()->GetResolveErrorInfo().is_secure_network_error);
 }
 
 TEST_F(HostResolverManagerDnsTest, SecureDnsMode_Secure_InsecureAsyncDisabled) {
@@ -5768,6 +5799,9 @@
       source_none_parameters, request_context_.get(), host_cache_.get()));
   EXPECT_TRUE(cache_miss_request.complete());
   EXPECT_THAT(cache_miss_request.result_error(), IsError(ERR_DNS_CACHE_MISS));
+  EXPECT_FALSE(cache_miss_request.request()
+                   ->GetResolveErrorInfo()
+                   .is_secure_network_error);
   EXPECT_FALSE(cache_miss_request.request()->GetAddressResults());
   EXPECT_FALSE(cache_miss_request.request()->GetStaleInfo());
 }
@@ -5796,6 +5830,8 @@
       base::nullopt, request_context_.get(), host_cache_.get()));
   EXPECT_TRUE(response_cached.complete());
   EXPECT_THAT(response_cached.result_error(), IsOk());
+  EXPECT_FALSE(
+      response_cached.request()->GetResolveErrorInfo().is_secure_network_error);
   EXPECT_THAT(
       response_cached.request()->GetAddressResults().value().endpoints(),
       testing::ElementsAre(kExpectedSecureIP));
diff --git a/net/dns/public/resolve_error_info.cc b/net/dns/public/resolve_error_info.cc
index 9a1b499..f61c3a4 100644
--- a/net/dns/public/resolve_error_info.cc
+++ b/net/dns/public/resolve_error_info.cc
@@ -8,8 +8,10 @@
 
 ResolveErrorInfo::ResolveErrorInfo() {}
 
-ResolveErrorInfo::ResolveErrorInfo(int resolve_error) {
-  error = resolve_error;
+ResolveErrorInfo::ResolveErrorInfo(int resolve_error,
+                                   bool is_secure_network_error)
+    : error(resolve_error), is_secure_network_error(is_secure_network_error) {
+  DCHECK(!(is_secure_network_error && resolve_error == net::OK));
 }
 
 ResolveErrorInfo::ResolveErrorInfo(const ResolveErrorInfo& resolve_error_info) =
@@ -24,7 +26,8 @@
     default;
 
 bool ResolveErrorInfo::operator==(const ResolveErrorInfo& other) const {
-  return error == other.error;
+  return error == other.error &&
+         is_secure_network_error == other.is_secure_network_error;
 }
 
 bool ResolveErrorInfo::operator!=(const ResolveErrorInfo& other) const {
diff --git a/net/dns/public/resolve_error_info.h b/net/dns/public/resolve_error_info.h
index b108fa79..07274f67 100644
--- a/net/dns/public/resolve_error_info.h
+++ b/net/dns/public/resolve_error_info.h
@@ -13,7 +13,7 @@
 // Host resolution error info.
 struct NET_EXPORT ResolveErrorInfo {
   ResolveErrorInfo();
-  ResolveErrorInfo(int resolve_error);
+  ResolveErrorInfo(int resolve_error, bool is_secure_network_error = false);
   ResolveErrorInfo(const ResolveErrorInfo& resolve_error_info);
   ResolveErrorInfo(ResolveErrorInfo&& other);
 
@@ -24,6 +24,12 @@
   bool operator!=(const ResolveErrorInfo& other) const;
 
   int error = net::OK;
+  // Whether |error| resulted from a DNS-over-HTTPS lookup. If an answer was
+  // obtained from the cache this field will be false, regardless of whether the
+  // answer was originally obtained securely, because this field is intended to
+  // identify secure DNS *network* failures. This field will also always be
+  // false if |error| is net::OK.
+  bool is_secure_network_error = false;
 };
 
 }  // namespace net
diff --git a/net/url_request/http_with_dns_over_https_unittest.cc b/net/url_request/http_with_dns_over_https_unittest.cc
index d1ff9f8..d65a847 100644
--- a/net/url_request/http_with_dns_over_https_unittest.cc
+++ b/net/url_request/http_with_dns_over_https_unittest.cc
@@ -287,5 +287,37 @@
   EXPECT_EQ(d.data_received(), kTestBody);
 }
 
+TEST_F(HttpWithDnsOverHttpsTest, EndToEndFail) {
+  // Shutdown the DoH server so that all DoH requests are refused.
+  EXPECT_TRUE(doh_server_.ShutdownAndWaitUntilComplete());
+
+  // Make a request that will trigger a DoH query.
+  TestDelegate d;
+  GURL main_url = test_server_.GetURL("fail.example.com", "/test");
+  std::unique_ptr<URLRequest> req(context()->CreateRequest(
+      main_url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
+  req->Start();
+  base::RunLoop().Run();
+  EXPECT_TRUE(test_server_.ShutdownAndWaitUntilComplete());
+
+  // The two DoH lookups for "fail.example.com" (both A and AAAA
+  // records are queried) should both have failed to reach the DoH server
+  // since it was not running.
+  EXPECT_EQ(doh_queries_served_, 0u);
+  // The requests to the DoH server are pooled, so there should only be one
+  // insecure lookup for the DoH server hostname.
+  EXPECT_EQ(host_resolver_proc_->insecure_queries_served(), 1u);
+  // No HTTPS connection to the test server will be attempted due to the
+  // host resolution error.
+  EXPECT_EQ(test_https_requests_served_, 0u);
+
+  EXPECT_TRUE(d.response_completed());
+  EXPECT_EQ(d.request_status(), net::ERR_CONNECTION_REFUSED);
+
+  const auto& resolve_error_info = req->response_info().resolve_error_info;
+  EXPECT_TRUE(resolve_error_info.is_secure_network_error);
+  EXPECT_EQ(resolve_error_info.error, net::ERR_CONNECTION_REFUSED);
+}
+
 }  // namespace
 }  // namespace net
diff --git a/services/network/public/cpp/host_resolver_mojom_traits.cc b/services/network/public/cpp/host_resolver_mojom_traits.cc
index 6211ddc..f5303393 100644
--- a/services/network/public/cpp/host_resolver_mojom_traits.cc
+++ b/services/network/public/cpp/host_resolver_mojom_traits.cc
@@ -422,11 +422,16 @@
   return false;
 }
 
+// static
 bool StructTraits<
     network::mojom::ResolveErrorInfoDataView,
     net::ResolveErrorInfo>::Read(network::mojom::ResolveErrorInfoDataView data,
                                  net::ResolveErrorInfo* out) {
-  *out = net::ResolveErrorInfo(data.error());
+  // There should not be a secure network error if the error code indicates no
+  // error.
+  if (data.error() == net::OK && data.is_secure_network_error())
+    return false;
+  *out = net::ResolveErrorInfo(data.error(), data.is_secure_network_error());
   return true;
 }
 
diff --git a/services/network/public/cpp/host_resolver_mojom_traits.h b/services/network/public/cpp/host_resolver_mojom_traits.h
index 80e1267..084ca52 100644
--- a/services/network/public/cpp/host_resolver_mojom_traits.h
+++ b/services/network/public/cpp/host_resolver_mojom_traits.h
@@ -131,6 +131,11 @@
     return resolve_error_info.error;
   }
 
+  static bool is_secure_network_error(
+      net::ResolveErrorInfo resolve_error_info) {
+    return resolve_error_info.is_secure_network_error;
+  }
+
   static bool Read(network::mojom::ResolveErrorInfoDataView data,
                    net::ResolveErrorInfo* out);
 };
diff --git a/services/network/public/cpp/net_ipc_param_traits.cc b/services/network/public/cpp/net_ipc_param_traits.cc
index edd0c305..8e79469 100644
--- a/services/network/public/cpp/net_ipc_param_traits.cc
+++ b/services/network/public/cpp/net_ipc_param_traits.cc
@@ -317,11 +317,13 @@
 void ParamTraits<net::ResolveErrorInfo>::Write(base::Pickle* m,
                                                const param_type& p) {
   WriteParam(m, p.error);
+  WriteParam(m, p.is_secure_network_error);
 }
 bool ParamTraits<net::ResolveErrorInfo>::Read(const base::Pickle* m,
                                               base::PickleIterator* iter,
                                               param_type* r) {
-  return ReadParam(m, iter, &r->error);
+  return ReadParam(m, iter, &r->error) &&
+         ReadParam(m, iter, &r->is_secure_network_error);
 }
 void ParamTraits<net::ResolveErrorInfo>::Log(const param_type& p,
                                              std::string* l) {
diff --git a/services/network/public/mojom/host_resolver.mojom b/services/network/public/mojom/host_resolver.mojom
index 04fa980..2648dda 100644
--- a/services/network/public/mojom/host_resolver.mojom
+++ b/services/network/public/mojom/host_resolver.mojom
@@ -117,6 +117,11 @@
   // Underlying network error code. See net/base/net_error_list.h for error
   // descriptions.
   int32 error;
+
+  // Whether |error| came from a DNS-over-HTTPS lookup. This will be false if
+  // the answer was obtained from the cache or if |error| is net::OK since this
+  // field is intended to identify secure DNS *network* failures.
+  bool is_secure_network_error = false;
 };
 
 // Control handle used to control outstanding NetworkContext::ResolveHost
diff --git a/services/network/public/mojom/network_service_test.mojom b/services/network/public/mojom/network_service_test.mojom
index 9d7686e..bed4308a8 100644
--- a/services/network/public/mojom/network_service_test.mojom
+++ b/services/network/public/mojom/network_service_test.mojom
@@ -74,6 +74,12 @@
   [Sync]
   SetTransportSecurityStateSource(uint16 reporting_port) => ();
 
+  // Allow host resolutions to reach the network. Since going to the network
+  // can be a source of flakiness, this should only be called in tests that
+  // can tolerate this, such as those run manually or on an FYI bot.
+  [Sync]
+  SetAllowNetworkAccessToHostResolutions() => ();
+
   // Causes the next host resolve to the given hostname to crash the process.
   CrashOnResolveHost(string host);
 
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index 3be8788..a82660e 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -55,7 +55,8 @@
           "-v",
           "--browser=android-chrome-bundle",
           "--upload-results",
-          "--test-shard-map-filename=android-pixel2-perf-aab-fyi_map.json"
+          "--test-shard-map-filename=android-pixel2-perf-aab-fyi_map.json",
+          "--run-ref-build"
         ],
         "isolate_name": "performance_test_suite",
         "merge": {
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index 3fd405af..f78d704 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -2188,6 +2188,45 @@
       {
         "args": [
           "--gtest-benchmark-name",
+          "dawn_perf_tests",
+          "--shard-timeout=300"
+        ],
+        "isolate_name": "dawn_perf_tests",
+        "merge": {
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "dawn_perf_tests",
+        "override_compile_targets": [
+          "dawn_perf_tests"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-23.20.16.4877",
+              "os": "Windows-10-16299.309",
+              "pool": "chrome.tests.perf",
+              "synthetic_product_name": "OptiPlex 7050 (Dell Inc.)"
+            }
+          ],
+          "expiration": 7200,
+          "hard_timeout": 19800,
+          "ignore_task_failure": false,
+          "io_timeout": 19800,
+          "shards": 1
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "requires_simultaneous_shard_dispatch": true,
+          "script": "//testing/trigger_scripts/perf_device_trigger.py"
+        }
+      },
+      {
+        "args": [
+          "--gtest-benchmark-name",
           "media_perftests"
         ],
         "isolate_name": "media_perftests",
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index 5f981f1..11c6fa2 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -162,10 +162,12 @@
 
 
 class GtestCommandGenerator(object):
-  def __init__(self, options, override_executable=None, additional_flags=None):
+  def __init__(self, options, override_executable=None, additional_flags=None,
+               ignore_shard_env_vars=False):
     self._options = options
     self._override_executable = override_executable
     self._additional_flags = additional_flags or []
+    self._ignore_shard_env_vars = ignore_shard_env_vars
 
   def generate(self, output_dir):
     """Generate the command to run to start the gtest perf test.
@@ -178,6 +180,7 @@
             self._generate_repeat_args() +
             self._generate_also_run_disabled_tests_args() +
             self._generate_output_args(output_dir) +
+            self._generate_shard_args() +
             self._get_passthrough_args()
            )
 
@@ -196,6 +199,17 @@
   def _get_passthrough_args(self):
     return self._options.passthrough_args + self._additional_flags
 
+  def _generate_shard_args(self):
+    """Teach the gtest to ignore the environment variables.
+
+    GTEST_SHARD_INDEX and GTEST_TOTAL_SHARDS will confuse the gtest
+    and convince it to only run some of its tests. Instead run all
+    of them.
+    """
+    if self._ignore_shard_env_vars:
+      return ['--test-launcher-total-shards=1', '--test-launcher-shard-index=0']
+    return []
+
   def _generate_filter_args(self):
     if self._options.isolated_script_test_filter:
       filter_list = common.extract_filter_list(
@@ -377,7 +391,12 @@
 
   def _generate_reference_build_args(self):
     if self._is_reference:
-      return ['--browser=reference',
+      reference_browser_flag = '--browser=reference'
+      # TODO(crbug.com/1038137): Make the logic generic once more reference
+      # settings are added
+      if '--browser=android-chrome-bundle' in self._get_passthrough_args():
+        reference_browser_flag = '--browser=reference-android-chrome-bundle'
+      return [reference_browser_flag,
               '--max-failures=5']
     return []
 
@@ -618,7 +637,7 @@
             additional_flags = configuration['arguments']
           command_generator = GtestCommandGenerator(
               options, override_executable=configuration['path'],
-              additional_flags=additional_flags)
+              additional_flags=additional_flags, ignore_shard_env_vars=True)
           output_paths = OutputFilePaths(isolated_out_dir, name).SetUp()
           print('\n### {folder} ###'.format(folder=name))
           return_code = execute_gtest_perf_test(
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/blink_v8_bridge.py b/third_party/blink/renderer/bindings/scripts/bind_gen/blink_v8_bridge.py
index 85d59f31..d22d56f 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/blink_v8_bridge.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/blink_v8_bridge.py
@@ -203,7 +203,7 @@
             if default_value.is_type_compatible_with(member_type):
                 idl_type = member_type
                 break
-        assert False
+        assert default_value.is_type_compatible_with(idl_type)
     type_info = blink_type_info(idl_type)
 
     is_initializer_lightweight = False
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py b/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py
index f218b33..6e84e8a6 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py
@@ -144,7 +144,7 @@
     return uniq_terms
 
 
-def expr_from_exposure(exposure, in_global=None):
+def expr_from_exposure(exposure, global_names=None):
     """
     Args:
         exposure: web_idl.Exposure
@@ -152,7 +152,9 @@
             supposed to be / represent.
     """
     assert isinstance(exposure, web_idl.Exposure)
-    assert in_global is None or isinstance(in_global, str)
+    assert (global_names is None
+            or (isinstance(global_names, (list, tuple))
+                and all(isinstance(name, str) for name in global_names)))
 
     def ref_enabled(feature):
         return _Expr("RuntimeEnabledFeatures::{}Enabled()".format(feature))
@@ -172,16 +174,10 @@
         "Worker": "IsWorkerGlobalScope",
         "Worklet": "IsWorkletGlobalScope",
     }
-    in_globals = set()
-    if in_global:
-        in_globals.add(in_global)
-        for category_name in ("Worker", "Worklet"):
-            if in_global.endswith(category_name):
-                in_globals.add(category_name)
     exposed_terms = []
     for entry in exposure.global_names_and_features:
         terms = []
-        if entry.global_name not in in_globals:
+        if entry.global_name not in (global_names or []):
             pred = GLOBAL_NAME_TO_EXECUTION_CONTEXT_TEST[entry.global_name]
             terms.append(_Expr("${{execution_context}}->{}()".format(pred)))
         if entry.feature:
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
index 2e01e5a..94748d8b 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
@@ -1818,7 +1818,7 @@
     assert isinstance(operation_entries, list)
 
     interface = cg_context.interface
-    global_name = interface.extended_attributes.value_of("Global")
+    global_names = interface.extended_attributes.values_of("Global")
 
     callback_def_nodes = ListNode()
 
@@ -1826,7 +1826,7 @@
         for member in members:
             is_context_dependent = member.exposure.is_context_dependent
             exposure_conditional = expr_from_exposure(member.exposure,
-                                                      global_name)
+                                                      global_names)
 
             if "PerWorldBindings" in member.extended_attributes:
                 worlds = (CodeGenContext.MAIN_WORLD,
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/mako_renderer.py b/third_party/blink/renderer/bindings/scripts/bind_gen/mako_renderer.py
index ba196f1..f99a03be 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/mako_renderer.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/mako_renderer.py
@@ -13,6 +13,8 @@
 class MakoTemplate(object):
     """Represents a compiled template object."""
 
+    _mako_template_cache = {}
+
     def __init__(self, template_text):
         assert isinstance(template_text, str)
 
@@ -20,8 +22,12 @@
             "strict_undefined": True,
         }
 
-        self._template = mako.template.Template(
-            text=template_text, **template_params)
+        template = self._mako_template_cache.get(template_text)
+        if template is None:
+            template = mako.template.Template(
+                text=template_text, **template_params)
+            self._mako_template_cache[template_text] = template
+        self._template = template
 
     def mako_template(self, pass_key=None):
         assert pass_key is _MAKO_TEMPLATE_PASS_KEY
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/function_like.py b/third_party/blink/renderer/bindings/scripts/web_idl/function_like.py
index 20841226..648c70d 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/function_like.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/function_like.py
@@ -271,6 +271,9 @@
             return True
 
         # step 4. Consider the two "innermost" types ...
+        def is_string_type(idl_type):
+            return idl_type.is_string or idl_type.is_enumeration
+
         def is_interface_like(idl_type):
             return idl_type.is_interface or idl_type.is_buffer_source_type
 
@@ -291,11 +294,11 @@
             return not type2.is_boolean
         if type1.is_numeric:
             return not type2.is_numeric
-        if type1.is_string:
-            return not type2.is_string
+        if is_string_type(type1):
+            return not is_string_type(type2)
         if type1.is_object:
-            return (type2.is_boolean or type2.is_numeric or type2.is_string
-                    or type2.is_symbol)
+            return (type2.is_boolean or type2.is_numeric
+                    or is_string_type(type2) or type2.is_symbol)
         if type1.is_symbol:
             return not type2.is_symbol
         if is_interface_like(type1):
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 5f7d9abe..7d154ea 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -3035,12 +3035,14 @@
   View()->InvokeFragmentAnchor();
 
   auto& frame_loader = GetFrame()->Loader();
-  auto& document_loader = *frame_loader.GetDocumentLoader();
+  auto* document_loader = frame_loader.GetDocumentLoader();
+  if (!document_loader)
+    return;
   if (frame_->IsLoading() &&
-      !FrameLoader::NeedsHistoryItemRestore(document_loader.LoadType()))
+      !FrameLoader::NeedsHistoryItemRestore(document_loader->LoadType()))
     return;
 
-  auto* history_item = frame_loader.GetDocumentLoader()->GetHistoryItem();
+  auto* history_item = document_loader->GetHistoryItem();
 
   if (!history_item || !history_item->GetViewState())
     return;
@@ -3062,18 +3064,14 @@
       scroll_offset;
 
   bool can_restore_without_annoying_user =
-      !document_loader.GetInitialScrollState().was_scrolled_by_user &&
+      !document_loader->GetInitialScrollState().was_scrolled_by_user &&
       (can_restore_without_clamping || !frame_->IsLoading() ||
        !should_restore_scroll);
   if (!can_restore_without_annoying_user)
     return;
 
   frame_loader.RestoreScrollPositionAndViewState();
-  if (View()->GetScrollableArea()->ApplyPendingHistoryRestoreScrollOffset()) {
-    if (ScrollingCoordinator* scrolling_coordinator =
-            View()->GetFrame().GetPage()->GetScrollingCoordinator())
-      scrolling_coordinator->FrameViewRootLayerDidChange(View());
-  }
+  View()->GetScrollableArea()->ApplyPendingHistoryRestoreScrollOffset();
 }
 
 void Document::UpdateStyleAndLayout(ForcedLayoutStatus status) {
diff --git a/third_party/blink/renderer/core/dom/node.h b/third_party/blink/renderer/core/dom/node.h
index 25ed2647..cbe914ae 100644
--- a/third_party/blink/renderer/core/dom/node.h
+++ b/third_party/blink/renderer/core/dom/node.h
@@ -80,14 +80,14 @@
 class WebPluginContainerImpl;
 struct PhysicalRect;
 
-const int kDOMNodeTypeShift = 1;
+const int kDOMNodeTypeShift = 2;
 const int kElementNamespaceTypeShift = 4;
 const int kNodeStyleChangeShift = 17;
 const int kNodeCustomElementShift = 19;
 
 // Values for kChildNeedsStyleRecalcFlag, controlling whether a node gets its
 // style recalculated.
-enum StyleChangeType {
+enum StyleChangeType : uint32_t {
   // This node does not need style recalculation.
   kNoStyleChange = 0,
   // This node needs style recalculation.
@@ -96,7 +96,7 @@
   kSubtreeStyleChange = 2 << kNodeStyleChangeShift,
 };
 
-enum class CustomElementState {
+enum class CustomElementState : uint32_t {
   // https://dom.spec.whatwg.org/#concept-element-custom-element-state
   kUncustomized = 0,
   kCustom = 1 << kNodeCustomElementShift,
@@ -272,11 +272,7 @@
 
   // Other methods (not part of DOM)
   bool IsTextNode() const { return GetDOMNodeType() == DOMNodeType::kText; }
-  bool IsContainerNode() const {
-    auto type = GetDOMNodeType();
-    return type == DOMNodeType::kContainer || type == DOMNodeType::kElement ||
-           type == DOMNodeType::kDocumentFragment;
-  }
+  bool IsContainerNode() const { return GetFlag(kIsContainerFlag); }
   bool IsElementNode() const {
     return GetDOMNodeType() == DOMNodeType::kElement;
   }
@@ -919,11 +915,12 @@
   void Trace(Visitor*) override;
 
  private:
-  enum NodeFlags {
+  enum NodeFlags : uint32_t {
     kHasRareDataFlag = 1,
 
     // Node type flags. These never change once created.
-    kDOMNodeTypeMask = 0x7 << kDOMNodeTypeShift,
+    kIsContainerFlag = 1 << 1,
+    kDOMNodeTypeMask = 0x3 << kDOMNodeTypeShift,
     kElementNamespaceTypeMask = 0x3 << kElementNamespaceTypeShift,
     kIsV0InsertionPointFlag = 1 << 6,
 
@@ -977,18 +974,17 @@
   void SetFlag(NodeFlags mask) { node_flags_ |= mask; }
   void ClearFlag(NodeFlags mask) { node_flags_ &= ~mask; }
 
-  enum class DOMNodeType {
+  enum class DOMNodeType : uint32_t {
     kOther = 0,
     kText = 1 << kDOMNodeTypeShift,
-    kContainer = 2 << kDOMNodeTypeShift,
-    kElement = 3 << kDOMNodeTypeShift,
-    kDocumentFragment = 4 << kDOMNodeTypeShift,
+    kElement = 2 << kDOMNodeTypeShift,
+    kDocumentFragment = 3 << kDOMNodeTypeShift,
   };
   DOMNodeType GetDOMNodeType() const {
     return static_cast<DOMNodeType>(node_flags_ & kDOMNodeTypeMask);
   }
 
-  enum class ElementNamespaceType {
+  enum class ElementNamespaceType : uint32_t {
     kOther = 0,
     kHTML = 1 << kElementNamespaceTypeShift,
     kMathML = 2 << kElementNamespaceTypeShift,
@@ -1005,12 +1001,11 @@
         kDefaultNodeFlags | static_cast<NodeFlags>(DOMNodeType::kOther),
     kCreateText =
         kDefaultNodeFlags | static_cast<NodeFlags>(DOMNodeType::kText),
-    kCreateContainer =
-        kDefaultNodeFlags | static_cast<NodeFlags>(DOMNodeType::kContainer),
+    kCreateContainer = kDefaultNodeFlags | kIsContainerFlag,
     kCreateElement =
-        kDefaultNodeFlags | static_cast<NodeFlags>(DOMNodeType::kElement),
+        kCreateContainer | static_cast<NodeFlags>(DOMNodeType::kElement),
     kCreateDocumentFragment =
-        kDefaultNodeFlags |
+        kCreateContainer |
         static_cast<NodeFlags>(DOMNodeType::kDocumentFragment),
     kCreateShadowRoot = kCreateDocumentFragment | kIsInShadowTreeFlag,
     kCreateHTMLElement =
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 11c628f..b97a2eb 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -458,13 +458,6 @@
     if (frame_->IsMainFrame())
       frame_->GetPage()->GetVisualViewport().MainFrameDidChangeSize();
     GetFrame().Loader().RestoreScrollPositionAndViewState();
-    if (GetScrollableArea()) {
-      if (GetScrollableArea()->ApplyPendingHistoryRestoreScrollOffset()) {
-        if (ScrollingCoordinator* scrolling_coordinator =
-                GetFrame().GetPage()->GetScrollingCoordinator())
-          scrolling_coordinator->FrameViewRootLayerDidChange(this);
-      }
-    }
   }
 }
 
diff --git a/third_party/blink/renderer/core/frame/root_frame_viewport.cc b/third_party/blink/renderer/core/frame/root_frame_viewport.cc
index 7102bef..1efda8d 100644
--- a/third_party/blink/renderer/core/frame/root_frame_viewport.cc
+++ b/third_party/blink/renderer/core/frame/root_frame_viewport.cc
@@ -241,9 +241,9 @@
   return LayoutViewport().ScrollCornerRect();
 }
 
-bool RootFrameViewport::ApplyPendingHistoryRestoreScrollOffset() {
+void RootFrameViewport::ApplyPendingHistoryRestoreScrollOffset() {
   if (!pending_view_state_)
-    return false;
+    return;
 
   bool should_restore_scale = pending_view_state_->page_scale_factor_;
 
@@ -289,7 +289,6 @@
   should_restore_scroll_ = false;
 
   pending_view_state_.reset();
-  return true;
 }
 
 void RootFrameViewport::SetScrollOffset(const ScrollOffset& offset,
diff --git a/third_party/blink/renderer/core/frame/root_frame_viewport.h b/third_party/blink/renderer/core/frame/root_frame_viewport.h
index 30ade1b..f9a4456 100644
--- a/third_party/blink/renderer/core/frame/root_frame_viewport.h
+++ b/third_party/blink/renderer/core/frame/root_frame_viewport.h
@@ -135,7 +135,7 @@
     should_restore_scroll_ = should_restore_scroll;
   }
 
-  bool ApplyPendingHistoryRestoreScrollOffset() override;
+  void ApplyPendingHistoryRestoreScrollOffset() override;
 
  private:
   FRIEND_TEST_ALL_PREFIXES(RootFrameViewportTest, DistributeScrollOrder);
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.cc b/third_party/blink/renderer/core/frame/visual_viewport.cc
index 4263efb6..2892126 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport.cc
@@ -190,11 +190,8 @@
       // As an optimization, attempt to directly update the compositor
       // scale translation node and return kChangedOnlyCompositedValues which
       // avoids an expensive PaintArtifactCompositor update.
-      // TODO(crbug.com/953322): We need to implement this optimization for
-      // CompositeAfterPaint as well.
-      if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() &&
-          effective_change_type ==
-              PaintPropertyChangeType::kChangedOnlySimpleValues) {
+      if (effective_change_type ==
+          PaintPropertyChangeType::kChangedOnlySimpleValues) {
         if (auto* paint_artifact_compositor = GetPaintArtifactCompositor()) {
           bool updated =
               paint_artifact_compositor->DirectlyUpdatePageScaleTransform(
@@ -273,11 +270,8 @@
       // As an optimization, attempt to directly update the compositor
       // translation node and return kChangedOnlyCompositedValues which avoids
       // an expensive PaintArtifactCompositor update.
-      // TODO(crbug.com/953322): We need to implement this optimization for
-      // CompositeAfterPaint as well.
-      if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() &&
-          effective_change_type ==
-              PaintPropertyChangeType::kChangedOnlySimpleValues) {
+      if (effective_change_type ==
+          PaintPropertyChangeType::kChangedOnlySimpleValues) {
         if (auto* paint_artifact_compositor = GetPaintArtifactCompositor()) {
           bool updated =
               paint_artifact_compositor->DirectlyUpdateScrollOffsetTransform(
diff --git a/third_party/blink/renderer/core/frame/visual_viewport_test.cc b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
index c5c73eb9..4832d69 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport_test.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
@@ -2688,11 +2688,6 @@
 // When a pinch-zoom occurs, the viewport scale and translation nodes can be
 // directly updated without a PaintArtifactCompositor update.
 TEST_P(VisualViewportTest, DirectPinchZoomPropertyUpdate) {
-  // TODO(crbug.com/953322): Implement this optimization for
-  // CompositeAfterPaint.
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    return;
-
   InitializeWithAndroidSettings();
 
   RegisterMockedHttpURLLoad("200-by-800-viewport.html");
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
index 7458ce2..d5c7c66 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
@@ -1390,11 +1390,8 @@
 
   page->GetVisualViewport().SetLocation(FloatPoint(10, 20));
   ForceFullCompositingUpdate();
-  // TODO(crbug.com/953322): Make this work for CAP.
-  if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-    EXPECT_EQ(gfx::ScrollOffset(10, 20),
-              CurrentScrollOffset(inner_viewport_scroll_node));
-  }
+  EXPECT_EQ(gfx::ScrollOffset(10, 20),
+            CurrentScrollOffset(inner_viewport_scroll_node));
 }
 
 TEST_P(ScrollingTest, UpdateUMAMetricUpdated) {
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
index 7644d57..2c2df57 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
@@ -262,12 +262,6 @@
 INSTANTIATE_LAYER_LIST_TEST_SUITE_P(CompositingSimTest);
 
 TEST_P(CompositingSimTest, LayerUpdatesDoNotInvalidateEarlierLayers) {
-  // TODO(crbug.com/765003): CAP may make different layerization decisions and
-  // we cannot guarantee that both divs will be composited in this test. When
-  // CAP gets closer to launch, this test should be updated to pass.
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    return;
-
   InitializeWithHTML(R"HTML(
       <!DOCTYPE html>
       <style>
@@ -276,6 +270,7 @@
           width: 100px;
           height: 100px;
           will-change: transform;
+          background: lightblue;
         }
       </style>
       <div id='a'></div>
@@ -284,16 +279,9 @@
 
   Compositor().BeginFrame();
 
-  auto* a_element = GetElementById("a");
   auto* a_layer = CcLayerByDOMElementId("a");
-  DCHECK_EQ(a_layer->element_id(), CompositorElementIdFromUniqueObjectId(
-                                       a_element->GetLayoutObject()->UniqueId(),
-                                       CompositorElementIdNamespace::kPrimary));
   auto* b_element = GetElementById("b");
   auto* b_layer = CcLayerByDOMElementId("b");
-  DCHECK_EQ(b_layer->element_id(), CompositorElementIdFromUniqueObjectId(
-                                       b_element->GetLayoutObject()->UniqueId(),
-                                       CompositorElementIdNamespace::kPrimary));
 
   // Initially, neither a nor b should have a layer that should push properties.
   cc::LayerTreeHost& host = Compositor().layer_tree_host();
@@ -313,12 +301,6 @@
 }
 
 TEST_P(CompositingSimTest, LayerUpdatesDoNotInvalidateLaterLayers) {
-  // TODO(crbug.com/765003): CAP may make different layerization decisions and
-  // we cannot guarantee that both divs will be composited in this test. When
-  // CAP gets closer to launch, this test should be updated to pass.
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    return;
-
   InitializeWithHTML(R"HTML(
       <!DOCTYPE html>
       <style>
@@ -327,6 +309,7 @@
           width: 100px;
           height: 100px;
           will-change: transform;
+          background: lightblue;
         }
       </style>
       <div id='a'></div>
@@ -338,19 +321,9 @@
 
   auto* a_element = GetElementById("a");
   auto* a_layer = CcLayerByDOMElementId("a");
-  DCHECK_EQ(a_layer->element_id(), CompositorElementIdFromUniqueObjectId(
-                                       a_element->GetLayoutObject()->UniqueId(),
-                                       CompositorElementIdNamespace::kPrimary));
   auto* b_element = GetElementById("b");
   auto* b_layer = CcLayerByDOMElementId("b");
-  DCHECK_EQ(b_layer->element_id(), CompositorElementIdFromUniqueObjectId(
-                                       b_element->GetLayoutObject()->UniqueId(),
-                                       CompositorElementIdNamespace::kPrimary));
-  auto* c_element = GetElementById("c");
   auto* c_layer = CcLayerByDOMElementId("c");
-  DCHECK_EQ(c_layer->element_id(), CompositorElementIdFromUniqueObjectId(
-                                       c_element->GetLayoutObject()->UniqueId(),
-                                       CompositorElementIdNamespace::kPrimary));
 
   // Initially, no layer should need to push properties.
   cc::LayerTreeHost& host = Compositor().layer_tree_host();
@@ -411,12 +384,6 @@
 // non-layer-list mode, this occurs in BuildPropertyTreesInternal (see:
 // SetLayerPropertyChangedForChild).
 TEST_P(CompositingSimTest, LayerSubtreeTransformPropertyChanged) {
-  // TODO(crbug.com/765003): CAP may make different layerization decisions and
-  // we cannot guarantee that both divs will be composited in this test. When
-  // CAP gets closer to launch, this test should be updated to pass.
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    return;
-
   InitializeWithHTML(R"HTML(
       <!DOCTYPE html>
       <style>
@@ -426,6 +393,7 @@
           height: 100px;
           will-change: transform;
           transform: translate(10px, 10px);
+          background: lightgreen;
         }
         #inner {
           width: 100px;
@@ -443,16 +411,7 @@
 
   auto* outer_element = GetElementById("outer");
   auto* outer_element_layer = CcLayerByDOMElementId("outer");
-  DCHECK_EQ(outer_element_layer->element_id(),
-            CompositorElementIdFromUniqueObjectId(
-                outer_element->GetLayoutObject()->UniqueId(),
-                CompositorElementIdNamespace::kPrimary));
-  auto* inner_element = GetElementById("inner");
   auto* inner_element_layer = CcLayerByDOMElementId("inner");
-  DCHECK_EQ(inner_element_layer->element_id(),
-            CompositorElementIdFromUniqueObjectId(
-                inner_element->GetLayoutObject()->UniqueId(),
-                CompositorElementIdNamespace::kPrimary));
 
   // Initially, no layer should have |subtree_property_changed| set.
   EXPECT_FALSE(outer_element_layer->subtree_property_changed());
@@ -488,12 +447,6 @@
 // |transform_changed| set. In non-layer-list mode, this occurs in
 // cc::TransformTree::OnTransformAnimated and cc::Layer::SetTransform.
 TEST_P(CompositingSimTest, DirectTransformPropertyUpdate) {
-  // TODO(crbug.com/765003): CAP may make different layerization decisions and
-  // we cannot guarantee that both divs will be composited in this test. When
-  // CAP gets closer to launch, this test should be updated to pass.
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    return;
-
   InitializeWithHTML(R"HTML(
       <!DOCTYPE html>
       <style>
@@ -503,6 +456,7 @@
           height: 100px;
           will-change: transform;
           transform: translate(10px, 10px) scale(1, 2);
+          background: lightgreen;
         }
         #inner {
           width: 100px;
@@ -520,10 +474,6 @@
 
   auto* outer_element = GetElementById("outer");
   auto* outer_element_layer = CcLayerByDOMElementId("outer");
-  DCHECK_EQ(outer_element_layer->element_id(),
-            CompositorElementIdFromUniqueObjectId(
-                outer_element->GetLayoutObject()->UniqueId(),
-                CompositorElementIdNamespace::kPrimary));
   auto transform_tree_index = outer_element_layer->transform_tree_index();
   auto* transform_node =
       GetPropertyTrees()->transform_tree.Node(transform_tree_index);
@@ -548,12 +498,6 @@
 // the changed value of a directly updated transform is still set if some other
 // change causes PaintArtifactCompositor to run and do non-direct updates.
 TEST_P(CompositingSimTest, DirectTransformPropertyUpdateCausesChange) {
-  // TODO(crbug.com/765003): CAP may make different layerization decisions and
-  // we cannot guarantee that both divs will be composited in this test. When
-  // CAP gets closer to launch, this test should be updated to pass.
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    return;
-
   InitializeWithHTML(R"HTML(
       <!DOCTYPE html>
       <style>
@@ -563,6 +507,7 @@
           height: 100px;
           will-change: transform;
           transform: translate(1px, 2px);
+          background: lightgreen;
         }
         #inner {
           width: 100px;
@@ -581,20 +526,12 @@
 
   auto* outer_element = GetElementById("outer");
   auto* outer_element_layer = CcLayerByDOMElementId("outer");
-  DCHECK_EQ(outer_element_layer->element_id(),
-            CompositorElementIdFromUniqueObjectId(
-                outer_element->GetLayoutObject()->UniqueId(),
-                CompositorElementIdNamespace::kPrimary));
   auto outer_transform_tree_index = outer_element_layer->transform_tree_index();
   auto* outer_transform_node =
       GetPropertyTrees()->transform_tree.Node(outer_transform_tree_index);
 
   auto* inner_element = GetElementById("inner");
   auto* inner_element_layer = CcLayerByDOMElementId("inner");
-  DCHECK_EQ(inner_element_layer->element_id(),
-            CompositorElementIdFromUniqueObjectId(
-                inner_element->GetLayoutObject()->UniqueId(),
-                CompositorElementIdNamespace::kPrimary));
   auto inner_transform_tree_index = inner_element_layer->transform_tree_index();
   auto* inner_transform_node =
       GetPropertyTrees()->transform_tree.Node(inner_transform_tree_index);
@@ -697,12 +634,6 @@
 // |transform_changed| set. In non-layer-list mode, this occurs in
 // cc::Layer::SetTransformOrigin.
 TEST_P(CompositingSimTest, DirectTransformOriginPropertyUpdate) {
-  // TODO(crbug.com/765003): CAP may make different layerization decisions and
-  // we cannot guarantee that both divs will be composited in this test. When
-  // CAP gets closer to launch, this test should be updated to pass.
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    return;
-
   InitializeWithHTML(R"HTML(
       <!DOCTYPE html>
       <style>
@@ -712,6 +643,7 @@
           height: 100px;
           transform: rotate3d(3, 2, 1, 45deg);
           transform-origin: 10px 10px 100px;
+          background: lightblue;
         }
       </style>
       <div id='box'></div>
@@ -721,10 +653,6 @@
 
   auto* box_element = GetElementById("box");
   auto* box_element_layer = CcLayerByDOMElementId("box");
-  DCHECK_EQ(box_element_layer->element_id(),
-            CompositorElementIdFromUniqueObjectId(
-                box_element->GetLayoutObject()->UniqueId(),
-                CompositorElementIdNamespace::kPrimary));
   auto transform_tree_index = box_element_layer->transform_tree_index();
   auto* transform_node =
       GetPropertyTrees()->transform_tree.Node(transform_tree_index);
@@ -820,12 +748,6 @@
 // This test is similar to |LayerSubtreeTransformPropertyChanged| but for
 // clip property node changes.
 TEST_P(CompositingSimTest, LayerSubtreeClipPropertyChanged) {
-  // TODO(crbug.com/765003): CAP may make different layerization decisions and
-  // we cannot guarantee that both divs will be composited in this test. When
-  // CAP gets closer to launch, this test should be updated to pass.
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    return;
-
   InitializeWithHTML(R"HTML(
       <!DOCTYPE html>
       <style>
@@ -836,6 +758,7 @@
           will-change: transform;
           position: absolute;
           clip: rect(10px, 80px, 70px, 40px);
+          background: lightgreen;
         }
         #inner {
           width: 100px;
@@ -853,12 +776,7 @@
 
   auto* outer_element = GetElementById("outer");
   auto* outer_element_layer = CcLayerByDOMElementId("outer");
-  auto* inner_element = GetElementById("inner");
   auto* inner_element_layer = CcLayerByDOMElementId("inner");
-  DCHECK_EQ(inner_element_layer->element_id(),
-            CompositorElementIdFromUniqueObjectId(
-                inner_element->GetLayoutObject()->UniqueId(),
-                CompositorElementIdNamespace::kPrimary));
 
   // Initially, no layer should have |subtree_property_changed| set.
   EXPECT_FALSE(outer_element_layer->subtree_property_changed());
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index 8d186b6..bd2094b 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -230,9 +230,9 @@
   layer_ = nullptr;
 }
 
-bool PaintLayerScrollableArea::ApplyPendingHistoryRestoreScrollOffset() {
+void PaintLayerScrollableArea::ApplyPendingHistoryRestoreScrollOffset() {
   if (!pending_view_state_)
-    return false;
+    return;
 
   // TODO(pnoland): attempt to restore the anchor in more places than this.
   // Anchor-based restore should allow for earlier restoration.
@@ -246,7 +246,6 @@
   }
 
   pending_view_state_.reset();
-  return true;
 }
 
 void PaintLayerScrollableArea::Trace(blink::Visitor* visitor) {
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
index b9a88b9f1..82490ed 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
@@ -584,7 +584,7 @@
     pending_view_state_ = view_state;
   }
 
-  bool ApplyPendingHistoryRestoreScrollOffset() override;
+  void ApplyPendingHistoryRestoreScrollOffset() override;
 
  private:
   bool NeedsScrollbarReconstruction() const;
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 4919cede0..2a4e27a 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -776,9 +776,7 @@
           style.IsRunningTransformAnimationOnCompositor();
       auto effective_change_type = properties_->UpdateTransform(
           *context_.current.transform, std::move(state), animation_state);
-      // TODO(crbug.com/953322): We need to fix this to work with CAP as well.
-      if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() &&
-          effective_change_type ==
+      if (effective_change_type ==
               PaintPropertyChangeType::kChangedOnlySimpleValues &&
           properties_->Transform()->HasDirectCompositingReasons()) {
         if (auto* paint_artifact_compositor =
@@ -1077,9 +1075,7 @@
       // If we have simple value change, which means opacity, we should try to
       // directly update it on the PaintArtifactCompositor in order to avoid
       // doing a full rebuild.
-      // TODO(crbug.com/953322): We need to fix this to work with CAP as well.
-      if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() &&
-          effective_change_type ==
+      if (effective_change_type ==
               PaintPropertyChangeType::kChangedOnlySimpleValues &&
           properties_->Effect()->HasDirectCompositingReasons()) {
         if (auto* paint_artifact_compositor =
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.h b/third_party/blink/renderer/core/scroll/scrollable_area.h
index 480285b..e34eecd 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.h
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.h
@@ -118,8 +118,7 @@
   virtual void SetPendingHistoryRestoreScrollOffset(
       const HistoryItem::ViewState& view_state,
       bool should_restore_scroll) {}
-  // Returns true if it applied anything.
-  virtual bool ApplyPendingHistoryRestoreScrollOffset() { return false; }
+  virtual void ApplyPendingHistoryRestoreScrollOffset() {}
 
   // Scrolls the area so that the given rect, given in absolute coordinates,
   // such that it's visible in the area. Returns the new location of the input
diff --git a/third_party/blink/web_tests/LeakExpectations b/third_party/blink/web_tests/LeakExpectations
index 19abf9f..ac27dcc 100644
--- a/third_party/blink/web_tests/LeakExpectations
+++ b/third_party/blink/web_tests/LeakExpectations
@@ -115,6 +115,9 @@
 # Sheriff 2019-11-29
 crbug.com/1029417 [ Linux ] external/wpt/web-nfc/NDEFReader_scan_filter.https.html [ Failure ]
 
+# Sheriff 2019-12-30
+crbug.com/1038388 [ Linux ] http/tests/devtools/tracing/timeline-time/timeline-time.js [ Pass Failure ]
+
 ###########################################################################
 # WARNING: Memory leaks must be fixed asap. Sheriff is expected to revert #
 # culprit CLs instead of suppressing the leaks. If you have any question, #
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 4a6f1ddd..4701bde 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2543,8 +2543,6 @@
 
 # 1px scroll offset difference for compositor threaded scrollbar scrolling on Linux.
 crbug.com/1009892 [ Mac ] virtual/compositor_threaded_scrollbar_scrolling_hidpi/fast/scrolling/scrollbars/dsf-ready/mouse-interactions-dsf-2.html [ Failure Timeout ]
-# FAIL Test mouse drags on non-custom composited div scrollbar thumb. assert_equals: Vertical thumb drag downwards did not scroll as expected. expected 46 but got 0
-crbug.com/1009892 [ Mac ] virtual/compositor_threaded_scrollbar_scrolling/fast/scrolling/scrollbars/dsf-ready/mouse-interactions-dsf-2.html [ Failure ]
 
 # Some control characters still not visible
 crbug.com/893490 [ Mac ] external/wpt/css/css-text/white-space/control-chars-001.html [ Failure ]
@@ -5016,6 +5014,7 @@
 
 # Flaky timeout only on Mac 10.13.
 crbug.com/853360 [ Mac10.13 ] http/tests/misc/slow-loading-image-in-pattern.html [ Timeout Pass ]
+crbug.com/853360 [ Retina ] http/tests/misc/slow-loading-image-in-pattern.html [ Timeout Pass ]
 
 # Origin Policy: Skip tests that rely on --feature-enabled=OriginPolicy, so
 #   they can be run via virtual/origin-policy instead.
@@ -6153,3 +6152,7 @@
 crbug.com/1037798 [ Win ] virtual/gpu-rasterization/images/exif-orientation-image-document.html [ Pass Failure ]
 crbug.com/1038091 [ Win ] virtual/gpu-rasterization/images/jpeg-yuv-image-decoding.html [ Pass Failure ]
 crbug.com/1038139 [ Win ] virtual/gpu-rasterization/images/2-comp.html [ Pass Failure ]
+
+# Sheriff 2019-12-30
+crbug.com/1037798 [ Mac ] virtual/gpu-rasterization/images/exif-orientation-image-document.html [ Pass Failure ]
+crbug.com/1038354 fast/scroll-snap/snaps-after-scrollbar-scrolling.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/animations/composition/border-bottom-left-radius-composition.html b/third_party/blink/web_tests/animations/composition/border-bottom-left-radius-composition.html
deleted file mode 100644
index 57a9ecc9..0000000
--- a/third_party/blink/web_tests/animations/composition/border-bottom-left-radius-composition.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<!DOCTYPE html>
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'border-bottom-left-radius',
-  underlying: '40px 40px',
-  addFrom: '60px 60px',
-  addTo: '160px 160px',
-}, [
-  {at: -0.25, is: '75px'},
-  {at: 0, is: '100px'},
-  {at: 0.25, is: '125px'},
-  {at: 0.5, is: '150px'},
-  {at: 0.75, is: '175px'},
-  {at: 1, is: '200px'},
-  {at: 1.25, is: '225px'},
-]);
-
-assertComposition({
-  property: 'border-bottom-left-radius',
-  underlying: '40px 140px',
-  replaceFrom: '100px 120px',
-  addTo: '160px 60px',
-}, [
-  {at: -0.25, is: '75px 100px'},
-  {at: 0, is: '100px 120px'},
-  {at: 0.25, is: '125px 140px'},
-  {at: 0.5, is: '150px 160px'},
-  {at: 0.75, is: '175px 180px'},
-  {at: 1, is: '200px'},
-  {at: 1.25, is: '225px 220px'},
-]);
-
-assertComposition({
-  property: 'border-bottom-left-radius',
-  underlying: '40px 60px',
-  addFrom: '60px 140px',
-  replaceTo: '200px 120px',
-}, [
-  {at: -0.25, is: '75px 220px'},
-  {at: 0, is: '100px 200px'},
-  {at: 0.25, is: '125px 180px'},
-  {at: 0.5, is: '150px 160px'},
-  {at: 0.75, is: '175px 140px'},
-  {at: 1, is: '200px 120px'},
-  {at: 1.25, is: '225px 100px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/border-bottom-right-radius-composition.html b/third_party/blink/web_tests/animations/composition/border-bottom-right-radius-composition.html
deleted file mode 100644
index cee2e8e..0000000
--- a/third_party/blink/web_tests/animations/composition/border-bottom-right-radius-composition.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<!DOCTYPE html>
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'border-bottom-right-radius',
-  underlying: '40px 40px',
-  addFrom: '60px 60px',
-  addTo: '160px 160px',
-}, [
-  {at: -0.25, is: '75px'},
-  {at: 0, is: '100px'},
-  {at: 0.25, is: '125px'},
-  {at: 0.5, is: '150px'},
-  {at: 0.75, is: '175px'},
-  {at: 1, is: '200px'},
-  {at: 1.25, is: '225px'},
-]);
-
-assertComposition({
-  property: 'border-bottom-right-radius',
-  underlying: '40px 140px',
-  replaceFrom: '100px 120px',
-  addTo: '160px 60px',
-}, [
-  {at: -0.25, is: '75px 100px'},
-  {at: 0, is: '100px 120px'},
-  {at: 0.25, is: '125px 140px'},
-  {at: 0.5, is: '150px 160px'},
-  {at: 0.75, is: '175px 180px'},
-  {at: 1, is: '200px'},
-  {at: 1.25, is: '225px 220px'},
-]);
-
-assertComposition({
-  property: 'border-bottom-right-radius',
-  underlying: '40px 60px',
-  addFrom: '60px 140px',
-  replaceTo: '200px 120px',
-}, [
-  {at: -0.25, is: '75px 220px'},
-  {at: 0, is: '100px 200px'},
-  {at: 0.25, is: '125px 180px'},
-  {at: 0.5, is: '150px 160px'},
-  {at: 0.75, is: '175px 140px'},
-  {at: 1, is: '200px 120px'},
-  {at: 1.25, is: '225px 100px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/border-bottom-width-composition.html b/third_party/blink/web_tests/animations/composition/border-bottom-width-composition.html
deleted file mode 100644
index b8375555..0000000
--- a/third_party/blink/web_tests/animations/composition/border-bottom-width-composition.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'border-bottom-width',
-  underlying: '50px',
-  addFrom: '100px',
-  addTo: '200px',
-}, [
-  {at: -0.3, is: '120px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '200px'},
-  {at: 1, is: '250px'},
-  {at: 1.5, is: '300px'},
-]);
-
-assertComposition({
-  property: 'border-bottom-width',
-  underlying: '100px',
-  addFrom: '10px',
-  addTo: '2px',
-}, [
-  {at: -0.5, is: '114px'},
-  {at: 0, is: '110px'},
-  {at: 0.5, is: '106px'},
-  {at: 1, is: '102px'},
-  {at: 1.5, is: '98px'}, // Value clamping should happen after composition.
-]);
-
-assertComposition({
-  property: 'border-bottom-width',
-  underlying: '10em',
-  addFrom: '100px',
-  addTo: '20em',
-}, [
-  {at: -0.3, is: 'calc(130px + 4em)'},
-  {at: 0, is: 'calc(100px + 10em)'},
-  {at: 0.5, is: 'calc(50px + 20em)'},
-  {at: 1, is: '30em'},
-  {at: 1.5, is: 'calc(-50px + 40em)'},
-]);
-
-assertComposition({
-  property: 'border-bottom-width',
-  underlying: '50px',
-  addFrom: '100px',
-  replaceTo: '200px',
-}, [
-  {at: -0.3, is: '135px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '175px'},
-  {at: 1, is: '200px'},
-  {at: 1.5, is: '225px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/border-image-outset-composition.html b/third_party/blink/web_tests/animations/composition/border-image-outset-composition.html
deleted file mode 100644
index 51cbeee67..0000000
--- a/third_party/blink/web_tests/animations/composition/border-image-outset-composition.html
+++ /dev/null
@@ -1,126 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'border-image-outset',
-  underlying: '1 2 3 4',
-  addFrom: '1 2 3 4',
-  addTo: '101 102 103 104',
-}, [
-  {at: -0.25, is: '0'}, // Non-negative.
-  {at: 0, is: '2 4 6 8'},
-  {at: 0.25, is: '27 29 31 33'},
-  {at: 0.5, is: '52 54 56 58'},
-  {at: 0.75, is: '77 79 81 83'},
-  {at: 1, is: '102 104 106 108'},
-  {at: 1.25, is: '127 129 131 133'},
-]);
-
-assertComposition({
-  property: 'border-image-outset',
-  underlying: '100 200 300 400',
-  addFrom: '100',
-  addTo: '200 300 500',
-}, [
-  {at: -0.25, is: '175 250 300 450'},
-  {at: 0, is: '200 300 400 500'},
-  {at: 0.25, is: '225 350 500 550'},
-  {at: 0.5, is: '250 400 600 600'},
-  {at: 0.75, is: '275 450 700 650'},
-  {at: 1, is: '300 500 800 700'},
-  {at: 1.25, is: '325 550 900 750'},
-]);
-
-assertComposition({
-  property: 'border-image-outset',
-  underlying: '1 2 3px 4px',
-  addFrom: '1 2 3px 4px',
-  addTo: '101 102 103px 104px',
-}, [
-  {at: -0.25, is: '0 0 0px 0px'}, // Non-negative.
-  {at: 0, is: '2 4 6px 8px'},
-  {at: 0.25, is: '27 29 31px 33px'},
-  {at: 0.5, is: '52 54 56px 58px'},
-  {at: 0.75, is: '77 79 81px 83px'},
-  {at: 1, is: '102 104 106px 108px'},
-  {at: 1.25, is: '127 129 131px 133px'},
-]);
-
-assertComposition({
-  property: 'border-image-outset',
-  underlying: '10px 20px',
-  addFrom: '190px 180px 290px 280px',
-  addTo: '90px 80px',
-}, [
-  {at: -0.25, is: '225px 225px 350px 350px'},
-  {at: 0, is: '200px 200px 300px 300px'},
-  {at: 0.25, is: '175px 175px 250px 250px'},
-  {at: 0.5, is: '150px 150px 200px 200px'},
-  {at: 0.75, is: '125px 125px 150px 150px'},
-  {at: 1, is: '100px'},
-  {at: 1.25, is: '75px 75px 50px 50px'},
-]);
-
-assertComposition({
-  property: 'border-image-outset',
-  underlying: '10 20px',
-  replaceFrom: '100 100px',
-  addTo: '190 180px',
-}, [
-  {at: -0.25, is: '75 75px'},
-  {at: 0, is: '100 100px'},
-  {at: 0.25, is: '125 125px'},
-  {at: 0.5, is: '150 150px'},
-  {at: 0.75, is: '175 175px'},
-  {at: 1, is: '200 200px'},
-  {at: 1.25, is: '225 225px'},
-]);
-
-assertComposition({
-  property: 'border-image-outset',
-  underlying: '10px 20',
-  addFrom: '90px 80',
-  replaceTo: '0px 0 0px 0',
-}, [
-  {at: -0.25, is: '125px 125'},
-  {at: 0, is: '100px 100'},
-  {at: 0.25, is: '75px 75'},
-  {at: 0.5, is: '50px 50'},
-  {at: 0.75, is: '25px 25'},
-  {at: 1, is: '0px 0'},
-  {at: 1.25, is: '0px 0'}, // Non-negative.
-]);
-
-assertComposition({
-  property: 'border-image-outset',
-  underlying: '10 20',
-  addFrom: '100px 150px',
-  addTo: '200px 250px',
-}, [
-  {at: -0.25, is: '75px 125px'},
-  {at: 0, is: '100px 150px'},
-  {at: 0.25, is: '125px 175px'},
-  {at: 0.5, is: '150px 200px'},
-  {at: 0.75, is: '175px 225px'},
-  {at: 1, is: '200px 250px'},
-  {at: 1.25, is: '225px 275px'},
-]);
-
-assertComposition({
-  property: 'border-image-outset',
-  underlying: '10 20',
-  addFrom: '100 150px',
-  addTo: '200px 250',
-}, [
-  {at: -0.25, is: '100 150px'},
-  {at: 0, is: '100 150px'},
-  {at: 0.25, is: '100 150px'},
-  {at: 0.5, is: '200px 250'},
-  {at: 0.75, is: '200px 250'},
-  {at: 1, is: '200px 250'},
-  {at: 1.25, is: '200px 250'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/border-image-slice-composition.html b/third_party/blink/web_tests/animations/composition/border-image-slice-composition.html
deleted file mode 100644
index df7489a..0000000
--- a/third_party/blink/web_tests/animations/composition/border-image-slice-composition.html
+++ /dev/null
@@ -1,126 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'border-image-slice',
-  underlying: '1 2 3 4',
-  addFrom: '1 2 3 4',
-  addTo: '101 102 103 104',
-}, [
-  {at: -0.25, is: '0'}, // Non-negative.
-  {at: 0, is: '2 4 6 8'},
-  {at: 0.25, is: '27 29 31 33'},
-  {at: 0.5, is: '52 54 56 58'},
-  {at: 0.75, is: '77 79 81 83'},
-  {at: 1, is: '102 104 106 108'},
-  {at: 1.25, is: '127 129 131 133'},
-]);
-
-assertComposition({
-  property: 'border-image-slice',
-  underlying: '100 200 300 400 fill',
-  addFrom: '100 fill',
-  addTo: '200 300 500 fill',
-}, [
-  {at: -0.25, is: '175 250 300 450 fill'},
-  {at: 0, is: '200 300 400 500 fill'},
-  {at: 0.25, is: '225 350 500 550 fill'},
-  {at: 0.5, is: '250 400 600 600 fill'},
-  {at: 0.75, is: '275 450 700 650 fill'},
-  {at: 1, is: '300 500 800 700 fill'},
-  {at: 1.25, is: '325 550 900 750 fill'},
-]);
-
-assertComposition({
-  property: 'border-image-slice',
-  underlying: '1 2 3% 4%',
-  addFrom: '1 2 3% 4%',
-  addTo: '101 102 103% 104%',
-}, [
-  {at: -0.25, is: '0 0 0% 0%'}, // Non-negative.
-  {at: 0, is: '2 4 6% 8%'},
-  {at: 0.25, is: '27 29 31% 33%'},
-  {at: 0.5, is: '52 54 56% 58%'},
-  {at: 0.75, is: '77 79 81% 83%'},
-  {at: 1, is: '102 104 106% 108%'},
-  {at: 1.25, is: '127 129 131% 133%'},
-]);
-
-assertComposition({
-  property: 'border-image-slice',
-  underlying: '10% 20%',
-  addFrom: '190% 180% 290% 280%',
-  addTo: '90% 80%',
-}, [
-  {at: -0.25, is: '225% 225% 350% 350%'},
-  {at: 0, is: '200% 200% 300% 300%'},
-  {at: 0.25, is: '175% 175% 250% 250%'},
-  {at: 0.5, is: '150% 150% 200% 200%'},
-  {at: 0.75, is: '125% 125% 150% 150%'},
-  {at: 1, is: '100%'},
-  {at: 1.25, is: '75% 75% 50% 50%'},
-]);
-
-assertComposition({
-  property: 'border-image-slice',
-  underlying: '10 20%',
-  replaceFrom: '100 100%',
-  addTo: '190 180%',
-}, [
-  {at: -0.25, is: '75 75%'},
-  {at: 0, is: '100 100%'},
-  {at: 0.25, is: '125 125%'},
-  {at: 0.5, is: '150 150%'},
-  {at: 0.75, is: '175 175%'},
-  {at: 1, is: '200 200%'},
-  {at: 1.25, is: '225 225%'},
-]);
-
-assertComposition({
-  property: 'border-image-slice',
-  underlying: '10% 20',
-  addFrom: '90% 80',
-  replaceTo: '0% 0 0% 0',
-}, [
-  {at: -0.25, is: '125% 125'},
-  {at: 0, is: '100% 100'},
-  {at: 0.25, is: '75% 75'},
-  {at: 0.5, is: '50% 50'},
-  {at: 0.75, is: '25% 25'},
-  {at: 1, is: '0% 0'},
-  {at: 1.25, is: '0% 0'}, // Non-negative.
-]);
-
-assertComposition({
-  property: 'border-image-slice',
-  underlying: '10 20',
-  addFrom: '100% 150%',
-  addTo: '200% 250% fill',
-}, [
-  {at: -0.25, is: '100% 150%'},
-  {at: 0, is: '100% 150%'},
-  {at: 0.25, is: '100% 150%'},
-  {at: 0.5, is: '200% 250% fill'},
-  {at: 0.75, is: '200% 250% fill'},
-  {at: 1, is: '200% 250% fill'},
-  {at: 1.25, is: '200% 250% fill'},
-]);
-
-assertComposition({
-  property: 'border-image-slice',
-  underlying: '10 20',
-  addFrom: '100 150%',
-  addTo: '200% 250',
-}, [
-  {at: -0.25, is: '100 150%'},
-  {at: 0, is: '100 150%'},
-  {at: 0.25, is: '100 150%'},
-  {at: 0.5, is: '200% 250'},
-  {at: 0.75, is: '200% 250'},
-  {at: 1, is: '200% 250'},
-  {at: 1.25, is: '200% 250'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/border-image-width-composition.html b/third_party/blink/web_tests/animations/composition/border-image-width-composition.html
deleted file mode 100644
index 9fecb4a..0000000
--- a/third_party/blink/web_tests/animations/composition/border-image-width-composition.html
+++ /dev/null
@@ -1,126 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'border-image-width',
-  underlying: '1 2 3 4',
-  addFrom: '1 2 3 4',
-  addTo: '101 102 103 104',
-}, [
-  {at: -0.25, is: '0'}, // Non-negative.
-  {at: 0, is: '2 4 6 8'},
-  {at: 0.25, is: '27 29 31 33'},
-  {at: 0.5, is: '52 54 56 58'},
-  {at: 0.75, is: '77 79 81 83'},
-  {at: 1, is: '102 104 106 108'},
-  {at: 1.25, is: '127 129 131 133'},
-]);
-
-assertComposition({
-  property: 'border-image-width',
-  underlying: '100 200 300 400',
-  addFrom: '100',
-  addTo: '200 300 500',
-}, [
-  {at: -0.25, is: '175 250 300 450'},
-  {at: 0, is: '200 300 400 500'},
-  {at: 0.25, is: '225 350 500 550'},
-  {at: 0.5, is: '250 400 600 600'},
-  {at: 0.75, is: '275 450 700 650'},
-  {at: 1, is: '300 500 800 700'},
-  {at: 1.25, is: '325 550 900 750'},
-]);
-
-assertComposition({
-  property: 'border-image-width',
-  underlying: '1 2 3px 4%',
-  addFrom: '1 2 3px 4%',
-  addTo: '101 102 103px 104%',
-}, [
-  {at: -0.25, is: '0 0 0px 0%'}, // Non-negative.
-  {at: 0, is: '2 4 6px 8%'},
-  {at: 0.25, is: '27 29 31px 33%'},
-  {at: 0.5, is: '52 54 56px 58%'},
-  {at: 0.75, is: '77 79 81px 83%'},
-  {at: 1, is: '102 104 106px 108%'},
-  {at: 1.25, is: '127 129 131px 133%'},
-]);
-
-assertComposition({
-  property: 'border-image-width',
-  underlying: '10px 20px',
-  addFrom: '190px 180px 290px 280px',
-  addTo: '90px 80px',
-}, [
-  {at: -0.25, is: '225px 225px 350px 350px'},
-  {at: 0, is: '200px 200px 300px 300px'},
-  {at: 0.25, is: '175px 175px 250px 250px'},
-  {at: 0.5, is: '150px 150px 200px 200px'},
-  {at: 0.75, is: '125px 125px 150px 150px'},
-  {at: 1, is: '100px'},
-  {at: 1.25, is: '75px 75px 50px 50px'},
-]);
-
-assertComposition({
-  property: 'border-image-width',
-  underlying: '10 20px',
-  replaceFrom: '100 100px',
-  addTo: '190 180px',
-}, [
-  {at: -0.25, is: '75 75px'},
-  {at: 0, is: '100 100px'},
-  {at: 0.25, is: '125 125px'},
-  {at: 0.5, is: '150 150px'},
-  {at: 0.75, is: '175 175px'},
-  {at: 1, is: '200 200px'},
-  {at: 1.25, is: '225 225px'},
-]);
-
-assertComposition({
-  property: 'border-image-width',
-  underlying: '10px 20',
-  addFrom: '90px 80',
-  replaceTo: '0px 0 0px 0',
-}, [
-  {at: -0.25, is: '125px 125'},
-  {at: 0, is: '100px 100'},
-  {at: 0.25, is: '75px 75'},
-  {at: 0.5, is: '50px 50'},
-  {at: 0.75, is: '25px 25'},
-  {at: 1, is: '0px 0'},
-  {at: 1.25, is: '0px 0'}, // Non-negative.
-]);
-
-assertComposition({
-  property: 'border-image-width',
-  underlying: '10 20',
-  addFrom: '100px 150px',
-  addTo: '200px 250px',
-}, [
-  {at: -0.25, is: '75px 125px'},
-  {at: 0, is: '100px 150px'},
-  {at: 0.25, is: '125px 175px'},
-  {at: 0.5, is: '150px 200px'},
-  {at: 0.75, is: '175px 225px'},
-  {at: 1, is: '200px 250px'},
-  {at: 1.25, is: '225px 275px'},
-]);
-
-assertComposition({
-  property: 'border-image-width',
-  underlying: '10 20',
-  addFrom: '100 150px',
-  addTo: '200% 250',
-}, [
-  {at: -0.25, is: '100 150px'},
-  {at: 0, is: '100 150px'},
-  {at: 0.25, is: '100 150px'},
-  {at: 0.5, is: '200% 250'},
-  {at: 0.75, is: '200% 250'},
-  {at: 1, is: '200% 250'},
-  {at: 1.25, is: '200% 250'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/border-left-width-composition.html b/third_party/blink/web_tests/animations/composition/border-left-width-composition.html
deleted file mode 100644
index 450d5a9f..0000000
--- a/third_party/blink/web_tests/animations/composition/border-left-width-composition.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'border-left-width',
-  underlying: '50px',
-  addFrom: '100px',
-  addTo: '200px',
-}, [
-  {at: -0.3, is: '120px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '200px'},
-  {at: 1, is: '250px'},
-  {at: 1.5, is: '300px'},
-]);
-
-assertComposition({
-  property: 'border-left-width',
-  underlying: '100px',
-  addFrom: '10px',
-  addTo: '2px',
-}, [
-  {at: -0.5, is: '114px'},
-  {at: 0, is: '110px'},
-  {at: 0.5, is: '106px'},
-  {at: 1, is: '102px'},
-  {at: 1.5, is: '98px'}, // Value clamping should happen after composition.
-]);
-
-assertComposition({
-  property: 'border-left-width',
-  underlying: '10em',
-  addFrom: '100px',
-  addTo: '20em',
-}, [
-  {at: -0.3, is: 'calc(130px + 4em)'},
-  {at: 0, is: 'calc(100px + 10em)'},
-  {at: 0.5, is: 'calc(50px + 20em)'},
-  {at: 1, is: '30em'},
-  {at: 1.5, is: 'calc(-50px + 40em)'},
-]);
-
-assertComposition({
-  property: 'border-left-width',
-  underlying: '50px',
-  addFrom: '100px',
-  replaceTo: '200px',
-}, [
-  {at: -0.3, is: '135px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '175px'},
-  {at: 1, is: '200px'},
-  {at: 1.5, is: '225px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/border-right-width-composition.html b/third_party/blink/web_tests/animations/composition/border-right-width-composition.html
deleted file mode 100644
index a41cfc1..0000000
--- a/third_party/blink/web_tests/animations/composition/border-right-width-composition.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'border-right-width',
-  underlying: '50px',
-  addFrom: '100px',
-  addTo: '200px',
-}, [
-  {at: -0.3, is: '120px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '200px'},
-  {at: 1, is: '250px'},
-  {at: 1.5, is: '300px'},
-]);
-
-assertComposition({
-  property: 'border-right-width',
-  underlying: '100px',
-  addFrom: '10px',
-  addTo: '2px',
-}, [
-  {at: -0.5, is: '114px'},
-  {at: 0, is: '110px'},
-  {at: 0.5, is: '106px'},
-  {at: 1, is: '102px'},
-  {at: 1.5, is: '98px'}, // Value clamping should happen after composition.
-]);
-
-assertComposition({
-  property: 'border-right-width',
-  underlying: '10em',
-  addFrom: '100px',
-  addTo: '20em',
-}, [
-  {at: -0.3, is: 'calc(130px + 4em)'},
-  {at: 0, is: 'calc(100px + 10em)'},
-  {at: 0.5, is: 'calc(50px + 20em)'},
-  {at: 1, is: '30em'},
-  {at: 1.5, is: 'calc(-50px + 40em)'},
-]);
-
-assertComposition({
-  property: 'border-right-width',
-  underlying: '50px',
-  addFrom: '100px',
-  replaceTo: '200px',
-}, [
-  {at: -0.3, is: '135px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '175px'},
-  {at: 1, is: '200px'},
-  {at: 1.5, is: '225px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/border-top-left-radius-composition.html b/third_party/blink/web_tests/animations/composition/border-top-left-radius-composition.html
deleted file mode 100644
index faf5aca..0000000
--- a/third_party/blink/web_tests/animations/composition/border-top-left-radius-composition.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<!DOCTYPE html>
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'border-top-left-radius',
-  underlying: '40px 40px',
-  addFrom: '60px 60px',
-  addTo: '160px 160px',
-}, [
-  {at: -0.25, is: '75px'},
-  {at: 0, is: '100px'},
-  {at: 0.25, is: '125px'},
-  {at: 0.5, is: '150px'},
-  {at: 0.75, is: '175px'},
-  {at: 1, is: '200px'},
-  {at: 1.25, is: '225px'},
-]);
-
-assertComposition({
-  property: 'border-top-left-radius',
-  underlying: '40px 140px',
-  replaceFrom: '100px 120px',
-  addTo: '160px 60px',
-}, [
-  {at: -0.25, is: '75px 100px'},
-  {at: 0, is: '100px 120px'},
-  {at: 0.25, is: '125px 140px'},
-  {at: 0.5, is: '150px 160px'},
-  {at: 0.75, is: '175px 180px'},
-  {at: 1, is: '200px'},
-  {at: 1.25, is: '225px 220px'},
-]);
-
-assertComposition({
-  property: 'border-top-left-radius',
-  underlying: '40px 60px',
-  addFrom: '60px 140px',
-  replaceTo: '200px 120px',
-}, [
-  {at: -0.25, is: '75px 220px'},
-  {at: 0, is: '100px 200px'},
-  {at: 0.25, is: '125px 180px'},
-  {at: 0.5, is: '150px 160px'},
-  {at: 0.75, is: '175px 140px'},
-  {at: 1, is: '200px 120px'},
-  {at: 1.25, is: '225px 100px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/border-top-right-radius-composition.html b/third_party/blink/web_tests/animations/composition/border-top-right-radius-composition.html
deleted file mode 100644
index ca246200..0000000
--- a/third_party/blink/web_tests/animations/composition/border-top-right-radius-composition.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<!DOCTYPE html>
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'border-top-right-radius',
-  underlying: '40px 40px',
-  addFrom: '60px 60px',
-  addTo: '160px 160px',
-}, [
-  {at: -0.25, is: '75px'},
-  {at: 0, is: '100px'},
-  {at: 0.25, is: '125px'},
-  {at: 0.5, is: '150px'},
-  {at: 0.75, is: '175px'},
-  {at: 1, is: '200px'},
-  {at: 1.25, is: '225px'},
-]);
-
-assertComposition({
-  property: 'border-top-right-radius',
-  underlying: '40px 140px',
-  replaceFrom: '100px 120px',
-  addTo: '160px 60px',
-}, [
-  {at: -0.25, is: '75px 100px'},
-  {at: 0, is: '100px 120px'},
-  {at: 0.25, is: '125px 140px'},
-  {at: 0.5, is: '150px 160px'},
-  {at: 0.75, is: '175px 180px'},
-  {at: 1, is: '200px'},
-  {at: 1.25, is: '225px 220px'},
-]);
-
-assertComposition({
-  property: 'border-top-right-radius',
-  underlying: '40px 60px',
-  addFrom: '60px 140px',
-  replaceTo: '200px 120px',
-}, [
-  {at: -0.25, is: '75px 220px'},
-  {at: 0, is: '100px 200px'},
-  {at: 0.25, is: '125px 180px'},
-  {at: 0.5, is: '150px 160px'},
-  {at: 0.75, is: '175px 140px'},
-  {at: 1, is: '200px 120px'},
-  {at: 1.25, is: '225px 100px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/border-top-width-composition.html b/third_party/blink/web_tests/animations/composition/border-top-width-composition.html
deleted file mode 100644
index 9ce9de2..0000000
--- a/third_party/blink/web_tests/animations/composition/border-top-width-composition.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'border-top-width',
-  underlying: '50px',
-  addFrom: '100px',
-  addTo: '200px',
-}, [
-  {at: -0.3, is: '120px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '200px'},
-  {at: 1, is: '250px'},
-  {at: 1.5, is: '300px'},
-]);
-
-assertComposition({
-  property: 'border-top-width',
-  underlying: '100px',
-  addFrom: '10px',
-  addTo: '2px',
-}, [
-  {at: -0.5, is: '114px'},
-  {at: 0, is: '110px'},
-  {at: 0.5, is: '106px'},
-  {at: 1, is: '102px'},
-  {at: 1.5, is: '98px'}, // Value clamping should happen after composition.
-]);
-
-assertComposition({
-  property: 'border-top-width',
-  underlying: '10em',
-  addFrom: '100px',
-  addTo: '20em',
-}, [
-  {at: -0.3, is: 'calc(130px + 4em)'},
-  {at: 0, is: 'calc(100px + 10em)'},
-  {at: 0.5, is: 'calc(50px + 20em)'},
-  {at: 1, is: '30em'},
-  {at: 1.5, is: 'calc(-50px + 40em)'},
-]);
-
-assertComposition({
-  property: 'border-top-width',
-  underlying: '50px',
-  addFrom: '100px',
-  replaceTo: '200px',
-}, [
-  {at: -0.3, is: '135px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '175px'},
-  {at: 1, is: '200px'},
-  {at: 1.5, is: '225px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/letter-spacing-composition.html b/third_party/blink/web_tests/animations/composition/letter-spacing-composition.html
deleted file mode 100644
index becabf70..0000000
--- a/third_party/blink/web_tests/animations/composition/letter-spacing-composition.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'letter-spacing',
-  underlying: '50px',
-  addFrom: '100px',
-  addTo: '200px',
-}, [
-  {at: -0.3, is: '120px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '200px'},
-  {at: 1, is: '250px'},
-  {at: 1.5, is: '300px'},
-]);
-
-assertComposition({
-  property: 'letter-spacing',
-  underlying: '100px',
-  addFrom: '10px',
-  addTo: '2px',
-}, [
-  {at: -0.5, is: '114px'},
-  {at: 0, is: '110px'},
-  {at: 0.5, is: '106px'},
-  {at: 1, is: '102px'},
-  {at: 1.5, is: '98px'},
-]);
-
-assertComposition({
-  property: 'letter-spacing',
-  underlying: '50px',
-  addFrom: '100px',
-  replaceTo: '200px',
-}, [
-  {at: -0.3, is: '135px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '175px'},
-  {at: 1, is: '200px'},
-  {at: 1.5, is: '225px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/offset-anchor-composition.html b/third_party/blink/web_tests/animations/composition/offset-anchor-composition.html
deleted file mode 100644
index 865d81b3..0000000
--- a/third_party/blink/web_tests/animations/composition/offset-anchor-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<style>
-.target {
-  width: 200px;
-  height: 200px;
-}
-</style>
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'offset-anchor',
-  underlying: '40px 60px',
-  addFrom: '60px 40px',
-  addTo: '160px 140px',
-}, [
-  {at: -0.25, is: '75px 75px'},
-  {at: 0, is: '100px 100px'},
-  {at: 0.25, is: '125px 125px'},
-  {at: 0.5, is: '150px 150px'},
-  {at: 0.75, is: '175px 175px'},
-  {at: 1, is: '200px 200px'},
-  {at: 1.25, is: '225px 225px'},
-]);
-
-assertComposition({
-  property: 'offset-anchor',
-  underlying: 'top 20% left 40%',
-  addFrom: 'left 60% top 80%',
-  addTo: 'right 80% bottom 40%',
-}, [
-  {at: -0.25, is: '110% 105%'},
-  {at: 0, is: '100% 100%'},
-  {at: 0.25, is: '90% 95%'},
-  {at: 0.5, is: '80% 90%'},
-  {at: 0.75, is: '70% 85%'},
-  {at: 1, is: '60% 80%'},
-  {at: 1.25, is: '50% 75%'},
-]);
-
-assertComposition({
-  property: 'offset-anchor',
-  underlying: '40px 60px',
-  replaceFrom: '100px 200px',
-  addTo: '160px 40px',
-}, [
-  {at: -0.25, is: '75px 225px'},
-  {at: 0, is: '100px 200px'},
-  {at: 0.25, is: '125px 175px'},
-  {at: 0.5, is: '150px 150px'},
-  {at: 0.75, is: '175px 125px'},
-  {at: 1, is: '200px 100px'},
-  {at: 1.25, is: '225px 75px'},
-]);
-
-assertComposition({
-  property: 'offset-anchor',
-  underlying: '40px 60px',
-  addFrom: '60px 140px',
-  replaceTo: '200px 100px',
-}, [
-  {at: -0.25, is: '75px 225px'},
-  {at: 0, is: '100px 200px'},
-  {at: 0.25, is: '125px 175px'},
-  {at: 0.5, is: '150px 150px'},
-  {at: 0.75, is: '175px 125px'},
-  {at: 1, is: '200px 100px'},
-  {at: 1.25, is: '225px 75px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/offset-distance-composition.html b/third_party/blink/web_tests/animations/composition/offset-distance-composition.html
deleted file mode 100644
index 6a595895..0000000
--- a/third_party/blink/web_tests/animations/composition/offset-distance-composition.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'offset-distance',
-  underlying: '50px',
-  addFrom: '100px',
-  addTo: '200px',
-}, [
-  {at: -0.3, is: '120px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '200px'},
-  {at: 1, is: '250px'},
-  {at: 1.5, is: '300px'},
-]);
-
-assertComposition({
-  property: 'offset-distance',
-  underlying: '100px',
-  addFrom: '10px',
-  addTo: '2px',
-}, [
-  {at: -0.5, is: '114px'},
-  {at: 0, is: '110px'},
-  {at: 0.5, is: '106px'},
-  {at: 1, is: '102px'},
-  {at: 1.5, is: '98px'},
-]);
-
-assertComposition({
-  property: 'offset-distance',
-  underlying: '10%',
-  addFrom: '100px',
-  addTo: '20%',
-}, [
-  {at: -0.3, is: 'calc(130px + 4%)'},
-  {at: 0, is: 'calc(100px + 10%)'},
-  {at: 0.5, is: 'calc(50px + 20%)'},
-  {at: 1, is: '30%'},
-  {at: 1.5, is: 'calc(-50px + 40%)'},
-]);
-
-assertComposition({
-  property: 'offset-distance',
-  underlying: '50px',
-  addFrom: '100px',
-  replaceTo: '200px',
-}, [
-  {at: -0.3, is: '135px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '175px'},
-  {at: 1, is: '200px'},
-  {at: 1.5, is: '225px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/offset-path-composition.html b/third_party/blink/web_tests/animations/composition/offset-path-composition.html
deleted file mode 100644
index 8d9e6c444..0000000
--- a/third_party/blink/web_tests/animations/composition/offset-path-composition.html
+++ /dev/null
@@ -1,112 +0,0 @@
-<!DOCTYPE html>
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-
-// TODO(ericwilligers) Support additive animation for path strings crbug.com/699308
-
-// Ray paths compose.
-assertComposition({
-  property: 'offset-path',
-  underlying: 'ray(20deg sides)',
-  addFrom: 'ray(10deg sides)',
-  addTo: 'ray(20deg sides)',
-}, [
-  {at: -0.3, is: 'ray(27deg sides)'},
-  {at: 0, is: 'ray(30deg sides)'},
-  {at: 0.3, is: 'ray(33deg sides)'},
-  {at: 0.6, is: 'ray(36deg sides)'},
-  {at: 1, is: 'ray(40deg sides)'},
-  {at: 1.5, is: 'ray(45deg sides)'},
-]);
-
-// Ray paths without contain don't compose with underlying contain.
-assertComposition({
-  property: 'offset-path',
-  underlying: 'ray(20deg closest-corner contain)',
-  addFrom: 'ray(10deg closest-corner)',
-  addTo: 'ray(20deg closest-corner)',
-}, [
-  {at: -0.3, is: 'ray(7deg closest-corner)'},
-  {at: 0, is: 'ray(10deg closest-corner)'},
-  {at: 0.3, is: 'ray(13deg closest-corner)'},
-  {at: 0.6, is: 'ray(16deg closest-corner)'},
-  {at: 1, is: 'ray(20deg closest-corner)'},
-  {at: 1.5, is: 'ray(25deg closest-corner)'},
-]);
-
-// Ray paths don't compose when underlying has different size.
-assertComposition({
-  property: 'offset-path',
-  underlying: 'ray(20deg closest-side)',
-  addFrom: 'ray(10deg closest-corner)',
-  addTo: 'ray(20deg closest-corner)',
-}, [
-  {at: -0.3, is: 'ray(7deg closest-corner)'},
-  {at: 0, is: 'ray(10deg closest-corner)'},
-  {at: 0.3, is: 'ray(13deg closest-corner)'},
-  {at: 0.6, is: 'ray(16deg closest-corner)'},
-  {at: 1, is: 'ray(20deg closest-corner)'},
-  {at: 1.5, is: 'ray(25deg closest-corner)'},
-]);
-
-// Ray contain paths compose with underlying contain.
-assertComposition({
-  property: 'offset-path',
-  underlying: 'ray(20deg farthest-side contain)',
-  addFrom: 'ray(190deg farthest-side contain)',
-  addTo: 'ray(20deg farthest-side contain)',
-}, [
-  {at: -0.3, is: 'ray(261deg farthest-side contain)'},
-  {at: 0, is: 'ray(210deg farthest-side contain)'},
-  {at: 0.3, is: 'ray(159deg farthest-side contain)'},
-  {at: 0.6, is: 'ray(108deg farthest-side contain)'},
-  {at: 1, is: 'ray(40deg farthest-side contain)'},
-  {at: 1.5, is: 'ray(-45deg farthest-side contain)'},
-]);
-
-// When we can't interpolate, we can't compose.
-assertComposition({
-  property: 'offset-path',
-  underlying: 'ray(20deg farthest-corner)',
-  addFrom: 'ray(190deg farthest-corner contain)',
-  addTo: 'ray(20deg farthest-corner)',
-}, [
-  {at: -0.3, is: 'ray(190deg farthest-corner contain)'},
-  {at: 0, is: 'ray(190deg farthest-corner contain)'},
-  {at: 0.3, is: 'ray(190deg farthest-corner contain)'},
-  {at: 0.6, is: 'ray(40deg farthest-corner)'},
-  {at: 1, is: 'ray(40deg farthest-corner)'},
-  {at: 1.5, is: 'ray(40deg farthest-corner)'},
-]);
-
-assertComposition({
-  property: 'offset-path',
-  underlying: 'ray(20deg sides)',
-  replaceFrom: 'ray(190deg sides contain)',
-  addTo: 'ray(20deg sides)',
-}, [
-  {at: -0.3, is: 'ray(190deg sides contain)'},
-  {at: 0, is: 'ray(190deg sides contain)'},
-  {at: 0.3, is: 'ray(190deg sides contain)'},
-  {at: 0.6, is: 'ray(40deg sides)'},
-  {at: 1, is: 'ray(40deg sides)'},
-  {at: 1.5, is: 'ray(40deg sides)'},
-]);
-
-// Ray paths compose with underlying.
-assertComposition({
-  property: 'offset-path',
-  underlying: 'ray(20deg closest-side)',
-  addFrom: 'ray(10deg closest-side)',
-  replaceTo: 'ray(10deg closest-side)',
-}, [
-  {at: -0.3, is: 'ray(36deg closest-side)'},
-  {at: 0, is: 'ray(30deg closest-side)'},
-  {at: 0.3, is: 'ray(24deg closest-side)'},
-  {at: 0.6, is: 'ray(18deg closest-side)'},
-  {at: 1, is: 'ray(10deg closest-side)'},
-  {at: 1.5, is: 'ray(0deg closest-side)'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/offset-position-composition.html b/third_party/blink/web_tests/animations/composition/offset-position-composition.html
deleted file mode 100644
index a724978..0000000
--- a/third_party/blink/web_tests/animations/composition/offset-position-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<style>
-.target {
-  width: 200px;
-  height: 200px;
-}
-</style>
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'offset-position',
-  underlying: '40px 60px',
-  addFrom: '60px 40px',
-  addTo: '160px 140px',
-}, [
-  {at: -0.25, is: '75px 75px'},
-  {at: 0, is: '100px 100px'},
-  {at: 0.25, is: '125px 125px'},
-  {at: 0.5, is: '150px 150px'},
-  {at: 0.75, is: '175px 175px'},
-  {at: 1, is: '200px 200px'},
-  {at: 1.25, is: '225px 225px'},
-]);
-
-assertComposition({
-  property: 'offset-position',
-  underlying: 'top 20% left 40%',
-  addFrom: 'left 60% top 80%',
-  addTo: 'right 80% bottom 40%',
-}, [
-  {at: -0.25, is: '110% 105%'},
-  {at: 0, is: '100% 100%'},
-  {at: 0.25, is: '90% 95%'},
-  {at: 0.5, is: '80% 90%'},
-  {at: 0.75, is: '70% 85%'},
-  {at: 1, is: '60% 80%'},
-  {at: 1.25, is: '50% 75%'},
-]);
-
-assertComposition({
-  property: 'offset-position',
-  underlying: '40px 60px',
-  replaceFrom: '100px 200px',
-  addTo: '160px 40px',
-}, [
-  {at: -0.25, is: '75px 225px'},
-  {at: 0, is: '100px 200px'},
-  {at: 0.25, is: '125px 175px'},
-  {at: 0.5, is: '150px 150px'},
-  {at: 0.75, is: '175px 125px'},
-  {at: 1, is: '200px 100px'},
-  {at: 1.25, is: '225px 75px'},
-]);
-
-assertComposition({
-  property: 'offset-position',
-  underlying: '40px 60px',
-  addFrom: '60px 140px',
-  replaceTo: '200px 100px',
-}, [
-  {at: -0.25, is: '75px 225px'},
-  {at: 0, is: '100px 200px'},
-  {at: 0.25, is: '125px 175px'},
-  {at: 0.5, is: '150px 150px'},
-  {at: 0.75, is: '175px 125px'},
-  {at: 1, is: '200px 100px'},
-  {at: 1.25, is: '225px 75px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/offset-rotate-composition.html b/third_party/blink/web_tests/animations/composition/offset-rotate-composition.html
deleted file mode 100644
index 2f566546..0000000
--- a/third_party/blink/web_tests/animations/composition/offset-rotate-composition.html
+++ /dev/null
@@ -1,94 +0,0 @@
-<!DOCTYPE html>
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-// Angle rotations compose.
-assertComposition({
-  property: 'offset-rotate',
-  underlying: '20deg',
-  addFrom: '10deg',
-  addTo: '20deg',
-}, [
-  {at: -0.3, is: '27deg'},
-  {at: 0, is: '30deg'},
-  {at: 0.3, is: '33deg'},
-  {at: 0.6, is: '36deg'},
-  {at: 1, is: '40deg'},
-  {at: 1.5, is: '45deg'},
-]);
-
-// Angle rotations don't compose with underlying 'auto'.
-assertComposition({
-  property: 'offset-rotate',
-  underlying: 'auto 20deg',
-  addFrom: '10deg',
-  addTo: '20deg',
-}, [
-  {at: -0.3, is: '7deg'},
-  {at: 0, is: '10deg'},
-  {at: 0.3, is: '13deg'},
-  {at: 0.6, is: '16deg'},
-  {at: 1, is: '20deg'},
-  {at: 1.5, is: '25deg'},
-]);
-
-// Auto rotations compose with underlying 'auto'.
-assertComposition({
-  property: 'offset-rotate',
-  underlying: 'auto 20deg',
-  addFrom: 'reverse 10deg',
-  addTo: 'auto 20deg',
-}, [
-  {at: -0.3, is: 'auto 261deg'},
-  {at: 0, is: 'auto 210deg'},
-  {at: 0.3, is: 'auto 159deg'},
-  {at: 0.6, is: 'auto 108deg'},
-  {at: 1, is: 'auto 40deg'},
-  {at: 1.5, is: 'auto -45deg'},
-]);
-
-// When we can't interpolate, we can't compose.
-assertComposition({
-  property: 'offset-rotate',
-  underlying: '20deg',
-  addFrom: 'reverse 10deg',
-  addTo: '20deg',
-}, [
-  {at: -0.3, is: 'auto 190deg'},
-  {at: 0, is: 'auto 190deg'},
-  {at: 0.3, is: 'auto 190deg'},
-  {at: 0.6, is: '40deg'},
-  {at: 1, is: '40deg'},
-  {at: 1.5, is: '40deg'},
-]);
-
-assertComposition({
-  property: 'offset-rotate',
-  underlying: '20deg',
-  replaceFrom: 'reverse 10deg',
-  addTo: '20deg',
-}, [
-  {at: -0.3, is: 'auto 190deg'},
-  {at: 0, is: 'auto 190deg'},
-  {at: 0.3, is: 'auto 190deg'},
-  {at: 0.6, is: '40deg'},
-  {at: 1, is: '40deg'},
-  {at: 1.5, is: '40deg'},
-]);
-
-// Angle rotations compose with underlying angle.
-assertComposition({
-  property: 'offset-rotate',
-  underlying: '20deg',
-  addFrom: '10deg',
-  replaceTo: '10deg',
-}, [
-  {at: -0.3, is: '36deg'},
-  {at: 0, is: '30deg'},
-  {at: 0.3, is: '24deg'},
-  {at: 0.6, is: '18deg'},
-  {at: 1, is: '10deg'},
-  {at: 1.5, is: '0deg'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/text-indent-composition.html b/third_party/blink/web_tests/animations/composition/text-indent-composition.html
deleted file mode 100644
index 1fe8c9a..0000000
--- a/third_party/blink/web_tests/animations/composition/text-indent-composition.html
+++ /dev/null
@@ -1,77 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body></body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'text-indent',
-  underlying: '100%',
-  addFrom: '50px',
-  addTo: '150px',
-}, [
-  {at: -0.3, is: 'calc(100% + 20px)'},
-  {at: 0, is: 'calc(100% + 50px)'},
-  {at: 0.3, is: 'calc(100% + 80px)'},
-  {at: 0.6, is: 'calc(100% + 110px)'},
-  {at: 1, is: 'calc(100% + 150px)'},
-  {at: 1.5, is: 'calc(100% + 200px)'},
-]);
-
-assertComposition({
-  property: 'text-indent',
-  underlying: '250px',
-  addFrom: '50px',
-  replaceTo: '100px',
-}, [
-  {at: -0.3, is: '360px'},
-  {at: 0, is: '300px'},
-  {at: 0.3, is: '240px'},
-  {at: 0.6, is: '180px'},
-  {at: 1, is: '100px'},
-  {at: 1.5, is: '0px'},
-]);
-
-assertComposition({
-  property: 'text-indent',
-  underlying: '50%',
-  replaceFrom: '-100%',
-  addTo: '50%',
-}, [
-  {at: -0.3, is: '-160%'},
-  {at: 0, is: '-100%'},
-  {at: 0.3, is: '-40%'},
-  {at: 0.5, is: '0%'},
-  {at: 0.6, is: '20%'},
-  {at: 1, is: '100%'},
-  {at: 1.5, is: '200%'},
-]);
-
-assertComposition({
-  property: 'text-indent',
-  underlying: '250px',
-  addFrom: '50px each-line hanging',
-  replaceTo: '150px hanging each-line',
-}, [
-  {at: -0.3, is: '20px hanging each-line'},
-  {at: 0, is: '50px hanging each-line'},
-  {at: 0.3, is: '80px hanging each-line'},
-  {at: 0.6, is: '110px hanging each-line'},
-  {at: 1, is: '150px hanging each-line'},
-  {at: 1.5, is: '200px hanging each-line'},
-]);
-
-assertComposition({
-  property: 'text-indent',
-  underlying: '250px each-line',
-  addFrom: '50px each-line',
-  replaceTo: '150px hanging',
-}, [
-  {at: -0.3, is: '300px each-line'},
-  {at: 0, is: '300px each-line'},
-  {at: 0.3, is: '300px each-line'},
-  {at: 0.6, is: '150px hanging'},
-  {at: 1, is: '150px hanging'},
-  {at: 1.5, is: '150px hanging'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/word-spacing-composition.html b/third_party/blink/web_tests/animations/composition/word-spacing-composition.html
deleted file mode 100644
index 7bcb3a9..0000000
--- a/third_party/blink/web_tests/animations/composition/word-spacing-composition.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'word-spacing',
-  underlying: '50px',
-  addFrom: '100px',
-  addTo: '200px',
-}, [
-  {at: -0.3, is: '120px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '200px'},
-  {at: 1, is: '250px'},
-  {at: 1.5, is: '300px'},
-]);
-
-assertComposition({
-  property: 'word-spacing',
-  underlying: '100px',
-  addFrom: '10px',
-  addTo: '2px',
-}, [
-  {at: -0.5, is: '114px'},
-  {at: 0, is: '110px'},
-  {at: 0.5, is: '106px'},
-  {at: 1, is: '102px'},
-  {at: 1.5, is: '98px'},
-]);
-
-assertComposition({
-  property: 'word-spacing',
-  underlying: '50px',
-  addFrom: '100px',
-  replaceTo: '200px',
-}, [
-  {at: -0.3, is: '135px'},
-  {at: 0, is: '150px'},
-  {at: 0.5, is: '175px'},
-  {at: 1, is: '200px'},
-  {at: 1.5, is: '225px'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
index 15eba57..0f3b0d8 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
@@ -209888,12 +209888,24 @@
      {}
     ]
    ],
+   "css/css-align/animation/column-gap-composition.html": [
+    [
+     "css/css-align/animation/column-gap-composition.html",
+     {}
+    ]
+   ],
    "css/css-align/animation/column-gap-interpolation.html": [
     [
      "css/css-align/animation/column-gap-interpolation.html",
      {}
     ]
    ],
+   "css/css-align/animation/row-gap-composition.html": [
+    [
+     "css/css-align/animation/row-gap-composition.html",
+     {}
+    ]
+   ],
    "css/css-align/animation/row-gap-interpolation.html": [
     [
      "css/css-align/animation/row-gap-interpolation.html",
@@ -210980,18 +210992,48 @@
      {}
     ]
    ],
+   "css/css-backgrounds/animations/border-bottom-left-radius-composition.html": [
+    [
+     "css/css-backgrounds/animations/border-bottom-left-radius-composition.html",
+     {}
+    ]
+   ],
+   "css/css-backgrounds/animations/border-bottom-right-radius-composition.html": [
+    [
+     "css/css-backgrounds/animations/border-bottom-right-radius-composition.html",
+     {}
+    ]
+   ],
+   "css/css-backgrounds/animations/border-bottom-width-composition.html": [
+    [
+     "css/css-backgrounds/animations/border-bottom-width-composition.html",
+     {}
+    ]
+   ],
    "css/css-backgrounds/animations/border-color-interpolation.html": [
     [
      "css/css-backgrounds/animations/border-color-interpolation.html",
      {}
     ]
    ],
+   "css/css-backgrounds/animations/border-image-outset-composition.html": [
+    [
+     "css/css-backgrounds/animations/border-image-outset-composition.html",
+     {}
+    ]
+   ],
    "css/css-backgrounds/animations/border-image-outset-interpolation.html": [
     [
      "css/css-backgrounds/animations/border-image-outset-interpolation.html",
      {}
     ]
    ],
+   "css/css-backgrounds/animations/border-image-slice-composition.html": [
+    [
+     "css/css-backgrounds/animations/border-image-slice-composition.html",
+     {}
+    ]
+   ],
    "css/css-backgrounds/animations/border-image-slice-interpolation-stability.html": [
     [
      "css/css-backgrounds/animations/border-image-slice-interpolation-stability.html",
@@ -211010,18 +211052,54 @@
      {}
     ]
    ],
+   "css/css-backgrounds/animations/border-image-width-composition.html": [
+    [
+     "css/css-backgrounds/animations/border-image-width-composition.html",
+     {}
+    ]
+   ],
    "css/css-backgrounds/animations/border-image-width-interpolation.html": [
     [
      "css/css-backgrounds/animations/border-image-width-interpolation.html",
      {}
     ]
    ],
+   "css/css-backgrounds/animations/border-left-width-composition.html": [
+    [
+     "css/css-backgrounds/animations/border-left-width-composition.html",
+     {}
+    ]
+   ],
    "css/css-backgrounds/animations/border-radius-interpolation.html": [
     [
      "css/css-backgrounds/animations/border-radius-interpolation.html",
      {}
     ]
    ],
+   "css/css-backgrounds/animations/border-right-width-composition.html": [
+    [
+     "css/css-backgrounds/animations/border-right-width-composition.html",
+     {}
+    ]
+   ],
+   "css/css-backgrounds/animations/border-top-left-radius-composition.html": [
+    [
+     "css/css-backgrounds/animations/border-top-left-radius-composition.html",
+     {}
+    ]
+   ],
+   "css/css-backgrounds/animations/border-top-right-radius-composition.html": [
+    [
+     "css/css-backgrounds/animations/border-top-right-radius-composition.html",
+     {}
+    ]
+   ],
+   "css/css-backgrounds/animations/border-top-width-composition.html": [
+    [
+     "css/css-backgrounds/animations/border-top-width-composition.html",
+     {}
+    ]
+   ],
    "css/css-backgrounds/animations/border-width-interpolation.html": [
     [
      "css/css-backgrounds/animations/border-width-interpolation.html",
@@ -211532,18 +211610,66 @@
      {}
     ]
    ],
+   "css/css-box/animation/margin-bottom-composition.html": [
+    [
+     "css/css-box/animation/margin-bottom-composition.html",
+     {}
+    ]
+   ],
    "css/css-box/animation/margin-interpolation.html": [
     [
      "css/css-box/animation/margin-interpolation.html",
      {}
     ]
    ],
+   "css/css-box/animation/margin-left-composition.html": [
+    [
+     "css/css-box/animation/margin-left-composition.html",
+     {}
+    ]
+   ],
+   "css/css-box/animation/margin-right-composition.html": [
+    [
+     "css/css-box/animation/margin-right-composition.html",
+     {}
+    ]
+   ],
+   "css/css-box/animation/margin-top-composition.html": [
+    [
+     "css/css-box/animation/margin-top-composition.html",
+     {}
+    ]
+   ],
+   "css/css-box/animation/padding-bottom-composition.html": [
+    [
+     "css/css-box/animation/padding-bottom-composition.html",
+     {}
+    ]
+   ],
    "css/css-box/animation/padding-interpolation.html": [
     [
      "css/css-box/animation/padding-interpolation.html",
      {}
     ]
    ],
+   "css/css-box/animation/padding-left-composition.html": [
+    [
+     "css/css-box/animation/padding-left-composition.html",
+     {}
+    ]
+   ],
+   "css/css-box/animation/padding-right-composition.html": [
+    [
+     "css/css-box/animation/padding-right-composition.html",
+     {}
+    ]
+   ],
+   "css/css-box/animation/padding-top-composition.html": [
+    [
+     "css/css-box/animation/padding-top-composition.html",
+     {}
+    ]
+   ],
    "css/css-box/box-chrome-crash-001.html": [
     [
      "css/css-box/box-chrome-crash-001.html",
@@ -218992,12 +219118,24 @@
      {}
     ]
    ],
+   "css/css-shapes/animation/shape-margin-composition.html": [
+    [
+     "css/css-shapes/animation/shape-margin-composition.html",
+     {}
+    ]
+   ],
    "css/css-shapes/animation/shape-margin-interpolation.html": [
     [
      "css/css-shapes/animation/shape-margin-interpolation.html",
      {}
     ]
    ],
+   "css/css-shapes/animation/shape-outside-composition.html": [
+    [
+     "css/css-shapes/animation/shape-outside-composition.html",
+     {}
+    ]
+   ],
    "css/css-shapes/animation/shape-outside-interpolation.html": [
     [
      "css/css-shapes/animation/shape-outside-interpolation.html",
@@ -219664,36 +219802,72 @@
      {}
     ]
    ],
+   "css/css-sizing/animation/height-composition.html": [
+    [
+     "css/css-sizing/animation/height-composition.html",
+     {}
+    ]
+   ],
    "css/css-sizing/animation/height-interpolation.html": [
     [
      "css/css-sizing/animation/height-interpolation.html",
      {}
     ]
    ],
+   "css/css-sizing/animation/max-height-composition.html": [
+    [
+     "css/css-sizing/animation/max-height-composition.html",
+     {}
+    ]
+   ],
    "css/css-sizing/animation/max-height-interpolation.html": [
     [
      "css/css-sizing/animation/max-height-interpolation.html",
      {}
     ]
    ],
+   "css/css-sizing/animation/max-width-composition.html": [
+    [
+     "css/css-sizing/animation/max-width-composition.html",
+     {}
+    ]
+   ],
    "css/css-sizing/animation/max-width-interpolation.html": [
     [
      "css/css-sizing/animation/max-width-interpolation.html",
      {}
     ]
    ],
+   "css/css-sizing/animation/min-height-composition.html": [
+    [
+     "css/css-sizing/animation/min-height-composition.html",
+     {}
+    ]
+   ],
    "css/css-sizing/animation/min-height-interpolation.html": [
     [
      "css/css-sizing/animation/min-height-interpolation.html",
      {}
     ]
    ],
+   "css/css-sizing/animation/min-width-composition.html": [
+    [
+     "css/css-sizing/animation/min-width-composition.html",
+     {}
+    ]
+   ],
    "css/css-sizing/animation/min-width-interpolation.html": [
     [
      "css/css-sizing/animation/min-width-interpolation.html",
      {}
     ]
    ],
+   "css/css-sizing/animation/width-composition.html": [
+    [
+     "css/css-sizing/animation/width-composition.html",
+     {}
+    ]
+   ],
    "css/css-sizing/animation/width-interpolation.html": [
     [
      "css/css-sizing/animation/width-interpolation.html",
@@ -220864,18 +221038,36 @@
      {}
     ]
    ],
+   "css/css-text/animations/letter-spacing-composition.html": [
+    [
+     "css/css-text/animations/letter-spacing-composition.html",
+     {}
+    ]
+   ],
    "css/css-text/animations/letter-spacing-interpolation.html": [
     [
      "css/css-text/animations/letter-spacing-interpolation.html",
      {}
     ]
    ],
+   "css/css-text/animations/text-indent-composition.html": [
+    [
+     "css/css-text/animations/text-indent-composition.html",
+     {}
+    ]
+   ],
    "css/css-text/animations/text-indent-interpolation.html": [
     [
      "css/css-text/animations/text-indent-interpolation.html",
      {}
     ]
    ],
+   "css/css-text/animations/word-spacing-composition.html": [
+    [
+     "css/css-text/animations/word-spacing-composition.html",
+     {}
+    ]
+   ],
    "css/css-text/animations/word-spacing-interpolation.html": [
     [
      "css/css-text/animations/word-spacing-interpolation.html",
@@ -225384,6 +225576,12 @@
      {}
     ]
    ],
+   "css/css-ui/animation/caret-color-composition.html": [
+    [
+     "css/css-ui/animation/caret-color-composition.html",
+     {}
+    ]
+   ],
    "css/css-ui/animation/caret-color-interpolation.html": [
     [
      "css/css-ui/animation/caret-color-interpolation.html",
@@ -225396,12 +225594,24 @@
      {}
     ]
    ],
+   "css/css-ui/animation/outline-offset-composition.html": [
+    [
+     "css/css-ui/animation/outline-offset-composition.html",
+     {}
+    ]
+   ],
    "css/css-ui/animation/outline-offset-interpolation.html": [
     [
      "css/css-ui/animation/outline-offset-interpolation.html",
      {}
     ]
    ],
+   "css/css-ui/animation/outline-width-composition.html": [
+    [
+     "css/css-ui/animation/outline-width-composition.html",
+     {}
+    ]
+   ],
    "css/css-ui/animation/outline-width-interpolation.html": [
     [
      "css/css-ui/animation/outline-width-interpolation.html",
@@ -228478,12 +228688,24 @@
      {}
     ]
    ],
+   "css/motion/animation/offset-anchor-composition.html": [
+    [
+     "css/motion/animation/offset-anchor-composition.html",
+     {}
+    ]
+   ],
    "css/motion/animation/offset-anchor-interpolation.html": [
     [
      "css/motion/animation/offset-anchor-interpolation.html",
      {}
     ]
    ],
+   "css/motion/animation/offset-distance-composition.html": [
+    [
+     "css/motion/animation/offset-distance-composition.html",
+     {}
+    ]
+   ],
    "css/motion/animation/offset-distance-interpolation.html": [
     [
      "css/motion/animation/offset-distance-interpolation.html",
@@ -228496,6 +228718,12 @@
      {}
     ]
    ],
+   "css/motion/animation/offset-path-composition.html": [
+    [
+     "css/motion/animation/offset-path-composition.html",
+     {}
+    ]
+   ],
    "css/motion/animation/offset-path-interpolation-001.html": [
     [
      "css/motion/animation/offset-path-interpolation-001.html",
@@ -228526,12 +228754,24 @@
      {}
     ]
    ],
+   "css/motion/animation/offset-position-composition.html": [
+    [
+     "css/motion/animation/offset-position-composition.html",
+     {}
+    ]
+   ],
    "css/motion/animation/offset-position-interpolation.html": [
     [
      "css/motion/animation/offset-position-interpolation.html",
      {}
     ]
    ],
+   "css/motion/animation/offset-rotate-composition.html": [
+    [
+     "css/motion/animation/offset-rotate-composition.html",
+     {}
+    ]
+   ],
    "css/motion/animation/offset-rotate-interpolation.html": [
     [
      "css/motion/animation/offset-rotate-interpolation.html",
@@ -264350,14 +264590,6 @@
      {}
     ]
    ],
-   "layout-instability/observe-layout-shift.html": [
-    [
-     "layout-instability/observe-layout-shift.html",
-     {
-      "testdriver": true
-     }
-    ]
-   ],
    "layout-instability/partially-clipped-visual-rect.html": [
     [
      "layout-instability/partially-clipped-visual-rect.html",
@@ -264380,6 +264612,14 @@
      }
     ]
    ],
+   "layout-instability/recent-input.html": [
+    [
+     "layout-instability/recent-input.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "layout-instability/rtl-distance.html": [
     [
      "layout-instability/rtl-distance.html",
@@ -369247,10 +369487,18 @@
    "01fa028a7d933a5a7480efb28cf565e7b7de845c",
    "support"
   ],
+  "css/css-align/animation/column-gap-composition.html": [
+   "0054206cca8b3448ad5d19e055e9f435a6e123b1",
+   "testharness"
+  ],
   "css/css-align/animation/column-gap-interpolation.html": [
    "c2f02d3ba87f4dee555be80caa45d6909e2495ae",
    "testharness"
   ],
+  "css/css-align/animation/row-gap-composition.html": [
+   "238253adf0cda81aca80d9345219ac8cd8e0df50",
+   "testharness"
+  ],
   "css/css-align/animation/row-gap-interpolation.html": [
    "1d85ffa3b07e8695e7b3a6ad82c02ae4da8d7b21",
    "testharness"
@@ -370419,10 +370667,26 @@
    "f6a480c7bd2ccc4a6c46fa2eade5e7231fab4938",
    "testharness"
   ],
+  "css/css-backgrounds/animations/border-bottom-left-radius-composition.html": [
+   "87042d1969d59b6865761c60beaeba5219c6148c",
+   "testharness"
+  ],
+  "css/css-backgrounds/animations/border-bottom-right-radius-composition.html": [
+   "2b5a72df6914f571ca00418484d9f782cd46aa63",
+   "testharness"
+  ],
+  "css/css-backgrounds/animations/border-bottom-width-composition.html": [
+   "5377c0ab42b44623c1d7e0aa81344345db795f68",
+   "testharness"
+  ],
   "css/css-backgrounds/animations/border-color-interpolation.html": [
    "3e7843b8a07577970279ef9a4e14bfb83c1816f0",
    "testharness"
   ],
+  "css/css-backgrounds/animations/border-image-outset-composition.html": [
+   "e3311711753e34295eb3c7c83d5aee3d4cd684aa",
+   "testharness"
+  ],
   "css/css-backgrounds/animations/border-image-outset-interpolation-expected.txt": [
    "6ca6cb7701110980a1adbf8f5e1cde9edda2cba6",
    "support"
@@ -370431,6 +370695,10 @@
    "aebadbbafb236a090aa543ecf82e5661bee7de74",
    "testharness"
   ],
+  "css/css-backgrounds/animations/border-image-slice-composition.html": [
+   "d0ccb1a3a64f2b3fe75b4957dc0a75036c3cd392",
+   "testharness"
+  ],
   "css/css-backgrounds/animations/border-image-slice-interpolation-stability.html": [
    "26431334e4acf13f6c095868a0e9cee6926a9ef1",
    "testharness"
@@ -370447,14 +370715,38 @@
    "60dcfceddc791737487fab07ea7035fac40856d6",
    "testharness"
   ],
+  "css/css-backgrounds/animations/border-image-width-composition.html": [
+   "0d0a1dc4ed263c80b472e3ba241fffeaf771d6aa",
+   "testharness"
+  ],
   "css/css-backgrounds/animations/border-image-width-interpolation.html": [
    "ea138201b15805cd86d8a63ed37b401b103bfee2",
    "testharness"
   ],
+  "css/css-backgrounds/animations/border-left-width-composition.html": [
+   "1b90effbc32cd56e9120fec92158f518a67d6ae8",
+   "testharness"
+  ],
   "css/css-backgrounds/animations/border-radius-interpolation.html": [
    "195469e83164c965ee33b6277983870100bda111",
    "testharness"
   ],
+  "css/css-backgrounds/animations/border-right-width-composition.html": [
+   "aa9e1dcc66764c2ec8c072cbf9365529e3729d88",
+   "testharness"
+  ],
+  "css/css-backgrounds/animations/border-top-left-radius-composition.html": [
+   "1c2056bc010a82228416983897c63676ac3aeb0f",
+   "testharness"
+  ],
+  "css/css-backgrounds/animations/border-top-right-radius-composition.html": [
+   "9a26d51375e78bfe54ba7a3689f6b72fe6c36417",
+   "testharness"
+  ],
+  "css/css-backgrounds/animations/border-top-width-composition.html": [
+   "475c3930b9d1704267267ff30d747b266ea3d851",
+   "testharness"
+  ],
   "css/css-backgrounds/animations/border-width-interpolation.html": [
    "11f92f41652a9f1b167c5a399849d1e1e61c4df7",
    "testharness"
@@ -374215,14 +374507,46 @@
    "dde409360faf79a301c3ae3ea34a995d154d7bb4",
    "support"
   ],
+  "css/css-box/animation/margin-bottom-composition.html": [
+   "c95f8de23efe6e853dd4b05eed07af01c1d02af7",
+   "testharness"
+  ],
   "css/css-box/animation/margin-interpolation.html": [
    "088836cbbd18d0daf203de7dbacfb65733e35813",
    "testharness"
   ],
+  "css/css-box/animation/margin-left-composition.html": [
+   "8f3c646dfec219d30d2fefafdcb89c9e7cabb2b5",
+   "testharness"
+  ],
+  "css/css-box/animation/margin-right-composition.html": [
+   "c903303313bcc06ecf67f86efc55b21537e4af4c",
+   "testharness"
+  ],
+  "css/css-box/animation/margin-top-composition.html": [
+   "5f050bd6c7d9663025d53a04ecd2ec6352275fba",
+   "testharness"
+  ],
+  "css/css-box/animation/padding-bottom-composition.html": [
+   "855b5d3dc2948a9dc4ae391aaaa5caee06f88665",
+   "testharness"
+  ],
   "css/css-box/animation/padding-interpolation.html": [
    "3bf284117960fe78300e95140244d309f8f439a4",
    "testharness"
   ],
+  "css/css-box/animation/padding-left-composition.html": [
+   "417777ae253428cf6c852e6846960494a8ea53f7",
+   "testharness"
+  ],
+  "css/css-box/animation/padding-right-composition.html": [
+   "3c80849bb2bc68a68c098f42a1a29b9247e6a224",
+   "testharness"
+  ],
+  "css/css-box/animation/padding-top-composition.html": [
+   "b5083ae79b3db8a55f7af45373804d223cbbbc47",
+   "testharness"
+  ],
   "css/css-box/box-chrome-crash-001.html": [
    "351df37f1550ab40818b7f7f1c51191cfae5583e",
    "testharness"
@@ -402151,10 +402475,18 @@
    "edac744592f76704ba82b0c4a7e5a53c7db6ba79",
    "testharness"
   ],
+  "css/css-shapes/animation/shape-margin-composition.html": [
+   "395bad063f4c1bfb036d650d6b0319cd3d572fe2",
+   "testharness"
+  ],
   "css/css-shapes/animation/shape-margin-interpolation.html": [
    "48b3d0c460794b18261ce7a6beedf980d8335d36",
    "testharness"
   ],
+  "css/css-shapes/animation/shape-outside-composition.html": [
+   "0115148ec1adde1a32b1c1fb4b3c33ea8b56ece0",
+   "testharness"
+  ],
   "css/css-shapes/animation/shape-outside-interpolation.html": [
    "3380acdba00db8e9440b33c60275f6fd6340d345",
    "testharness"
@@ -403471,26 +403803,50 @@
    "086e654a8e039f259b5e828d024f808c2e95016b",
    "support"
   ],
+  "css/css-sizing/animation/height-composition.html": [
+   "094e247dcf22d9bd665b244993b6239265ee73bb",
+   "testharness"
+  ],
   "css/css-sizing/animation/height-interpolation.html": [
    "10ceed5b2cc0d5511b8020aeaced36be39834c3a",
    "testharness"
   ],
+  "css/css-sizing/animation/max-height-composition.html": [
+   "fb5b241d00865fe68c198a9fee88d932a8977f7e",
+   "testharness"
+  ],
   "css/css-sizing/animation/max-height-interpolation.html": [
    "c4cab0e1cf4534d3705801f3159b6b8724977b66",
    "testharness"
   ],
+  "css/css-sizing/animation/max-width-composition.html": [
+   "8b6d8b704c8771491419db0aa2a3c783a1dea2b3",
+   "testharness"
+  ],
   "css/css-sizing/animation/max-width-interpolation.html": [
    "111199baa7ed89c6023d43b56313413cc5aeeeeb",
    "testharness"
   ],
+  "css/css-sizing/animation/min-height-composition.html": [
+   "1e92b0ec2fd664e7b3dd6dc1cd8310c7b9526e7c",
+   "testharness"
+  ],
   "css/css-sizing/animation/min-height-interpolation.html": [
    "6fd5b4e2f5366f6b18678f60b982e82905558e51",
    "testharness"
   ],
+  "css/css-sizing/animation/min-width-composition.html": [
+   "e8bd41030bbd8a273f7e7c45f5f445d706d044eb",
+   "testharness"
+  ],
   "css/css-sizing/animation/min-width-interpolation.html": [
    "d11fb3d5cb139f870d1eb40618bf547176f109b1",
    "testharness"
   ],
+  "css/css-sizing/animation/width-composition.html": [
+   "bfe45cb31471d0c8623dbb9e84000bc1208bb76f",
+   "testharness"
+  ],
   "css/css-sizing/animation/width-interpolation.html": [
    "d165c994b5de6fe1561498aa04c075196357f5f6",
    "testharness"
@@ -406159,14 +406515,26 @@
    "9b2e5be0a19c48f73b57fe0ad8bbeea81238a1d1",
    "support"
   ],
+  "css/css-text/animations/letter-spacing-composition.html": [
+   "c1b614b5ebaa5bc190a080bd8da5694096a2be20",
+   "testharness"
+  ],
   "css/css-text/animations/letter-spacing-interpolation.html": [
    "7d4958113915913bb8a3a5af13f5cee90b080825",
    "testharness"
   ],
+  "css/css-text/animations/text-indent-composition.html": [
+   "57c528ca271646a284048bb49e11025ac25469da",
+   "testharness"
+  ],
   "css/css-text/animations/text-indent-interpolation.html": [
    "2269fdfa5b60f6f4a8d63a7f777e6abbf499c4b7",
    "testharness"
   ],
+  "css/css-text/animations/word-spacing-composition.html": [
+   "17aacaef1506eddbc8ef40ade114a856310a508e",
+   "testharness"
+  ],
   "css/css-text/animations/word-spacing-interpolation.html": [
    "ffd6bb476ad87976a1183cde70a10892bab77982",
    "testharness"
@@ -420583,6 +420951,10 @@
    "5f43f9116bc8073cfadd3bf840519a3ad96c296f",
    "support"
   ],
+  "css/css-ui/animation/caret-color-composition.html": [
+   "6c69578677896e2463331deba85731e13fd94a25",
+   "testharness"
+  ],
   "css/css-ui/animation/caret-color-interpolation.html": [
    "b3a4e30130843163d76a0a24196c66853bd4160a",
    "testharness"
@@ -420591,10 +420963,18 @@
    "f49aa79a382c8e5a8f4c9d834f5f12aea551818f",
    "testharness"
   ],
+  "css/css-ui/animation/outline-offset-composition.html": [
+   "984a63fdc34274fab133308dbb0b9a5c2eca03b9",
+   "testharness"
+  ],
   "css/css-ui/animation/outline-offset-interpolation.html": [
    "46c1c51c6eefaa490fc9d55e4cadfb0cb7804337",
    "testharness"
   ],
+  "css/css-ui/animation/outline-width-composition.html": [
+   "b770feda61ca6c74467c597749a053d3569af012",
+   "testharness"
+  ],
   "css/css-ui/animation/outline-width-interpolation.html": [
    "c024c7cf6a08e0f6e02ccb451ca04d0b4a8c9251",
    "testharness"
@@ -434195,10 +434575,18 @@
    "d10225e4dc5237552bb74812dc35edb193a481de",
    "support"
   ],
+  "css/motion/animation/offset-anchor-composition.html": [
+   "53210fdf38a86ec9f4e852a33527fc88b1eb9fe3",
+   "testharness"
+  ],
   "css/motion/animation/offset-anchor-interpolation.html": [
    "9c69c0f0bed2b14c02091ccf63f50ace61f2eb2a",
    "testharness"
   ],
+  "css/motion/animation/offset-distance-composition.html": [
+   "4ff6e95b05792505b2f98c511f5a80d64e86624d",
+   "testharness"
+  ],
   "css/motion/animation/offset-distance-interpolation.html": [
    "bc0c094a8e5305c970f0a4d44ee8a3726d2965a1",
    "testharness"
@@ -434207,6 +434595,10 @@
    "2ee011bd77a975e9b566d24658995693c574f620",
    "testharness"
   ],
+  "css/motion/animation/offset-path-composition.html": [
+   "eedd363efafe05b870314ad797d9dc63f2c7409c",
+   "testharness"
+  ],
   "css/motion/animation/offset-path-interpolation-001.html": [
    "5b90813bb591d0aa7e17eeddcb1a9e3a908670f0",
    "testharness"
@@ -434227,10 +434619,18 @@
    "9924106f4b75ede89e5270a76fe217f85ef20050",
    "testharness"
   ],
+  "css/motion/animation/offset-position-composition.html": [
+   "0ee517a73a8e9e673a83a0f81bbdea98d6a3d808",
+   "testharness"
+  ],
   "css/motion/animation/offset-position-interpolation.html": [
    "9faaf9487afb20059046e95a8cd5b3a796e8fd42",
    "testharness"
   ],
+  "css/motion/animation/offset-rotate-composition.html": [
+   "bf60c19abeffafa36c50f1d1da0e9f9d64b1a151",
+   "testharness"
+  ],
   "css/motion/animation/offset-rotate-interpolation.html": [
    "55845108ebf5f3c42a8b0532121199136160d695",
    "testharness"
@@ -475236,7 +475636,7 @@
    "support"
   ],
   "interfaces/webaudio.idl": [
-   "674673d90b1e8fd3e6d182f25289948fc0290cac",
+   "9491090337c342fc06a35a97ee6dc6887829c1bc",
    "support"
   ],
   "interfaces/webauthn.idl": [
@@ -475272,7 +475672,7 @@
    "support"
   ],
   "interfaces/webrtc.idl": [
-   "e30fc38fd003a283ebb4184786d181c5b4293dc4",
+   "49ed6bb0333020a6eee8d5411ae88a4e3a7dbd9a",
    "support"
   ],
   "interfaces/webusb.idl": [
@@ -475860,11 +476260,11 @@
    "testharness"
   ],
   "layout-instability/buffer-layout-shift.html": [
-   "0cfce2f7124226cf4256284c1238ba1ef024c42b",
+   "b6a33f579b4009d1497fdc104c07633793d53f01",
    "testharness"
   ],
   "layout-instability/buffered-flag.html": [
-   "dabc8068931ff3b15eb4b80481ed2102a7725a62",
+   "cd1260e3613f58c191688ab2f492647b0cc72c0c",
    "testharness"
   ],
   "layout-instability/clip-negative-bottom-margin.html": [
@@ -475895,10 +476295,6 @@
    "36475d4c826c11807e9c0a7fbf4457c33c92c2c0",
    "testharness"
   ],
-  "layout-instability/observe-layout-shift.html": [
-   "1c35fe2aa234c96fce8798e6a1c35362f418e6f1",
-   "testharness"
-  ],
   "layout-instability/partially-clipped-visual-rect.html": [
    "3b18b98dd93312c37b9e2f25918df50266a09243",
    "testharness"
@@ -475911,6 +476307,10 @@
    "e2e7a911dc043bb21cebfd4a5b625795f3523a14",
    "testharness"
   ],
+  "layout-instability/recent-input.html": [
+   "a4fa0d8b0d92a83984034926de30958b840c1028",
+   "testharness"
+  ],
   "layout-instability/resources/slow-image.py": [
    "ee7988c551f6429eea2b929af083ad30cbd5c73d",
    "support"
@@ -475948,7 +476348,7 @@
    "testharness"
   ],
   "layout-instability/toJSON.html": [
-   "3d39d623e13314b183463fa1c365df3a7b725243",
+   "374a7de0cd1c4d5d5b089b7d026c8eb5709e91f1",
    "testharness"
   ],
   "layout-instability/transform.html": [
@@ -525204,7 +525604,7 @@
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html": [
-   "0522c43b16a881a14b339294e5ed56b8ae064f92",
+   "a1f9e4f3acea04337dd0147fbfa373f950a67409",
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html": [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-bottom-left-radius-composition.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-bottom-left-radius-composition.html
new file mode 100644
index 0000000..87042d19
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-bottom-left-radius-composition.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>border-bottom-left-radius composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#the-border-radius">
+<meta name="assert" content="border-bottom-left-radius supports animation by computed value">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'border-bottom-left-radius',
+  underlying: '40px 40px',
+  addFrom: '60px 60px',
+  addTo: '160px 160px',
+}, [
+  {at: -0.25, expect: '75px'},
+  {at: 0, expect: '100px'},
+  {at: 0.25, expect: '125px'},
+  {at: 0.5, expect: '150px'},
+  {at: 0.75, expect: '175px'},
+  {at: 1, expect: '200px'},
+  {at: 1.25, expect: '225px'},
+]);
+
+test_composition({
+  property: 'border-bottom-left-radius',
+  underlying: '40px 140px',
+  replaceFrom: '100px 120px',
+  addTo: '160px 60px',
+}, [
+  {at: -0.25, expect: '75px 100px'},
+  {at: 0, expect: '100px 120px'},
+  {at: 0.25, expect: '125px 140px'},
+  {at: 0.5, expect: '150px 160px'},
+  {at: 0.75, expect: '175px 180px'},
+  {at: 1, expect: '200px'},
+  {at: 1.25, expect: '225px 220px'},
+]);
+
+test_composition({
+  property: 'border-bottom-left-radius',
+  underlying: '40px 60px',
+  addFrom: '60px 140px',
+  replaceTo: '200px 120px',
+}, [
+  {at: -0.25, expect: '75px 220px'},
+  {at: 0, expect: '100px 200px'},
+  {at: 0.25, expect: '125px 180px'},
+  {at: 0.5, expect: '150px 160px'},
+  {at: 0.75, expect: '175px 140px'},
+  {at: 1, expect: '200px 120px'},
+  {at: 1.25, expect: '225px 100px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-bottom-right-radius-composition.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-bottom-right-radius-composition.html
new file mode 100644
index 0000000..2b5a72d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-bottom-right-radius-composition.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>border-bottom-right-radius composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#the-border-radius">
+<meta name="assert" content="border-bottom-right-radius supports animation by computed value">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'border-bottom-right-radius',
+  underlying: '40px 40px',
+  addFrom: '60px 60px',
+  addTo: '160px 160px',
+}, [
+  {at: -0.25, expect: '75px'},
+  {at: 0, expect: '100px'},
+  {at: 0.25, expect: '125px'},
+  {at: 0.5, expect: '150px'},
+  {at: 0.75, expect: '175px'},
+  {at: 1, expect: '200px'},
+  {at: 1.25, expect: '225px'},
+]);
+
+test_composition({
+  property: 'border-bottom-right-radius',
+  underlying: '40px 140px',
+  replaceFrom: '100px 120px',
+  addTo: '160px 60px',
+}, [
+  {at: -0.25, expect: '75px 100px'},
+  {at: 0, expect: '100px 120px'},
+  {at: 0.25, expect: '125px 140px'},
+  {at: 0.5, expect: '150px 160px'},
+  {at: 0.75, expect: '175px 180px'},
+  {at: 1, expect: '200px'},
+  {at: 1.25, expect: '225px 220px'},
+]);
+
+test_composition({
+  property: 'border-bottom-right-radius',
+  underlying: '40px 60px',
+  addFrom: '60px 140px',
+  replaceTo: '200px 120px',
+}, [
+  {at: -0.25, expect: '75px 220px'},
+  {at: 0, expect: '100px 200px'},
+  {at: 0.25, expect: '125px 180px'},
+  {at: 0.5, expect: '150px 160px'},
+  {at: 0.75, expect: '175px 140px'},
+  {at: 1, expect: '200px 120px'},
+  {at: 1.25, expect: '225px 100px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-bottom-width-composition.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-bottom-width-composition.html
new file mode 100644
index 0000000..5377c0a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-bottom-width-composition.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>border-bottom-width composition</title>
+<link rel="help" href="https://www.w3.org/TR/CSS2/box.html#border-width-properties">
+<meta name="assert" content="border-bottom-width supports animation by computed value">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'border-bottom-width',
+  underlying: '50px',
+  addFrom: '100px',
+  addTo: '200px',
+}, [
+  {at: -0.3, expect: '120px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '200px'},
+  {at: 1, expect: '250px'},
+  {at: 1.5, expect: '300px'},
+]);
+
+test_composition({
+  property: 'border-bottom-width',
+  underlying: '100px',
+  addFrom: '10px',
+  addTo: '2px',
+}, [
+  {at: -0.5, expect: '114px'},
+  {at: 0, expect: '110px'},
+  {at: 0.5, expect: '106px'},
+  {at: 1, expect: '102px'},
+  {at: 1.5, expect: '98px'}, // Value clamping should happen after composition.
+]);
+
+test_composition({
+  property: 'border-bottom-width',
+  underlying: '10em',
+  addFrom: '100px',
+  addTo: '20em',
+}, [
+  {at: -0.3, expect: 'calc(130px + 4em)'},
+  {at: 0, expect: 'calc(100px + 10em)'},
+  {at: 0.5, expect: 'calc(50px + 20em)'},
+  {at: 1, expect: '30em'},
+  {at: 1.5, expect: 'calc(-50px + 40em)'},
+]);
+
+test_composition({
+  property: 'border-bottom-width',
+  underlying: '50px',
+  addFrom: '100px',
+  replaceTo: '200px',
+}, [
+  {at: -0.3, expect: '135px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '175px'},
+  {at: 1, expect: '200px'},
+  {at: 1.5, expect: '225px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-image-outset-composition.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-image-outset-composition.html
new file mode 100644
index 0000000..e331171
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-image-outset-composition.html
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>border-image-outset composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-image-outset">
+<meta name="assert" content="border-image-outset supports animation by computed value">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'border-image-outset',
+  underlying: '1 2 3 4',
+  addFrom: '1 2 3 4',
+  addTo: '101 102 103 104',
+}, [
+  {at: -0.25, expect: '0'}, // Non-negative.
+  {at: 0, expect: '2 4 6 8'},
+  {at: 0.25, expect: '27 29 31 33'},
+  {at: 0.5, expect: '52 54 56 58'},
+  {at: 0.75, expect: '77 79 81 83'},
+  {at: 1, expect: '102 104 106 108'},
+  {at: 1.25, expect: '127 129 131 133'},
+]);
+
+test_composition({
+  property: 'border-image-outset',
+  underlying: '100 200 300 400',
+  addFrom: '100',
+  addTo: '200 300 500',
+}, [
+  {at: -0.25, expect: '175 250 300 450'},
+  {at: 0, expect: '200 300 400 500'},
+  {at: 0.25, expect: '225 350 500 550'},
+  {at: 0.5, expect: '250 400 600 600'},
+  {at: 0.75, expect: '275 450 700 650'},
+  {at: 1, expect: '300 500 800 700'},
+  {at: 1.25, expect: '325 550 900 750'},
+]);
+
+test_composition({
+  property: 'border-image-outset',
+  underlying: '1 2 3px 4px',
+  addFrom: '1 2 3px 4px',
+  addTo: '101 102 103px 104px',
+}, [
+  {at: -0.25, expect: '0 0 0px 0px'}, // Non-negative.
+  {at: 0, expect: '2 4 6px 8px'},
+  {at: 0.25, expect: '27 29 31px 33px'},
+  {at: 0.5, expect: '52 54 56px 58px'},
+  {at: 0.75, expect: '77 79 81px 83px'},
+  {at: 1, expect: '102 104 106px 108px'},
+  {at: 1.25, expect: '127 129 131px 133px'},
+]);
+
+test_composition({
+  property: 'border-image-outset',
+  underlying: '10px 20px',
+  addFrom: '190px 180px 290px 280px',
+  addTo: '90px 80px',
+}, [
+  {at: -0.25, expect: '225px 225px 350px 350px'},
+  {at: 0, expect: '200px 200px 300px 300px'},
+  {at: 0.25, expect: '175px 175px 250px 250px'},
+  {at: 0.5, expect: '150px 150px 200px 200px'},
+  {at: 0.75, expect: '125px 125px 150px 150px'},
+  {at: 1, expect: '100px'},
+  {at: 1.25, expect: '75px 75px 50px 50px'},
+]);
+
+test_composition({
+  property: 'border-image-outset',
+  underlying: '10 20px',
+  replaceFrom: '100 100px',
+  addTo: '190 180px',
+}, [
+  {at: -0.25, expect: '75 75px'},
+  {at: 0, expect: '100 100px'},
+  {at: 0.25, expect: '125 125px'},
+  {at: 0.5, expect: '150 150px'},
+  {at: 0.75, expect: '175 175px'},
+  {at: 1, expect: '200 200px'},
+  {at: 1.25, expect: '225 225px'},
+]);
+
+test_composition({
+  property: 'border-image-outset',
+  underlying: '10px 20',
+  addFrom: '90px 80',
+  replaceTo: '0px 0 0px 0',
+}, [
+  {at: -0.25, expect: '125px 125'},
+  {at: 0, expect: '100px 100'},
+  {at: 0.25, expect: '75px 75'},
+  {at: 0.5, expect: '50px 50'},
+  {at: 0.75, expect: '25px 25'},
+  {at: 1, expect: '0px 0'},
+  {at: 1.25, expect: '0px 0'}, // Non-negative.
+]);
+
+test_composition({
+  property: 'border-image-outset',
+  underlying: '10 20',
+  addFrom: '100px 150px',
+  addTo: '200px 250px',
+}, [
+  {at: -0.25, expect: '75px 125px'},
+  {at: 0, expect: '100px 150px'},
+  {at: 0.25, expect: '125px 175px'},
+  {at: 0.5, expect: '150px 200px'},
+  {at: 0.75, expect: '175px 225px'},
+  {at: 1, expect: '200px 250px'},
+  {at: 1.25, expect: '225px 275px'},
+]);
+
+test_composition({
+  property: 'border-image-outset',
+  underlying: '10 20',
+  addFrom: '100 150px',
+  addTo: '200px 250',
+}, [
+  {at: -0.25, expect: '100 150px'},
+  {at: 0, expect: '100 150px'},
+  {at: 0.25, expect: '100 150px'},
+  {at: 0.5, expect: '200px 250'},
+  {at: 0.75, expect: '200px 250'},
+  {at: 1, expect: '200px 250'},
+  {at: 1.25, expect: '200px 250'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-image-slice-composition.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-image-slice-composition.html
new file mode 100644
index 0000000..d0ccb1a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-image-slice-composition.html
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>border-image-slice composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-image-slice">
+<meta name="assert" content="border-image-slice supports animation by computed value">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'border-image-slice',
+  underlying: '1 2 3 4',
+  addFrom: '1 2 3 4',
+  addTo: '101 102 103 104',
+}, [
+  {at: -0.25, expect: '0'}, // Non-negative.
+  {at: 0, expect: '2 4 6 8'},
+  {at: 0.25, expect: '27 29 31 33'},
+  {at: 0.5, expect: '52 54 56 58'},
+  {at: 0.75, expect: '77 79 81 83'},
+  {at: 1, expect: '102 104 106 108'},
+  {at: 1.25, expect: '127 129 131 133'},
+]);
+
+test_composition({
+  property: 'border-image-slice',
+  underlying: '100 200 300 400 fill',
+  addFrom: '100 fill',
+  addTo: '200 300 500 fill',
+}, [
+  {at: -0.25, expect: '175 250 300 450 fill'},
+  {at: 0, expect: '200 300 400 500 fill'},
+  {at: 0.25, expect: '225 350 500 550 fill'},
+  {at: 0.5, expect: '250 400 600 600 fill'},
+  {at: 0.75, expect: '275 450 700 650 fill'},
+  {at: 1, expect: '300 500 800 700 fill'},
+  {at: 1.25, expect: '325 550 900 750 fill'},
+]);
+
+test_composition({
+  property: 'border-image-slice',
+  underlying: '1 2 3% 4%',
+  addFrom: '1 2 3% 4%',
+  addTo: '101 102 103% 104%',
+}, [
+  {at: -0.25, expect: '0 0 0% 0%'}, // Non-negative.
+  {at: 0, expect: '2 4 6% 8%'},
+  {at: 0.25, expect: '27 29 31% 33%'},
+  {at: 0.5, expect: '52 54 56% 58%'},
+  {at: 0.75, expect: '77 79 81% 83%'},
+  {at: 1, expect: '102 104 106% 108%'},
+  {at: 1.25, expect: '127 129 131% 133%'},
+]);
+
+test_composition({
+  property: 'border-image-slice',
+  underlying: '10% 20%',
+  addFrom: '190% 180% 290% 280%',
+  addTo: '90% 80%',
+}, [
+  {at: -0.25, expect: '225% 225% 350% 350%'},
+  {at: 0, expect: '200% 200% 300% 300%'},
+  {at: 0.25, expect: '175% 175% 250% 250%'},
+  {at: 0.5, expect: '150% 150% 200% 200%'},
+  {at: 0.75, expect: '125% 125% 150% 150%'},
+  {at: 1, expect: '100%'},
+  {at: 1.25, expect: '75% 75% 50% 50%'},
+]);
+
+test_composition({
+  property: 'border-image-slice',
+  underlying: '10 20%',
+  replaceFrom: '100 100%',
+  addTo: '190 180%',
+}, [
+  {at: -0.25, expect: '75 75%'},
+  {at: 0, expect: '100 100%'},
+  {at: 0.25, expect: '125 125%'},
+  {at: 0.5, expect: '150 150%'},
+  {at: 0.75, expect: '175 175%'},
+  {at: 1, expect: '200 200%'},
+  {at: 1.25, expect: '225 225%'},
+]);
+
+test_composition({
+  property: 'border-image-slice',
+  underlying: '10% 20',
+  addFrom: '90% 80',
+  replaceTo: '0% 0 0% 0',
+}, [
+  {at: -0.25, expect: '125% 125'},
+  {at: 0, expect: '100% 100'},
+  {at: 0.25, expect: '75% 75'},
+  {at: 0.5, expect: '50% 50'},
+  {at: 0.75, expect: '25% 25'},
+  {at: 1, expect: '0% 0'},
+  {at: 1.25, expect: '0% 0'}, // Non-negative.
+]);
+
+test_composition({
+  property: 'border-image-slice',
+  underlying: '10 20',
+  addFrom: '100% 150%',
+  addTo: '200% 250% fill',
+}, [
+  {at: -0.25, expect: '100% 150%'},
+  {at: 0, expect: '100% 150%'},
+  {at: 0.25, expect: '100% 150%'},
+  {at: 0.5, expect: '200% 250% fill'},
+  {at: 0.75, expect: '200% 250% fill'},
+  {at: 1, expect: '200% 250% fill'},
+  {at: 1.25, expect: '200% 250% fill'},
+]);
+
+test_composition({
+  property: 'border-image-slice',
+  underlying: '10 20',
+  addFrom: '100 150%',
+  addTo: '200% 250',
+}, [
+  {at: -0.25, expect: '100 150%'},
+  {at: 0, expect: '100 150%'},
+  {at: 0.25, expect: '100 150%'},
+  {at: 0.5, expect: '200% 250'},
+  {at: 0.75, expect: '200% 250'},
+  {at: 1, expect: '200% 250'},
+  {at: 1.25, expect: '200% 250'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-image-width-composition.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-image-width-composition.html
new file mode 100644
index 0000000..0d0a1dc4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-image-width-composition.html
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>border-image-width composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-image-width">
+<meta name="assert" content="border-image-width supports animation by computed value">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'border-image-width',
+  underlying: '1 2 3 4',
+  addFrom: '1 2 3 4',
+  addTo: '101 102 103 104',
+}, [
+  {at: -0.25, expect: '0'}, // Non-negative.
+  {at: 0, expect: '2 4 6 8'},
+  {at: 0.25, expect: '27 29 31 33'},
+  {at: 0.5, expect: '52 54 56 58'},
+  {at: 0.75, expect: '77 79 81 83'},
+  {at: 1, expect: '102 104 106 108'},
+  {at: 1.25, expect: '127 129 131 133'},
+]);
+
+test_composition({
+  property: 'border-image-width',
+  underlying: '100 200 300 400',
+  addFrom: '100',
+  addTo: '200 300 500',
+}, [
+  {at: -0.25, expect: '175 250 300 450'},
+  {at: 0, expect: '200 300 400 500'},
+  {at: 0.25, expect: '225 350 500 550'},
+  {at: 0.5, expect: '250 400 600 600'},
+  {at: 0.75, expect: '275 450 700 650'},
+  {at: 1, expect: '300 500 800 700'},
+  {at: 1.25, expect: '325 550 900 750'},
+]);
+
+test_composition({
+  property: 'border-image-width',
+  underlying: '1 2 3px 4%',
+  addFrom: '1 2 3px 4%',
+  addTo: '101 102 103px 104%',
+}, [
+  {at: -0.25, expect: '0 0 0px 0%'}, // Non-negative.
+  {at: 0, expect: '2 4 6px 8%'},
+  {at: 0.25, expect: '27 29 31px 33%'},
+  {at: 0.5, expect: '52 54 56px 58%'},
+  {at: 0.75, expect: '77 79 81px 83%'},
+  {at: 1, expect: '102 104 106px 108%'},
+  {at: 1.25, expect: '127 129 131px 133%'},
+]);
+
+test_composition({
+  property: 'border-image-width',
+  underlying: '10px 20px',
+  addFrom: '190px 180px 290px 280px',
+  addTo: '90px 80px',
+}, [
+  {at: -0.25, expect: '225px 225px 350px 350px'},
+  {at: 0, expect: '200px 200px 300px 300px'},
+  {at: 0.25, expect: '175px 175px 250px 250px'},
+  {at: 0.5, expect: '150px 150px 200px 200px'},
+  {at: 0.75, expect: '125px 125px 150px 150px'},
+  {at: 1, expect: '100px'},
+  {at: 1.25, expect: '75px 75px 50px 50px'},
+]);
+
+test_composition({
+  property: 'border-image-width',
+  underlying: '10 20px',
+  replaceFrom: '100 100px',
+  addTo: '190 180px',
+}, [
+  {at: -0.25, expect: '75 75px'},
+  {at: 0, expect: '100 100px'},
+  {at: 0.25, expect: '125 125px'},
+  {at: 0.5, expect: '150 150px'},
+  {at: 0.75, expect: '175 175px'},
+  {at: 1, expect: '200 200px'},
+  {at: 1.25, expect: '225 225px'},
+]);
+
+test_composition({
+  property: 'border-image-width',
+  underlying: '10px 20',
+  addFrom: '90px 80',
+  replaceTo: '0px 0 0px 0',
+}, [
+  {at: -0.25, expect: '125px 125'},
+  {at: 0, expect: '100px 100'},
+  {at: 0.25, expect: '75px 75'},
+  {at: 0.5, expect: '50px 50'},
+  {at: 0.75, expect: '25px 25'},
+  {at: 1, expect: '0px 0'},
+  {at: 1.25, expect: '0px 0'}, // Non-negative.
+]);
+
+test_composition({
+  property: 'border-image-width',
+  underlying: '10 20',
+  addFrom: '100px 150px',
+  addTo: '200px 250px',
+}, [
+  {at: -0.25, expect: '75px 125px'},
+  {at: 0, expect: '100px 150px'},
+  {at: 0.25, expect: '125px 175px'},
+  {at: 0.5, expect: '150px 200px'},
+  {at: 0.75, expect: '175px 225px'},
+  {at: 1, expect: '200px 250px'},
+  {at: 1.25, expect: '225px 275px'},
+]);
+
+test_composition({
+  property: 'border-image-width',
+  underlying: '10 20',
+  addFrom: '100 150px',
+  addTo: '200% 250',
+}, [
+  {at: -0.25, expect: '100 150px'},
+  {at: 0, expect: '100 150px'},
+  {at: 0.25, expect: '100 150px'},
+  {at: 0.5, expect: '200% 250'},
+  {at: 0.75, expect: '200% 250'},
+  {at: 1, expect: '200% 250'},
+  {at: 1.25, expect: '200% 250'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-left-width-composition.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-left-width-composition.html
new file mode 100644
index 0000000..1b90eff
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-left-width-composition.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>border-left-width composition</title>
+<link rel="help" href="https://www.w3.org/TR/CSS2/box.html#border-width-properties">
+<meta name="assert" content="border-left-width supports animation by computed value">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'border-left-width',
+  underlying: '50px',
+  addFrom: '100px',
+  addTo: '200px',
+}, [
+  {at: -0.3, expect: '120px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '200px'},
+  {at: 1, expect: '250px'},
+  {at: 1.5, expect: '300px'},
+]);
+
+test_composition({
+  property: 'border-left-width',
+  underlying: '100px',
+  addFrom: '10px',
+  addTo: '2px',
+}, [
+  {at: -0.5, expect: '114px'},
+  {at: 0, expect: '110px'},
+  {at: 0.5, expect: '106px'},
+  {at: 1, expect: '102px'},
+  {at: 1.5, expect: '98px'}, // Value clamping should happen after composition.
+]);
+
+test_composition({
+  property: 'border-left-width',
+  underlying: '10em',
+  addFrom: '100px',
+  addTo: '20em',
+}, [
+  {at: -0.3, expect: 'calc(130px + 4em)'},
+  {at: 0, expect: 'calc(100px + 10em)'},
+  {at: 0.5, expect: 'calc(50px + 20em)'},
+  {at: 1, expect: '30em'},
+  {at: 1.5, expect: 'calc(-50px + 40em)'},
+]);
+
+test_composition({
+  property: 'border-left-width',
+  underlying: '50px',
+  addFrom: '100px',
+  replaceTo: '200px',
+}, [
+  {at: -0.3, expect: '135px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '175px'},
+  {at: 1, expect: '200px'},
+  {at: 1.5, expect: '225px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-right-width-composition.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-right-width-composition.html
new file mode 100644
index 0000000..aa9e1dcc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-right-width-composition.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>border-right-width composition</title>
+<link rel="help" href="https://www.w3.org/TR/CSS2/box.html#border-width-properties">
+<meta name="assert" content="border-right-width supports animation by computed value">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'border-right-width',
+  underlying: '50px',
+  addFrom: '100px',
+  addTo: '200px',
+}, [
+  {at: -0.3, expect: '120px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '200px'},
+  {at: 1, expect: '250px'},
+  {at: 1.5, expect: '300px'},
+]);
+
+test_composition({
+  property: 'border-right-width',
+  underlying: '100px',
+  addFrom: '10px',
+  addTo: '2px',
+}, [
+  {at: -0.5, expect: '114px'},
+  {at: 0, expect: '110px'},
+  {at: 0.5, expect: '106px'},
+  {at: 1, expect: '102px'},
+  {at: 1.5, expect: '98px'}, // Value clamping should happen after composition.
+]);
+
+test_composition({
+  property: 'border-right-width',
+  underlying: '10em',
+  addFrom: '100px',
+  addTo: '20em',
+}, [
+  {at: -0.3, expect: 'calc(130px + 4em)'},
+  {at: 0, expect: 'calc(100px + 10em)'},
+  {at: 0.5, expect: 'calc(50px + 20em)'},
+  {at: 1, expect: '30em'},
+  {at: 1.5, expect: 'calc(-50px + 40em)'},
+]);
+
+test_composition({
+  property: 'border-right-width',
+  underlying: '50px',
+  addFrom: '100px',
+  replaceTo: '200px',
+}, [
+  {at: -0.3, expect: '135px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '175px'},
+  {at: 1, expect: '200px'},
+  {at: 1.5, expect: '225px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-top-left-radius-composition.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-top-left-radius-composition.html
new file mode 100644
index 0000000..1c2056b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-top-left-radius-composition.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>border-top-left-radius composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#the-border-radius">
+<meta name="assert" content="border-top-left-radius supports animation by computed value">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'border-top-left-radius',
+  underlying: '40px 40px',
+  addFrom: '60px 60px',
+  addTo: '160px 160px',
+}, [
+  {at: -0.25, expect: '75px'},
+  {at: 0, expect: '100px'},
+  {at: 0.25, expect: '125px'},
+  {at: 0.5, expect: '150px'},
+  {at: 0.75, expect: '175px'},
+  {at: 1, expect: '200px'},
+  {at: 1.25, expect: '225px'},
+]);
+
+test_composition({
+  property: 'border-top-left-radius',
+  underlying: '40px 140px',
+  replaceFrom: '100px 120px',
+  addTo: '160px 60px',
+}, [
+  {at: -0.25, expect: '75px 100px'},
+  {at: 0, expect: '100px 120px'},
+  {at: 0.25, expect: '125px 140px'},
+  {at: 0.5, expect: '150px 160px'},
+  {at: 0.75, expect: '175px 180px'},
+  {at: 1, expect: '200px'},
+  {at: 1.25, expect: '225px 220px'},
+]);
+
+test_composition({
+  property: 'border-top-left-radius',
+  underlying: '40px 60px',
+  addFrom: '60px 140px',
+  replaceTo: '200px 120px',
+}, [
+  {at: -0.25, expect: '75px 220px'},
+  {at: 0, expect: '100px 200px'},
+  {at: 0.25, expect: '125px 180px'},
+  {at: 0.5, expect: '150px 160px'},
+  {at: 0.75, expect: '175px 140px'},
+  {at: 1, expect: '200px 120px'},
+  {at: 1.25, expect: '225px 100px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-top-right-radius-composition.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-top-right-radius-composition.html
new file mode 100644
index 0000000..9a26d513
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-top-right-radius-composition.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>border-top-right-radius composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#the-border-radius">
+<meta name="assert" content="border-top-right-radius supports animation by computed value">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'border-top-right-radius',
+  underlying: '40px 40px',
+  addFrom: '60px 60px',
+  addTo: '160px 160px',
+}, [
+  {at: -0.25, expect: '75px'},
+  {at: 0, expect: '100px'},
+  {at: 0.25, expect: '125px'},
+  {at: 0.5, expect: '150px'},
+  {at: 0.75, expect: '175px'},
+  {at: 1, expect: '200px'},
+  {at: 1.25, expect: '225px'},
+]);
+
+test_composition({
+  property: 'border-top-right-radius',
+  underlying: '40px 140px',
+  replaceFrom: '100px 120px',
+  addTo: '160px 60px',
+}, [
+  {at: -0.25, expect: '75px 100px'},
+  {at: 0, expect: '100px 120px'},
+  {at: 0.25, expect: '125px 140px'},
+  {at: 0.5, expect: '150px 160px'},
+  {at: 0.75, expect: '175px 180px'},
+  {at: 1, expect: '200px'},
+  {at: 1.25, expect: '225px 220px'},
+]);
+
+test_composition({
+  property: 'border-top-right-radius',
+  underlying: '40px 60px',
+  addFrom: '60px 140px',
+  replaceTo: '200px 120px',
+}, [
+  {at: -0.25, expect: '75px 220px'},
+  {at: 0, expect: '100px 200px'},
+  {at: 0.25, expect: '125px 180px'},
+  {at: 0.5, expect: '150px 160px'},
+  {at: 0.75, expect: '175px 140px'},
+  {at: 1, expect: '200px 120px'},
+  {at: 1.25, expect: '225px 100px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-top-width-composition.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-top-width-composition.html
new file mode 100644
index 0000000..475c393
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/animations/border-top-width-composition.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>border-top-width composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<meta name="assert" content="border-top-width supports animation by computed value">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'border-top-width',
+  underlying: '50px',
+  addFrom: '100px',
+  addTo: '200px',
+}, [
+  {at: -0.3, expect: '120px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '200px'},
+  {at: 1, expect: '250px'},
+  {at: 1.5, expect: '300px'},
+]);
+
+test_composition({
+  property: 'border-top-width',
+  underlying: '100px',
+  addFrom: '10px',
+  addTo: '2px',
+}, [
+  {at: -0.5, expect: '114px'},
+  {at: 0, expect: '110px'},
+  {at: 0.5, expect: '106px'},
+  {at: 1, expect: '102px'},
+  {at: 1.5, expect: '98px'}, // Value clamping should happen after composition.
+]);
+
+test_composition({
+  property: 'border-top-width',
+  underlying: '10em',
+  addFrom: '100px',
+  addTo: '20em',
+}, [
+  {at: -0.3, expect: 'calc(130px + 4em)'},
+  {at: 0, expect: 'calc(100px + 10em)'},
+  {at: 0.5, expect: 'calc(50px + 20em)'},
+  {at: 1, expect: '30em'},
+  {at: 1.5, expect: 'calc(-50px + 40em)'},
+]);
+
+test_composition({
+  property: 'border-top-width',
+  underlying: '50px',
+  addFrom: '100px',
+  replaceTo: '200px',
+}, [
+  {at: -0.3, expect: '135px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '175px'},
+  {at: 1, expect: '200px'},
+  {at: 1.5, expect: '225px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/animations/letter-spacing-composition.html b/third_party/blink/web_tests/external/wpt/css/css-text/animations/letter-spacing-composition.html
new file mode 100644
index 0000000..c1b614b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/animations/letter-spacing-composition.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>letter-spacing composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#letter-spacing-property">
+<meta name="test" content="letter-spacing supports animation by computed value type">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script src="../interpolation/resources/interpolation-test.js"></script>
+<script>
+test_composition({
+  property: 'letter-spacing',
+  underlying: '50px',
+  addFrom: '100px',
+  addTo: '200px',
+}, [
+  {at: -0.3, expect: '120px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '200px'},
+  {at: 1, expect: '250px'},
+  {at: 1.5, expect: '300px'},
+]);
+
+test_composition({
+  property: 'letter-spacing',
+  underlying: '100px',
+  addFrom: '10px',
+  addTo: '2px',
+}, [
+  {at: -0.5, expect: '114px'},
+  {at: 0, expect: '110px'},
+  {at: 0.5, expect: '106px'},
+  {at: 1, expect: '102px'},
+  {at: 1.5, expect: '98px'},
+]);
+
+test_composition({
+  property: 'letter-spacing',
+  underlying: '50px',
+  addFrom: '100px',
+  replaceTo: '200px',
+}, [
+  {at: -0.3, expect: '135px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '175px'},
+  {at: 1, expect: '200px'},
+  {at: 1.5, expect: '225px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/animations/text-indent-composition.html b/third_party/blink/web_tests/external/wpt/css/css-text/animations/text-indent-composition.html
new file mode 100644
index 0000000..57c528c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/animations/text-indent-composition.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>letter-spacing composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#text-indent-property">
+<meta name="test" content="text-indent supports animation by computed value type">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body></body>
+<script>
+test_composition({
+  property: 'text-indent',
+  underlying: '100%',
+  addFrom: '50px',
+  addTo: '150px',
+}, [
+  {at: -0.3, expect: 'calc(100% + 20px)'},
+  {at: 0, expect: 'calc(100% + 50px)'},
+  {at: 0.3, expect: 'calc(100% + 80px)'},
+  {at: 0.6, expect: 'calc(100% + 110px)'},
+  {at: 1, expect: 'calc(100% + 150px)'},
+  {at: 1.5, expect: 'calc(100% + 200px)'},
+]);
+
+test_composition({
+  property: 'text-indent',
+  underlying: '250px',
+  addFrom: '50px',
+  replaceTo: '100px',
+}, [
+  {at: -0.3, expect: '360px'},
+  {at: 0, expect: '300px'},
+  {at: 0.3, expect: '240px'},
+  {at: 0.6, expect: '180px'},
+  {at: 1, expect: '100px'},
+  {at: 1.5, expect: '0px'},
+]);
+
+test_composition({
+  property: 'text-indent',
+  underlying: '50%',
+  replaceFrom: '-100%',
+  addTo: '50%',
+}, [
+  {at: -0.3, expect: '-160%'},
+  {at: 0, expect: '-100%'},
+  {at: 0.3, expect: '-40%'},
+  {at: 0.5, expect: '0%'},
+  {at: 0.6, expect: '20%'},
+  {at: 1, expect: '100%'},
+  {at: 1.5, expect: '200%'},
+]);
+
+test_composition({
+  property: 'text-indent',
+  underlying: '250px',
+  addFrom: '50px each-line hanging',
+  replaceTo: '150px hanging each-line',
+}, [
+  {at: -0.3, expect: '20px hanging each-line'},
+  {at: 0, expect: '50px hanging each-line'},
+  {at: 0.3, expect: '80px hanging each-line'},
+  {at: 0.6, expect: '110px hanging each-line'},
+  {at: 1, expect: '150px hanging each-line'},
+  {at: 1.5, expect: '200px hanging each-line'},
+]);
+
+test_composition({
+  property: 'text-indent',
+  underlying: '250px each-line',
+  addFrom: '50px each-line',
+  replaceTo: '150px hanging',
+}, [
+  {at: -0.3, expect: '300px each-line'},
+  {at: 0, expect: '300px each-line'},
+  {at: 0.3, expect: '300px each-line'},
+  {at: 0.6, expect: '150px hanging'},
+  {at: 1, expect: '150px hanging'},
+  {at: 1.5, expect: '150px hanging'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/animations/word-spacing-composition.html b/third_party/blink/web_tests/external/wpt/css/css-text/animations/word-spacing-composition.html
new file mode 100644
index 0000000..17aacae
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/animations/word-spacing-composition.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>word-spacing composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#word-spacing-property">
+<meta name="test" content="word-spacing supports animation by computed value type">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'word-spacing',
+  underlying: '50px',
+  addFrom: '100px',
+  addTo: '200px',
+}, [
+  {at: -0.3, expect: '120px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '200px'},
+  {at: 1, expect: '250px'},
+  {at: 1.5, expect: '300px'},
+]);
+
+test_composition({
+  property: 'word-spacing',
+  underlying: '100px',
+  addFrom: '10px',
+  addTo: '2px',
+}, [
+  {at: -0.5, expect: '114px'},
+  {at: 0, expect: '110px'},
+  {at: 0.5, expect: '106px'},
+  {at: 1, expect: '102px'},
+  {at: 1.5, expect: '98px'},
+]);
+
+test_composition({
+  property: 'word-spacing',
+  underlying: '50px',
+  addFrom: '100px',
+  replaceTo: '200px',
+}, [
+  {at: -0.3, expect: '135px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '175px'},
+  {at: 1, expect: '200px'},
+  {at: 1.5, expect: '225px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-anchor-composition.html b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-anchor-composition.html
new file mode 100644
index 0000000..53210fd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-anchor-composition.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>offset-anchor composition</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-anchor-property">
+<meta name="assert" content="offset-anchor supports animation.">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<style>
+.target {
+  width: 200px;
+  height: 200px;
+}
+</style>
+<body>
+<script>
+test_composition({
+  property: 'offset-anchor',
+  underlying: '40px 60px',
+  addFrom: '60px 40px',
+  addTo: '160px 140px',
+}, [
+  {at: -0.25, expect: '75px 75px'},
+  {at: 0, expect: '100px 100px'},
+  {at: 0.25, expect: '125px 125px'},
+  {at: 0.5, expect: '150px 150px'},
+  {at: 0.75, expect: '175px 175px'},
+  {at: 1, expect: '200px 200px'},
+  {at: 1.25, expect: '225px 225px'},
+]);
+
+test_composition({
+  property: 'offset-anchor',
+  underlying: 'top 20% left 40%',
+  addFrom: 'left 60% top 80%',
+  addTo: 'right 80% bottom 40%',
+}, [
+  {at: -0.25, expect: '110% 105%'},
+  {at: 0, expect: '100% 100%'},
+  {at: 0.25, expect: '90% 95%'},
+  {at: 0.5, expect: '80% 90%'},
+  {at: 0.75, expect: '70% 85%'},
+  {at: 1, expect: '60% 80%'},
+  {at: 1.25, expect: '50% 75%'},
+]);
+
+test_composition({
+  property: 'offset-anchor',
+  underlying: '40px 60px',
+  replaceFrom: '100px 200px',
+  addTo: '160px 40px',
+}, [
+  {at: -0.25, expect: '75px 225px'},
+  {at: 0, expect: '100px 200px'},
+  {at: 0.25, expect: '125px 175px'},
+  {at: 0.5, expect: '150px 150px'},
+  {at: 0.75, expect: '175px 125px'},
+  {at: 1, expect: '200px 100px'},
+  {at: 1.25, expect: '225px 75px'},
+]);
+
+test_composition({
+  property: 'offset-anchor',
+  underlying: '40px 60px',
+  addFrom: '60px 140px',
+  replaceTo: '200px 100px',
+}, [
+  {at: -0.25, expect: '75px 225px'},
+  {at: 0, expect: '100px 200px'},
+  {at: 0.25, expect: '125px 175px'},
+  {at: 0.5, expect: '150px 150px'},
+  {at: 0.75, expect: '175px 125px'},
+  {at: 1, expect: '200px 100px'},
+  {at: 1.25, expect: '225px 75px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-distance-composition.html b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-distance-composition.html
new file mode 100644
index 0000000..4ff6e95
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-distance-composition.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>offset-distance composition</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-distance-property">
+<meta name="assert" content="offset-distance supports animation.">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_composition({
+  property: 'offset-distance',
+  underlying: '50px',
+  addFrom: '100px',
+  addTo: '200px',
+}, [
+  {at: -0.3, expect: '120px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '200px'},
+  {at: 1, expect: '250px'},
+  {at: 1.5, expect: '300px'},
+]);
+
+test_composition({
+  property: 'offset-distance',
+  underlying: '100px',
+  addFrom: '10px',
+  addTo: '2px',
+}, [
+  {at: -0.5, expect: '114px'},
+  {at: 0, expect: '110px'},
+  {at: 0.5, expect: '106px'},
+  {at: 1, expect: '102px'},
+  {at: 1.5, expect: '98px'},
+]);
+
+test_composition({
+  property: 'offset-distance',
+  underlying: '10%',
+  addFrom: '100px',
+  addTo: '20%',
+}, [
+  {at: -0.3, expect: 'calc(130px + 4%)'},
+  {at: 0, expect: 'calc(100px + 10%)'},
+  {at: 0.5, expect: 'calc(50px + 20%)'},
+  {at: 1, expect: '30%'},
+  {at: 1.5, expect: 'calc(-50px + 40%)'},
+]);
+
+test_composition({
+  property: 'offset-distance',
+  underlying: '50px',
+  addFrom: '100px',
+  replaceTo: '200px',
+}, [
+  {at: -0.3, expect: '135px'},
+  {at: 0, expect: '150px'},
+  {at: 0.5, expect: '175px'},
+  {at: 1, expect: '200px'},
+  {at: 1.5, expect: '225px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-path-composition.html b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-path-composition.html
new file mode 100644
index 0000000..eedd363
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-path-composition.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>offset-distance composition</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-distance-property">
+<meta name="assert" content="offset-distance supports animation.">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+// TODO(ericwilligers) Support additive animation for path strings crbug.com/699308
+
+// Ray paths compose.
+test_composition({
+  property: 'offset-path',
+  underlying: 'ray(20deg sides)',
+  addFrom: 'ray(10deg sides)',
+  addTo: 'ray(20deg sides)',
+}, [
+  {at: -0.3, expect: 'ray(27deg sides)'},
+  {at: 0, expect: 'ray(30deg sides)'},
+  {at: 0.3, expect: 'ray(33deg sides)'},
+  {at: 0.6, expect: 'ray(36deg sides)'},
+  {at: 1, expect: 'ray(40deg sides)'},
+  {at: 1.5, expect: 'ray(45deg sides)'},
+]);
+
+// Ray paths without contain don't compose with underlying contain.
+test_composition({
+  property: 'offset-path',
+  underlying: 'ray(20deg closest-corner contain)',
+  addFrom: 'ray(10deg closest-corner)',
+  addTo: 'ray(20deg closest-corner)',
+}, [
+  {at: -0.3, expect: 'ray(7deg closest-corner)'},
+  {at: 0, expect: 'ray(10deg closest-corner)'},
+  {at: 0.3, expect: 'ray(13deg closest-corner)'},
+  {at: 0.6, expect: 'ray(16deg closest-corner)'},
+  {at: 1, expect: 'ray(20deg closest-corner)'},
+  {at: 1.5, expect: 'ray(25deg closest-corner)'},
+]);
+
+// Ray paths don't compose when underlying has different size.
+test_composition({
+  property: 'offset-path',
+  underlying: 'ray(20deg closest-side)',
+  addFrom: 'ray(10deg closest-corner)',
+  addTo: 'ray(20deg closest-corner)',
+}, [
+  {at: -0.3, expect: 'ray(7deg closest-corner)'},
+  {at: 0, expect: 'ray(10deg closest-corner)'},
+  {at: 0.3, expect: 'ray(13deg closest-corner)'},
+  {at: 0.6, expect: 'ray(16deg closest-corner)'},
+  {at: 1, expect: 'ray(20deg closest-corner)'},
+  {at: 1.5, expect: 'ray(25deg closest-corner)'},
+]);
+
+// Ray contain paths compose with underlying contain.
+test_composition({
+  property: 'offset-path',
+  underlying: 'ray(20deg farthest-side contain)',
+  addFrom: 'ray(190deg farthest-side contain)',
+  addTo: 'ray(20deg farthest-side contain)',
+}, [
+  {at: -0.3, expect: 'ray(261deg farthest-side contain)'},
+  {at: 0, expect: 'ray(210deg farthest-side contain)'},
+  {at: 0.3, expect: 'ray(159deg farthest-side contain)'},
+  {at: 0.6, expect: 'ray(108deg farthest-side contain)'},
+  {at: 1, expect: 'ray(40deg farthest-side contain)'},
+  {at: 1.5, expect: 'ray(-45deg farthest-side contain)'},
+]);
+
+// When we can't interpolate, we can't compose.
+test_composition({
+  property: 'offset-path',
+  underlying: 'ray(20deg farthest-corner)',
+  addFrom: 'ray(190deg farthest-corner contain)',
+  addTo: 'ray(20deg farthest-corner)',
+}, [
+  {at: -0.3, expect: 'ray(190deg farthest-corner contain)'},
+  {at: 0, expect: 'ray(190deg farthest-corner contain)'},
+  {at: 0.3, expect: 'ray(190deg farthest-corner contain)'},
+  {at: 0.6, expect: 'ray(40deg farthest-corner)'},
+  {at: 1, expect: 'ray(40deg farthest-corner)'},
+  {at: 1.5, expect: 'ray(40deg farthest-corner)'},
+]);
+
+test_composition({
+  property: 'offset-path',
+  underlying: 'ray(20deg sides)',
+  replaceFrom: 'ray(190deg sides contain)',
+  addTo: 'ray(20deg sides)',
+}, [
+  {at: -0.3, expect: 'ray(190deg sides contain)'},
+  {at: 0, expect: 'ray(190deg sides contain)'},
+  {at: 0.3, expect: 'ray(190deg sides contain)'},
+  {at: 0.6, expect: 'ray(40deg sides)'},
+  {at: 1, expect: 'ray(40deg sides)'},
+  {at: 1.5, expect: 'ray(40deg sides)'},
+]);
+
+// Ray paths compose with underlying.
+test_composition({
+  property: 'offset-path',
+  underlying: 'ray(20deg closest-side)',
+  addFrom: 'ray(10deg closest-side)',
+  replaceTo: 'ray(10deg closest-side)',
+}, [
+  {at: -0.3, expect: 'ray(36deg closest-side)'},
+  {at: 0, expect: 'ray(30deg closest-side)'},
+  {at: 0.3, expect: 'ray(24deg closest-side)'},
+  {at: 0.6, expect: 'ray(18deg closest-side)'},
+  {at: 1, expect: 'ray(10deg closest-side)'},
+  {at: 1.5, expect: 'ray(0deg closest-side)'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-position-composition.html b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-position-composition.html
new file mode 100644
index 0000000..0ee517a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-position-composition.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>offset-position composition</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-position-property">
+<meta name="assert" content="offset-position supports <position> animation.">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<style>
+.target {
+  width: 200px;
+  height: 200px;
+}
+</style>
+<body>
+<script>
+test_composition({
+  property: 'offset-position',
+  underlying: '40px 60px',
+  addFrom: '60px 40px',
+  addTo: '160px 140px',
+}, [
+  {at: -0.25, expect: '75px 75px'},
+  {at: 0, expect: '100px 100px'},
+  {at: 0.25, expect: '125px 125px'},
+  {at: 0.5, expect: '150px 150px'},
+  {at: 0.75, expect: '175px 175px'},
+  {at: 1, expect: '200px 200px'},
+  {at: 1.25, expect: '225px 225px'},
+]);
+
+test_composition({
+  property: 'offset-position',
+  underlying: 'top 20% left 40%',
+  addFrom: 'left 60% top 80%',
+  addTo: 'right 80% bottom 40%',
+}, [
+  {at: -0.25, expect: '110% 105%'},
+  {at: 0, expect: '100% 100%'},
+  {at: 0.25, expect: '90% 95%'},
+  {at: 0.5, expect: '80% 90%'},
+  {at: 0.75, expect: '70% 85%'},
+  {at: 1, expect: '60% 80%'},
+  {at: 1.25, expect: '50% 75%'},
+]);
+
+test_composition({
+  property: 'offset-position',
+  underlying: '40px 60px',
+  replaceFrom: '100px 200px',
+  addTo: '160px 40px',
+}, [
+  {at: -0.25, expect: '75px 225px'},
+  {at: 0, expect: '100px 200px'},
+  {at: 0.25, expect: '125px 175px'},
+  {at: 0.5, expect: '150px 150px'},
+  {at: 0.75, expect: '175px 125px'},
+  {at: 1, expect: '200px 100px'},
+  {at: 1.25, expect: '225px 75px'},
+]);
+
+test_composition({
+  property: 'offset-position',
+  underlying: '40px 60px',
+  addFrom: '60px 140px',
+  replaceTo: '200px 100px',
+}, [
+  {at: -0.25, expect: '75px 225px'},
+  {at: 0, expect: '100px 200px'},
+  {at: 0.25, expect: '125px 175px'},
+  {at: 0.5, expect: '150px 150px'},
+  {at: 0.75, expect: '175px 125px'},
+  {at: 1, expect: '200px 100px'},
+  {at: 1.25, expect: '225px 75px'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-rotate-composition.html b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-rotate-composition.html
new file mode 100644
index 0000000..bf60c19a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/motion/animation/offset-rotate-composition.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>offset-rotate composition</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-rotate-property">
+<meta name="assert" content="offset-rotate supports animation.">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+// Angle rotations compose.
+test_composition({
+  property: 'offset-rotate',
+  underlying: '20deg',
+  addFrom: '10deg',
+  addTo: '20deg',
+}, [
+  {at: -0.3, expect: '27deg'},
+  {at: 0, expect: '30deg'},
+  {at: 0.3, expect: '33deg'},
+  {at: 0.6, expect: '36deg'},
+  {at: 1, expect: '40deg'},
+  {at: 1.5, expect: '45deg'},
+]);
+
+// Angle rotations don't compose with underlying 'auto'.
+test_composition({
+  property: 'offset-rotate',
+  underlying: 'auto 20deg',
+  addFrom: '10deg',
+  addTo: '20deg',
+}, [
+  {at: -0.3, expect: '7deg'},
+  {at: 0, expect: '10deg'},
+  {at: 0.3, expect: '13deg'},
+  {at: 0.6, expect: '16deg'},
+  {at: 1, expect: '20deg'},
+  {at: 1.5, expect: '25deg'},
+]);
+
+// Auto rotations compose with underlying 'auto'.
+test_composition({
+  property: 'offset-rotate',
+  underlying: 'auto 20deg',
+  addFrom: 'reverse 10deg',
+  addTo: 'auto 20deg',
+}, [
+  {at: -0.3, expect: 'auto 261deg'},
+  {at: 0, expect: 'auto 210deg'},
+  {at: 0.3, expect: 'auto 159deg'},
+  {at: 0.6, expect: 'auto 108deg'},
+  {at: 1, expect: 'auto 40deg'},
+  {at: 1.5, expect: 'auto -45deg'},
+]);
+
+// When we can't interpolate, we can't compose.
+test_composition({
+  property: 'offset-rotate',
+  underlying: '20deg',
+  addFrom: 'reverse 10deg',
+  addTo: '20deg',
+}, [
+  {at: -0.3, expect: 'auto 190deg'},
+  {at: 0, expect: 'auto 190deg'},
+  {at: 0.3, expect: 'auto 190deg'},
+  {at: 0.6, expect: '40deg'},
+  {at: 1, expect: '40deg'},
+  {at: 1.5, expect: '40deg'},
+]);
+
+test_composition({
+  property: 'offset-rotate',
+  underlying: '20deg',
+  replaceFrom: 'reverse 10deg',
+  addTo: '20deg',
+}, [
+  {at: -0.3, expect: 'auto 190deg'},
+  {at: 0, expect: 'auto 190deg'},
+  {at: 0.3, expect: 'auto 190deg'},
+  {at: 0.6, expect: '40deg'},
+  {at: 1, expect: '40deg'},
+  {at: 1.5, expect: '40deg'},
+]);
+
+// Angle rotations compose with underlying angle.
+test_composition({
+  property: 'offset-rotate',
+  underlying: '20deg',
+  addFrom: '10deg',
+  replaceTo: '10deg',
+}, [
+  {at: -0.3, expect: '36deg'},
+  {at: 0, expect: '30deg'},
+  {at: 0.3, expect: '24deg'},
+  {at: 0.6, expect: '18deg'},
+  {at: 1, expect: '10deg'},
+  {at: 1.5, expect: '0deg'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/webaudio.idl b/third_party/blink/web_tests/external/wpt/interfaces/webaudio.idl
index 674673d..94910903 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/webaudio.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/webaudio.idl
@@ -26,28 +26,36 @@
 
   AnalyserNode createAnalyser ();
   BiquadFilterNode createBiquadFilter ();
-  AudioBuffer createBuffer (unsigned long numberOfChannels, unsigned long length, float sampleRate);
+  AudioBuffer createBuffer (unsigned long numberOfChannels,
+                            unsigned long length,
+                            float sampleRate);
   AudioBufferSourceNode createBufferSource ();
   ChannelMergerNode createChannelMerger (optional unsigned long numberOfInputs = 6);
-  ChannelSplitterNode createChannelSplitter (optional unsigned long numberOfOutputs = 6);
+  ChannelSplitterNode createChannelSplitter (
+    optional unsigned long numberOfOutputs = 6);
   ConstantSourceNode createConstantSource ();
   ConvolverNode createConvolver ();
   DelayNode createDelay (optional double maxDelayTime = 1.0);
   DynamicsCompressorNode createDynamicsCompressor ();
   GainNode createGain ();
-  IIRFilterNode createIIRFilter (sequence<double> feedforward, sequence<double> feedback);
+  IIRFilterNode createIIRFilter (sequence<double> feedforward,
+                                 sequence<double> feedback);
   OscillatorNode createOscillator ();
   PannerNode createPanner ();
-  PeriodicWave createPeriodicWave (sequence<float> real, sequence<float> imag, optional PeriodicWaveConstraints constraints = {});
-  ScriptProcessorNode createScriptProcessor(optional unsigned long bufferSize = 0,
-                                            optional unsigned long numberOfInputChannels = 2,
-                                            optional unsigned long numberOfOutputChannels = 2);
+  PeriodicWave createPeriodicWave (sequence<float> real,
+                                   sequence<float> imag,
+                                   optional PeriodicWaveConstraints constraints = {});
+  ScriptProcessorNode createScriptProcessor(
+    optional unsigned long bufferSize = 0,
+    optional unsigned long numberOfInputChannels = 2,
+    optional unsigned long numberOfOutputChannels = 2);
   StereoPannerNode createStereoPanner ();
   WaveShaperNode createWaveShaper ();
 
-  Promise<AudioBuffer> decodeAudioData (ArrayBuffer audioData,
-                                        optional DecodeSuccessCallback? successCallback,
-                                        optional DecodeErrorCallback? errorCallback);
+  Promise<AudioBuffer> decodeAudioData (
+    ArrayBuffer audioData,
+    optional DecodeSuccessCallback? successCallback,
+    optional DecodeErrorCallback? errorCallback);
 };
 
 enum AudioContextLatencyCategory {
@@ -67,7 +75,8 @@
   Promise<void> close ();
   MediaElementAudioSourceNode createMediaElementSource (HTMLMediaElement mediaElement);
   MediaStreamAudioSourceNode createMediaStreamSource (MediaStream mediaStream);
-  MediaStreamTrackAudioSourceNode createMediaStreamTrackSource (MediaStreamTrack mediaStreamTrack);
+  MediaStreamTrackAudioSourceNode createMediaStreamTrackSource (
+    MediaStreamTrack mediaStreamTrack);
   MediaStreamAudioDestinationNode createMediaStreamDestination ();
 };
 
@@ -116,8 +125,14 @@
   readonly attribute double duration;
   readonly attribute unsigned long numberOfChannels;
   Float32Array getChannelData (unsigned long channel);
-  void copyFromChannel (Float32Array destination, unsigned long channelNumber, optional unsigned long bufferOffset = 0);
-  void copyToChannel (Float32Array source, unsigned long channelNumber, optional unsigned long bufferOffset = 0);
+  void copyFromChannel (Float32Array destination,
+                        unsigned long channelNumber,
+                        optional unsigned long bufferOffset = 0);
+  void copyToChannel (Float32Array source,
+
+                      unsigned long channelNumber,
+
+                      optional unsigned long bufferOffset = 0);
 };
 
 dictionary AudioBufferOptions {
@@ -136,7 +151,9 @@
   void disconnect (unsigned long output);
   void disconnect (AudioNode destinationNode);
   void disconnect (AudioNode destinationNode, unsigned long output);
-  void disconnect (AudioNode destinationNode, unsigned long output, unsigned long input);
+  void disconnect (AudioNode destinationNode,
+                   unsigned long output,
+                   unsigned long input);
   void disconnect (AudioParam destinationParam);
   void disconnect (AudioParam destinationParam, unsigned long output);
   readonly attribute BaseAudioContext context;
@@ -180,7 +197,11 @@
   AudioParam linearRampToValueAtTime (float value, double endTime);
   AudioParam exponentialRampToValueAtTime (float value, double endTime);
   AudioParam setTargetAtTime (float target, double startTime, float timeConstant);
-  AudioParam setValueCurveAtTime (sequence<float> values, double startTime, double duration);
+  AudioParam setValueCurveAtTime (sequence<float> values,
+
+                                  double startTime,
+
+                                  double duration);
   AudioParam cancelScheduledValues (double cancelTime);
   AudioParam cancelAndHoldAtTime (double cancelTime);
 };
@@ -215,7 +236,8 @@
 
 [Exposed=Window]
 interface AudioBufferSourceNode : AudioScheduledSourceNode {
-  constructor (BaseAudioContext context, optional AudioBufferSourceOptions options = {});
+  constructor (BaseAudioContext context,
+               optional AudioBufferSourceOptions options = {});
   attribute AudioBuffer? buffer;
   readonly attribute AudioParam playbackRate;
   readonly attribute AudioParam detune;
@@ -289,7 +311,9 @@
   readonly attribute AudioParam detune;
   readonly attribute AudioParam Q;
   readonly attribute AudioParam gain;
-  void getFrequencyResponse (Float32Array frequencyHz, Float32Array magResponse, Float32Array phaseResponse);
+  void getFrequencyResponse (Float32Array frequencyHz,
+                             Float32Array magResponse,
+                             Float32Array phaseResponse);
 };
 
 dictionary BiquadFilterOptions : AudioNodeOptions {
@@ -353,7 +377,8 @@
 
 [Exposed=Window]
 interface DynamicsCompressorNode : AudioNode {
-  constructor (BaseAudioContext context, optional DynamicsCompressorOptions options = {});
+  constructor (BaseAudioContext context,
+               optional DynamicsCompressorOptions options = {});
   readonly attribute AudioParam threshold;
   readonly attribute AudioParam knee;
   readonly attribute AudioParam ratio;
@@ -383,7 +408,9 @@
 [Exposed=Window]
 interface IIRFilterNode : AudioNode {
   constructor (BaseAudioContext context, IIRFilterOptions options);
-  void getFrequencyResponse (Float32Array frequencyHz, Float32Array magResponse, Float32Array phaseResponse);
+  void getFrequencyResponse (Float32Array frequencyHz,
+                             Float32Array magResponse,
+                             Float32Array phaseResponse);
 };
 
 dictionary IIRFilterOptions : AudioNodeOptions {
@@ -555,7 +582,8 @@
 
 [Global=(Worklet, AudioWorklet), Exposed=AudioWorklet]
 interface AudioWorkletGlobalScope : WorkletGlobalScope {
-  void registerProcessor (DOMString name, AudioWorkletProcessorConstructor processorCtor);
+  void registerProcessor (DOMString name,
+                          AudioWorkletProcessorConstructor processorCtor);
   readonly attribute unsigned long long currentFrame;
   readonly attribute double currentTime;
   readonly attribute float sampleRate;
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/webrtc.idl b/third_party/blink/web_tests/external/wpt/interfaces/webrtc.idl
index e30fc38..49ed6bb 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/webrtc.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/webrtc.idl
@@ -13,7 +13,7 @@
 };
 
 enum RTCIceCredentialType {
-  "password",
+  "password"
 };
 
 dictionary RTCIceServer {
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html b/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html
index 0522c43..a1f9e4f 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html
+++ b/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html
@@ -97,8 +97,8 @@
 
 promise_test(async t => {
   const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
-  animation.currentTime = 50 * MS_PER_SEC;
   await animation.ready;
+  animation.currentTime = 50 * MS_PER_SEC;
   animation.playbackRate = 0;
   // Ensure that current time does not drift.
   assert_equals(animation.playState, 'running');
diff --git a/third_party/blink/web_tests/fast/scrolling/scrollbars/dsf-ready/mouse-interactions-dsf-2.html b/third_party/blink/web_tests/fast/scrolling/scrollbars/dsf-ready/mouse-interactions-dsf-2.html
index 871b08b..fbe2515 100644
--- a/third_party/blink/web_tests/fast/scrolling/scrollbars/dsf-ready/mouse-interactions-dsf-2.html
+++ b/third_party/blink/web_tests/fast/scrolling/scrollbars/dsf-ready/mouse-interactions-dsf-2.html
@@ -104,14 +104,11 @@
     if (onLinuxPlatform)
       expected_offset = window.devicePixelRatio == 1 ? 87 : 82;
     else if (onMacPlatform)
-      expected_offset = 74;
+      expected_offset = 72;
     else
       expected_offset = 82;
 
-    // TODO(arakeri): crbug.com/1019076 Option + click is off by 3-4 px.
-    assert_approx_equals(standardDivFast.scrollTop, expected_offset, 3,
-    modifier + " + click forward didn't scroll.");
-
+    assert_equals(standardDivFast.scrollTop, expected_offset, modifier + " + click forward didn't scroll.");
     await mouseClickOn(SCROLLBAR_BUTTON_FWD.x, standardRectFast.top +
     ((onMacPlatform ? 0 : BUTTON_WIDTH) + 2), /*left_click*/0, modifier);
     assert_equals(standardDivFast.scrollTop, 0, modifier + " + click backward didn't scroll.");
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/multiple-backgrounds-style-change-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/multiple-backgrounds-style-change-expected.txt
index 313bfe96..3dfd4c10 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/multiple-backgrounds-style-change-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/multiple-backgrounds-style-change-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='test' class='composited box changed'",
+      "name": "LayoutNGBlockFlow DIV id='test' class='composited box'",
       "bounds": [202, 202],
       "transform": 2
     }
diff --git a/third_party/closure_compiler/compiler.py b/third_party/closure_compiler/compiler.py
index c0c0bf2..f2bedcb1 100755
--- a/third_party/closure_compiler/compiler.py
+++ b/third_party/closure_compiler/compiler.py
@@ -24,7 +24,7 @@
   and produce minified output."""
 
   _JAR_COMMAND = [
-    "java",
+    os.path.join(_CURRENT_DIR, "..", "jdk", "current", "bin", "java"),
     "-jar",
     "-Xms1024m",
     "-client",
diff --git a/tools/binary_size/generate_milestone_reports.py b/tools/binary_size/generate_milestone_reports.py
index 0322a253..c9c4acb 100755
--- a/tools/binary_size/generate_milestone_reports.py
+++ b/tools/binary_size/generate_milestone_reports.py
@@ -232,7 +232,7 @@
   if args.sync:
     subprocess.check_call(cmd)
     subprocess.check_call([
-        'gsutil.py', 'setmeta', '-h', 'Cache-Control:no-cache',
+        _GSUTIL, 'setmeta', '-h', 'Cache-Control:no-cache',
         _PUSH_URL + 'milestones.json'
     ])
   else:
diff --git a/tools/binary_size/generate_official_build_report.py b/tools/binary_size/generate_official_build_report.py
index b08ccce..9387260 100755
--- a/tools/binary_size/generate_official_build_report.py
+++ b/tools/binary_size/generate_official_build_report.py
@@ -17,14 +17,19 @@
 _REPORTS_JSON_GS_URL = os.path.join(_REPORTS_BASE_URL, 'canary_reports.json')
 _REPORTS_GS_URL = os.path.join(_REPORTS_BASE_URL, 'reports')
 
+_DIR_SOURCE_ROOT = os.path.normpath(
+    os.path.join(os.path.dirname(__file__), '..', '..'))
+_GSUTIL = os.path.join(_DIR_SOURCE_ROOT, 'third_party', 'depot_tools',
+                       'gsutil.py')
+
 
 def _WriteReportsJson(out):
-  output = subprocess.check_output(['gsutil.py', 'ls', '-R', _REPORTS_GS_URL])
+  output = subprocess.check_output([_GSUTIL, 'ls', '-R', _REPORTS_GS_URL])
 
   reports = []
   report_re = re.compile(
       re.escape(_REPORTS_GS_URL) +
-      r'/(?P<version>.+?)/(?P<apk>.+?)/(?P<cpu>.+?)\.size')
+      r'/(?P<version>.+?)/(?P<cpu>.+?)/(?P<apk>.+?)\.size')
   for line in output.splitlines():
     m = report_re.search(line)
     if m:
@@ -37,6 +42,30 @@
   json.dump({'pushed': reports}, out)
 
 
+def _UploadReportsJson():
+  with tempfile.NamedTemporaryFile() as f:
+    _WriteReportsJson(f)
+    f.flush()
+    cmd = [
+        _GSUTIL, '--', '-h', 'Cache-Control:no-cache', 'cp', '-a',
+        'public-read', f.name, _REPORTS_JSON_GS_URL
+    ]
+    logging.warning(' '.join(cmd))
+    subprocess.check_call(cmd)
+
+
+def _UploadSizeFile(size_path, version, arch):
+  report_basename = os.path.splitext(os.path.basename(size_path))[0]
+  # Maintain name through transition to bundles.
+  report_basename = report_basename.replace('.minimal.apks', '.apk')
+  dst_url = os.path.join(_REPORTS_GS_URL, version, arch,
+                         report_basename + '.size')
+
+  cmd = [_GSUTIL, 'cp', '-a', 'public-read', size_path, dst_url]
+  logging.warning(' '.join(cmd))
+  subprocess.check_call(cmd)
+
+
 def main():
   parser = argparse.ArgumentParser()
   parser.add_argument(
@@ -47,36 +76,16 @@
       '--size-path',
       required=True,
       help='Path to .size file for the given version.')
-  parser.add_argument('--gs-size-url', help='Unused')
-  parser.add_argument('--gs-size-path', help='Unused')
   parser.add_argument(
       '--arch', required=True, help='Compiler architecture of build.')
-  parser.add_argument(
-      '--platform',
-      required=True,
-      help='OS corresponding to those used by omahaproxy.',
-      choices=['android', 'webview'])
+  parser.add_argument('--gs-size-url', help='Unused')
+  parser.add_argument('--gs-size-path', help='Unused')
+  parser.add_argument('--platform', help='Unused')
 
   args = parser.parse_args()
 
-  report_basename = os.path.splitext(os.path.basename(args.size_path))[0]
-  # Maintain name through transition to bundles.
-  report_basename = report_basename.replace('.minimal.apks', '.apk')
-  dst_url = os.path.join(_REPORTS_GS_URL, args.version, args.arch,
-                         report_basename + '.size')
-
-  cmd = ['gsutil.py', 'cp', '-a', 'public-read', args.size_path, dst_url]
-  logging.warning(' '.join(cmd))
-  subprocess.check_call(cmd)
-
-  with tempfile.NamedTemporaryFile() as f:
-    _WriteReportsJson(f)
-    cmd = [
-        'gsutil.py', '--', '-h', 'Cache-Control:no-cache', 'cp', '-a',
-        'public-read', f.name, _REPORTS_JSON_GS_URL
-    ]
-    logging.warning(' '.join(cmd))
-    subprocess.check_call(cmd)
+  _UploadSizeFile(args.size_path, args.version, args.arch)
+  _UploadReportsJson()
 
 
 if __name__ == '__main__':
diff --git a/tools/binary_size/libsupersize/static/index.js b/tools/binary_size/libsupersize/static/index.js
index e6611fc..2a510ff 100644
--- a/tools/binary_size/libsupersize/static/index.js
+++ b/tools/binary_size/libsupersize/static/index.js
@@ -116,7 +116,7 @@
           mainVersions.filter(v2 => compareVersions(v2, '71.0.0.0') > 0);
     }
 
-    if (showAll.value) {
+    if (showAll.checked) {
       activeVersions = [...mainVersions, ...canaryVersions];
       activeVersions.sort(compareVersions);
     } else {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 9a58d9f5..f84ff66 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -41517,16 +41517,16 @@
   <int value="7" label="Ephemeral icon and local file"/>
   <int value="8" label="Context menu and presentation"/>
   <int value="9" label="Context menu and tab mirroring"/>
-  <int value="11" label="Context menu and desktop mirroring"/>
-  <int value="12" label="Context menu and local file"/>
-  <int value="13" label="Page and presentation"/>
-  <int value="14" label="Page and tab mirroring"/>
-  <int value="15" label="Page and desktop mirroring"/>
-  <int value="16" label="Page and local file"/>
-  <int value="17" label="App menu and presentation"/>
-  <int value="18" label="App menu and tab mirroring"/>
-  <int value="19" label="App menu and desktop mirroring"/>
-  <int value="20" label="App menu and local file"/>
+  <int value="10" label="Context menu and desktop mirroring"/>
+  <int value="11" label="Context menu and local file"/>
+  <int value="12" label="Page and presentation"/>
+  <int value="13" label="Page and tab mirroring"/>
+  <int value="14" label="Page and desktop mirroring"/>
+  <int value="15" label="Page and local file"/>
+  <int value="16" label="App menu and presentation"/>
+  <int value="17" label="App menu and tab mirroring"/>
+  <int value="18" label="App menu and desktop mirroring"/>
+  <int value="19" label="App menu and local file"/>
 </enum>
 
 <enum name="MediaRouterDialogOpenOrigin">
@@ -51032,10 +51032,10 @@
   <int value="41" label="WOULD_HAVE_BEEN_USED (Obsolete)"/>
   <int value="42" label="REGISTER_PROTOCOL_HANDLER"/>
   <int value="43" label="CREATING_AUDIO_STREAM"/>
-  <int value="44" label="PAGE_BEING_CAPTURED"/>
+  <int value="44" label="PAGE_BEING_CAPTURED (Obsolete)"/>
   <int value="45" label="BAD_DEFERRED_REDIRECT"/>
   <int value="46" label="NAVIGATION_UNCOMMITTED"/>
-  <int value="47" label="NEW_NAVIGATION_ENTRY"/>
+  <int value="47" label="NEW_NAVIGATION_ENTRY (Obsolete)"/>
   <int value="48" label="COOKIE_STORE_NOT_LOADED (Obsolete)"/>
   <int value="49" label="COOKIE_CONFLICT (Obsolete)"/>
   <int value="50" label="NON_EMPTY_BROWSING_INSTANCE"/>
@@ -62967,6 +62967,7 @@
   <int value="1941011596" label="IFX 9655 rev 35 fw 4.34 build 03f2"/>
   <int value="1953654937" label="IFX 9645 rev 45 fw 133.33 build 00e3"/>
   <int value="2024714959" label="IFX 9655 rev 32 fw 4.34 build 03f2"/>
+  <int value="2036182876" label="CROS Cr50 0.3.24"/>
   <int value="2061474747"
       label="CROS Cr50 0.2.2 aka 0.4.4 Flags 0x10(pre-pvt)"/>
   <int value="2104725364" label="CROS Cr50 0.3.20"/>
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index 2519a9e2..cbe58186 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -18,6 +18,7 @@
 blink_perf.shadow_dom,masonfreed@chromium.org,Blink>DOM>ShadowDOM,https://bit.ly/blink-perf-benchmarks,
 blink_perf.svg,"fs@opera.com, pdr@chromium.org",Blink>SVG,https://bit.ly/blink-perf-benchmarks,
 components_perftests,csharrison@chromium.org,,,
+dawn_perf_tests,"enga@chromium.org, chrome-gpu-perf-owners@chromium.org",Internals>GPU>Dawn,https://dawn.googlesource.com/dawn/+/HEAD/src/tests/perf_tests/README.md,
 dromaeo,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",Blink>Bindings,,
 dummy_benchmark.noisy_benchmark_1,crouleau@chromium.org,Test>Telemetry,,
 dummy_benchmark.stable_benchmark_1,crouleau@chromium.org,Test>Telemetry,,
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py
index b1fc7d5..74f94c8 100644
--- a/tools/perf/core/bot_platforms.py
+++ b/tools/perf/core/bot_platforms.py
@@ -33,6 +33,7 @@
   def __init__(self, name, description, benchmark_configs,
                num_shards, platform_os, is_fyi=False,
                run_reference_build=True, executables=None):
+    benchmark_configs = benchmark_configs.Frozenset()
     self._name = name
     self._description = description
     self._platform_os = platform_os
@@ -159,6 +160,39 @@
     self.repeat = 1
 
 
+class PerfSuite(object):
+  def __init__(self, configs):
+    self._configs = dict()
+    self.Add(configs)
+
+  def Frozenset(self):
+    return frozenset(self._configs.values())
+
+  def Add(self, configs):
+    if isinstance(configs, PerfSuite):
+      configs = configs.Frozenset()
+    for config in configs:
+      if config.name in self._configs:
+        raise ValueError('Cannot have duplicate benchmarks/executables.')
+      self._configs[config.name] = config
+    return self
+
+  def Remove(self, configs):
+    for config in configs:
+      name = config
+      if isinstance(config, PerfSuite):
+        name = config.name
+      del self._configs[name]
+    return self
+
+  def Abridge(self, config_names):
+    for name in config_names:
+      del self._configs[name]
+      self._configs[name] = _GetBenchmarkConfig(
+          name, abridged=True)
+    return self
+
+
 # Global |benchmarks| is convenient way to keep BenchmarkConfig objects
 # unique, which allows us to use set subtraction below.
 benchmarks = {b.Name(): {True: BenchmarkConfig(b, abridged=True),
@@ -168,22 +202,20 @@
 def _GetBenchmarkConfig(benchmark_name, abridged=False):
   return benchmarks[benchmark_name][abridged]
 
-OFFICIAL_BENCHMARK_CONFIGS = frozenset(
-    _GetBenchmarkConfig(b.Name()) for b in OFFICIAL_BENCHMARKS)
+OFFICIAL_BENCHMARK_CONFIGS = PerfSuite(
+    [_GetBenchmarkConfig(b.Name()) for b in OFFICIAL_BENCHMARKS])
 # TODO(crbug.com/965158): Remove OFFICIAL_BENCHMARK_NAMES once sharding
 # scripts are no longer using it.
 OFFICIAL_BENCHMARK_NAMES = frozenset(
-    b.name for b in OFFICIAL_BENCHMARK_CONFIGS)
-_DL_BENCHMARK_CONFIGS = frozenset([_GetBenchmarkConfig(
-    'blink_perf.display_locking')])
-_JETSTREAM2 = frozenset([_GetBenchmarkConfig('jetstream2')])
+    b.name for b in OFFICIAL_BENCHMARK_CONFIGS.Frozenset())
 
-_OFFICIAL_EXCEPT_DISPLAY_LOCKING = (
-    OFFICIAL_BENCHMARK_CONFIGS - _DL_BENCHMARK_CONFIGS)
-_OFFICIAL_EXCEPT_JETSTREAM2 = (
-    OFFICIAL_BENCHMARK_CONFIGS - _JETSTREAM2)
-_OFFICIAL_EXCEPT_DISPLAY_LOCKING_JETSTREAM2 = (
-    OFFICIAL_BENCHMARK_CONFIGS - _DL_BENCHMARK_CONFIGS - _JETSTREAM2)
+_OFFICIAL_EXCEPT_DISPLAY_LOCKING = PerfSuite(OFFICIAL_BENCHMARK_CONFIGS).Remove(
+    ['blink_perf.display_locking'])
+_OFFICIAL_EXCEPT_JETSTREAM2 = PerfSuite(OFFICIAL_BENCHMARK_CONFIGS).Remove(
+    ['jetstream2'])
+_OFFICIAL_EXCEPT_DISPLAY_LOCKING_JETSTREAM2 = PerfSuite(
+    OFFICIAL_BENCHMARK_CONFIGS).Remove(
+        ['blink_perf.display_locking', 'jetstream2'])
 
 _TRACING_PERFTESTS = ExecutableConfig('tracing_perftests', estimated_runtime=50)
 _COMPONENTS_PERFTESTS = ExecutableConfig('components_perftests',
@@ -195,12 +227,12 @@
 _MAC_LOW_END_BENCHMARK_CONFIGS = _OFFICIAL_EXCEPT_JETSTREAM2
 _WIN_10_BENCHMARK_CONFIGS = _OFFICIAL_EXCEPT_DISPLAY_LOCKING
 _WIN_10_LOW_END_BENCHMARK_CONFIGS = _OFFICIAL_EXCEPT_DISPLAY_LOCKING
-_WIN_10_LOW_END_HP_CANDIDATE_BENCHMARK_CONFIGS = frozenset([
-    _GetBenchmarkConfig('v8.browsing_desktop')])
-_WIN_7_BENCHMARK_CONFIGS = (_OFFICIAL_EXCEPT_DISPLAY_LOCKING_JETSTREAM2 -
-                          frozenset([_GetBenchmarkConfig('rendering.desktop')]))
+_WIN_10_LOW_END_HP_CANDIDATE_BENCHMARK_CONFIGS = PerfSuite(
+    [_GetBenchmarkConfig('v8.browsing_desktop')])
+_WIN_7_BENCHMARK_CONFIGS = PerfSuite(
+    _OFFICIAL_EXCEPT_DISPLAY_LOCKING_JETSTREAM2).Remove(['rendering.desktop'])
 _WIN_7_GPU_BENCHMARK_CONFIGS = _OFFICIAL_EXCEPT_DISPLAY_LOCKING_JETSTREAM2
-_ANDROID_GO_BENCHMARK_CONFIGS = frozenset([
+_ANDROID_GO_BENCHMARK_CONFIGS = PerfSuite([
     _GetBenchmarkConfig('system_health.memory_mobile'),
     _GetBenchmarkConfig('system_health.common_mobile'),
     _GetBenchmarkConfig('startup.mobile'),
@@ -212,15 +244,9 @@
 _ANDROID_NEXUS_5_BENCHMARK_CONFIGS = _OFFICIAL_EXCEPT_DISPLAY_LOCKING_JETSTREAM2
 _ANDROID_NEXUS_5_EXECUTABLE_CONFIGS = frozenset([
     _TRACING_PERFTESTS, _COMPONENTS_PERFTESTS, _GPU_PERFTESTS])
-_ANDROID_NEXUS_5X_BENCHMARK_CONFIGS = (
-    (((_OFFICIAL_EXCEPT_JETSTREAM2
-    # Remove unabridged rendering benchmark and replace with abridged benchmark.
-     - frozenset([_GetBenchmarkConfig('rendering.mobile')]))
-     | frozenset([_GetBenchmarkConfig('rendering.mobile', True)]))
-    # Remove unabridged system health memory benchmark and replace with abridged
-    # benchmark: crbug.com/1030788
-     - frozenset([_GetBenchmarkConfig('system_health.memory_mobile')]))
-    | frozenset([_GetBenchmarkConfig('system_health.memory_mobile', True)]))
+_ANDROID_NEXUS_5X_BENCHMARK_CONFIGS = PerfSuite(
+    _OFFICIAL_EXCEPT_JETSTREAM2).Abridge(
+        ['rendering.mobile', 'system_health.memory_mobile'])
 _ANDROID_NEXUS_5X_WEBVIEW_BENCHMARK_CONFIGS = (
     _OFFICIAL_EXCEPT_DISPLAY_LOCKING_JETSTREAM2)
 _ANDROID_NEXUS_6_WEBVIEW_BENCHMARK_CONFIGS = (
@@ -228,17 +254,17 @@
 _ANDROID_PIXEL2_BENCHMARK_CONFIGS = _OFFICIAL_EXCEPT_DISPLAY_LOCKING
 _ANDROID_PIXEL2_WEBVIEW_BENCHMARK_CONFIGS = (
     _OFFICIAL_EXCEPT_DISPLAY_LOCKING_JETSTREAM2)
-_ANDROID_PIXEL2_WEBLAYER_BENCHMARK_CONFIGS = frozenset([
+_ANDROID_PIXEL2_WEBLAYER_BENCHMARK_CONFIGS = PerfSuite([
     _GetBenchmarkConfig('system_health.common_mobile', True),
     _GetBenchmarkConfig('system_health.memory_mobile', True),
     _GetBenchmarkConfig('startup.mobile')])
-_ANDROID_NEXUS5X_FYI_BENCHMARK_CONFIGS = frozenset([
+_ANDROID_NEXUS5X_FYI_BENCHMARK_CONFIGS = PerfSuite([
      # Running a sample benchmark to help testing out the work on
      # trace_processor_shell: crbug.com/1028612
     _GetBenchmarkConfig('tracing.tracing_with_background_memory_infra')])
-_ANDROID_PIXEL2_AAB_FYI_BENCHMARK_CONFIGS = frozenset([
+_ANDROID_PIXEL2_AAB_FYI_BENCHMARK_CONFIGS = PerfSuite([
     _GetBenchmarkConfig('rendering.mobile', True)])
-_ANDROID_PIXEL2_FYI_BENCHMARK_CONFIGS = frozenset([
+_ANDROID_PIXEL2_FYI_BENCHMARK_CONFIGS = PerfSuite([
     _GetBenchmarkConfig('v8.browsing_mobile'),
     _GetBenchmarkConfig('system_health.memory_mobile'),
     _GetBenchmarkConfig('system_health.common_mobile'),
@@ -246,9 +272,9 @@
     _GetBenchmarkConfig('speedometer2'),
     _GetBenchmarkConfig('octane'),
     _GetBenchmarkConfig('jetstream')])
-_CHROMEOS_KEVIN_FYI_BENCHMARK_CONFIGS = frozenset([
+_CHROMEOS_KEVIN_FYI_BENCHMARK_CONFIGS = PerfSuite([
     _GetBenchmarkConfig('rendering.desktop')])
-_LINUX_PERF_FYI_BENCHMARK_CONFIGS = frozenset([
+_LINUX_PERF_FYI_BENCHMARK_CONFIGS = PerfSuite([
     _GetBenchmarkConfig('power.desktop')])
 
 
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index 6c2dae8..4f2afc80 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -131,6 +131,9 @@
     'tests': [
       {
         'isolate': 'performance_test_suite',
+        'extra_args': [
+           '--run-ref-build',
+        ],
       }
     ],
     'platform': 'android-chrome-bundle',
@@ -570,6 +573,14 @@
         'num_shards': 1,
         'type': TEST_TYPES.GTEST,
       },
+      {
+        'isolate': 'dawn_perf_tests',
+        'num_shards': 1,
+        'type': TEST_TYPES.GTEST,
+        'extra_args': [
+            '--shard-timeout=300'
+        ],
+      },
     ],
     'platform': 'win',
     'target_bits': 64,
@@ -881,6 +892,10 @@
     'views_perftests': BenchmarkMetadata(
         'tapted@chromium.org', 'Internals>Views'),
     'components_perftests': BenchmarkMetadata('csharrison@chromium.org'),
+    'dawn_perf_tests': BenchmarkMetadata(
+        'enga@chromium.org, chrome-gpu-perf-owners@chromium.org',
+        'Internals>GPU>Dawn',
+        'https://dawn.googlesource.com/dawn/+/HEAD/src/tests/perf_tests/README.md'),
 }
 
 
diff --git a/tools/perf/core/results_processor/command_line.py b/tools/perf/core/results_processor/command_line.py
index c1b7f87..dfa838cd 100644
--- a/tools/perf/core/results_processor/command_line.py
+++ b/tools/perf/core/results_processor/command_line.py
@@ -184,10 +184,17 @@
   the path to trace processor binary located in that directory. Otherwise
   we don't guess, but leave it to the user to supply a path.
   """
-  build_dirs = ['build', 'out', 'xcodebuild']
-  build_types = ['Debug', 'Debug_x64', 'Release', 'Release_x64', 'Default']
   executable_names = [trace_processor.TP_BINARY_NAME,
                       trace_processor.TP_BINARY_NAME + '.exe']
+  chromium_output_dir = os.environ.get('CHROMIUM_OUTPUT_DIR')
+  if chromium_output_dir:
+    for executable_name in executable_names:
+      candidate_path = os.path.join(chromium_output_dir, executable_name)
+      if os.path.isfile(candidate_path):
+        return candidate_path
+
+  build_dirs = ['build', 'out', 'xcodebuild']
+  build_types = ['Debug', 'Debug_x64', 'Release', 'Release_x64', 'Default']
   candidate_paths = []
   for build_dir in build_dirs:
     for build_type in build_types:
diff --git a/tools/perf/core/results_processor/command_line_unittest.py b/tools/perf/core/results_processor/command_line_unittest.py
index fc531bb..5dd1f9ab 100644
--- a/tools/perf/core/results_processor/command_line_unittest.py
+++ b/tools/perf/core/results_processor/command_line_unittest.py
@@ -137,9 +137,26 @@
     self.assertEqual(options.output_formats, ['csv', 'html'])
 
   def testTraceProcessorPath_noBuildDir(self):
-    options = self.ParseArgs([])
+    with mock.patch(module('os.environ.get'), return_value=None):
+      options = self.ParseArgs([])
     self.assertIsNone(options.trace_processor_path)
 
+  def testTraceProcessorPath_chromiumOutputDir(self):
+    def isfile(path):
+      return path == '/path/to/chromium/out_test/Debug/trace_processor_shell'
+
+    def env_get(name):
+      if name == 'CHROMIUM_OUTPUT_DIR':
+        return '/path/to/chromium/out_test/Debug'
+
+    with mock.patch(module('os.path.isfile')) as isfile_patch:
+      with mock.patch(module('os.environ.get')) as env_patch:
+        isfile_patch.side_effect = isfile
+        env_patch.side_effect = env_get
+        options = self.ParseArgs([])
+    self.assertEqual(options.trace_processor_path,
+                     '/path/to/chromium/out_test/Debug/trace_processor_shell')
+
   def testTraceProcessorPath_oneBuildDir(self):
     def isfile(path):
       return path == '/path/to/chromium/out/Release/trace_processor_shell'
diff --git a/tools/perf/core/shard_maps/smoke_test_benchmark_shard_map.json b/tools/perf/core/shard_maps/smoke_test_benchmark_shard_map.json
index 992dd83..1524b68 100644
--- a/tools/perf/core/shard_maps/smoke_test_benchmark_shard_map.json
+++ b/tools/perf/core/shard_maps/smoke_test_benchmark_shard_map.json
@@ -4,14 +4,6 @@
             "dummy_benchmark.stable_benchmark_1": {
                 "abridged": false
             }
-        },
-        "executables": {
-            "dummy_gtest": {
-                "path": "../../tools/perf/testdata/dummy_gtest",
-                "arguments": [
-                    "--argument-to-check-that-arguments-work"
-                ]
-            }
         }
     },
     "1": {
diff --git a/tools/perf/scripts_smoke_unittest.py b/tools/perf/scripts_smoke_unittest.py
index 968b7b90..007311aa 100644
--- a/tools/perf/scripts_smoke_unittest.py
+++ b/tools/perf/scripts_smoke_unittest.py
@@ -163,7 +163,10 @@
   # Android: crbug.com/932301
   # ChromeOS: crbug.com/754913
   # Windows: crbug.com/1024767
-  @decorators.Disabled('chromeos', 'android', 'win')
+  # Linux: crbug.com/1024767
+  # all: Disabled everywhere because the smoke test shard map
+  # needed to be changed to fix crbug.com/1024767.
+  @decorators.Disabled('all')
   def testRunPerformanceTestsTelemetrySharded_end2end(self):
     tempdir = tempfile.mkdtemp()
     env = os.environ.copy()
diff --git a/ui/color/BUILD.gn b/ui/color/BUILD.gn
index 347b2847..12d0512 100644
--- a/ui/color/BUILD.gn
+++ b/ui/color/BUILD.gn
@@ -13,76 +13,104 @@
   flags = [ "USE_COLOR_PIPELINE=$use_color_pipeline" ]
 }
 
-jumbo_component("color") {
+source_set("color_headers") {
   sources = [
     "color_id.h",
-    "color_mixer.cc",
-    "color_mixer.h",
-    "color_provider.cc",
+    "color_id_macros.inc",
     "color_provider.h",
-    "color_recipe.cc",
-    "color_recipe.h",
-    "color_set.cc",
-    "color_set.h",
-    "color_transform.cc",
-    "color_transform.h",
   ]
 
-  defines = [ "IS_COLOR_IMPL" ]
-
   public_deps = [
     ":color_buildflags",
-    "//base",
-    "//skia",
-    "//ui/gfx:color_utils",
   ]
+
+  if (use_color_pipeline) {
+    sources += [
+      "color_mixer.h",
+      "color_set.h",
+    ]
+
+    public_deps += [
+      "//base:base",
+      "//skia:skia",
+    ]
+  } else {
+    public_deps += [
+      "//ui/base:base",
+      "//ui/native_theme:native_theme",
+    ]
+  }
 }
 
-test("color_unittests") {
-  testonly = true
+if (use_color_pipeline) {
+  jumbo_component("color") {
+    sources = [
+      "color_mixer.cc",
+      "color_provider.cc",
+      "color_recipe.cc",
+      "color_recipe.h",
+      "color_set.cc",
+      "color_transform.cc",
+      "color_transform.h",
+    ]
 
-  sources = [
-    "color_mixer_unittest.cc",
-    "color_provider_unittest.cc",
-    "color_recipe_unittest.cc",
-    "color_test_ids.h",
-    "color_transform_unittest.cc",
-    "run_all_unittests.cc",
-  ]
+    defines = [ "IS_COLOR_IMPL" ]
 
-  deps = [
-    ":color",
-    "//base/test:test_support",
-    "//testing/gtest",
-  ]
-}
+    public_deps = [
+      ":color_headers",
+      "//base:base",
+      "//skia:skia",
+      "//ui/gfx:color_utils",
+    ]
+  }
 
-jumbo_component("mixers") {
-  sources = [
-    "color_mixers.h",
-    "core_default_color_mixer.cc",
-    "ui_color_mixer.cc",
-  ]
+  test("color_unittests") {
+    testonly = true
 
-  defines = [ "IS_COLOR_IMPL" ]
+    sources = [
+      "color_mixer_unittest.cc",
+      "color_provider_unittest.cc",
+      "color_recipe_unittest.cc",
+      "color_test_ids.h",
+      "color_transform_unittest.cc",
+      "run_all_unittests.cc",
+    ]
 
-  deps = [
-    ":color",
-    "//skia",
-    "//ui/gfx:color_utils",
-  ]
+    deps = [
+      ":color",
+      "//base/test:test_support",
+      "//testing/gtest",
+    ]
+  }
 
-  public_deps = [
-    "//base",
-  ]
+  jumbo_component("mixers") {
+    sources = [
+      "color_mixers.h",
+      "core_default_color_mixer.cc",
+      "ui_color_mixer.cc",
+    ]
 
-  if (is_chromeos) {
-    sources += [ "cros/native_color_mixer.cc" ]
-  } else if (is_linux) {
-    sources += [ "linux/native_color_mixer.cc" ]
-  } else if (is_mac) {
-    sources += [ "mac/native_color_mixer.cc" ]
-  } else if (is_win) {
-    sources += [ "win/native_color_mixer.cc" ]
+    defines = [ "IS_COLOR_IMPL" ]
+
+    deps = [
+      ":color",
+      ":color_headers",
+      "//skia",
+      "//ui/gfx:color_utils",
+    ]
+
+    public_deps = [
+      "//base",
+    ]
+
+    if (is_chromeos) {
+      sources += [ "cros/native_color_mixer.cc" ]
+    } else if (is_linux) {
+      sources += [ "linux/native_color_mixer.cc" ]
+    } else if (is_mac) {
+      sources += [ "mac/native_color_mixer.cc" ]
+    } else if (is_win) {
+      sources += [ "win/native_color_mixer.cc" ]
+    }
   }
 }
diff --git a/ui/color/DEPS b/ui/color/DEPS
index 41134f5..c5a8c5f9 100644
--- a/ui/color/DEPS
+++ b/ui/color/DEPS
@@ -1,4 +1,6 @@
 include_rules = [
   "+third_party/skia/include",
+  "+ui/base/theme_provider.h",  # used by color_provider.h when !USE_COLOR_PIPELINE.
   "+ui/gfx",
+  "+ui/native_theme/native_theme.h",  # used by color_id.h when !USE_COLOR_PIPELINE.
 ]
diff --git a/ui/color/color_id.h b/ui/color/color_id.h
index 29ecf72..e5b2d49d 100644
--- a/ui/color/color_id.h
+++ b/ui/color/color_id.h
@@ -6,128 +6,171 @@
 #define UI_COLOR_COLOR_ID_H_
 
 #include "build/build_config.h"
+#include "build/buildflag.h"
+#include "ui/color/color_buildflags.h"
+
+#if !BUILDFLAG(USE_COLOR_PIPELINE)
+#include "ui/native_theme/native_theme.h"  // nogncheck
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+#endif
 
 // clang-format off
 #define CROSS_PLATFORM_COLOR_IDS \
   /* Core color concepts */ \
-  E(kColorAccent, kUiColorsStart), \
-  E(kColorAlertHighSeverity), \
-  E(kColorAlertLowSeverity), \
-  E(kColorAlertMediumSeverity), \
-  E(kColorBorderAndSeparatorForeground), \
-  E(kColorDisabledForeground), \
-  E(kColorItemSelectionBackground), \
-  E(kColorPrimaryBackground), \
-  E(kColorPrimaryForeground), \
-  E(kColorSecondaryForeground), \
-  E(kColorSubtleEmphasisBackground), \
-  E(kColorTextSelectionBackground), \
+  E_CPONLY(kColorAccent, kUiColorsStart) \
+  E(kColorAlertHighSeverity, NativeTheme::kColorId_AlertSeverityHigh) \
+  E(kColorAlertLowSeverity, NativeTheme::kColorId_AlertSeverityLow) \
+  E(kColorAlertMediumSeverity, NativeTheme::kColorId_AlertSeverityMedium) \
+  E_CPONLY(kColorBorderAndSeparatorForeground) \
+  E_CPONLY(kColorDisabledForeground) \
+  E_CPONLY(kColorItemSelectionBackground) \
+  E_CPONLY(kColorPrimaryBackground) \
+  E_CPONLY(kColorPrimaryForeground) \
+  E_CPONLY(kColorSecondaryForeground) \
+  E_CPONLY(kColorSubtleEmphasisBackground) \
+  E_CPONLY(kColorTextSelectionBackground) \
   \
   /* Further UI element colors */ \
-  E(kColorBubbleBackground), \
-  E(kColorBubbleFooterBackground), \
-  E(kColorButtonBackground), \
-  E(kColorButtonBorder), \
-  E(kColorButtonDisabledForeground), \
-  E(kColorButtonForeground), \
-  E(kColorButtonPressedBackground), \
-  E(kColorButtonProminentBackground), \
-  E(kColorButtonProminentDisabledBackground), \
-  E(kColorButtonProminentFocusedBackground), \
-  E(kColorButtonProminentForeground), \
-  E(kColorButtonUncheckedForeground), \
-  E(kColorDialogBackground), \
-  E(kColorDialogForeground), \
-  E(kColorFocusableBorderFocused), \
-  E(kColorFocusableBorderUnfocused), \
-  E(kColorIcon), \
-  E(kColorLabelDisabledForeground), \
-  E(kColorLabelForeground), \
-  E(kColorLabelSecondaryForeground), \
-  E(kColorLabelSelectionBackground), \
-  E(kColorLabelSelectionForeground), \
-  E(kColorLinkDisabledForeground), \
-  E(kColorLinkForeground), \
-  E(kColorLinkPressedForeground), \
-  E(kColorMenuBackground), \
-  E(kColorMenuBorder), \
-  E(kColorMenuItemAlertedBackground), \
-  E(kColorMenuItemDisabledForeground), \
-  E(kColorMenuItemForeground), \
-  E(kColorMenuItemHighlightedBackground), \
-  E(kColorMenuItemHighlightedForeground), \
-  E(kColorMenuItemSecondaryForeground), \
-  E(kColorMenuItemSelectedBackground), \
-  E(kColorMenuItemSelectedForeground), \
-  E(kColorMenuSeparator), \
-  E(kColorTabContentSeparator), \
-  E(kColorTabForeground), \
-  E(kColorTabSelectedForeground), \
-  E(kColorTableBackground), \
-  E(kColorTableForeground), \
-  E(kColorTableGroupingIndicator), \
-  E(kColorTableHeaderBackground), \
-  E(kColorTableHeaderForeground), \
-  E(kColorTableHeaderSeparator), \
-  E(kColorTableSelectedFocusedBackground), \
-  E(kColorTableSelectedFocusedForeground), \
-  E(kColorTableSelectedUnfocusedBackground), \
-  E(kColorTableSelectedUnfocusedForeground), \
-  E(kColorTextfieldBackground), \
-  E(kColorTextfieldDisabledBackground), \
-  E(kColorTextfieldDisabledForeground), \
-  E(kColorTextfieldForeground), \
-  E(kColorTextfieldSelectionBackground), \
-  E(kColorTextfieldSelectionForeground), \
-  E(kColorThrobber), \
-  E(kColorTooltipBackground), \
-  E(kColorTooltipForeground), \
-  E(kColorTreeBackground), \
-  E(kColorTreeNodeForeground), \
-  E(kColorTreeNodeSelectedFocusedBackground), \
-  E(kColorTreeNodeSelectedFocusedForeground), \
-  E(kColorTreeNodeSelectedUnfocusedBackground), \
-  E(kColorTreeNodeSelectedUnfocusedForeground), \
-  E(kColorWindowBackground)
+  E(kColorBubbleBackground, NativeTheme::kColorId_BubbleBackground) \
+  E(kColorBubbleFooterBackground, \
+    NativeTheme::kColorId_BubbleFooterBackground) \
+  E(kColorButtonBackground, NativeTheme::kColorId_DialogBackground) \
+  E(kColorButtonBorder, NativeTheme::kColorId_ButtonBorderColor) \
+  E(kColorButtonDisabledForeground, NativeTheme::kColorId_ButtonDisabledColor) \
+  E(kColorButtonForeground, NativeTheme::kColorId_ButtonEnabledColor) \
+  /* TODO(https://crbug.com/1003612): Map this to old color id. */ \
+  E_CPONLY(kColorButtonPressedBackground) \
+  E(kColorButtonProminentBackground, \
+    NativeTheme::kColorId_ProminentButtonColor) \
+  E(kColorButtonProminentDisabledBackground, \
+    NativeTheme::kColorId_ProminentButtonDisabledColor) \
+  E(kColorButtonProminentFocusedBackground, \
+    NativeTheme::kColorId_ProminentButtonFocusedColor) \
+  E(kColorButtonProminentForeground, \
+    NativeTheme::kColorId_TextOnProminentButtonColor) \
+  E(kColorButtonUncheckedForeground, \
+    NativeTheme::kColorId_ButtonUncheckedColor) \
+  E(kColorDialogBackground, NativeTheme::kColorId_DialogBackground) \
+  E(kColorDialogForeground, NativeTheme::kColorId_DialogForeground) \
+  E(kColorFocusableBorderFocused, NativeTheme::kColorId_FocusedBorderColor) \
+  E(kColorFocusableBorderUnfocused, \
+    NativeTheme::kColorId_UnfocusedBorderColor) \
+  E(kColorIcon, NativeTheme::kColorId_DefaultIconColor) \
+  E(kColorLabelDisabledForeground, NativeTheme::kColorId_LabelDisabledColor) \
+  E(kColorLabelForeground, NativeTheme::kColorId_LabelEnabledColor) \
+  E(kColorLabelSecondaryForeground, NativeTheme::kColorId_LabelSecondaryColor) \
+  E(kColorLabelSelectionBackground, \
+    NativeTheme::kColorId_LabelTextSelectionBackgroundFocused) \
+  E(kColorLabelSelectionForeground, \
+    NativeTheme::kColorId_LabelTextSelectionColor) \
+  E(kColorLinkDisabledForeground, NativeTheme::kColorId_LinkDisabled) \
+  E(kColorLinkForeground, NativeTheme::kColorId_LinkEnabled) \
+  E(kColorLinkPressedForeground, NativeTheme::kColorId_LinkPressed) \
+  E(kColorMenuBackground, NativeTheme::kColorId_MenuBackgroundColor) \
+  E(kColorMenuBorder, NativeTheme::kColorId_MenuBorderColor) \
+  E(kColorMenuItemAlertedBackground, \
+    NativeTheme::kColorId_MenuItemAlertBackgroundColor) \
+  E(kColorMenuItemDisabledForeground, \
+    NativeTheme::kColorId_DisabledMenuItemForegroundColor) \
+  E(kColorMenuItemForeground, \
+    NativeTheme::kColorId_EnabledMenuItemForegroundColor) \
+  E(kColorMenuItemHighlightedBackground, \
+    NativeTheme::kColorId_HighlightedMenuItemBackgroundColor) \
+  E(kColorMenuItemHighlightedForeground, \
+    NativeTheme::kColorId_HighlightedMenuItemForegroundColor) \
+  E(kColorMenuItemSecondaryForeground, \
+    NativeTheme::kColorId_MenuItemMinorTextColor) \
+  E(kColorMenuItemSelectedBackground, \
+    NativeTheme::kColorId_FocusedMenuItemBackgroundColor) \
+  E(kColorMenuItemSelectedForeground, \
+    NativeTheme::kColorId_SelectedMenuItemForegroundColor) \
+  E(kColorMenuSeparator, NativeTheme::kColorId_MenuSeparatorColor) \
+  E(kColorTabContentSeparator, NativeTheme::kColorId_TabBottomBorder) \
+  E(kColorTabForeground, NativeTheme::kColorId_TabTitleColorInactive) \
+  E(kColorTabSelectedForeground, \
+    NativeTheme::kColorId_TabTitleColorActive) \
+  E(kColorTableBackground, NativeTheme::kColorId_TableBackground) \
+  E(kColorTableForeground, NativeTheme::kColorId_TableText) \
+  E(kColorTableGroupingIndicator, \
+    NativeTheme::kColorId_TableGroupingIndicatorColor) \
+  E(kColorTableHeaderBackground, NativeTheme::kColorId_TableHeaderBackground) \
+  E(kColorTableHeaderForeground, NativeTheme::kColorId_TableHeaderText) \
+  E(kColorTableHeaderSeparator, NativeTheme::kColorId_TableHeaderSeparator) \
+  E(kColorTableSelectedFocusedBackground, \
+    NativeTheme::kColorId_TableSelectionBackgroundFocused) \
+  E(kColorTableSelectedFocusedForeground, \
+    NativeTheme::kColorId_TableSelectedText) \
+  E(kColorTableSelectedUnfocusedBackground, \
+    NativeTheme::kColorId_TableSelectionBackgroundUnfocused) \
+  E(kColorTableSelectedUnfocusedForeground, \
+    NativeTheme::kColorId_TableSelectedTextUnfocused) \
+  E(kColorTextfieldBackground, \
+    NativeTheme::kColorId_TextfieldDefaultBackground) \
+  E(kColorTextfieldDisabledBackground, \
+    NativeTheme::kColorId_TextfieldReadOnlyBackground) \
+  E(kColorTextfieldDisabledForeground, \
+    NativeTheme::kColorId_TextfieldReadOnlyColor) \
+  E(kColorTextfieldForeground, NativeTheme::kColorId_TextfieldDefaultColor) \
+  E(kColorTextfieldSelectionBackground, \
+    NativeTheme::kColorId_TextfieldSelectionBackgroundFocused) \
+  E(kColorTextfieldSelectionForeground, \
+    NativeTheme::kColorId_TextfieldSelectionColor) \
+  E(kColorThrobber, NativeTheme::kColorId_ThrobberSpinningColor) \
+  E(kColorTooltipBackground, NativeTheme::kColorId_TooltipBackground) \
+  E(kColorTooltipForeground, NativeTheme::kColorId_TooltipText) \
+  E(kColorTreeBackground, NativeTheme::kColorId_TreeBackground) \
+  E(kColorTreeNodeForeground, NativeTheme::kColorId_TreeText) \
+  E(kColorTreeNodeSelectedFocusedBackground, \
+    NativeTheme::kColorId_TreeSelectionBackgroundFocused) \
+  E(kColorTreeNodeSelectedFocusedForeground, \
+    NativeTheme::kColorId_TreeSelectedText) \
+  E(kColorTreeNodeSelectedUnfocusedBackground, \
+    NativeTheme::kColorId_TreeSelectionBackgroundUnfocused) \
+  E(kColorTreeNodeSelectedUnfocusedForeground, \
+    NativeTheme::kColorId_TreeSelectedTextUnfocused) \
+  E(kColorWindowBackground, NativeTheme::kColorId_WindowBackground)
 
 #if defined(OS_WIN)
 #define WIN_COLOR_IDS \
   /* Windows native colors */ \
-  E(kColorNative3dDkShadow), \
-  E(kColorNative3dLight), \
-  E(kColorNativeActiveBorder), \
-  E(kColorNativeActiveCaption), \
-  E(kColorNativeAppWorkspace), \
-  E(kColorNativeBackground), \
-  E(kColorNativeBtnFace), \
-  E(kColorNativeBtnHighlight), \
-  E(kColorNativeBtnShadow), \
-  E(kColorNativeBtnText), \
-  E(kColorNativeCaptionText), \
-  E(kColorNativeGradientActiveCaption), \
-  E(kColorNativeGradientInactiveCaption), \
-  E(kColorNativeGrayText), \
-  E(kColorNativeHighlight), \
-  E(kColorNativeHighlightText), \
-  E(kColorNativeHotlight), \
-  E(kColorNativeInactiveBorder), \
-  E(kColorNativeInactiveCaption), \
-  E(kColorNativeInactiveCaptionText), \
-  E(kColorNativeInfoBk), \
-  E(kColorNativeInfoText), \
-  E(kColorNativeMenu), \
-  E(kColorNativeMenuBar), \
-  E(kColorNativeMenuHilight), \
-  E(kColorNativeMenuText), \
-  E(kColorNativeScrollbar), \
-  E(kColorNativeWindow), \
-  E(kColorNativeWindowFrame), \
-  E(kColorNativeWindowText)
+  E(kColorNative3dDkShadow, COLOR_3DDKSHADOW) \
+  E(kColorNative3dLight, COLOR_3DLIGHT) \
+  E(kColorNativeActiveBorder, COLOR_ACTIVEBORDER) \
+  E(kColorNativeActiveCaption, COLOR_ACTIVECAPTION) \
+  E(kColorNativeAppWorkspace, COLOR_APPWORKSPACE) \
+  E(kColorNativeBackground, COLOR_BACKGROUND) \
+  E(kColorNativeBtnFace, COLOR_BTNFACE) \
+  E(kColorNativeBtnHighlight, COLOR_BTNHIGHLIGHT) \
+  E(kColorNativeBtnShadow, COLOR_BTNSHADOW) \
+  E(kColorNativeBtnText, COLOR_BTNTEXT) \
+  E(kColorNativeCaptionText, COLOR_CAPTIONTEXT) \
+  E(kColorNativeGradientActiveCaption, COLOR_GRADIENTACTIVECAPTION) \
+  E(kColorNativeGradientInactiveCaption, COLOR_GRADIENTINACTIVECAPTION) \
+  E(kColorNativeGrayText, COLOR_GRAYTEXT) \
+  E(kColorNativeHighlight, COLOR_HIGHLIGHT) \
+  E(kColorNativeHighlightText, COLOR_HIGHLIGHTTEXT) \
+  E(kColorNativeHotlight, COLOR_HOTLIGHT) \
+  E(kColorNativeInactiveBorder, COLOR_INACTIVEBORDER) \
+  E(kColorNativeInactiveCaption, COLOR_INACTIVECAPTION) \
+  E(kColorNativeInactiveCaptionText, COLOR_INACTIVECAPTIONTEXT) \
+  E(kColorNativeInfoBk, COLOR_INFOBK) \
+  E(kColorNativeInfoText, COLOR_INFOTEXT) \
+  E(kColorNativeMenu, COLOR_MENU) \
+  E(kColorNativeMenuBar, COLOR_MENUBAR) \
+  E(kColorNativeMenuHilight, COLOR_MENUHILIGHT) \
+  E(kColorNativeMenuText, COLOR_MENUTEXT) \
+  E(kColorNativeScrollbar, COLOR_SCROLLBAR) \
+  E(kColorNativeWindow, COLOR_WINDOW) \
+  E(kColorNativeWindowFrame, COLOR_WINDOWFRAME) \
+  E(kColorNativeWindowText, COLOR_WINDOWTEXT)
 #endif
 
 #if defined(OS_WIN)
 #define COLOR_IDS \
-  CROSS_PLATFORM_COLOR_IDS, \
+  CROSS_PLATFORM_COLOR_IDS \
   WIN_COLOR_IDS
 #else
 #define COLOR_IDS CROSS_PLATFORM_COLOR_IDS
@@ -144,10 +187,11 @@
 // define enum values from kUiColorsEnd.  Values named beginning with "kColor"
 // represent the actual colors; the rest are markers.
 using ColorId = int;
+// clang-format off
 enum ColorIds : ColorId {
   kUiColorsStart = 0,
 
-  COLOR_IDS,
+  COLOR_IDS
 
   // TODO(pkasting): Other native colors
 
@@ -158,9 +202,12 @@
   // verify that color IDs and color set IDs are not interchanged.
   kUiColorsLast = 0xffff
 };
+// clang-format on
 
 #include "ui/color/color_id_macros.inc"
 
+#if BUILDFLAG(USE_COLOR_PIPELINE)
+
 // ColorSetId contains identifiers for all distinct color sets known to the core
 // UI layer.  As with ColorId, embedders can extend this enum with additional
 // values that are understood by the ColorProvider implementation.  Embedders
@@ -190,6 +237,8 @@
 // Verifies that |id| is a color set ID, not a color ID.
 #define DCHECK_COLOR_SET_ID_VALID(id) DCHECK_GE(id, kUiColorSetsStart)
 
+#endif  // BUILDFLAG(USE_COLOR_PIPELINE)
+
 }  // namespace ui
 
 #endif  // UI_COLOR_COLOR_ID_H_
diff --git a/ui/color/color_id_macros.inc b/ui/color/color_id_macros.inc
index 97f20f9..66e4a492 100644
--- a/ui/color/color_id_macros.inc
+++ b/ui/color/color_id_macros.inc
@@ -11,17 +11,39 @@
 #if !defined(COLOR_ID_MACROS_DEFINED)
 #define COLOR_ID_MACROS_DEFINED
 #if defined(STRINGIZE_COLOR_IDS)
-#define E1(enum_name) #enum_name
-#define E2(enum_name, enum_value) #enum_name
+// Convert first token to string, throw away the rest.
+#define D1(enum_name) #enum_name
+#define D2(enum_name, enum_value) #enum_name
 #else
-#define E1(enum_name) enum_name
-#define E2(enum_name, enum_value) enum_name = enum_value
-#endif
-#define GET_E(_1, _2, macro_name, ...) macro_name
-#define E(...) GET_E(__VA_ARGS__, E2, E1)(__VA_ARGS__)
+// Declare enum with optional assigned value.
+#define D1(enum_name) enum_name
+#define D2(enum_name, enum_value) enum_name = enum_value
+#endif  // defined(STRINGIZE_COLOR_IDS)
+// Select which token in the declaration is the assigned value.
+#if BUILDFLAG(USE_COLOR_PIPELINE)
+// Use first and optional third token, ignore optional second.
+#define E1(enum_name) D1(enum_name)
+#define E2(enum_name, old_enum_name) D1(enum_name)
+#define E3(enum_name, old_enum_name, enum_value) D2(enum_name, enum_value)
+#define E_CPONLY(...) E(__VA_ARGS__)
 #else
+// Use first and mandatory second token, ignore optional third.
+#define E1(enum_name) \
+  static_assert(false, "New-style color compiled for !USE_COLOR_PIPELINE")
+#define E2(enum_name, old_enum_name) D2(enum_name, old_enum_name)
+#define E3(enum_name, old_enum_name, enum_value) D2(enum_name, old_enum_name)
+// Ignore any new color id defined only for color pipeline enabled.
+#define E_CPONLY(...)
+#endif  // BUILDFLAG(USE_COLOR_PIPELINE)
+#define GET_E(_1, _2, _3, macro_name, ...) macro_name
+#define E(...) GET_E(__VA_ARGS__, E3, E2, E1)(__VA_ARGS__),
+#else
+#undef D1
+#undef D2
 #undef E1
 #undef E2
+#undef E3
+#undef E_CPONLY
 #undef GET_E
 #undef E
 #undef COLOR_ID_MACROS_DEFINED
diff --git a/ui/color/color_provider.h b/ui/color/color_provider.h
index c735a5e..1c60ea9 100644
--- a/ui/color/color_provider.h
+++ b/ui/color/color_provider.h
@@ -8,13 +8,21 @@
 #include <forward_list>
 #include <map>
 
+#include "ui/color/color_buildflags.h"
+
+#if BUILDFLAG(USE_COLOR_PIPELINE)
 #include "base/component_export.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/color/color_id.h"
 #include "ui/color/color_mixer.h"
+#else
+#include "ui/base/theme_provider.h"  // nogncheck
+#endif
 
 namespace ui {
 
+#if BUILDFLAG(USE_COLOR_PIPELINE)
+
 // A ColorProvider holds the complete pipeline of ColorMixers that compute
 // result colors for UI elements.  ColorProvider is meant to be a long-lived
 // object whose internal list of mixers does not change after initial
@@ -49,6 +57,12 @@
   mutable std::map<ColorId, SkColor> cache_;
 };
 
+#else
+
+using ColorProvider = ThemeProvider;
+
+#endif  // !BUILDFLAG(USE_COLOR_PIPELINE)
+
 }  // namespace ui
 
 #endif  // UI_COLOR_COLOR_PROVIDER_H_
diff --git a/ui/views/widget/native_widget_mac.mm b/ui/views/widget/native_widget_mac.mm
index a1beb41..f34479e 100644
--- a/ui/views/widget/native_widget_mac.mm
+++ b/ui/views/widget/native_widget_mac.mm
@@ -944,11 +944,8 @@
       NativeWidgetMacNSWindowHost::GetFromNativeView(native_view);
   if (!window_host)
     return nullptr;
-  while (window_host->parent()) {
-    if (window_host->native_widget_mac()->GetWidget()->is_top_level())
-      break;
+  while (window_host->parent())
     window_host = window_host->parent();
-  }
   return window_host->native_widget_mac();
 }
 
diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm
index ff72483..6735f70 100644
--- a/ui/views/widget/native_widget_mac_unittest.mm
+++ b/ui/views/widget/native_widget_mac_unittest.mm
@@ -737,7 +737,6 @@
   Widget* child = new Widget;
   Widget::InitParams init_params;
   init_params.parent = anchor_view.get();
-  init_params.child = true;
   init_params.type = Widget::InitParams::TYPE_POPUP;
   child->Init(std::move(init_params));
   return child;
@@ -754,7 +753,6 @@
   EXPECT_EQ(1u, children.size());
 
   Widget* child = AttachPopupToNativeParent(native_parent);
-  EXPECT_FALSE(child->is_top_level());
   TestWidgetObserver child_observer(child);
 
   // GetTopLevelNativeWidget() will go up through |native_parent|'s Widget.
diff --git a/ui/views_content_client/views_content_browser_client.cc b/ui/views_content_client/views_content_browser_client.cc
index 35936ba..3ece6bf 100644
--- a/ui/views_content_client/views_content_browser_client.cc
+++ b/ui/views_content_client/views_content_browser_client.cc
@@ -6,7 +6,6 @@
 
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
-#include "storage/browser/quota/quota_settings.h"
 #include "ui/views_content_client/views_content_client_main_parts.h"
 
 namespace ui {
@@ -24,13 +23,4 @@
   return ViewsContentClientMainParts::Create(parameters, views_content_client_);
 }
 
-void ViewsContentBrowserClient::GetQuotaSettings(
-    content::BrowserContext* context,
-    content::StoragePartition* partition,
-    storage::OptionalQuotaSettingsCallback callback) {
-  storage::GetNominalDynamicSettings(
-      partition->GetPath(), context->IsOffTheRecord(),
-      storage::GetDefaultDeviceInfoHelper(), std::move(callback));
-}
-
 }  // namespace ui
diff --git a/ui/views_content_client/views_content_browser_client.h b/ui/views_content_client/views_content_browser_client.h
index 5159aa85..cea9c03c 100644
--- a/ui/views_content_client/views_content_browser_client.h
+++ b/ui/views_content_client/views_content_browser_client.h
@@ -9,7 +9,6 @@
 
 #include "base/macros.h"
 #include "content/public/browser/content_browser_client.h"
-#include "storage/browser/quota/quota_settings.h"
 
 namespace ui {
 
@@ -24,10 +23,6 @@
   // content::ContentBrowserClient:
   std::unique_ptr<content::BrowserMainParts> CreateBrowserMainParts(
       const content::MainFunctionParams& parameters) override;
-  void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      storage::OptionalQuotaSettingsCallback callback) override;
 
  private:
   ViewsContentClient* views_content_client_;
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index a5a13384..9bd7a857 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -102,6 +102,8 @@
     "app/content_main_delegate_impl.cc",
     "app/content_main_delegate_impl.h",
     "app/main.cc",
+    "browser/autofill_client_impl.cc",
+    "browser/autofill_client_impl.h",
     "browser/browser_context_impl.cc",
     "browser/browser_context_impl.h",
     "browser/browser_main_parts_impl.cc",
@@ -158,6 +160,8 @@
     "renderer/content_renderer_client_impl.h",
     "renderer/ssl_error_helper.cc",
     "renderer/ssl_error_helper.h",
+    "renderer/weblayer_render_frame_observer.cc",
+    "renderer/weblayer_render_frame_observer.h",
     "utility/content_utility_client_impl.cc",
     "utility/content_utility_client_impl.h",
   ]
@@ -198,6 +202,9 @@
     "//base:base_static",
     "//base/third_party/dynamic_annotations",
     "//cc",
+    "//components/autofill/content/browser",
+    "//components/autofill/content/renderer",
+    "//components/autofill/core/browser",
     "//components/crash/content/app",
     "//components/crash/content/browser",
     "//components/embedder_support:switches",
diff --git a/weblayer/browser/DEPS b/weblayer/browser/DEPS
index cd242a9..12982e9 100644
--- a/weblayer/browser/DEPS
+++ b/weblayer/browser/DEPS
@@ -5,6 +5,9 @@
   # that implementation and remove the need for this dependency.
   "+android_webview/grit",
   "+cc",
+  "+components/autofill/content/browser",
+  "+components/autofill/core/browser",
+  "+components/autofill/core/common",
   "+components/crash/content/browser",
   "+components/embedder_support",
   "+components/prefs",
diff --git a/weblayer/browser/autofill_browsertest.cc b/weblayer/browser/autofill_browsertest.cc
new file mode 100644
index 0000000..870b935
--- /dev/null
+++ b/weblayer/browser/autofill_browsertest.cc
@@ -0,0 +1,85 @@
+// Copyright 2019 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 "weblayer/test/weblayer_browser_test.h"
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/common/form_data.h"
+#include "components/autofill/core/common/form_field_data.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "weblayer/shell/browser/shell.h"
+#include "weblayer/test/weblayer_browser_test_utils.h"
+
+namespace weblayer {
+
+namespace {
+
+// Method that is passed to the autofill system to be invoked on detection of
+// autofill forms.
+// NOTE: This method can currently be invoked only once within the context of
+// a given test. If that restriction ever needs to be relaxed, it could be
+// done by changing |quit_closure| to a global that could be reset between
+// expected invocations of the method.
+void OnReceivedFormDataFromRenderer(base::OnceClosure quit_closure,
+                                    autofill::FormData* output,
+                                    const autofill::FormData& form) {
+  ASSERT_TRUE(quit_closure);
+
+  *output = form;
+  std::move(quit_closure).Run();
+}
+
+}  // namespace
+
+// Cross-platform tests of autofill parsing in the renderer and communication
+// to the browser. Does not test integration with any platform's underlying
+// system autofill mechanisms.
+class AutofillBrowserTest : public WebLayerBrowserTest {
+ public:
+  AutofillBrowserTest() = default;
+  ~AutofillBrowserTest() override = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AutofillBrowserTest);
+};
+
+// Tests that the renderer detects a password form and passes the appropriate
+// data to the browser.
+IN_PROC_BROWSER_TEST_F(AutofillBrowserTest, TestPasswordFormDetection) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  base::RunLoop run_loop;
+  autofill::FormData observed_form;
+
+  InitializeAutofillWithEventForwarding(
+      shell(), base::BindRepeating(&OnReceivedFormDataFromRenderer,
+                                   run_loop.QuitClosure(), &observed_form));
+
+  GURL password_form_url =
+      embedded_test_server()->GetURL("/simple_password_form.html");
+  NavigateAndWaitForCompletion(password_form_url, shell());
+
+  // Focus the username field (note that a user gesture is necessary for
+  // autofill to trigger) ...
+  ExecuteScriptWithUserGesture(
+      shell(), "document.getElementById('username_field').focus();");
+
+  // ... and wait for the parsed data to be passed to the browser.
+  run_loop.Run();
+
+  // Verify that that the form data matches that of the document.
+  EXPECT_EQ(base::ASCIIToUTF16("testform"), observed_form.name);
+  EXPECT_EQ(password_form_url.spec(), observed_form.url);
+
+  auto fields = observed_form.fields;
+  EXPECT_EQ(2u, fields.size());
+  autofill::FormFieldData username_field = fields[0];
+  EXPECT_EQ(base::ASCIIToUTF16("username_field"), username_field.name);
+  autofill::FormFieldData password_field = fields[1];
+  EXPECT_EQ(base::ASCIIToUTF16("password_field"), password_field.name);
+}
+
+}  // namespace weblayer
diff --git a/weblayer/browser/autofill_client_impl.cc b/weblayer/browser/autofill_client_impl.cc
new file mode 100644
index 0000000..2f6db72
--- /dev/null
+++ b/weblayer/browser/autofill_client_impl.cc
@@ -0,0 +1,287 @@
+// Copyright 2019 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 "weblayer/browser/autofill_client_impl.h"
+
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/ssl_status.h"
+#include "content/public/browser/web_contents.h"
+
+namespace weblayer {
+
+AutofillClientImpl::~AutofillClientImpl() = default;
+
+autofill::PersonalDataManager* AutofillClientImpl::GetPersonalDataManager() {
+  NOTREACHED();
+  return nullptr;
+}
+
+autofill::AutocompleteHistoryManager*
+AutofillClientImpl::GetAutocompleteHistoryManager() {
+  NOTREACHED();
+  return nullptr;
+}
+
+PrefService* AutofillClientImpl::GetPrefs() {
+  NOTREACHED();
+  return nullptr;
+}
+
+syncer::SyncService* AutofillClientImpl::GetSyncService() {
+  NOTREACHED();
+  return nullptr;
+}
+
+signin::IdentityManager* AutofillClientImpl::GetIdentityManager() {
+  NOTREACHED();
+  return nullptr;
+}
+
+autofill::FormDataImporter* AutofillClientImpl::GetFormDataImporter() {
+  NOTREACHED();
+  return nullptr;
+}
+
+autofill::payments::PaymentsClient* AutofillClientImpl::GetPaymentsClient() {
+  NOTREACHED();
+  return nullptr;
+}
+
+autofill::SmsClient* AutofillClientImpl::GetSmsClient() {
+  NOTREACHED();
+  return nullptr;
+}
+
+autofill::StrikeDatabase* AutofillClientImpl::GetStrikeDatabase() {
+  NOTREACHED();
+  return nullptr;
+}
+
+ukm::UkmRecorder* AutofillClientImpl::GetUkmRecorder() {
+  NOTREACHED();
+  return nullptr;
+}
+
+ukm::SourceId AutofillClientImpl::GetUkmSourceId() {
+  NOTREACHED();
+  return ukm::kInvalidSourceId;
+}
+
+autofill::AddressNormalizer* AutofillClientImpl::GetAddressNormalizer() {
+  NOTREACHED();
+  return nullptr;
+}
+
+security_state::SecurityLevel
+AutofillClientImpl::GetSecurityLevelForUmaHistograms() {
+  NOTREACHED();
+  return security_state::SecurityLevel::SECURITY_LEVEL_COUNT;
+}
+
+void AutofillClientImpl::ShowAutofillSettings(bool show_credit_card_settings) {
+  NOTREACHED();
+}
+
+#if !defined(OS_ANDROID)
+std::vector<std::string>
+AutofillClientImpl::GetMerchantWhitelistForVirtualCards() {
+  NOTREACHED();
+  return std::vector<std::string>();
+}
+
+std::vector<std::string>
+AutofillClientImpl::GetBinRangeWhitelistForVirtualCards() {
+  NOTREACHED();
+  return std::vector<std::string>();
+}
+#endif
+
+void AutofillClientImpl::ShowUnmaskPrompt(
+    const autofill::CreditCard& card,
+    UnmaskCardReason reason,
+    base::WeakPtr<autofill::CardUnmaskDelegate> delegate) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::OnUnmaskVerificationResult(PaymentsRpcResult result) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::ShowLocalCardMigrationDialog(
+    base::OnceClosure show_migration_dialog_closure) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::ConfirmMigrateLocalCardToCloud(
+    const autofill::LegalMessageLines& legal_message_lines,
+    const std::string& user_email,
+    const std::vector<autofill::MigratableCreditCard>& migratable_credit_cards,
+    LocalCardMigrationCallback start_migrating_cards_callback) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::ShowLocalCardMigrationResults(
+    const bool has_server_error,
+    const base::string16& tip_message,
+    const std::vector<autofill::MigratableCreditCard>& migratable_credit_cards,
+    MigrationDeleteCardCallback delete_local_card_callback) {
+  NOTREACHED();
+}
+
+#if !defined(OS_ANDROID)
+void AutofillClientImpl::ShowWebauthnOfferDialog(
+    WebauthnDialogCallback offer_dialog_callback) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::ShowWebauthnVerifyPendingDialog(
+    WebauthnDialogCallback verify_pending_dialog_callback) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::UpdateWebauthnOfferDialogWithError() {
+  NOTREACHED();
+}
+
+bool AutofillClientImpl::CloseWebauthnDialog() {
+  NOTREACHED();
+  return false;
+}
+
+void AutofillClientImpl::ConfirmSaveUpiIdLocally(
+    const std::string& upi_id,
+    base::OnceCallback<void(bool user_decision)> callback) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::OfferVirtualCardOptions(
+    const std::vector<autofill::CreditCard*>& candidates,
+    base::OnceCallback<void(const std::string&)> callback) {
+  NOTREACHED();
+}
+#endif
+
+void AutofillClientImpl::ConfirmSaveAutofillProfile(
+    const autofill::AutofillProfile& profile,
+    base::OnceClosure callback) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::ConfirmSaveCreditCardLocally(
+    const autofill::CreditCard& card,
+    SaveCreditCardOptions options,
+    LocalSaveCardPromptCallback callback) {
+  NOTREACHED();
+}
+
+#if defined(OS_ANDROID)
+void AutofillClientImpl::ConfirmAccountNameFixFlow(
+    base::OnceCallback<void(const base::string16&)> callback) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::ConfirmExpirationDateFixFlow(
+    const autofill::CreditCard& card,
+    base::OnceCallback<void(const base::string16&, const base::string16&)>
+        callback) {
+  NOTREACHED();
+}
+#endif
+
+void AutofillClientImpl::ConfirmSaveCreditCardToCloud(
+    const autofill::CreditCard& card,
+    const autofill::LegalMessageLines& legal_message_lines,
+    SaveCreditCardOptions options,
+    UploadSaveCardPromptCallback callback) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::CreditCardUploadCompleted(bool card_saved) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::ConfirmCreditCardFillAssist(
+    const autofill::CreditCard& card,
+    base::OnceClosure callback) {
+  NOTREACHED();
+}
+
+bool AutofillClientImpl::HasCreditCardScanFeature() {
+  NOTREACHED();
+  return false;
+}
+
+void AutofillClientImpl::ScanCreditCard(CreditCardScanCallback callback) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::ShowAutofillPopup(
+    const gfx::RectF& element_bounds,
+    base::i18n::TextDirection text_direction,
+    const std::vector<autofill::Suggestion>& suggestions,
+    bool /*unused_autoselect_first_suggestion*/,
+    autofill::PopupType popup_type,
+    base::WeakPtr<autofill::AutofillPopupDelegate> delegate) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::UpdateAutofillPopupDataListValues(
+    const std::vector<base::string16>& values,
+    const std::vector<base::string16>& labels) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::HideAutofillPopup() {
+  // This is invoked on the user moving away from an autofill context (e.g., a
+  // navigation finishing or a tab being hidden). As all showing/hiding of
+  // autofill UI in WebLayer is driven by the system, there is no action to
+  // take.
+}
+
+bool AutofillClientImpl::IsAutocompleteEnabled() {
+  NOTREACHED();
+  return false;
+}
+
+void AutofillClientImpl::PropagateAutofillPredictions(
+    content::RenderFrameHost* rfh,
+    const std::vector<autofill::FormStructure*>& forms) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::DidFillOrPreviewField(
+    const base::string16& autofilled_value,
+    const base::string16& profile_full_name) {
+  NOTREACHED();
+}
+
+bool AutofillClientImpl::IsContextSecure() {
+  NOTREACHED();
+  return false;
+}
+
+bool AutofillClientImpl::ShouldShowSigninPromo() {
+  NOTREACHED();
+  return false;
+}
+
+bool AutofillClientImpl::AreServerCardsSupported() {
+  NOTREACHED();
+  return false;
+}
+
+void AutofillClientImpl::ExecuteCommand(int id) {
+  NOTREACHED();
+}
+
+void AutofillClientImpl::LoadRiskData(
+    base::OnceCallback<void(const std::string&)> callback) {
+  NOTREACHED();
+}
+
+AutofillClientImpl::AutofillClientImpl(content::WebContents* web_contents) {}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(AutofillClientImpl)
+
+}  // namespace weblayer
diff --git a/weblayer/browser/autofill_client_impl.h b/weblayer/browser/autofill_client_impl.h
new file mode 100644
index 0000000..f522b682
--- /dev/null
+++ b/weblayer/browser/autofill_client_impl.h
@@ -0,0 +1,143 @@
+// Copyright 2019 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 WEBLAYER_BROWSER_AUTOFILL_CLIENT_IMPL_H_
+#define WEBLAYER_BROWSER_AUTOFILL_CLIENT_IMPL_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace weblayer {
+
+// A minimal implementation of autofill::AutofillClient to satisfy the minor
+// touchpoints between the autofill implementation and its client that get
+// exercised within the WebLayer autofill flow.
+class AutofillClientImpl
+    : public autofill::AutofillClient,
+      public content::WebContentsUserData<AutofillClientImpl> {
+ public:
+  ~AutofillClientImpl() override;
+
+  // AutofillClient:
+  autofill::PersonalDataManager* GetPersonalDataManager() override;
+  autofill::AutocompleteHistoryManager* GetAutocompleteHistoryManager()
+      override;
+  PrefService* GetPrefs() override;
+  syncer::SyncService* GetSyncService() override;
+  signin::IdentityManager* GetIdentityManager() override;
+  autofill::FormDataImporter* GetFormDataImporter() override;
+  autofill::payments::PaymentsClient* GetPaymentsClient() override;
+  autofill::SmsClient* GetSmsClient() override;
+  autofill::StrikeDatabase* GetStrikeDatabase() override;
+  ukm::UkmRecorder* GetUkmRecorder() override;
+  ukm::SourceId GetUkmSourceId() override;
+  autofill::AddressNormalizer* GetAddressNormalizer() override;
+  security_state::SecurityLevel GetSecurityLevelForUmaHistograms() override;
+  void ShowAutofillSettings(bool show_credit_card_settings) override;
+
+#if !defined(OS_ANDROID)
+  std::vector<std::string> GetMerchantWhitelistForVirtualCards() override;
+  std::vector<std::string> GetBinRangeWhitelistForVirtualCards() override;
+#endif
+
+  void ShowUnmaskPrompt(
+      const autofill::CreditCard& card,
+      UnmaskCardReason reason,
+      base::WeakPtr<autofill::CardUnmaskDelegate> delegate) override;
+  void OnUnmaskVerificationResult(PaymentsRpcResult result) override;
+  void ShowLocalCardMigrationDialog(
+      base::OnceClosure show_migration_dialog_closure) override;
+  void ConfirmMigrateLocalCardToCloud(
+      const autofill::LegalMessageLines& legal_message_lines,
+      const std::string& user_email,
+      const std::vector<autofill::MigratableCreditCard>&
+          migratable_credit_cards,
+      LocalCardMigrationCallback start_migrating_cards_callback) override;
+  void ShowLocalCardMigrationResults(
+      const bool has_server_error,
+      const base::string16& tip_message,
+      const std::vector<autofill::MigratableCreditCard>&
+          migratable_credit_cards,
+      MigrationDeleteCardCallback delete_local_card_callback) override;
+
+#if !defined(OS_ANDROID)
+  void ShowWebauthnOfferDialog(
+      WebauthnDialogCallback offer_dialog_callback) override;
+  void ShowWebauthnVerifyPendingDialog(
+      WebauthnDialogCallback verify_pending_dialog_callback) override;
+  void UpdateWebauthnOfferDialogWithError() override;
+  bool CloseWebauthnDialog() override;
+  void ConfirmSaveUpiIdLocally(
+      const std::string& upi_id,
+      base::OnceCallback<void(bool user_decision)> callback) override;
+  void OfferVirtualCardOptions(
+      const std::vector<autofill::CreditCard*>& candidates,
+      base::OnceCallback<void(const std::string&)> callback) override;
+#endif
+
+  void ConfirmSaveAutofillProfile(const autofill::AutofillProfile& profile,
+                                  base::OnceClosure callback) override;
+  void ConfirmSaveCreditCardLocally(
+      const autofill::CreditCard& card,
+      SaveCreditCardOptions options,
+      LocalSaveCardPromptCallback callback) override;
+#if defined(OS_ANDROID)
+  void ConfirmAccountNameFixFlow(
+      base::OnceCallback<void(const base::string16&)> callback) override;
+  void ConfirmExpirationDateFixFlow(
+      const autofill::CreditCard& card,
+      base::OnceCallback<void(const base::string16&, const base::string16&)>
+          callback) override;
+#endif
+  void ConfirmSaveCreditCardToCloud(
+      const autofill::CreditCard& card,
+      const autofill::LegalMessageLines& legal_message_lines,
+      SaveCreditCardOptions options,
+      UploadSaveCardPromptCallback callback) override;
+  void CreditCardUploadCompleted(bool card_saved) override;
+  void ConfirmCreditCardFillAssist(const autofill::CreditCard& card,
+                                   base::OnceClosure callback) override;
+  bool HasCreditCardScanFeature() override;
+  void ScanCreditCard(CreditCardScanCallback callback) override;
+  void ShowAutofillPopup(
+      const gfx::RectF& element_bounds,
+      base::i18n::TextDirection text_direction,
+      const std::vector<autofill::Suggestion>& suggestions,
+      bool /*unused_autoselect_first_suggestion*/,
+      autofill::PopupType popup_type,
+      base::WeakPtr<autofill::AutofillPopupDelegate> delegate) override;
+  void UpdateAutofillPopupDataListValues(
+      const std::vector<base::string16>& values,
+      const std::vector<base::string16>& labels) override;
+  void HideAutofillPopup() override;
+  bool IsAutocompleteEnabled() override;
+  void PropagateAutofillPredictions(
+      content::RenderFrameHost* rfh,
+      const std::vector<autofill::FormStructure*>& forms) override;
+  void DidFillOrPreviewField(const base::string16& autofilled_value,
+                             const base::string16& profile_full_name) override;
+  bool IsContextSecure() override;
+  bool ShouldShowSigninPromo() override;
+  bool AreServerCardsSupported() override;
+  void ExecuteCommand(int id) override;
+
+  // RiskDataLoader:
+  void LoadRiskData(
+      base::OnceCallback<void(const std::string&)> callback) override;
+
+ private:
+  explicit AutofillClientImpl(content::WebContents* web_contents);
+  friend class content::WebContentsUserData<AutofillClientImpl>;
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+  DISALLOW_COPY_AND_ASSIGN(AutofillClientImpl);
+};
+
+}  // namespace weblayer
+
+#endif  // WEBLAYER_BROWSER_AUTOFILL_CLIENT_IMPL_H_
diff --git a/weblayer/browser/content_browser_client_impl.cc b/weblayer/browser/content_browser_client_impl.cc
index 83475eac..39e88ed 100644
--- a/weblayer/browser/content_browser_client_impl.cc
+++ b/weblayer/browser/content_browser_client_impl.cc
@@ -12,6 +12,7 @@
 #include "base/path_service.h"
 #include "base/stl_util.h"
 #include "build/build_config.h"
+#include "components/autofill/content/browser/content_autofill_driver_factory.h"
 #include "components/embedder_support/switches.h"
 #include "components/security_interstitials/content/ssl_cert_reporter.h"
 #include "components/security_interstitials/content/ssl_error_navigation_throttle.h"
@@ -33,7 +34,6 @@
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "services/network/public/mojom/network_service.mojom.h"
 #include "services/service_manager/public/cpp/binder_map.h"
-#include "storage/browser/quota/quota_settings.h"
 #include "third_party/blink/public/common/loader/url_loader_throttle.h"
 #include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
 #include "third_party/blink/public/mojom/installedapp/installed_app_provider.mojom.h"
@@ -359,6 +359,21 @@
       true, 0, ProfileImpl::GetCachePath(context));
 }
 
+bool ContentBrowserClientImpl::BindAssociatedReceiverFromFrame(
+    content::RenderFrameHost* render_frame_host,
+    const std::string& interface_name,
+    mojo::ScopedInterfaceEndpointHandle* handle) {
+  if (interface_name == autofill::mojom::AutofillDriver::Name_) {
+    autofill::ContentAutofillDriverFactory::BindAutofillDriver(
+        mojo::PendingAssociatedReceiver<autofill::mojom::AutofillDriver>(
+            std::move(*handle)),
+        render_frame_host);
+    return true;
+  }
+
+  return false;
+}
+
 void ContentBrowserClientImpl::ExposeInterfacesToRenderer(
     service_manager::BinderRegistry* registry,
     blink::AssociatedInterfaceRegistry* associated_registry,
@@ -390,15 +405,6 @@
 #endif
 }
 
-void ContentBrowserClientImpl::GetQuotaSettings(
-    content::BrowserContext* context,
-    content::StoragePartition* partition,
-    base::OnceCallback<void(base::Optional<storage::QuotaSettings>)> callback) {
-  storage::GetNominalDynamicSettings(
-      partition->GetPath(), context->IsOffTheRecord(),
-      storage::GetDefaultDeviceInfoHelper(), std::move(callback));
-}
-
 #if defined(OS_ANDROID)
 SafeBrowsingService* ContentBrowserClientImpl::GetSafeBrowsingService() {
   if (!safe_browsing_service_) {
diff --git a/weblayer/browser/content_browser_client_impl.h b/weblayer/browser/content_browser_client_impl.h
index c869f91..d10624a8 100644
--- a/weblayer/browser/content_browser_client_impl.h
+++ b/weblayer/browser/content_browser_client_impl.h
@@ -70,6 +70,10 @@
   CreateThrottlesForNavigation(content::NavigationHandle* handle) override;
   content::GeneratedCodeCacheSettings GetGeneratedCodeCacheSettings(
       content::BrowserContext* context) override;
+  bool BindAssociatedReceiverFromFrame(
+      content::RenderFrameHost* render_frame_host,
+      const std::string& interface_name,
+      mojo::ScopedInterfaceEndpointHandle* handle) override;
   void ExposeInterfacesToRenderer(
       service_manager::BinderRegistry* registry,
       blink::AssociatedInterfaceRegistry* associated_registry,
@@ -78,11 +82,6 @@
       content::RenderFrameHost* render_frame_host,
       service_manager::BinderMapWithContext<content::RenderFrameHost*>* map)
       override;
-  void GetQuotaSettings(
-      content::BrowserContext* context,
-      content::StoragePartition* partition,
-      base::OnceCallback<void(base::Optional<storage::QuotaSettings>)> callback)
-      override;
 
 #if defined(OS_LINUX) || defined(OS_ANDROID)
   void GetAdditionalMappedFilesForChildProcess(
diff --git a/weblayer/browser/ssl_browsertest.cc b/weblayer/browser/ssl_browsertest.cc
index 513a10c9..6b006c5 100644
--- a/weblayer/browser/ssl_browsertest.cc
+++ b/weblayer/browser/ssl_browsertest.cc
@@ -7,7 +7,9 @@
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/optional.h"
+#include "build/build_config.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "weblayer/browser/ssl_error_handler.h"
 #include "weblayer/shell/browser/shell.h"
 #include "weblayer/test/interstitial_utils.h"
 #include "weblayer/test/load_completion_observer.h"
@@ -53,7 +55,7 @@
     EXPECT_FALSE(IsShowingSecurityInterstitial(shell()->tab()));
   }
 
-  void NavigateToPageWithSslErrorExpectBlocked() {
+  void NavigateToPageWithSslErrorExpectSSLInterstitial() {
     // Do a navigation that should result in an SSL error.
     NavigateAndWaitForFailure(bad_ssl_url(), shell());
     // First check that there *is* an interstitial.
@@ -67,6 +69,21 @@
     // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
   }
 
+  void NavigateToPageWithSslErrorExpectCaptivePortalInterstitial() {
+    // Do a navigation that should result in an SSL error.
+    NavigateAndWaitForFailure(bad_ssl_url(), shell());
+    // First check that there *is* an interstitial.
+    ASSERT_TRUE(IsShowingSecurityInterstitial(shell()->tab()));
+
+    // Now verify that the interstitial is in fact a captive portal
+    // interstitial.
+    EXPECT_TRUE(IsShowingCaptivePortalInterstitial(shell()->tab()));
+
+    // TODO(blundell): Check the security state once security state is available
+    // via the public WebLayer API, following the example of //chrome's
+    // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
+  }
+
   void NavigateToPageWithSslErrorExpectNotBlocked() {
     NavigateAndWaitForCompletion(bad_ssl_url(), shell());
     EXPECT_FALSE(IsShowingSecurityInterstitial(shell()->tab()));
@@ -107,6 +124,21 @@
     EXPECT_TRUE(IsShowingSSLInterstitial(shell()->tab()));
   }
 
+#if defined(OS_ANDROID)
+  void SendInterstitialOpenLoginCommandAndWait() {
+    ASSERT_TRUE(IsShowingCaptivePortalInterstitial(shell()->tab()));
+
+    // Note: The embedded test server cannot actually load the captive portal
+    // login URL, so simply detect the start of the navigation to the page.
+    TestNavigationObserver navigation_observer(
+        GetCaptivePortalLoginPageUrlForTesting(),
+        TestNavigationObserver::NavigationEvent::Start, shell());
+    ExecuteScript(shell(), "window.certificateErrorPageController.openLogin();",
+                  false /*use_separate_isolate*/);
+    navigation_observer.Wait();
+  }
+#endif
+
   void NavigateToOtherOkPage() {
     NavigateAndWaitForCompletion(https_server_->GetURL("/simple_page2.html"),
                                  shell());
@@ -129,7 +161,7 @@
 // Tests clicking "take me back" on the interstitial page.
 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, TakeMeBack) {
   NavigateToOkPage();
-  NavigateToPageWithSslErrorExpectBlocked();
+  NavigateToPageWithSslErrorExpectSSLInterstitial();
 
   // Click "Take me back".
   SendInterstitialNavigationCommandAndWait(false /*proceed*/);
@@ -139,13 +171,13 @@
 
   // Navigate to the bad SSL page again, an interstitial shows again (in
   // contrast to what would happen had the user chosen to proceed).
-  NavigateToPageWithSslErrorExpectBlocked();
+  NavigateToPageWithSslErrorExpectSSLInterstitial();
 }
 
 // Tests clicking "take me back" on the interstitial page when there's no
 // navigation history. The user should be taken to a safe page (about:blank).
 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, TakeMeBackEmptyNavigationHistory) {
-  NavigateToPageWithSslErrorExpectBlocked();
+  NavigateToPageWithSslErrorExpectSSLInterstitial();
 
   // Click "Take me back".
   SendInterstitialNavigationCommandAndWait(false /*proceed*/,
@@ -154,7 +186,7 @@
 
 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, Reload) {
   NavigateToOkPage();
-  NavigateToPageWithSslErrorExpectBlocked();
+  NavigateToPageWithSslErrorExpectSSLInterstitial();
 
   SendInterstitialReloadCommandAndWait();
 
@@ -174,7 +206,7 @@
 // across restarts.
 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, PRE_Proceed) {
   NavigateToOkPage();
-  NavigateToPageWithSslErrorExpectBlocked();
+  NavigateToPageWithSslErrorExpectSSLInterstitial();
   SendInterstitialNavigationCommandAndWait(true /*proceed*/);
 
   // Go back to an OK page, then try to navigate again. The "Proceed" decision
@@ -187,14 +219,40 @@
 // WebLayer will block again when navigating to the same bad page that was
 // previously proceeded through.
 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, Proceed) {
-  NavigateToPageWithSslErrorExpectBlocked();
+  NavigateToPageWithSslErrorExpectSSLInterstitial();
 }
 
 // Tests navigating away from the interstitial page.
 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, NavigateAway) {
   NavigateToOkPage();
-  NavigateToPageWithSslErrorExpectBlocked();
+  NavigateToPageWithSslErrorExpectSSLInterstitial();
   NavigateToOtherOkPage();
 }
 
+// Tests the scenario where the OS reports that an SSL error is due to a
+// captive portal. A captive portal interstitial should be displayed. The test
+// then switches OS captive portal status to false and reloads the page. This
+// time, a normal SSL interstitial should be displayed.
+IN_PROC_BROWSER_TEST_F(SSLBrowserTest, OSReportsCaptivePortal) {
+  SetDiagnoseSSLErrorsAsCaptivePortalForTesting(true);
+
+  NavigateToPageWithSslErrorExpectCaptivePortalInterstitial();
+
+  // Check that clearing the test setting causes behavior to revert to normal.
+  SetDiagnoseSSLErrorsAsCaptivePortalForTesting(false);
+  NavigateToPageWithSslErrorExpectSSLInterstitial();
+}
+
+#if defined(OS_ANDROID)
+// Tests that after reaching a captive portal interstitial, clicking on the
+// connect link will cause a navigation to the login page.
+IN_PROC_BROWSER_TEST_F(SSLBrowserTest, CaptivePortalConnectToLoginPage) {
+  SetDiagnoseSSLErrorsAsCaptivePortalForTesting(true);
+
+  NavigateToPageWithSslErrorExpectCaptivePortalInterstitial();
+
+  SendInterstitialOpenLoginCommandAndWait();
+}
+#endif
+
 }  // namespace weblayer
diff --git a/weblayer/browser/ssl_error_handler.cc b/weblayer/browser/ssl_error_handler.cc
index d053f78..2ea089a 100644
--- a/weblayer/browser/ssl_error_handler.cc
+++ b/weblayer/browser/ssl_error_handler.cc
@@ -5,6 +5,8 @@
 #include "weblayer/browser/ssl_error_handler.h"
 
 #include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/security_interstitials/content/captive_portal_blocking_page.h"
 #include "components/security_interstitials/content/ssl_blocking_page.h"
 #include "components/security_interstitials/content/ssl_cert_reporter.h"
 #include "components/security_interstitials/content/ssl_error_navigation_throttle.h"
@@ -14,10 +16,99 @@
 #include "weblayer/browser/ssl_error_controller_client.h"
 #include "weblayer/browser/weblayer_content_browser_overlay_manifest.h"
 
+#if defined(OS_ANDROID)
+#include "content/public/browser/page_navigator.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/referrer.h"
+#include "net/android/network_library.h"
+#include "ui/base/window_open_disposition.h"
+#endif
+
 namespace weblayer {
 
 namespace {
 
+bool g_is_behind_captive_portal_for_testing = false;
+
+// Returns whether the user is behind a captive portal.
+bool IsBehindCaptivePortal() {
+  if (g_is_behind_captive_portal_for_testing)
+    return true;
+
+#if defined(OS_ANDROID)
+  return net::android::GetIsCaptivePortal();
+#else
+  // WebLayer does not currently integrate CaptivePortalService, which Chrome
+  // uses on non-Android platforms to detect the user being behind a captive
+  // portal.
+  return false;
+#endif
+}
+
+#if defined(OS_ANDROID)
+GURL GetCaptivePortalLoginPageUrlInternal() {
+  // NOTE: This is taken from the default login URL in //chrome's
+  // CaptivePortalHelper.java, which is used in the implementation referenced
+  // in OpenLoginPage() below.
+  return GURL("http://connectivitycheck.gstatic.com/generate_204");
+}
+#endif
+
+void OpenLoginPage(content::WebContents* web_contents) {
+  // TODO(https://crbug.com/1030692): Componentize and share the
+  // Android implementation from //chrome's
+  // ChromeSecurityBlockingPageFactory::OpenLoginPage(), from which this is
+  // adapted.
+#if defined(OS_ANDROID)
+  // NOTE: In Chrome this opens in a new tab; however, as WebLayer doesn't have
+  // the ability to open new tabs it must open in the current tab.
+  content::OpenURLParams params(
+      GetCaptivePortalLoginPageUrlInternal(), content::Referrer(),
+      WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_LINK, false);
+  web_contents->OpenURL(params);
+#else
+  NOTIMPLEMENTED();
+#endif
+}
+
+// Constructs and shows a captive portal interstitial. Adapted from //chrome's
+// SSLErrorHandlerDelegateImpl::ShowCaptivePortalInterstitial().
+void ShowCaptivePortalInterstitial(
+    content::WebContents* web_contents,
+    int cert_error,
+    const net::SSLInfo& ssl_info,
+    const GURL& request_url,
+    std::unique_ptr<SSLCertReporter> ssl_cert_reporter,
+    base::OnceCallback<
+        void(std::unique_ptr<security_interstitials::SecurityInterstitialPage>)>
+        blocking_page_ready_callback) {
+  security_interstitials::MetricsHelper::ReportDetails report_details;
+  report_details.metric_prefix = "captive_portal";
+  auto metrics_helper = std::make_unique<security_interstitials::MetricsHelper>(
+      request_url, report_details, /*history_service=*/nullptr);
+
+  auto controller_client = std::make_unique<SSLErrorControllerClient>(
+      web_contents, cert_error, ssl_info, request_url,
+      std::move(metrics_helper));
+
+  // When captive portals are detected by the underlying platform (the only
+  // context in which captive portals are currently detected in WebLayer),
+  // the login URL is not specified by the client but is determined internally.
+  GURL login_url;
+
+  auto* interstitial_page = new CaptivePortalBlockingPage(
+      web_contents, request_url, login_url, std::move(ssl_cert_reporter),
+      ssl_info, std::move(controller_client),
+      base::BindRepeating(&OpenLoginPage));
+
+  // Note: |blocking_page_ready_callback| must be posted due to
+  // HandleSSLError()'s guarantee that it will not invoke this callback
+  // synchronously.
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(blocking_page_ready_callback),
+                                base::WrapUnique(interstitial_page)));
+}
+
 // Constructs and shows an SSL interstitial. Adapted from //chrome's
 // SSLErrorHandlerDelegateImpl::ShowSSLInterstitial().
 void ShowSSLInterstitial(
@@ -66,6 +157,25 @@
     base::OnceCallback<
         void(std::unique_ptr<security_interstitials::SecurityInterstitialPage>)>
         blocking_page_ready_callback) {
+  // First check for captive portal.
+
+  // TODO(https://crbug.com/1030692): Share the check for known captive
+  // portal certificates from //chrome's SSLErrorHandler:757.
+  if (IsBehindCaptivePortal()) {
+    // TODO(https://crbug.com/1030692): Share the reporting of network
+    // connectivity and tracking UMA from //chrome's SSLErrorHandler:743.
+    ShowCaptivePortalInterstitial(web_contents, cert_error, ssl_info,
+                                  request_url, std::move(ssl_cert_reporter),
+                                  std::move(blocking_page_ready_callback));
+    return;
+  }
+
+  // Handle all remaining errors by showing SSL interstitials. If this needs to
+  // get more refined in the short-term, can adapt logic from
+  // SSLErrorHandler::StartHandlingError() as needed (in the long-term,
+  // WebLayer will most likely share a componentized version of //chrome's
+  // SSLErrorHandler).
+
   // NOTE: In Chrome hard overrides can be disabled for the Profile by setting
   // the kSSLErrorOverrideAllowed preference (which defaults to true) to false.
   // However, in WebLayer there is currently no way for the user to set this
@@ -74,14 +184,19 @@
   int options_mask = security_interstitials::CalculateSSLErrorOptionsMask(
       cert_error, hard_override_disabled, ssl_info.is_fatal_cert_error);
 
-  // Handle all errors by showing SSL interstitials. If this needs to get more
-  // refined in the short-term, can adapt logic from
-  // SSLErrorHandler::StartHandlingError() as needed (in the long-term, WebLayer
-  // will most likely share a componentized version of //chrome's
-  // SSLErrorHandler).
   ShowSSLInterstitial(web_contents, cert_error, ssl_info, request_url,
                       std::move(ssl_cert_reporter),
                       std::move(blocking_page_ready_callback), options_mask);
 }
 
+void SetDiagnoseSSLErrorsAsCaptivePortalForTesting(bool enabled) {
+  g_is_behind_captive_portal_for_testing = enabled;
+}
+
+#if defined(OS_ANDROID)
+GURL GetCaptivePortalLoginPageUrlForTesting() {
+  return GetCaptivePortalLoginPageUrlInternal();
+}
+#endif
+
 }  // namespace weblayer
diff --git a/weblayer/browser/ssl_error_handler.h b/weblayer/browser/ssl_error_handler.h
index bf3124e0..0eae1de 100644
--- a/weblayer/browser/ssl_error_handler.h
+++ b/weblayer/browser/ssl_error_handler.h
@@ -10,6 +10,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/time/time.h"
+#include "build/build_config.h"
 #include "components/security_interstitials/content/security_interstitial_page.h"
 #include "content/public/browser/certificate_request_result_type.h"
 #include "net/ssl/ssl_info.h"
@@ -42,6 +43,17 @@
     std::unique_ptr<SSLCertReporter> ssl_cert_reporter,
     BlockingPageReadyCallback blocking_page_ready_callback);
 
+// Pass true to simulate the OS reporting that SSL errors are due to captive
+// portals.
+void SetDiagnoseSSLErrorsAsCaptivePortalForTesting(bool enabled);
+
+#if defined(OS_ANDROID)
+// Returns the URL that will be navigated to when the user clicks on the
+// "Connect" button of the captive portal interstitial. Used by tests to
+// verify this flow.
+GURL GetCaptivePortalLoginPageUrlForTesting();
+#endif
+
 }  // namespace weblayer
 
 #endif  // WEBLAYER_BROWSER_SSL_ERROR_HANDLER_H_
diff --git a/weblayer/browser/tab_impl.cc b/weblayer/browser/tab_impl.cc
index e0db45a..cdad274 100644
--- a/weblayer/browser/tab_impl.cc
+++ b/weblayer/browser/tab_impl.cc
@@ -7,6 +7,9 @@
 #include "base/auto_reset.h"
 #include "base/feature_list.h"
 #include "base/logging.h"
+#include "components/autofill/content/browser/content_autofill_driver_factory.h"
+#include "components/autofill/core/browser/autofill_manager.h"
+#include "components/autofill/core/browser/autofill_provider.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/interstitial_page.h"
 #include "content/public/browser/navigation_controller.h"
@@ -16,7 +19,9 @@
 #include "content/public/browser/web_contents.h"
 #include "third_party/blink/public/mojom/renderer_preferences.mojom.h"
 #include "ui/base/window_open_disposition.h"
+#include "weblayer/browser/autofill_client_impl.h"
 #include "weblayer/browser/file_select_helper.h"
+#include "weblayer/browser/i18n_util.h"
 #include "weblayer/browser/isolated_world_ids.h"
 #include "weblayer/browser/navigation_controller_impl.h"
 #include "weblayer/browser/profile_impl.h"
@@ -213,6 +218,12 @@
   }
 }
 
+void TabImpl::ExecuteScriptWithUserGestureForTests(
+    const base::string16& script) {
+  web_contents_->GetMainFrame()->ExecuteJavaScriptWithUserGestureForTests(
+      script);
+}
+
 #if !defined(OS_ANDROID)
 void TabImpl::AttachToView(views::WebView* web_view) {
   web_view->SetWebContents(web_contents_.get());
@@ -457,4 +468,25 @@
 }
 #endif
 
+void TabImpl::InitializeAutofillForTests(
+    std::unique_ptr<autofill::AutofillProvider> provider) {
+  autofill_provider_ = std::move(provider);
+  InitializeAutofill();
+}
+
+void TabImpl::InitializeAutofill() {
+  DCHECK(autofill_provider_);
+
+  content::WebContents* web_contents = web_contents_.get();
+  DCHECK(
+      !autofill::ContentAutofillDriverFactory::FromWebContents(web_contents));
+
+  AutofillClientImpl::CreateForWebContents(web_contents);
+  autofill::ContentAutofillDriverFactory::CreateForWebContentsAndDelegate(
+      web_contents, AutofillClientImpl::FromWebContents(web_contents),
+      i18n::GetApplicationLocale(),
+      autofill::AutofillManager::DISABLE_AUTOFILL_DOWNLOAD_MANAGER,
+      autofill_provider_.get());
+}
+
 }  // namespace weblayer
diff --git a/weblayer/browser/tab_impl.h b/weblayer/browser/tab_impl.h
index 5b6b8a7..a10af73 100644
--- a/weblayer/browser/tab_impl.h
+++ b/weblayer/browser/tab_impl.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
@@ -22,6 +23,10 @@
 #include "base/android/scoped_java_ref.h"
 #endif
 
+namespace autofill {
+class AutofillProvider;
+}  // namespace autofill
+
 namespace content {
 class WebContents;
 }
@@ -92,6 +97,13 @@
   void AttachToView(views::WebView* web_view) override;
 #endif
 
+  // Executes |script| with a user gesture.
+  void ExecuteScriptWithUserGestureForTests(const base::string16& script);
+
+  // Initializes the autofill system with |provider| for tests.
+  void InitializeAutofillForTests(
+      std::unique_ptr<autofill::AutofillProvider> provider);
+
  private:
   // content::WebContentsDelegate:
   content::WebContents* OpenURLFromTab(
@@ -138,6 +150,8 @@
 
   void UpdateRendererPrefs(bool should_sync_prefs);
 
+  void InitializeAutofill();
+
 #if defined(OS_ANDROID)
   void UpdateBrowserControlsState(content::BrowserControlsState constraints,
                                   content::BrowserControlsState current,
@@ -163,6 +177,8 @@
   // Set to true doing EnterFullscreenModeForTab().
   bool processing_enter_fullscreen_ = false;
 
+  std::unique_ptr<autofill::AutofillProvider> autofill_provider_;
+
   base::WeakPtrFactory<TabImpl> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(TabImpl);
diff --git a/weblayer/grit_strings_whitelist.txt b/weblayer/grit_strings_whitelist.txt
index 907d69c..e6ad7d739 100644
--- a/weblayer/grit_strings_whitelist.txt
+++ b/weblayer/grit_strings_whitelist.txt
@@ -1,3 +1,12 @@
+IDS_CAPTIVE_PORTAL_BUTTON_OPEN_LOGIN_PAGE
+IDS_CAPTIVE_PORTAL_HEADING_WIFI
+IDS_CAPTIVE_PORTAL_HEADING_WIRED
+IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_NO_LOGIN_URL_WIFI
+IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_NO_LOGIN_URL_WIRED
+IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_NO_LOGIN_URL_WIFI_SSID
+IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_WIFI
+IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_WIRED
+IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_WIFI_SSID
 IDS_SSL_OPEN_DETAILS_BUTTON
 IDS_SSL_CLOSE_DETAILS_BUTTON
 IDS_SSL_NONOVERRIDABLE_HSTS
diff --git a/weblayer/renderer/DEPS b/weblayer/renderer/DEPS
index e83c2333..b792c33 100644
--- a/weblayer/renderer/DEPS
+++ b/weblayer/renderer/DEPS
@@ -4,6 +4,7 @@
   # long-term, componentize these strings/resources as part of componentizing
   # that implementation and remove the need for this dependency.
   "+android_webview/grit",
+  "+components/autofill/content/renderer",
   "+components/safe_browsing/common",
   "+components/safe_browsing/renderer",
   "+components/security_interstitials/content/renderer",
diff --git a/weblayer/renderer/content_renderer_client_impl.cc b/weblayer/renderer/content_renderer_client_impl.cc
index 35c2649..d56a7cf 100644
--- a/weblayer/renderer/content_renderer_client_impl.cc
+++ b/weblayer/renderer/content_renderer_client_impl.cc
@@ -8,6 +8,8 @@
 #include "base/i18n/rtl.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "components/autofill/content/renderer/autofill_agent.h"
+#include "components/autofill/content/renderer/password_autofill_agent.h"
 #include "content/public/renderer/render_thread.h"
 #include "net/base/escape.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -15,6 +17,7 @@
 #include "ui/base/resource/resource_bundle.h"
 #include "weblayer/common/features.h"
 #include "weblayer/renderer/ssl_error_helper.h"
+#include "weblayer/renderer/weblayer_render_frame_observer.h"
 
 #if defined(OS_ANDROID)
 #include "android_webview/grit/aw_resources.h"
@@ -125,8 +128,16 @@
 
 void ContentRendererClientImpl::RenderFrameCreated(
     content::RenderFrame* render_frame) {
+  auto* render_frame_observer = new WebLayerRenderFrameObserver(render_frame);
+
   SSLErrorHelper::Create(render_frame);
 
+  autofill::PasswordAutofillAgent* password_autofill_agent =
+      new autofill::PasswordAutofillAgent(
+          render_frame, render_frame_observer->associated_interfaces());
+  new autofill::AutofillAgent(render_frame, password_autofill_agent, nullptr,
+                              render_frame_observer->associated_interfaces());
+
 #if defined(OS_ANDROID)
   // |SpellCheckProvider| manages its own lifetime (and destroys itself when the
   // RenderFrame is destroyed).
diff --git a/weblayer/renderer/ssl_error_helper.cc b/weblayer/renderer/ssl_error_helper.cc
index edadda2..e1c40cb 100644
--- a/weblayer/renderer/ssl_error_helper.cc
+++ b/weblayer/renderer/ssl_error_helper.cc
@@ -66,9 +66,11 @@
     case security_interstitials::CMD_RELOAD:
       interface->Reload();
       break;
+    case security_interstitials::CMD_OPEN_LOGIN:
+      interface->OpenLogin();
+      break;
     case security_interstitials::CMD_OPEN_DIAGNOSTIC:
     case security_interstitials::CMD_OPEN_DATE_SETTINGS:
-    case security_interstitials::CMD_OPEN_LOGIN:
     case security_interstitials::CMD_DO_REPORT:
     case security_interstitials::CMD_DONT_REPORT:
     case security_interstitials::CMD_OPEN_REPORTING_PRIVACY:
diff --git a/weblayer/renderer/weblayer_render_frame_observer.cc b/weblayer/renderer/weblayer_render_frame_observer.cc
new file mode 100644
index 0000000..038d69a3
--- /dev/null
+++ b/weblayer/renderer/weblayer_render_frame_observer.cc
@@ -0,0 +1,27 @@
+// Copyright 2019 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 "weblayer/renderer/weblayer_render_frame_observer.h"
+
+#include "content/public/renderer/render_frame.h"
+
+namespace weblayer {
+
+WebLayerRenderFrameObserver::WebLayerRenderFrameObserver(
+    content::RenderFrame* render_frame)
+    : content::RenderFrameObserver(render_frame) {}
+
+WebLayerRenderFrameObserver::~WebLayerRenderFrameObserver() = default;
+
+bool WebLayerRenderFrameObserver::OnAssociatedInterfaceRequestForFrame(
+    const std::string& interface_name,
+    mojo::ScopedInterfaceEndpointHandle* handle) {
+  return associated_interfaces_.TryBindInterface(interface_name, handle);
+}
+
+void WebLayerRenderFrameObserver::OnDestruct() {
+  delete this;
+}
+
+}  // namespace weblayer
diff --git a/weblayer/renderer/weblayer_render_frame_observer.h b/weblayer/renderer/weblayer_render_frame_observer.h
new file mode 100644
index 0000000..d45b57f
--- /dev/null
+++ b/weblayer/renderer/weblayer_render_frame_observer.h
@@ -0,0 +1,40 @@
+// Copyright 2019 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 WEBLAYER_RENDERER_WEBLAYER_RENDER_FRAME_OBSERVER_H_
+#define WEBLAYER_RENDERER_WEBLAYER_RENDER_FRAME_OBSERVER_H_
+
+#include "base/macros.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+
+namespace weblayer {
+
+// This class holds the WebLayer-specific parts of RenderFrame, and has the
+// same lifetime. It is analogous to //chrome's ChromeRenderFrameObserver.
+class WebLayerRenderFrameObserver : public content::RenderFrameObserver {
+ public:
+  explicit WebLayerRenderFrameObserver(content::RenderFrame* render_frame);
+
+  blink::AssociatedInterfaceRegistry* associated_interfaces() {
+    return &associated_interfaces_;
+  }
+
+ private:
+  ~WebLayerRenderFrameObserver() override;
+
+  // RenderFrameObserver:
+  bool OnAssociatedInterfaceRequestForFrame(
+      const std::string& interface_name,
+      mojo::ScopedInterfaceEndpointHandle* handle) override;
+  void OnDestruct() override;
+
+  blink::AssociatedInterfaceRegistry associated_interfaces_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebLayerRenderFrameObserver);
+};
+
+}  // namespace weblayer
+
+#endif  // WEBLAYER_RENDERER_WEBLAYER_RENDER_FRAME_OBSERVER_H_
diff --git a/weblayer/test/BUILD.gn b/weblayer/test/BUILD.gn
index 5b3cfb2..41ffbe9 100644
--- a/weblayer/test/BUILD.gn
+++ b/weblayer/test/BUILD.gn
@@ -78,6 +78,9 @@
     "//base",
     "//base",
     "//base/test:test_support",
+    "//components/autofill/core/browser",
+    "//components/autofill/core/browser:test_support",
+    "//components/autofill/core/common",
     "//components/security_interstitials/content:security_interstitial_page",
     "//content/public/browser",
     "//content/test:test_support",
@@ -88,6 +91,7 @@
   ]
 
   sources = [
+    "../browser/autofill_browsertest.cc",
     "../browser/errorpage_browsertest.cc",
     "../browser/navigation_browsertest.cc",
     "../browser/ssl_browsertest.cc",
@@ -97,6 +101,8 @@
     "interstitial_utils.h",
     "load_completion_observer.cc",
     "load_completion_observer.h",
+    "stub_autofill_provider.cc",
+    "stub_autofill_provider.h",
     "test_launcher_delegate_impl.cc",
     "test_launcher_delegate_impl.h",
     "test_navigation_observer.cc",
diff --git a/weblayer/test/DEPS b/weblayer/test/DEPS
index a861b20..9dfe23d 100644
--- a/weblayer/test/DEPS
+++ b/weblayer/test/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/autofill/core/browser",
   "+components/security_interstitials",
   "+content/public/common",
   "+content/public/test",
diff --git a/weblayer/test/data/simple_password_form.html b/weblayer/test/data/simple_password_form.html
new file mode 100644
index 0000000..7608c78
--- /dev/null
+++ b/weblayer/test/data/simple_password_form.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+<form method="POST" action="done.html" onsubmit="return true;" id="testform">
+  <input type="text" id="username_field" name="username_field">
+  <input type="password" id="password_field" name="password_field">
+  <input type="submit" id="input_submit_button" name="input_submit_button">
+</form>
+</body>
+</html>
diff --git a/weblayer/test/interstitial_utils.cc b/weblayer/test/interstitial_utils.cc
index 2bbfbf6..4266a77 100644
--- a/weblayer/test/interstitial_utils.cc
+++ b/weblayer/test/interstitial_utils.cc
@@ -4,6 +4,7 @@
 
 #include "weblayer/test/interstitial_utils.h"
 
+#include "components/security_interstitials/content/captive_portal_blocking_page.h"
 #include "components/security_interstitials/content/security_interstitial_tab_helper.h"
 #include "components/security_interstitials/content/ssl_blocking_page.h"
 #include "weblayer/browser/tab_impl.h"
@@ -28,6 +29,19 @@
              : nullptr;
 }
 
+// Returns true if a security interstitial of type |type| is currently showing
+// in |tab|.
+bool IsShowingInterstitialOfType(
+    Tab* tab,
+    content::InterstitialPageDelegate::TypeID type) {
+  auto* blocking_page = GetCurrentlyShowingInterstitial(tab);
+
+  if (!blocking_page)
+    return false;
+
+  return blocking_page->GetTypeForTesting() == type;
+}
+
 }  // namespace
 
 bool IsShowingSecurityInterstitial(Tab* tab) {
@@ -35,12 +49,12 @@
 }
 
 bool IsShowingSSLInterstitial(Tab* tab) {
-  auto* blocking_page = GetCurrentlyShowingInterstitial(tab);
+  return IsShowingInterstitialOfType(tab, SSLBlockingPage::kTypeForTesting);
+}
 
-  if (!blocking_page)
-    return false;
-
-  return blocking_page->GetTypeForTesting() == SSLBlockingPage::kTypeForTesting;
+bool IsShowingCaptivePortalInterstitial(Tab* tab) {
+  return IsShowingInterstitialOfType(
+      tab, CaptivePortalBlockingPage::kTypeForTesting);
 }
 
 }  // namespace weblayer
diff --git a/weblayer/test/interstitial_utils.h b/weblayer/test/interstitial_utils.h
index 7bf546e..e8b7834 100644
--- a/weblayer/test/interstitial_utils.h
+++ b/weblayer/test/interstitial_utils.h
@@ -20,6 +20,10 @@
 // |tab|.
 bool IsShowingSSLInterstitial(Tab* tab);
 
+// Returns true iff a captive portal interstitial is currently displaying in
+// |tab|.
+bool IsShowingCaptivePortalInterstitial(Tab* tab);
+
 }  // namespace weblayer
 
 #endif  // WEBLAYER_TEST_INTERSTITIAL_UTILS_H_
diff --git a/weblayer/test/stub_autofill_provider.cc b/weblayer/test/stub_autofill_provider.cc
new file mode 100644
index 0000000..c1c1b94a
--- /dev/null
+++ b/weblayer/test/stub_autofill_provider.cc
@@ -0,0 +1,26 @@
+// Copyright 2019 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 "weblayer/test/stub_autofill_provider.h"
+
+namespace weblayer {
+
+StubAutofillProvider::StubAutofillProvider(
+    const base::RepeatingCallback<void(const autofill::FormData&)>&
+        on_received_form_data)
+    : on_received_form_data_(on_received_form_data) {}
+
+StubAutofillProvider::~StubAutofillProvider() = default;
+
+void StubAutofillProvider::OnQueryFormFieldAutofill(
+    autofill::AutofillHandlerProxy* handler,
+    int32_t id,
+    const autofill::FormData& form,
+    const autofill::FormFieldData& field,
+    const gfx::RectF& bounding_box,
+    bool /*unused_autoselect_first_suggestion*/) {
+  on_received_form_data_.Run(form);
+}
+
+}  // namespace weblayer
diff --git a/weblayer/test/stub_autofill_provider.h b/weblayer/test/stub_autofill_provider.h
new file mode 100644
index 0000000..8f648e4
--- /dev/null
+++ b/weblayer/test/stub_autofill_provider.h
@@ -0,0 +1,41 @@
+// Copyright 2019 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 WEBLAYER_TEST_STUB_AUTOFILL_PROVIDER_H_
+#define WEBLAYER_TEST_STUB_AUTOFILL_PROVIDER_H_
+
+#include "base/callback_forward.h"
+#include "components/autofill/core/browser/test_autofill_provider.h"
+
+namespace weblayer {
+
+// A stub AutofillProvider implementation that is used in cross-platform
+// integration tests of renderer-side autofill detection and communication to
+// the browser.
+class StubAutofillProvider : public autofill::TestAutofillProvider {
+ public:
+  explicit StubAutofillProvider(
+      const base::RepeatingCallback<void(const autofill::FormData&)>&
+          on_received_form_data);
+  ~StubAutofillProvider() override;
+
+  // AutofillProvider:
+  void OnQueryFormFieldAutofill(
+      autofill::AutofillHandlerProxy* handler,
+      int32_t id,
+      const autofill::FormData& form,
+      const autofill::FormFieldData& field,
+      const gfx::RectF& bounding_box,
+      bool /*unused_autoselect_first_suggestion*/) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(StubAutofillProvider);
+
+  base::RepeatingCallback<void(const autofill::FormData&)>
+      on_received_form_data_;
+};
+
+}  // namespace weblayer
+
+#endif  // WEBLAYER_TEST_STUB_AUTOFILL_PROVIDER_H_
diff --git a/weblayer/test/test_navigation_observer.cc b/weblayer/test/test_navigation_observer.cc
index 00de95c5..4b7b351 100644
--- a/weblayer/test/test_navigation_observer.cc
+++ b/weblayer/test/test_navigation_observer.cc
@@ -24,6 +24,15 @@
   tab_->GetNavigationController()->RemoveObserver(this);
 }
 
+void TestNavigationObserver::NavigationStarted(Navigation* navigation) {
+  // Note: We don't go through CheckNavigationCompleted() here as that waits
+  // for the load to be complete, which isn't appropriate when just waiting for
+  // the navigation to be started.
+  if (navigation->GetURL() == url_ && target_event_ == NavigationEvent::Start) {
+    run_loop_.Quit();
+  }
+}
+
 void TestNavigationObserver::NavigationCompleted(Navigation* navigation) {
   if (navigation->GetURL() == url_)
     observed_event_ = NavigationEvent::Completion;
diff --git a/weblayer/test/test_navigation_observer.h b/weblayer/test/test_navigation_observer.h
index fbfddee8..535fa536 100644
--- a/weblayer/test/test_navigation_observer.h
+++ b/weblayer/test/test_navigation_observer.h
@@ -19,10 +19,10 @@
 // A helper that waits for a navigation to finish.
 class TestNavigationObserver : public NavigationObserver {
  public:
-  enum class NavigationEvent { Completion, Failure };
+  enum class NavigationEvent { Start, Completion, Failure };
 
   // Creates an instance that begins waiting for a Navigation within |shell| and
-  // to |url| to either complete or fail as per |target_event|.
+  // to |url| to reach the specified |target_event|.
   TestNavigationObserver(const GURL& url,
                          NavigationEvent target_event,
                          Shell* shell);
@@ -33,6 +33,7 @@
 
  private:
   // NavigationObserver implementation:
+  void NavigationStarted(Navigation* navigation) override;
   void NavigationCompleted(Navigation* navigation) override;
   void NavigationFailed(Navigation* navigation) override;
   void LoadStateChanged(bool is_loading, bool to_different_document) override;
diff --git a/weblayer/test/weblayer_browser_test_utils.cc b/weblayer/test/weblayer_browser_test_utils.cc
index 1ba8d9a..b81f4cf 100644
--- a/weblayer/test/weblayer_browser_test_utils.cc
+++ b/weblayer/test/weblayer_browser_test_utils.cc
@@ -11,6 +11,7 @@
 #include "weblayer/public/navigation_controller.h"
 #include "weblayer/public/tab.h"
 #include "weblayer/shell/browser/shell.h"
+#include "weblayer/test/stub_autofill_provider.h"
 #include "weblayer/test/test_navigation_observer.h"
 
 namespace weblayer {
@@ -54,10 +55,25 @@
   return final_result;
 }
 
+void ExecuteScriptWithUserGesture(Shell* shell, const std::string& script) {
+  TabImpl* tab_impl = static_cast<TabImpl*>(shell->tab());
+  tab_impl->ExecuteScriptWithUserGestureForTests(base::ASCIIToUTF16(script));
+}
+
 const base::string16& GetTitle(Shell* shell) {
   TabImpl* tab_impl = static_cast<TabImpl*>(shell->tab());
 
   return tab_impl->web_contents()->GetTitle();
 }
 
+void InitializeAutofillWithEventForwarding(
+    Shell* shell,
+    const base::RepeatingCallback<void(const autofill::FormData&)>&
+        on_received_form_data) {
+  TabImpl* tab_impl = static_cast<TabImpl*>(shell->tab());
+
+  tab_impl->InitializeAutofillForTests(
+      std::make_unique<StubAutofillProvider>(on_received_form_data));
+}
+
 }  // namespace weblayer
diff --git a/weblayer/test/weblayer_browser_test_utils.h b/weblayer/test/weblayer_browser_test_utils.h
index 99c0ad6..916fc99 100644
--- a/weblayer/test/weblayer_browser_test_utils.h
+++ b/weblayer/test/weblayer_browser_test_utils.h
@@ -5,11 +5,17 @@
 #ifndef WEBLAYER_TEST_WEBLAYER_BROWSER_TEST_UTILS_H_
 #define WEBLAYER_TEST_WEBLAYER_BROWSER_TEST_UTILS_H_
 
+#include "base/callback_forward.h"
+#include "base/optional.h"
 #include "base/strings/string16.h"
 #include "base/values.h"
 
 class GURL;
 
+namespace autofill {
+struct FormData;
+}
+
 namespace weblayer {
 class Shell;
 
@@ -24,9 +30,27 @@
                           const std::string& script,
                           bool use_separate_isolate);
 
-// Gets the title of the current webpage in |shell|.
+// Executes |script| in |shell| with a user gesture. Useful for tests of
+// functionality that gates action on a user gesture having occurred.
+// Differs from ExecuteScript() as follows:
+// - Does not notify the caller of the result as the underlying implementation
+//   does not. Thus, unlike the above, the caller of this function will need to
+//   explicitly listen *after* making this call for any expected event to
+//   occur.
+// - Does not allow running in a separate isolate as the  machinery for
+//   setting a user gesture works only in the main isolate.
+void ExecuteScriptWithUserGesture(Shell* shell, const std::string& script);
+
+/// Gets the title of the current webpage in |shell|.
 const base::string16& GetTitle(Shell* shell);
 
+// Sets up the autofill system to be one that simply forwards detected forms to
+// the passed-in callback.
+void InitializeAutofillWithEventForwarding(
+    Shell* shell,
+    const base::RepeatingCallback<void(const autofill::FormData&)>&
+        on_received_form_data);
+
 }  // namespace weblayer
 
 #endif  // WEBLAYER_TEST_WEBLAYER_BROWSER_TEST_UTILS_H_